diff -Nru virtualbox-6.1.16-dfsg/Config.kmk virtualbox-6.1.38-dfsg/Config.kmk --- virtualbox-6.1.16-dfsg/Config.kmk 2020-10-16 16:27:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/Config.kmk 2022-09-01 13:17:42.000000000 +0000 @@ -431,7 +431,7 @@ VBOX_WITH_NATIVE_NEM = 1 endif # Enables mapping guest RAM into host kernel space. -if1of ($(KBUILD_TARGET), darwin linux win) +if1of ($(KBUILD_TARGET), darwin linux solaris win) VBOX_WITH_RAM_IN_KERNEL := 1 endif ## @} @@ -480,9 +480,6 @@ # Enable building debugging backend. # Only will be used at runtime when "VBoxInternal2/Audio/Debug/Enabled" (VM / global) is set. VBOX_WITH_AUDIO_DEBUG = 1 -ifdef VBOX_WITH_VALIDATIONKIT ## @todo r=bird: test is too early! - VBOX_WITH_AUDIO_VALIDATIONKIT = 1 -endif # Enables the audio endpoint detection on Windows hosts. VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT = 1 # Enable PCI passthrough support. @@ -766,6 +763,9 @@ endif # Enable audio support for VRDE. VBOX_WITH_AUDIO_VRDE = 1 +ifdef VBOX_WITH_VALIDATIONKIT + VBOX_WITH_AUDIO_VALIDATIONKIT = 1 +endif # Use the VRDE external authentication library from VBoxSVC. if1of ($(KBUILD_TARGET), win) VBOX_WITH_VRDEAUTH_IN_VBOXSVC = 1 @@ -774,6 +774,10 @@ # the overall installer size significantly because merge modules are not able # to use a common .cab file to reduce their size. #VBOX_WITH_MSM_INSTALL = 1 +# Enables the 'sign' command in bldRTSignTool. We may need this on windows. +if1of ($(KBUILD_TARGET), win) + VBOX_WITH_BLD_RTSIGNTOOL_SIGNING = 1 +endif ## @} @@ -1049,6 +1053,7 @@ VBOX_WITH_VBOXSDL= VBOX_WITH_HEADLESS= VBOX_WITH_VRDP= + VBOX_WITH_VRDP_RDESKTOP= VBOX_WITH_DOCS= VBOX_WITH_WEBSERVICES= VBOX_WITH_32_ON_64_MAIN_API= @@ -1425,7 +1430,7 @@ # Derived / helper config indicators. Not configurable. # -# Use the OpenGL module in qt when the video hardware acceleartion feature +# Use the OpenGL module in qt when the video hardware acceleration feature # is enabled. if defined(VBOX_WITH_VIDEOHWACCEL) && defined(VBOX_WITH_QTGUI) VBOX_GUI_USE_QGL = 1 @@ -1842,6 +1847,7 @@ LIB_XPCOM_IMP = $(PATH_STAGE_LIB)/VBoxXPCOMImp.dylib VBOX_LIB_XPCOM_X86 = $(PATH_STAGE_BIN)/VBoxXPCOM-x86.dylib LIB_DDU = $(PATH_STAGE_BIN)/VBoxDDU.dylib + VBOX_LIB_SUPR0 = $(PATH_STAGE_LIB)/SUPR0$(VBOX_SUFF_LIB) endif if1of ($(KBUILD_TARGET), freebsd haiku linux netbsd openbsd solaris) LIB_RUNTIME = $(PATH_STAGE_BIN)/VBoxRT.so @@ -1855,6 +1861,7 @@ LIB_XPCOM_IMP = $(PATH_STAGE_LIB)/VBoxXPCOMImp.so VBOX_LIB_XPCOM_X86 = $(PATH_STAGE_BIN)/VBoxXPCOM-x86.so LIB_DDU = $(PATH_STAGE_BIN)/VBoxDDU.so + VBOX_LIB_SUPR0 := endif ifeq ($(KBUILD_TARGET),os2) LIB_RUNTIME = $(PATH_STAGE_BIN)/VBoxRT.dll @@ -1869,6 +1876,7 @@ VBOX_LIB_XPCOM_X86 = $(PATH_STAGE_BIN)/VBoxXPCOM-x86.dll LIB_DDU = $(PATH_STAGE_BIN)/VBoxDDU.dll VBOX_OBJ_SYS0 = $(PATH_OBJ)/RuntimeR0/os2/sys0.obj + VBOX_LIB_SUPR0 = $(PATH_STAGE_LIB)/SUPR0$(VBOX_SUFF_LIB) endif ifeq ($(KBUILD_TARGET),win) LIB_RUNTIME = $(PATH_STAGE_LIB)/VBoxRT.lib @@ -1882,6 +1890,7 @@ LIB_XPCOM_IMP = VBOX_LIB_XPCOM_X86 = LIB_DDU = $(PATH_STAGE_LIB)/VBoxDDU.lib + VBOX_LIB_SUPR0 = $(PATH_STAGE_LIB)/SUPR0$(VBOX_SUFF_LIB) endif if1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), win.amd64 linux.amd64) VBOX_LIB_VMM_LAZY = $(PATH_STAGE_LIB)/VMMR3LazyImp$(VBOX_SUFF_LIB) @@ -2190,7 +2199,10 @@ ifneq ($(KBUILD_TARGET),os2) # libIDL-config (for xpcom18a4) ifeq ($(origin VBOX_LIBIDL_CONFIG),undefined) - export VBOX_LIBIDL_CONFIG := $(firstword $(shell which libIDL-config-2 libIDL-config 2> /dev/null)) + ifeq ($(KBUILD_HOST),solaris) + VBOX_LIBIDL_CONFIG_FALLBACK = $(lastword $(wildcard $(KBUILD_DEVTOOLS)/solaris.amd64/libIDL/*/bin/libIDL-config-2)) + endif + export VBOX_LIBIDL_CONFIG := $(firstword $(which libIDL-config-2 libIDL-config $(VBOX_LIBIDL_CONFIG_FALLBACK)) libIDL-config-2-not-found) endif endif endif @@ -2665,17 +2677,17 @@ # Java stuff. ifeq ($(KBUILD_TARGET), darwin) - VBOX_JAVA_COMMANDS = $(firstword $(wildcard \ + VBOX_JAVA_BIN_PATH = $(firstword $(wildcard \ /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Commands \ /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Commands \ $(if-expr $(VBOX_XCODE_VERSION_MAJOR) >= 4,/System/Library/Frameworks/JavaVM.framework/Versions/A/Commands,) \ ) /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Commands) - VBOX_JAVA = $(VBOX_JAVA_COMMANDS)/java - VBOX_JAVAC = $(VBOX_JAVA_COMMANDS)/javac - VBOX_JAVAH = $(VBOX_JAVA_COMMANDS)/javah - VBOX_JAR = $(VBOX_JAVA_COMMANDS)/jar - VBOX_JAVADOC = $(VBOX_JAVA_COMMANDS)/javadoc - VBOX_WSIMPORT = $(VBOX_JAVA_COMMANDS)/wsimport + VBOX_JAVA = $(VBOX_JAVA_BIN_PATH)/java + VBOX_JAVAC = $(VBOX_JAVA_BIN_PATH)/javac + VBOX_JAVAH = $(VBOX_JAVA_BIN_PATH)/javah + VBOX_JAR = $(VBOX_JAVA_BIN_PATH)/jar + VBOX_JAVADOC = $(VBOX_JAVA_BIN_PATH)/javadoc + VBOX_WSIMPORT = $(VBOX_JAVA_BIN_PATH)/wsimport if $(VBOX_XCODE_VERSION_MAJOR) >= 4 # Lion (4.1-preview 5) has a broken CurrentJDK link. Blindly applying it to 4.0. VBOX_JAVA_INC = $(VBOX_PATH_MACOSX_SDK)/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers else @@ -2717,21 +2729,25 @@ VBOX_JAVA_HOME ?= c:/jdk endif - VBOX_JAVA ?= "$(VBOX_JAVA_HOME)/bin/java$(HOSTSUFF_EXE)" - VBOX_JAVAC = "$(VBOX_JAVA_HOME)/bin/javac$(HOSTSUFF_EXE)" - VBOX_JAVAH = "$(VBOX_JAVA_HOME)/bin/javah$(HOSTSUFF_EXE)" - VBOX_JAR = "$(VBOX_JAVA_HOME)/bin/jar$(HOSTSUFF_EXE)" - VBOX_JAVADOC = "$(VBOX_JAVA_HOME)/bin/javadoc$(HOSTSUFF_EXE)" + VBOX_JAVA_BIN_PATH ?= $(VBOX_JAVA_HOME)/bin + VBOX_JAVA ?= "$(VBOX_JAVA_BIN_PATH)/java$(HOSTSUFF_EXE)" + VBOX_JAVAC = "$(VBOX_JAVA_BIN_PATH)/javac$(HOSTSUFF_EXE)" + VBOX_JAVAH = "$(VBOX_JAVA_BIN_PATH)/javah$(HOSTSUFF_EXE)" + VBOX_JAR = "$(VBOX_JAVA_BIN_PATH)/jar$(HOSTSUFF_EXE)" + VBOX_JAVADOC = "$(VBOX_JAVA_BIN_PATH)/javadoc$(HOSTSUFF_EXE)" # With Java 11 wsimport was removed, usually part of a separate install now. - VBOX_WSIMPORT = $(firstword $(wildcard $(VBOX_JAVA_HOME)/bin/wsimport$(HOSTSUFF_EXE)) wsimport$(HOSTSUFF_EXE)) + VBOX_WSIMPORT = $(firstword $(wildcard $(VBOX_JAVA_BIN_PATH)/wsimport$(HOSTSUFF_EXE)) wsimport$(HOSTSUFF_EXE)) # correct for targets we care about - VBOX_MD_OS = $(KBUILD_TARGET) - VBOX_JAVA_INC = \ + VBOX_MD_OS = $(KBUILD_TARGET) + VBOX_JAVA_INC = \ $(VBOX_JAVA_HOME)/include \ $(VBOX_JAVA_HOME)/include/$(VBOX_MD_OS) endif # !darwin +# The first transform the almost usless openjdk versions like "javac 9-Ubuntu" into something the 2nd expression groks. VBOX_JAVA_FIGURE_VERSION = $(shell $(1) -version 2>&1 \ - | $(SED_EXT) -n -e 's|^[^ ]* *\([0-9][0-9]*\)\.\([0-9][0-9]*\)\.\([0-9][0-9]*\).*$(DOLLAR)|$$(expr \1 * 10000 + \2 * 100 + \3)|p' ) + | $(SED_EXT) -n \ + -e 's/ \([0-9][0-9]*\)\(-[[:alpha:]][[:alpha:]]\)/ \1.0.0\2/' \ + -e 's|^[^ ]* *\([0-9][0-9]*\)\.\([0-9][0-9]*\)\.\([0-9][0-9]*\).*$(DOLLAR)|$$(expr \1 * 10000 + \2 * 100 + \3)|p' ) # Test C and C++ files. $(PATH_OUT)/DynamicConfig.c $(PATH_OUT)/DynamicConfig.cpp: @@ -2745,10 +2761,10 @@ $(AUTOCFG) \ $(VBOX_GCC_PATH_CC) \ $(VBOX_GCC_PATH_CXX) \ - $(VBOX_GCC_LIBGCC) \ + $(VBOX_GCC_LIBGCC) \ $(VBOX_GCC32_PATH_CC) \ $(VBOX_GCC32_PATH_CXX) \ - $(VBOX_GCC32_LIBGCC) \ + $(VBOX_GCC32_LIBGCC) \ $(if-expr "$(KBUILD_HOST).$(KBUILD_HOST_ARCH)" == "solaris.amd64" && $(KBUILD_HOST_VERSION_MINOR) >= 11 \ , /platform/i86pc/kernel/$(KBUILD_HOST_ARCH)/unix,) \ | $(PATH_OUT)/DynamicConfig.c $(PATH_OUT)/DynamicConfig.cpp @@ -2800,6 +2816,8 @@ $(QUIET)$(APPEND) '$@' 'VBOX_GCC_Wno-stringop-overflow ?= $(call VBOX_GCC_CHECK_CXX,-Wno-stringop-overflow,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_Wno-stringop-truncation ?= $(call VBOX_GCC_CHECK_CXX,-Wno-stringop-truncation,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_Wno-cast-function-type ?= $(call VBOX_GCC_CHECK_CC,-Wno-cast-function-type,)' + $(QUIET)$(APPEND) '$@' 'VBOX_GCC_Wno-deprecated-declarations ?= $(call VBOX_GCC_CHECK_CC,-Wno-deprecated-declarations,)' + $(QUIET)$(APPEND) '$@' 'VBOX_GCC_Wno-implicit-fallthrough ?= $(call VBOX_GCC_CHECK_CC,-Wno-implicit-fallthrough,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_fno-stack-protector ?= $(call VBOX_GCC_CHECK_CC,-fno-stack-protector,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_fno-dwarf2-cfi-asm ?= $(call VBOX_GCC_CHECK_CC,-fno-dwarf2-cfi-asm,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_m64 ?= $(call VBOX_GCC_CHECK_CC,-m64,)' @@ -2808,6 +2826,7 @@ $(QUIET)$(APPEND) '$@' 'VBOX_GCC_mavx2 ?= $(call VBOX_GCC_CHECK_CC,-mavx2,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_no-pie ?= $(call VBOX_GCC_CHECK_CC,-no-pie,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_fdiagnostics-show-option ?= $(call VBOX_GCC_CHECK_CC,-fdiagnostics-show-option,)' + $(QUIET)$(APPEND) '$@' 'VBOX_GCC_fno-printf-return-value ?= $(call VBOX_GCC_CHECK_CC,-fno-printf-return-value,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_Wa_cma_nocompress_debug_sections ?= $(call VBOX_GCC_CHECK_CC,-Wa$(COMMA)--nocompress-debug-sections,)' # Produce code optimized for the most common IA32/AMD64/EM64T processors. Introduced with gcc version 4.2. $(QUIET)$(APPEND) '$@' 'VBOX_GCC_mtune-generic ?= $(call VBOX_GCC_CHECK_CC,-mtune=generic,)' @@ -2918,15 +2937,20 @@ endif $(QUIET)$(APPEND) '$@' 'VBOX_HAVE_XMLLINT ?= $(which xmllint)' if "$(KBUILD_HOST)" == "solaris" && $(KBUILD_HOST_VERSION_MAJOR) == 5 && $(KBUILD_HOST_VERSION_MINOR) == 11 -# uname -v can report "snv_XYZ" or "11.X" - $(QUIET)$(APPEND) '$@' 'VBOX_SOLARIS_11_VERSION ?= $(shell uname -v | sed "s/snv_//" | cut -f1 -d".")' -# FULLVERSION format e.g. "5.11-0.175.1.0.0.16.0", we're interested in 175 (VERSION), 1 (UPDATE), 16 (BUILD). - $(QUIET)$(APPEND) '$@' "VBOX_SOLARIS_11_FULLVERSION := $(shell pkg contents -H -t set -o pkg.fmri system/kernel | $(SED_EXT) -e '1!d' -e 's/^.*\@//;s/\:.*//;s/.*,//')" - $(QUIET)$(APPEND) '$@' 'ifeq ($$(VBOX_SOLARIS_11_VERSION),11)' - $(QUIET)$(APPEND) '$@' ' VBOX_SOLARIS_11_VERSION := $$(word 2, $$(subst ., ,$$(word 2,$$(subst -, ,$$(VBOX_SOLARIS_11_FULLVERSION)))))' +# BRANCH_VERSION format on S11 - S11.3: +# ..... +# e.g.: 0.175.3.32.0.4.0:20180427T232405Z - N.B. trunk_id = 0.175 +# BRANCH_VERSION format on S11.4: +# ...... +# e.g.: 11.4.0.0.1.10.0:20180702T173343Z +# We're interested in and . + $(QUIET)$(APPEND) '$@' "VBOX_SOLARIS_11_BRANCH_VERSION := $(shell pkg contents -H -t set -o pkg.fmri system/kernel | $(SED_EXT) -e '1!d' -e 's/^.*\-//;s/\:.*//;s/.*,//')" + $(QUIET)$(APPEND) '$@' 'ifeq ($$(word 2, $$(subst ., ,$$(VBOX_SOLARIS_11_BRANCH_VERSION))),175)' + $(QUIET)$(APPEND) '$@' ' VBOX_SOLARIS_11_UPDATE_VERSION := $$(word 3, $$(subst ., ,$$(VBOX_SOLARIS_11_BRANCH_VERSION)))' + $(QUIET)$(APPEND) '$@' 'else' + $(QUIET)$(APPEND) '$@' ' VBOX_SOLARIS_11_UPDATE_VERSION := $$(word 2, $$(subst ., ,$$(VBOX_SOLARIS_11_BRANCH_VERSION)))' $(QUIET)$(APPEND) '$@' 'endif' - $(QUIET)$(APPEND) '$@' 'VBOX_SOLARIS_11_UPDATE_VERSION := $$(word 3, $$(subst ., ,$$(word 2,$$(subst -, ,$$(VBOX_SOLARIS_11_FULLVERSION)))))' - $(QUIET)$(APPEND) '$@' 'VBOX_SOLARIS_11_BUILD_VERSION := $$(word 6, $$(subst ., ,$$(word 2,$$(subst -, ,$$(VBOX_SOLARIS_11_FULLVERSION)))))' + $(QUIET)$(APPEND) '$@' 'VBOX_SOLARIS_11_BUILD_VERSION := $$(word 6, $$(subst ., ,$$(VBOX_SOLARIS_11_BRANCH_VERSION)))' endif if1of ($(KBUILD_HOST), darwin freebsd solaris) $(QUIET)$(APPEND) '$@' 'VBOX_HOST_DTRACE_VERSION := $(shell dtrace -V)' @@ -3379,6 +3403,8 @@ $(error VBOX_SIGNING_MODE must be either 'test' or 'release'. The value '$(VBOX_SIGNING_MODE)' is not recognized.) endif VBOX_RETRY ?= $(ASH) $(KBUILD_DEVTOOLS)/bin/retry.sh + # temporary solution for a $(dir ...) equivalent which assumes that it gets a single path, possibly with spaces. + VBOX_DIRX = $(subst ?,$(SP),$(dir $(subst $(SP),?,$1))) # Corp code signing client. VBOX_CCS_CLIENT_JAR := $(firstword $(rsort \ $(wildcard $(KBUILD_DEVTOOLS)/common/ccs/v*/Client.jar)) \ @@ -3395,7 +3421,7 @@ VBOX_CCS_SIGN_CMD = $(VBOX_RETRY) $(VBOX_JAVA) -jar "$(VBOX_CCS_CLIENT_JAR)" \ sign -user "$(VBOX_CCS_USER)" -global_uid "$(VBOX_CCS_GLOBAL_UID)" \ -job_timeout 90 -server_timeout 75 -server "$(VBOX_CCS_SERVER)" \ - -sign_method "$1" -file_to_sign "$2" -signed_location "$(if $3,$3,$(dir $2))" $4 + -sign_method "$1" -file_to_sign "$2" -signed_location "$(if $3,$3,$(call VBOX_DIRX,$2))" $4 ifeq ($(KBUILD_HOST),win) # @@ -3421,21 +3447,28 @@ endif endif - VBOX_SIGNTOOL ?= $(VBOX_RETRY) "$(VBOX_PATH_SIGN_TOOLS)/signtool.exe" - VBOX_INF2CAT ?= $(VBOX_PATH_SELFSIGN)/inf2cat.exe + VBOX_SIGNTOOL ?= $(VBOX_RETRY) "$(VBOX_PATH_SIGN_TOOLS)/signtool.exe" + VBOX_SIGNTOOL_SHA1 ?= $(VBOX_SIGNTOOL) + VBOX_SIGNTOOL_SHA1_ORDERDEPS ?= + VBOX_SIGNTOOL_SHA2 ?= $(VBOX_SIGNTOOL) + VBOX_SIGNTOOL_SHA2_ORDERDEPS ?= + VBOX_SIGNTOOL_ORDERDEPS ?= $(VBOX_SIGNTOOL_SHA1_ORDERDEPS) $(VBOX_SIGNTOOL_SHA2_ORDERDEPS) + VBOX_INF2CAT ?= $(VBOX_PATH_SELFSIGN)/inf2cat.exe ifeq ($(VBOX_SIGNING_MODE),test) VBOX_CERTIFICATE_SUBJECT_NAME ?= MyTestCertificate VBOX_CERTIFICATE_SUBJECT_NAME_ARGS ?= /n "$(VBOX_CERTIFICATE_SUBJECT_NAME)" else # release - VBOX_CERTIFICATE_SUBJECT_NAME ?= Oracle Corporation + VBOX_CERTIFICATE_SUBJECT_NAME ?= VirtualBox 2022 VBOX_CERTIFICATE_SUBJECT_NAME_ARGS ?= /n "$(VBOX_CERTIFICATE_SUBJECT_NAME)" /a #VBOX_CERTIFICATE_FINGERPRINT ?= 7e 92 b6 6b e5 1b 79 d8 ce 3f f2 5c 15 c2 df 6a b8 c7 f2 f2 #VBOX_CERTIFICATE_FINGERPRINT ?= 5b de fe 58 0a 81 66 61 cd b5 7a 57 10 7b f4 18 74 86 ef cc - VBOX_CERTIFICATE_FINGERPRINT ?= 6f 47 42 06 bc bb 39 1b b8 2b a9 e5 dc 03 02 de f3 7a eb be + #VBOX_CERTIFICATE_FINGERPRINT ?= 6f 47 42 06 bc bb 39 1b b8 2b a9 e5 dc 03 02 de f3 7a eb be + VBOX_CERTIFICATE_FINGERPRINT ?= 5f 0b fe c5 53 17 c1 25 5a a4 7f cd bc 49 a2 fb 61 09 03 cc #VBOX_CROSS_CERTIFICATE_FILE ?= $(VBOX_PATH_SELFSIGN)/VeriSign Class 3 Public Primary Certification Authority - G5.cer - VBOX_CROSS_CERTIFICATE_FILE ?= $(VBOX_PATH_SELFSIGN)/DigiCert Assured ID Root CA.crt - VBOX_TSA_URL ?= http://timestamp.verisign.com/scripts/timstamp.dll + #VBOX_CROSS_CERTIFICATE_FILE ?= $(VBOX_PATH_SELFSIGN)/DigiCert Assured ID Root CA.crt + #VBOX_TSA_URL ?= http://timestamp.verisign.com/scripts/timstamp.dll - Appears to be broken between 2020-12-25 and 2020-12-30 (bugref:9849). + VBOX_TSA_URL ?= http://timestamp.digicert.com endif if !defined(VBOX_CROSS_CERTIFICATE_FILE_ARGS) && defined(VBOX_CROSS_CERTIFICATE_FILE) VBOX_CROSS_CERTIFICATE_FILE_ARGS = /ac "$(VBOX_CROSS_CERTIFICATE_FILE)" @@ -3456,9 +3489,11 @@ VBOX_CERTIFICATE_SHA2_SUBJECT_NAME_ARGS ?= /n "$(VBOX_CERTIFICATE_SHA2_SUBJECT_NAME)" /a #VBOX_CERTIFICATE_SHA2_FINGERPRINT ?= 31 31 bb 58 8b 19 9e 6e 85 0f d3 35 82 b0 c5 82 55 e1 6c 49 #VBOX_CERTIFICATE_SHA2_FINGERPRINT ?= 22 05 6a 4d 46 2e 3d 2b b2 c3 2f bf b0 5b 84 c4 65 9c f7 fe - VBOX_CERTIFICATE_SHA2_FINGERPRINT ?= 17 3a 19 bf 8e 62 72 be 25 04 d3 08 aa 68 b1 b0 0e 03 33 2c - VBOX_CROSS_CERTIFICATE_SHA2_FILE ?= $(VBOX_PATH_SELFSIGN)/VeriSign Class 3 Public Primary Certification Authority - G5.cer - VBOX_TSA_SHA2_URL ?= http://sha256timestamp.ws.symantec.com/sha256/timestamp + #VBOX_CERTIFICATE_SHA2_FINGERPRINT ?= 17 3a 19 bf 8e 62 72 be 25 04 d3 08 aa 68 b1 b0 0e 03 33 2c + VBOX_CERTIFICATE_SHA2_FINGERPRINT ?= 30 65 6f ca 8c 48 b1 d9 86 23 a9 4b 40 a6 bc 98 bd 87 bf ad + VBOX_CROSS_CERTIFICATE_SHA2_FILE ?= $(VBOX_PATH_SELFSIGN)/DigiCert Assured ID Root CA.crt + #VBOX_TSA_SHA2_URL ?= http://sha256timestamp.ws.symantec.com/sha256/timestamp - phase out for consistency reasons + VBOX_TSA_SHA2_URL ?= http://timestamp.digicert.com endif if !defined(VBOX_CROSS_CERTIFICATE_SHA2_FILE_ARGS) && defined(VBOX_CROSS_CERTIFICATE_SHA2_FILE) VBOX_CROSS_CERTIFICATE_SHA2_FILE_ARGS = /ac "$(VBOX_CROSS_CERTIFICATE_SHA2_FILE)" @@ -3483,28 +3518,28 @@ # @param 2 File description. Optional. # @param 3 Additional parameters. Optional. # @param 4 Set to 2 if the expression will be expanded twice before chopped into commands (for _CMDS). - # @param 5 Disables dual signing if non-empty. + # @param 5 Disables dual signing if non-empty, picking the SHA2 signature (since 2022-07-18). # @param 6 non-zero for alternative command separator. This is used for generating repacking scripts. ifndef VBOX_SIGN_FILE_FN if $(intersects win all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) - VBOX_SIGN_FILE_FN = $(call VBOX_CCS_SIGN_CMD,driver$(if-expr "$3" == "/ph",_pagehash,),$1,$(dir $1),-digest_algo SHA1) \ + VBOX_SIGN_FILE_FN = $(call VBOX_CCS_SIGN_CMD,driver$(if-expr "$3" == "/ph",_pagehash,),$1,,-digest_algo $(if-expr "$5" == "",SHA1,SHA2)) \ $(if-expr "$5" == "",\ $(if-expr "$6" == "",$(if-expr "$4" == "2",$$(NLTAB),$(NLTAB)),$6) \ - $(call VBOX_CCS_SIGN_CMD,driver$(if-expr "$3" == "/ph",_pagehash,),$1,$(dir $1),-dual_sign -digest_algo SHA2)) + $(call VBOX_CCS_SIGN_CMD,driver$(if-expr "$3" == "/ph",_pagehash,),$1,,-dual_sign -digest_algo SHA2)) else ifdef VBOX_CERTIFICATE_SHA2_SUBJECT_NAME - VBOX_SIGN_FILE_FN = $(VBOX_SIGNTOOL) \ - sign /fd sha1\ + ifdef VBOX_CERTIFICATE_SUBJECT_NAME + VBOX_SIGN_FILE_FN = $(if-expr "$5" == "",$(VBOX_SIGNTOOL_SHA1) \ + sign /fd sha1 \ $(VBOX_CROSS_CERTIFICATE_FILE_ARGS) \ $(VBOX_CERTIFICATE_STORE_ARGS) \ $(VBOX_CERTIFICATE_SUBJECT_NAME_ARGS) \ - $(VBOX_CERTIFICATE_FINGERPRINT_ARGS) \ + $(VBOX_CERTIFICATE_FINGERPRINT_ARGS) \ $(VBOX_TSA_URL_ARGS) \ $(if $(strip $(2)),/d "$(strip $(2))",) \ $(3) \ - $(1) \ - $(if-expr "$5" == "",\ - $(if-expr "$6" == "",$(if-expr "$4" == "2",$$(NLTAB),$(NLTAB)),$6)$(VBOX_SIGNTOOL) \ - sign /as /fd sha256 \ + "$(1)" \ + $(if-expr "$6" == "",$(if-expr "$4" == "2",$$(NLTAB),$(NLTAB)),$6))$(VBOX_SIGNTOOL_SHA2) \ + sign $(if-expr "$5" == "",/as,) /fd sha256 \ $(VBOX_CROSS_CERTIFICATE_SHA2_FILE_ARGS) \ $(VBOX_CERTIFICATE_SHA2_STORE_ARGS) \ $(VBOX_CERTIFICATE_SHA2_SUBJECT_NAME_ARGS) \ @@ -3512,9 +3547,22 @@ $(VBOX_TSA_SHA2_URL_ARGS) \ $(if $(strip $(2)),/d "$(strip $(2))",) \ $(3) \ - $(1),) + "$(1)" + else + VBOX_SIGN_FILE_FN = $(VBOX_SIGNTOOL_SHA2) \ + sign /fd sha256 \ + $(VBOX_CROSS_CERTIFICATE_SHA2_FILE_ARGS) \ + $(VBOX_CERTIFICATE_SHA2_STORE_ARGS) \ + $(VBOX_CERTIFICATE_SHA2_SUBJECT_NAME_ARGS) \ + $(VBOX_CERTIFICATE_SHA2_FINGERPRINT_ARGS) \ + $(VBOX_TSA_SHA2_URL_ARGS) \ + $(if $(strip $(2)),/d "$(strip $(2))",) \ + $(3) \ + "$(1)" + endif else - VBOX_SIGN_FILE_FN = $(VBOX_SIGNTOOL) sign \ + VBOX_SIGN_FILE_FN = $(VBOX_SIGNTOOL) \ + sign /fd $(firstword $(VBOX_TEST_SIGN_ALGORITHM) sha256) \ $(VBOX_CROSS_CERTIFICATE_FILE_ARGS) \ $(VBOX_CERTIFICATE_STORE_ARGS) \ $(VBOX_CERTIFICATE_SUBJECT_NAME_ARGS) \ @@ -3522,7 +3570,7 @@ $(VBOX_TSA_URL_ARGS) \ $(if $(strip $(2)),/d "$(strip $(2))",) \ $(3) \ - $(1) + "$(1)" endif endif @@ -3531,18 +3579,49 @@ # @param 2 The directory to put the signed file in. Defaults to $(dir $1). ifndef VBOX_SIGN_EV_FILE_FN if $(intersects win_ev all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) - VBOX_SIGN_EV_FILE_FN = $(call VBOX_CCS_SIGN_CMD,microsoftev,$1,$(if $2,$2,$(dir $1))) + VBOX_SIGN_EV_FILE_FN = $(call VBOX_CCS_SIGN_CMD,microsoftev,$1,$(if $2,$2,)) else ifdef VBOX_CERTIFICATE_EV_SUBJECT_NAME - VBOX_SIGN_EV_FILE_FN = $(VBOX_SIGNTOOL) \ - sign /as /fd sha256 \ + VBOX_SIGN_EV_FILE_FN = $(VBOX_SIGNTOOL_SHA2) \ + sign /fd sha256 \ $(VBOX_CERTIFICATE_EV_STORE_ARGS) \ $(VBOX_CERTIFICATE_EV_SUBJECT_NAME_ARGS) \ $(VBOX_CERTIFICATE_EV_FINGERPRINT_ARGS) \ $(VBOX_TSA_SHA2_URL_ARGS) \ - $(1) + "$(1)" endif endif + ## Local SHA-1 and SHA-256 signatures with EV SHA-256 signature from corp code signing. + # + # This builds on Plan B, since the corp code signing always replaces existing + # signatures. Since we're signing more, though, we do things slightly differently + # so we can apply this to VBOX_RTSIGNTOOL as well - only that didn't work because + # kmk tries to help windows caching images it executes. So HACK ALERT on that. + # + # So, here is what we do. + # 1. Sign $1 using the regular signing, probably dual signing it using local certs. + # 2. Make temporary copy of $1 as $1.dual + # 3. Do SHA-256 corp code signing of $1 + # 4. Add the SHA-256 signature from $1 to $1.dual using bldRTSignTool. + # 5. Replace $1 with $1.dual. + # + # @param 1 The file to sign. + # @param 2 File description. Optional. + # @param 3 Additional parameters. Optional. + # @param 4 Set to 2 if the expression will be expanded twice before chopped into commands (for _CMDS). + # @param 5 Disables dual & tripple signing if non-empty. + # @param 6 Disables tripple signing if non-empty. + # + # @remarks The parameters are the same as VBOX_SIGN_FILE_FN. + VBOX_SIGN_IMAGE_WITH_EV_FN = $(call VBOX_SIGN_FILE_FN,$1,$2,$3,$4,$5)$(if-expr "$5" == "" && "$(target)" != "bldRTSignTool",\ + $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(RM) -f -- "$1.dual" \ + $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(CP) -- "$1" "$1.dual" \ + $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(call VBOX_CCS_SIGN_CMD,microsoftev,$1,,-digest_algo SHA2) \ + $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(VBOX_RTSIGNTOOL) add-nested-$(if-expr "$(suffix $1)" == ".cat",cat,exe)-signature -v "$1.dual" "$1" \ + $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(MV) -f -- "$1.dual" "$1" \ + ,) + + ## Corp code signing for drivers and catalogs, plan B. # # Since the corp code signing cannot dual signing and doesn't even have a @@ -3560,7 +3639,7 @@ # @param 5 Disables dual signing if non-empty. # # @remarks The parameters are the same as VBOX_SIGN_FILE_FN. - VBOX_SIGN_IMAGE_PLAN_B_FN = $(VBOX_SIGNTOOL) \ + VBOX_SIGN_IMAGE_PLAN_B_FN = $(VBOX_SIGNTOOL_SHA1) \ sign /fd sha1\ $(VBOX_CROSS_CERTIFICATE_FILE_ARGS) \ $(VBOX_CERTIFICATE_STORE_ARGS) \ @@ -3569,11 +3648,11 @@ $(VBOX_TSA_URL_ARGS) \ $(if $(strip $(2)),/d "$(strip $(2))",) \ $(3) \ - $(1) \ + "$(1)" \ $(if-expr "$5" == "",\ $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(RM) -f -- "$1.ccs" \ $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(CP) -- "$1" "$1.ccs" \ - $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(call VBOX_CCS_SIGN_CMD,driver$(if-expr "$3" == "/ph",_pagehash,),$1.ccs,$(dir $1.ccs),-digest_algo SHA2) \ + $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(call VBOX_CCS_SIGN_CMD,driver$(if-expr "$3" == "/ph",_pagehash,),$1.ccs,,-digest_algo SHA2) \ $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(VBOX_RTSIGNTOOL) add-nested-$(if-expr "$(suffix $1)" == ".cat",cat,exe)-signature -v "$1" "$1.ccs" \ $(if-expr "$4" == "2",$$(NLTAB),$(NLTAB))$(RM) -f -- "$1.ccs" \ ,) @@ -3582,14 +3661,21 @@ # @param 1 The file to sign. # @param 2 File description. Optional. # @param 3 Set to 2 if the expression will be expanded twice before chopped into commands (for _CMDS). - VBOX_SIGN_IMAGE_FN ?= $(call VBOX_SIGN_FILE_FN,$(1),$(2),/ph,$(3)) + if1of (win_with_ev,$(VBOX_WITH_CORP_CODE_SIGNING)) + VBOX_SIGN_IMAGE_FN ?= $(call VBOX_SIGN_IMAGE_WITH_EV_FN,$(1),$(2),/ph,$(3)) + VBOX_SIGN_IMAGE_ORDERDEPS ?= $(VBOX_RTSIGNTOOL) $(VBOX_SIGNTOOL_ORDERDEPS) + else + VBOX_SIGN_IMAGE_FN ?= $(call VBOX_SIGN_FILE_FN,$(1),$(2),/ph,$(3)) + VBOX_SIGN_IMAGE_ORDERDEPS ?= $(VBOX_SIGNTOOL_ORDERDEPS) + endif ## Commands for signing a driver image after link. if $(intersects win_planb,$(VBOX_WITH_CORP_CODE_SIGNING)) - VBOX_SIGN_DRIVER_CMDS ?= $(if $(eq $(tool_do),LINK_LIBRARY),,$(call VBOX_SIGN_IMAGE_PLAN_B_FN,$(out),,/ph,2)) - VBOX_SIGN_DRIVER_ORDERDEPS ?= $(VBOX_RTSIGNTOOL) + VBOX_SIGN_DRIVER_CMDS ?= $(if $(eq $(tool_do),LINK_LIBRARY),,$(call VBOX_SIGN_IMAGE_PLAN_B_FN,$(out),,/ph,2)) + VBOX_SIGN_DRIVER_ORDERDEPS ?= $(VBOX_RTSIGNTOOL) $(VBOX_SIGNTOOL_ORDERDEPS) else - VBOX_SIGN_DRIVER_CMDS ?= $(if $(eq $(tool_do),LINK_LIBRARY),,$(call VBOX_SIGN_IMAGE_FN,$(out),,2)) + VBOX_SIGN_DRIVER_CMDS ?= $(if $(eq $(tool_do),LINK_LIBRARY),,$(call VBOX_SIGN_IMAGE_FN,$(out),,2)) + VBOX_SIGN_DRIVER_ORDERDEPS ?= $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_SIGN_IMAGE_ORDERDEPS)) endif ## Create a security catalog file. @@ -3618,10 +3704,18 @@ # Go nuts, sign everything. if "$(VBOX_SIGNING_MODE)" == "release" || defined(VBOX_WITH_HARDENING) ## Commands for signing an executable or a dll image after link. - VBOX_SIGN_IMAGE_CMDS ?= $(if $(eq $(tool_do),LINK_LIBRARY),,$(call VBOX_SIGN_IMAGE_FN,$(out),,2)) + VBOX_SIGN_IMAGE_CMDS ?= $(if $(eq $(tool_do),LINK_LIBRARY),,$(call VBOX_SIGN_IMAGE_FN,$(out),,2)) + VBOX_SIGN_IMAGE_CMDS_ORDERDEPS ?= $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_SIGN_IMAGE_ORDERDEPS)) endif ## Enable signing of the additions. VBOX_SIGN_ADDITIONS ?= 1 + ## Set if we should incldue the legacy timestamp CA. + ifndef VBOX_WITH_VBOX_LEGACY_TS_CA + if "$(findstring 55287c0d517e273696d67c690dd5d9f0a1d6d725,$(VBOX_TSA_URL_ARGS))" != "" + VBOX_WITH_VBOX_LEGACY_TS_CA = 1 + endif + endif + VBOX_LEGACY_TS_CA_FILE = $(PATH_ROOT)/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.crt else ifeq ($(KBUILD_HOST),darwin) # @@ -3648,6 +3742,10 @@ VBOX_DARWIN_KEXT_SIGN_FILES = CodeResources endif + # Always enable the hardened runtime when signing. Can be disabled if + # trying to build on quite old macOS, which will likely need some effort. + VBOX_WITH_MACOS_HARDENED_RUNTIME ?= 1 + ## # Corp Code Notarization command line. Modifies the file because the tickets are stapled. # @param 1 The file to be submitted for signing. @@ -3656,7 +3754,7 @@ VBOX_CCS_NOTARIZE_CMD = $(VBOX_RETRY) $(VBOX_JAVA) -jar "$(VBOX_CCS_CLIENT_JAR)" \ mac_notarize -user "$(VBOX_CCS_USER)" -global_uid "$(VBOX_CCS_GLOBAL_UID)" \ -job_timeout 90 -server_timeout 75 -server "$(VBOX_CCS_SERVER)" \ - -file_to_notarize "$1" -bundle_id "$2" -download_location "$(if $3,$3,$(dir $1))" + -file_to_notarize "$1" -bundle_id "$2" -download_location "$(if $3,$3,$(call VBOX_DIRX,$1))" ## Sign an application bundle, framework or kernel extension. # @param 1 The bundle to sign. @@ -3681,7 +3779,7 @@ $(if-expr defined(VBOX_TSA_URL),--timestamp="$(VBOX_TSA_URL)") \ $(3) \ $(VBOX_CERTIFICATE_SUBJECT_NAME_ARGS) \ - $(1) $(if $(2),--identifier "$(2)",) + "$(1)" $(if $(2),--identifier "$(2)",) endif ## Sign a Mach-O image. @@ -3704,7 +3802,7 @@ $(if-expr defined(VBOX_TSA_URL),--timestamp="$(VBOX_TSA_URL)") \ $(VBOX_CERTIFICATE_SUBJECT_NAME_ARGS) \ $(3) \ - $(1) \ + "$(1)" \ $(if $(2),--identifier "$(2)",) endif @@ -3746,7 +3844,7 @@ $(if-expr defined(VBOX_TSA_URL),--timestamp="$(VBOX_TSA_URL)") \ $(if $(2),--identifier "$(2)",) \ $(VBOX_CERTIFICATE_SUBJECT_NAME_ARGS) \ - $(1) + "$(1)" endif ## Sign a DMG image. @@ -3837,7 +3935,7 @@ $(VBOX_CERTIFICATE_FILE_ARGS) \ $(VBOX_TOKEN_NAME_ARGS) \ $(VBOX_PIN_ARGS) \ - -e $(1) + -e "$(1)" ## Commands for signing a driver image after link. if $(intersects solaris all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) @@ -3914,7 +4012,7 @@ $(1)_VBOX_RE_SIGNED_$(n) := 1 $(eval $(1)_CLEAN += $$($(1)_0_OUTDIR)/$(n)) - $$($(1)_0_OUTDIR)/$(n): $(2) $(VBOX_VERSION_STAMP) | $$(dir $$@) + $$($(1)_0_OUTDIR)/$(n): $(2) $(VBOX_VERSION_STAMP) | $$(dir $$@) $(VBOX_SIGN_IMAGE_ORDERDEPS) $(call MSG_TOOL,SIGNTOOL,,$<,$@) $(RM) -f -- "$@" $(CP) -- "$<" "$@" @@ -3946,13 +4044,14 @@ # @param 3 Optional icon file. # @param 4 The template base name. # @param 5 Additional RC options. +# @param 6 Original filename to use. # # @remarks ASSUMES RCFLAGS isn't a simple variable (var := value). define VBOX_SET_VER_INFO_INTERNAL ifeq ($(KBUILD_TARGET),win) $(1)_SOURCES.win += $(PATH_ROOT)/src/VBox/Artwork/win/$(4) $(1)_RCFLAGS += /nologo /dIN_INTERNAL_NAME="\"$(1)\"" /dIN_FILE_DESCRIPTION="\"$(2)\"" \ - /dIN_ORIGINAL_NAME="\"$$(notdir $$($(1)_1_INST_TARGET))\"" \ + /dIN_ORIGINAL_NAME="\"$(if $(6),$6,$$(notdir $$($(1)_1_INST_TARGET)))\"" \ $(if $(3), /dIN_ICON_FILE="\"$(subst \\,/,$(strip $(3)))\"") $(5) $$$$($(1)_0_OUTDIR)/src/VBox/Artwork/win/$(basename $(4)).res: \ $(PATH_ROOT)/include/VBox/version.h $$$$(VBOX_VERSION_HEADER) @@ -3978,7 +4077,8 @@ # @param 1 The target name. # @param 2 The description # @param 3 Optional icon file. -VBOX_SET_VER_INFO_EXE = $(evalcall2 VBOX_SET_VER_INFO_INTERNAL,$1,$2,$3,TemplateExe.rc,) +# @param 4 Optional OriginalFilename value to use. Defaults to target name w/o dir. +VBOX_SET_VER_INFO_EXE = $(evalcall2 VBOX_SET_VER_INFO_INTERNAL,$1,$2,$3,TemplateExe.rc,,$4) ## # Macro for setting driver version information and description. @@ -4150,6 +4250,19 @@ $(PATH_STAGE_LIB)/VBox-libssl-x86$(VBOX_SUFF_LIB) \ $(PATH_STAGE_LIB)/VBox-libcrypto-x86$(VBOX_SUFF_LIB) +ifdef VBOX_WITH_BLD_RTSIGNTOOL_SIGNING + SDK_VBOX_OPENSSL_BLDPROG := OpenSSL - Build tools verison (i.e. bldRTSignTool) + SDK_VBOX_OPENSSL_BLDPROG_INCS ?= $(SDK_VBOX_OPENSSL_VBOX_DEFAULT_INCS) + SDK_VBOX_OPENSSL_BLDPROG_ORDERDEPS ?= $(crypto-headers_1_TARGET) + if !defined(VBOX_ONLY_SDK) \ + && ("$(SDK_VBOX_OPENSSL_INCS)" == "$(SDK_VBOX_OPENSSL_VBOX_DEFAULT_INCS)") + SDK_VBOX_OPENSSL_BLDPROG_DEPS ?= $(SDK_VBOX_OPENSSL_INCS)/openssl/openssl-mangling.h + endif + SDK_VBOX_OPENSSL_BLDPROG_LIBS ?= \ + $(PATH_STAGE_LIB)/VBoxBldProg-libssl$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBoxBldProg-libcrypto$(VBOX_SUFF_LIB) +endif + SDK_VBOX_OPENSSL2 = What you should be using. SDK_VBOX_OPENSSL2_EXTENDS = VBOX_OPENSSL if "$(SDK_VBOX_OPENSSL_INCS)" == "$(SDK_VBOX_OPENSSL_VBOX_DEFAULT_INCS)" @@ -4210,7 +4323,7 @@ VBOX_WITH_ADDITIONS = else ifeq ($(VBOX_SOLARIS_VERSION),511) # OSS audio support for Solaris - VBOX_WITH_AUDIO_OSS := $(if-expr $(VBOX_SOLARIS_11_VERSION) >= 115,1,) + VBOX_WITH_AUDIO_OSS := 1 endif # XPCOM namespace cleanup issue with Solaris GCC 4.5.2 and newer, see @bugref{5838}. @@ -4270,6 +4383,13 @@ endef TOOL_DTraceAndVBoxTpG_DTRACE_OBJ_NOT_NEEDED = $(TOOL_StandardDTrace_DTRACE_OBJ_NOT_NEEDED) TOOL_DTraceAndVBoxTpG_DTRACE_OBJ_CMDS = $(TOOL_StandardDTrace_DTRACE_OBJ_CMDS) +define TOOL_DTraceAndVBoxTpG_DTRACE_OBJ_CMDS # remove when fixed upstream + $(QUIET)$(TOOL_StandardDTrace_DTRACE) \ + $(if-expr $(intersects $(bld_trg_arch),$(KBUILD_ARCHES_64)),-64,-32) \ + $(flags) \ + -o "$(out)" -s "$(source)" \ + $$(filter-out %-dtrace-object-format.o %.gch, $$($(target)_2_OBJS)) +endef ifdef VBOX_WITH_RAW_MODE @@ -4316,6 +4436,7 @@ TEMPLATE_VBoxRc_LIBS += \ $(PATH_STAGE_LIB)/RuntimeRCStub$(VBOX_SUFF_LIB) TEMPLATE_VBoxRc_POST_CMDS = $(VBOX_SIGN_IMAGE_CMDS) + TEMPLATE_VBoxRc_ORDERDEPS = $(VBOX_SIGN_IMAGE_CMDS_ORDERDEPS) endif # pe ifeq ($(VBOX_LDR_FMT32),elf) @@ -4469,11 +4590,14 @@ TEMPLATE_VBoxR0_TOOL = $(VBOX_GCC_TOOL) TEMPLATE_VBoxR0_CFLAGS = -fno-pie -nostdinc -g $(VBOX_GCC_pipe) $(VBOX_GCC_WERR) $(VBOX_GCC_PEDANTIC_C) \ $(VBOX_GCC_Wno-variadic-macros) $(VBOX_GCC_R0_OPT) $(VBOX_GCC_R0_FP) -fno-strict-aliasing -fno-exceptions \ - $(VBOX_GCC_fno-stack-protector) -fno-common $(VBOX_GCC_fvisibility-hidden) -std=gnu99 $(VBOX_GCC_IPRT_FMT_CHECK) + $(VBOX_GCC_fno-stack-protector) -fno-common -ffreestanding $(VBOX_GCC_fvisibility-hidden) -std=gnu99 $(VBOX_GCC_IPRT_FMT_CHECK) TEMPLATE_VBoxR0_CXXFLAGS = -fno-pie -nostdinc -g $(VBOX_GCC_pipe) $(VBOX_GCC_WERR) $(VBOX_GCC_PEDANTIC_CXX) \ $(VBOX_GCC_Wno-variadic-macros) $(VBOX_GCC_R0_OPT) $(VBOX_GCC_R0_FP) -fno-strict-aliasing -fno-exceptions \ $(VBOX_GCC_fno-stack-protector) -fno-common $(VBOX_GCC_fvisibility-inlines-hidden) $(VBOX_GCC_fvisibility-hidden) \ -fno-rtti $(VBOX_GCC_IPRT_FMT_CHECK) + if $(VBOX_GCC_VERSION_CC) >= 40500 # 4.1.2 complains, 4.5.2 is okay, didn't check which version inbetween made it okay with g++. +TEMPLATE_VBoxR0_CXXFLAGS += -ffreestanding + endif TEMPLATE_VBoxR0_CFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables -ffreestanding TEMPLATE_VBoxR0_CXXFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables TEMPLATE_VBoxR0_CXXFLAGS.freebsd = -ffreestanding @@ -4658,12 +4782,20 @@ $(QUIET)$$(RM) -Rf -- "$$(PATH_TARGET)/tst$(1)_mod" $(QUIET)$$(MKDIR) -p -- "$$(PATH_TARGET)/tst$(1)_mod" $(QUIET)$$(CP) -R -- "$$(PATH_STAGE_BIN)/../$$($(1)_INST)" "$$(PATH_TARGET)/tst$(1)_mod/" - + $(QUIET)make KBUILD_VERBOSE=$(KBUILD_VERBOSE) KERN_DIR=$(KERN_DIR) VBOX_KERN_QUIET=1 -C $$(PATH_TARGET)/tst$(1)_mod clean + + $(QUIET)make KBUILD_VERBOSE=$(KBUILD_VERBOSE) KERN_DIR=$(KERN_DIR) VBOX_KERN_QUIET=1 \ + KBUILD_TYPE= KBUILD_TARGET= KBUILD_TARGET_ARCH= KBUILD_HOST= KBUILD_HOST_ARCH= \ + KBUILD_TYPE= KBUILD_TARGET= KBUILD_TARGET_ARCH= KBUILD_TARGET_CPU= KBUILD_HOST= KBUILD_HOST_ARCH= KBUILD_HOST_CPU= \ + BUILD_TYPE= BUILD_TARGET= BUILD_TARGET_ARCH= BUILD_TARGET_CPU= BUILD_PLATFORM= BUILD_PLATFORM_ARCH= BUILD_PLATFORM_CPU= \ + -C $$(PATH_TARGET)/tst$(1)_mod clean ifneq ($(2),) $(QUIET)$$(CP) -f -- "$$<" "$$(PATH_TARGET)/tst$(1)_mod/Module.symvers" endif + $(QUIET)$(REDIRECT_EXT) -d2=1 -w1 "$$(PATH_TARGET)/tst$(1)_mod/make.err" -- \ - make KBUILD_VERBOSE=$(KBUILD_VERBOSE) KERN_DIR=$(KERN_DIR) VBOX_KERN_QUIET=1 $(if $(2),KBUILD_EXTRA_SYMBOLS="$$(PATH_TARGET)/tst$(1)_mod/Module.symvers",) $(if-expr $(KMK_OPTS_JOBS) != 0,JOBS=$(KMK_OPTS_JOBS),) -C $$(PATH_TARGET)/tst$(1)_mod + make KBUILD_VERBOSE=$(KBUILD_VERBOSE) KERN_DIR=$(KERN_DIR) VBOX_KERN_QUIET=1 $(if $(2),KBUILD_EXTRA_SYMBOLS="$$(PATH_TARGET)/tst$(1)_mod/Module.symvers",) $(if-expr $(KMK_OPTS_JOBS) != 0,JOBS=$(KMK_OPTS_JOBS),) \ + VBOX_KBUILD_TYPE=$(KBUILD_TYPE) VBOX_KBUILD_TARGET_ARCH=$(KBUILD_TARGET_ARCH) \ + KBUILD_TYPE= KBUILD_TARGET= KBUILD_TARGET_ARCH= KBUILD_TARGET_CPU= KBUILD_HOST= KBUILD_HOST_ARCH= KBUILD_HOST_CPU= \ + BUILD_TYPE= BUILD_TARGET= BUILD_TARGET_ARCH= BUILD_TARGET_CPU= BUILD_PLATFORM= BUILD_PLATFORM_ARCH= BUILD_PLATFORM_CPU= \ + -C $$(PATH_TARGET)/tst$(1)_mod $(CAT) "$$(PATH_TARGET)/tst$(1)_mod/make.err" $(QUIET)! grep "^WARNING: .* undefined!$$$$" "$$(PATH_TARGET)/tst$(1)_mod/make.err" if1of ($(USERNAME), bird) @@ -5141,6 +5273,7 @@ $(PATH_TOOL_$(TEMPLATE_VBOXR3EXE_TOOL.win.amd64)_LIB)/msvcprt$(VBOX_VCC_CRT_TYPE).lib \ $(PATH_TOOL_$(TEMPLATE_VBOXR3EXE_TOOL.win.amd64)_LIB)/oldnames.lib TEMPLATE_VBOXR3EXE_POST_CMDS = $(VBOX_SIGN_IMAGE_CMDS) + TEMPLATE_VBOXR3EXE_ORDERDEPS = $(VBOX_SIGN_IMAGE_CMDS_ORDERDEPS) if defined(VBOX_WITH_MORE_NT4_COMPAT_BINARIES) && "$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" == "win.x86" TEMPLATE_VBOXR3EXE_POST_CMDS.win.x86 = $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_PE_SET_VERSION) $(out)$$(NLTAB)$(TEMPLATE_VBOXR3EXE_POST_CMDS)) TEMPLATE_VBOXR3EXE_LNK_DEPS.win.x86 = $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_PE_SET_VERSION)) @@ -6057,6 +6190,7 @@ $(PATH_TOOL_$(TEMPLATE_VBOXMAINEXE_TOOL.win.amd64)_LIB)/msvcprt$(VBOX_VCC_CRT_TYPE).lib \ $(PATH_TOOL_$(TEMPLATE_VBOXMAINEXE_TOOL.win.amd64)_LIB)/oldnames.lib TEMPLATE_VBOXMAINEXE_POST_CMDS = $(VBOX_SIGN_IMAGE_CMDS) + TEMPLATE_VBOXMAINEXE_ORDERDEPS = $(VBOX_SIGN_IMAGE_CMDS_ORDERDEPS) else # the GCC guys: @@ -6436,9 +6570,10 @@ TEMPLATE_VBoxR0ExtPack_DEFS = VBOX_IN_EXTPACK VBOX_IN_EXTPACK_R0 if1of ($(VBOX_LDR_FMT), pe lx) TEMPLATE_VBoxR0ExtPack_LIBS = \ - $(PATH_STAGE_LIB)/VMMR0Imp$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/SUPR0$(VBOX_SUFF_LIB) + $(PATH_STAGE_LIB)/VMMR0Imp$(VBOX_SUFF_LIB) endif + TEMPLATE_VBoxR0ExtPack_LIBS += \ + $(VBOX_LIB_SUPR0) TEMPLATE_VBoxRcExtPack = For the raw-mode context extension pack modules. TEMPLATE_VBoxRcExtPack_EXTENDS = VBoxRc @@ -6645,6 +6780,7 @@ $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/oldnames.lib \ $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/delayimp.lib TEMPLATE_VBOXQTGUIEXE_POST_CMDS = $(VBOX_SIGN_IMAGE_CMDS) + TEMPLATE_VBOXQTGUIEXE_ORDERDEPS = $(VBOX_SIGN_IMAGE_CMDS_ORDERDEPS) else # the gcc guys: TEMPLATE_VBOXQTGUIEXE_TOOL = $(VBOX_GCC_TOOL) @@ -6663,6 +6799,9 @@ ifdef VBOX_WITH_NO_GCC_WARNING_POLICY TEMPLATE_VBOXQTGUIEXE_CXXFLAGS += $(VBOX_GCC_WERR) endif + ifn1of ($(USERNAME),dsen serkan) # Bunch of stuff deprecated after 5.6.*. These guys knows how to deal with it. :) + TEMPLATE_VBOXQTGUIEXE_CXXFLAGS += $(VBOX_GCC_Wno-deprecated-declarations) + endif TEMPLATE_VBOXQTGUIEXE_CXXFLAGS.x86 = -m32 TEMPLATE_VBOXQTGUIEXE_CXXFLAGS.amd64 = -m64 TEMPLATE_VBOXQTGUIEXE_CXXFLAGS.linux = -pthread @@ -7078,7 +7217,7 @@ TEMPLATE_VBOXGUESTR3EXE_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK_GST) VBOX_NTDLL TEMPLATE_VBOXGUESTR3EXE_CFLAGS = $(filter-out -MD$(VBOX_VCC_CRT_TYPE), $(TEMPLATE_VBOXR3EXE_CFLAGS)) -MT$(VBOX_VCC_CRT_TYPE) TEMPLATE_VBOXGUESTR3EXE_CXXFLAGS = $(filter-out -MD$(VBOX_VCC_CRT_TYPE), $(TEMPLATE_VBOXR3EXE_CFLAGS)) -MT$(VBOX_VCC_CRT_TYPE) - TEMPLATE_VBOXGUESTR3EXE_LDFLAGS = $(filter-out /DISALLOWLIB:libcmt$(VBOX_VCC_CRT_TYPE).lib /DISALLOWLIB:libcpmt$(VBOX_VCC_CRT_TYPE).lib, $(TEMPLATE_VBOXR3EXE_LDFLAGS)) \ + TEMPLATE_VBOXGUESTR3EXE_LDFLAGS = $(filter-out -IntegrityCheck /DISALLOWLIB:libcmt$(VBOX_VCC_CRT_TYPE).lib /DISALLOWLIB:libcpmt$(VBOX_VCC_CRT_TYPE).lib, $(TEMPLATE_VBOXR3EXE_LDFLAGS)) \ /DISALLOWLIB:msvcrt$(VBOX_VCC_CRT_TYPE).lib \ /DISALLOWLIB:msvcprt$(VBOX_VCC_CRT_TYPE).lib TEMPLATE_VBOXGUESTR3EXE_LIBS.x86 = \ @@ -7664,7 +7803,7 @@ SVN ?= svn$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 140381 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 152435 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) ifeq ($(which $(SVN)),) @@ -7677,8 +7816,10 @@ $(QUIET)$(RM) -f $@ $@.tmp $(QUIET)$(MKDIR) -p $(@D) ifneq ($(VBOX_SVN_DEP),) - $(REDIRECT) -E 'LC_ALL=C' -wo $@.tmp -- $(SVN) info $(PATH_ROOT) - $(SED) -e '/^Last Changed Rev/!d' -e 's/Last Changed Rev\: */export VBOX_SVN_REV=/' --output $@ $@.tmp + -$(REDIRECT) -E 'LC_ALL=C' -wo $@.tmp -- $(SVN) info $(PATH_ROOT) + # Append fallback. Will be ignored if "svn info" provides meaningful data. + $(QUIET)$(APPEND) $@.tmp 'Last Changed Rev: $(VBOX_SVN_REV_FALLBACK)' + $(SED) -e '/^Last Changed Rev/!d' -e 's/Last Changed Rev\: */export VBOX_SVN_REV=/; t a; :a q' --output $@ $@.tmp $(QUIET)$(RM) -f $@.tmp else $(QUIET)$(APPEND) $@ 'export VBOX_SVN_REV=$(VBOX_SVN_REV_FALLBACK)' @@ -7738,7 +7879,7 @@ endif if defined(VBOX_JAVA_VERSION) && $(VBOX_JAVA_VERSION) >= 110000 VBOX_JAVAC_OPTS = -encoding UTF-8 -source 9 -target 9 -Xlint:unchecked -else if defined(VBOX_JAVA_VERSION) && $(VBOX_JAVA_VERSION) >= 100000 +else if defined(VBOX_JAVA_VERSION) && $(VBOX_JAVA_VERSION) >= 90000 VBOX_JAVAC_OPTS = -encoding UTF-8 -source 6 -target 6 -Xlint:unchecked else VBOX_JAVAC_OPTS = -encoding UTF-8 -source 1.5 -target 1.5 -Xlint:unchecked diff -Nru virtualbox-6.1.16-dfsg/configure virtualbox-6.1.38-dfsg/configure --- virtualbox-6.1.16-dfsg/configure 2020-10-16 16:27:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/configure 2022-09-01 13:17:42.000000000 +0000 @@ -3,7 +3,7 @@ # libraries VBox OSE depends on. # -# Copyright (C) 2006-2020 Oracle Corporation +# Copyright (C) 2006-2021 Oracle Corporation # # This file is part of VirtualBox Open Source Edition (OSE), as # available from http://www.virtualbox.org. This file is free software; @@ -70,8 +70,8 @@ OSE=1 ODIR="`pwd`/" ODIR_OVERRIDE=0 -OUT_PATH="" -OUT_PATH_OVERRIDE=0 +OUT_BASE_PATH="" +OUT_BASE_PATH_OVERRIDE=0 SETUP_WINE= ONLY_ADDITIONS=0 TARGET_MACHINE="" @@ -81,7 +81,6 @@ WITH_JAVA=1 WITH_VMMRAW=1 WITH_LIBIDL=1 -WITH_GSOAP=0 WITH_QT5=1 WITH_SDL=1 WITH_SDL_TTF=1 @@ -549,7 +548,7 @@ test_header "Open Watcom" if [ -z "$WATCOM" ]; then - WATCOM=`/bin/ls -rd1 $PWD/tools/common/openwatcom/* 2> /dev/null | head -1` + WATCOM=`/bin/ls -rd1 $DEVDIR/common/openwatcom/* 2> /dev/null | head -1` if [ -z "$WATCOM" ]; then if [ $OSE -eq 0 -a $OS = "linux" ]; then log_failure "Open Watcom was not found" @@ -1529,7 +1528,7 @@ if [ "$OS" = "darwin" ]; then # First check if there is the internal version of Qt. If yes nothing else # has to be done. - QT_INTERNAL=`/bin/ls -rd1 $PWD/tools/$BUILD_TARGET.$BUILD_PLATFORM_ARCH/qt/* 2> /dev/null` + QT_INTERNAL=`/bin/ls -rd1 $DEVDIR/$BUILD_TARGET.$BUILD_PLATFORM_ARCH/qt/* 2> /dev/null` for t in $QT_INTERNAL; do if [ -f "$t/Frameworks/QtCoreVBox.framework/QtCoreVBox" ]; then cnf_append "VBOX_WITH_ORACLE_QT" "1" @@ -1564,10 +1563,10 @@ echo "(Qt5 from pkg-config)" >> $LOG FLGQT5=`pkg-config Qt5Core --cflags` # gcc 4.8 is able to compile with C++11 (see also VBOX_GCC_std in Config.kmk) - [ $cc_maj -eq 4 -a $cc_min -eq 8 ] && FLGQT5="$FLGQT5 -std=c++11" + [ $(($cc_maj * 100 + $cc_min)) -ge 408 ] && FLGQT5="$FLGQT5 -std=c++11" INCQT5=`strip_I "$FLGQT5"` LIBDIR5=`pkg-config Qt5Core --variable=libdir` - LIBQT5=`pkg-config Qt5Core --libs` + LIBQT5=`pkg-config Qt5Core Qt5Gui --libs` LIBQT5="-L$LIBDIR5 $LIBQT5" TOOLQT5=`pkg-config Qt5Core --variable=prefix` TOOLQT5BIN=`pkg-config Qt5Core --variable=host_bins` @@ -1582,19 +1581,19 @@ fi if [ -z "$foundqt5" ]; then # Do it the old way (e.g. user has specified QT5DIR): - for q in $QT5DIR "$PWD/tools/linux.$TARGET_MACHINE"/qt/v5.*; do + for q in $QT5DIR "$DEVDIR/linux.$TARGET_MACHINE"/qt/v5.*; do echo "(Qt5 from '$q')" >> $LOG INCQT5="$q/include $q/include/QtCore" FLGQT5="-DQT_SHARED" I_INCQT5=`prefix_I "$INCQT5"` - LIBQT5="-L$q/lib -lQt5CoreVBox" + LIBQT5="-L$q/lib -lQt5CoreVBox -lQt5GuiVBox" TOOLQT5="$q" if test_compile "$LIBQT5 $LIBPTHREAD $I_INCQT5 $FLGQT5" qt5 qt5 nofatal && test_execute_path "`L_to_PATH "$LIBQT5"`" nofatal; then foundqt5=2 # internal break; fi - LIBQT5="-L$q/lib -lQt5Core" + LIBQT5="-L$q/lib -lQt5Core -lQt5Gui" if test_compile "$LIBQT5 $LIBPTHREAD $I_INCQT5 $FLGQT5" qt5 qt5 nofatal && test_execute_path "`L_to_PATH "$LIBQT5"`" nofatal; then foundqt5=1 # no pkg-config, Qt directory @@ -2028,7 +2027,7 @@ } EOF found= - SUPPYTHONLIBS="python2.7 python2.6 python3.1 python3.2 python3.3 python3.4 python3.4m python3.5 python3.5m python3.6 python3.6m python3.7 python3.7m python3.8 python3.8m" + SUPPYTHONLIBS="python2.7 python2.6 python3.1 python3.2 python3.3 python3.4 python3.4m python3.5 python3.5m python3.6 python3.6m python3.7 python3.7m python3.8 python3.8m python3.9 python3.9m python3.10 python3.10m" for p in $PYTHONDIR; do for d in $SUPPYTHONLIBS; do for b in lib/x86_64-linux-gnu lib/i386-linux-gnu lib64 lib/64 lib; do @@ -2158,6 +2157,15 @@ return fi fi + if [ -d "$DEVDIR/common/gsoap" ]; then + GSOAP_DIR=`ls -d1 "$DEVDIR"/common/gsoap/v* 2>/dev/null | tail -1` + if [ -n "$GSOAP_DIR" -a -d "$GSOAP_DIR" ]; then + gsoap_version=`echo "$GSOAP_DIR" | sed -ne 's/^.*\/v\([1-9][0-9.]*\).*$/\1/p'` + log_success "found gSOAP tool version $gsoap_version" + # No need to configure anything, the build system knows what to do. + return + fi + fi fi if [ -z "$GSOAP" ]; then GSOAP="/usr" @@ -2371,7 +2379,6 @@ [ $WITH_KMODS -eq 1 ] && echo " --disable-kmods don't build Linux kernel modules (host and guest)" [ $WITH_OPENGL -eq 1 ] && echo " --disable-opengl disable OpenGL support (2D & 3D)" [ $WITH_QT5 -eq 0 ] && echo " --enable-qt5 enable Qt5 detection" -[ $WITH_GSOAP -eq 0 ] && echo " --enable-webservice enable the webservice stuff" [ $OSE -eq 1 ] && echo " --enable-vnc enable the VNC server" [ $OSE -eq 0 ] && echo " --disable-extpack don't build the extpack" [ $WITH_DOCS -eq 1 ] && echo " --disable-docs don't build the documentation" @@ -2405,14 +2412,14 @@ [ "$OS" = "darwin" ] && echo " only, ignored for the rest)" [ "$OS" = "linux" ] && echo " --with-linux=DIR Linux kernel source directory [$LINUX]" [ $WITH_QT5 -eq 1 ] && echo " --with-qt-dir=DIR directory for Qt headers/libraries [pkgconfig]" -[ $WITH_GSOAP -eq 1 ] && echo " --with-gsoap-dir=PATH directory for gSOAP compiler/headers/libraries" -[ $WITH_GSOAP -eq 1 ] && echo " (soapcpp2 and wsdl2h, soapstd2.h, libgsoap++.a/so)" -[ $WITH_GSOAP -eq 1 ] && echo " --with-gsoap-import=PATH directory for gSOAP import files (stlvector.h)" cat << EOF + --with-gsoap-dir=PATH directory for gSOAP compiler/headers/libraries + (soapcpp2 and wsdl2h, soapstd2.h, libgsoap++.a/so) + --with-gsoap-import=PATH directory for gSOAP import files (stlvector.h) --with-openssl-dir=DIR directory for OpenSSL headers/libraries --with-ow-dir=DIR directory where Open Watcom can be found [$WATCOM] - --out-path=PATH the folder to which configuration and build output - should go + --out-base-dir=DIR directory where configuration and build output + should go, in subdirectory out Build type: -d, --build-debug build with debugging symbols and assertions @@ -2512,7 +2519,9 @@ --with-openssl-dir=*) OPENSSLDIR=`echo $option | cut -d'=' -f2` INCCRYPTO="-I${OPENSSLDIR}/include" - LIBCRYPTO="${OPENSSLDIR}/lib/libcrypto.a ${OPENSSLDIR}/lib/libssl.a" + LIBCRYPTO="${OPENSSLDIR}/lib/libssl.a ${OPENSSLDIR}/lib/libcrypto.a" + # On Linux static OpenSSL typically needs a few additional libraries. + [ "$OS" = "linux" ] && LIBCRYPTO="-ldl $LIBPTHREAD -lm" ;; --with-ow-dir=*) WATCOM=`echo $option | cut -d'=' -f2` @@ -2582,7 +2591,6 @@ [ $WITH_OPENGL -eq 1 ] && WITH_OPENGL=0 ;; --enable-webservice) - [ $WITH_GSOAP -eq 0 ] && WITH_GSOAP=1 ;; --enable-vnc) WITH_VNC=1 @@ -2653,20 +2661,20 @@ ODIR="`echo $option | cut -d'=' -f2`/" ODIR_OVERRIDE=1 ;; - --out-path=*) - out_path="`echo $option | cut -d'=' -f2`/" - if [ -d $out_path ]; then - saved_path="`pwd`" - cd $out_path - OUT_PATH="`pwd`" - cd $saved_path - OUT_PATH_OVERRIDE=1 + --out-base-dir=*) + out_base_dir="`echo $option | cut -d'=' -f2`/" + if [ -d $out_base_dir ]; then + saved_pwd="$PWD" + cd $out_base_dir + OUT_BASE_PATH="`pwd`" + cd $saved_pwd + OUT_BASE_PATH_OVERRIDE=1 if [ $ODIR_OVERRIDE -eq 0 ]; then # This variable has not *yet* been overridden. That can still happen. - ODIR=$OUT_PATH/ + ODIR=$OUT_BASE_PATH/ fi else - echo "Error: invalid folder \"$out_path\" in option \"$option\"" + echo "Error: invalid folder \"$out_base_dir\" in option \"$option\"" exit 1 fi ;; @@ -2745,12 +2753,12 @@ export PATH # if we will be writing to a different out directory then set this up now -if [ $OUT_PATH_OVERRIDE -eq 1 ]; then - echo "AUTOCFG=$OUT_PATH/AutoConfig.kmk" >> $ENV +if [ $ODIR_OVERRIDE -eq 1 ]; then + echo "AUTOCFG=$CFG" >> $ENV echo "export AUTOCFG" >> $ENV - echo "LOCALCFG=$OUT_PATH/LocalConfig.kmk" >> $ENV - echo "export LOCALCFG" >> $ENV - echo "PATH_OUT_BASE=$OUT_PATH" >> $ENV +fi +if [ $OUT_BASE_PATH_OVERRIDE -eq 1 ]; then + echo "PATH_OUT_BASE=$OUT_BASE_PATH" >> $ENV echo "export PATH_OUT_BASE" >> $ENV fi @@ -2901,12 +2909,10 @@ [ -n "$SETUP_WINE" ] && setup_wine -if [ $ONLY_ADDITIONS -eq 0 -a $WITH_GSOAP -eq 1 ]; then +if [ $ONLY_ADDITIONS -eq 0 ]; then check_gsoap else - if [ $OSE -ge 1 ]; then - cnf_append "VBOX_WITH_WEBSERVICES" "" - fi + cnf_append "VBOX_WITH_WEBSERVICES" "" fi # UDPTUNNEL @@ -2949,14 +2955,14 @@ echo " kmk" echo "" if [ "$OS" = "linux" ]; then - if [ $OUT_PATH_OVERRIDE -eq 1 ]; then - vbox_out_path=$OUT_PATH + if [ $OUT_BASE_PATH_OVERRIDE -eq 1 ]; then + out_base_dir=$OUT_BASE_PATH else - vbox_out_path=./out + out_base_dir=. fi echo "To compile the kernel modules, do:" echo "" - echo " cd $vbox_out_path/$OS.$TARGET_MACHINE/$BUILD_TYPE/bin/src" + echo " cd $out_base_dir/out/$OS.$TARGET_MACHINE/$BUILD_TYPE/bin/src" echo " make" echo "" fi diff -Nru virtualbox-6.1.16-dfsg/debian/changelog virtualbox-6.1.38-dfsg/debian/changelog --- virtualbox-6.1.16-dfsg/debian/changelog 2021-04-29 15:42:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/changelog 2022-09-07 11:18:57.000000000 +0000 @@ -1,14 +1,226 @@ -virtualbox (6.1.16-dfsg-6~ubuntu1.20.04.2) focal; urgency=medium +virtualbox (6.1.38-dfsg-3~ubuntu1.20.04.1) focal; urgency=medium - * No change rebuild in security pocket. LP: #1914279. + * SRU the latest package for Ubuntu focal (LP: #1988473) + * Revert drop of *guest-dkms packages for focal only. + Such packages are still needed because kernel < 5.5. doesn't provide + vboxsf + * Revert switch to dh-sequence-dkms, not enough new dkms is available in + focal - -- Dimitri John Ledkov Thu, 29 Apr 2021 16:42:15 +0100 + -- Gianfranco Costamagna Wed, 07 Sep 2022 13:18:57 +0200 -virtualbox (6.1.16-dfsg-6~ubuntu1.20.04.1) focal; urgency=medium +virtualbox (6.1.38-dfsg-3) unstable; urgency=medium - * SRU the latest package for Ubuntu focal (LP: #1901904) + * Switch to upstream approach to fix FTBFS - -- Gianfranco Costamagna Thu, 17 Dec 2020 23:06:23 +0100 + -- Gianfranco Costamagna Tue, 06 Sep 2022 22:01:06 +0200 + +virtualbox (6.1.38-dfsg-2) unstable; urgency=medium + + [ Andreas Beckmann ] + * Switch to dh-sequence-dkms. + * Declare Testsuite: autopkgtest-pkg-dkms. + + [ Gianfranco Costamagna ] + * Drop utf8x from https://github.com/latex3/latex2e/issues/833 + * And replace double arrow with two different arrows (left and right) + to avoid utf8 chars in documentation. + + -- Gianfranco Costamagna Tue, 06 Sep 2022 09:34:38 +0200 + +virtualbox (6.1.38-dfsg-1) unstable; urgency=medium + + * New upstream version 6.1.38-dfsg (Closes: #1012627, LP: #1988473) + + -- Gianfranco Costamagna Sun, 04 Sep 2022 18:12:39 +0200 + +virtualbox (6.1.36-dfsg-1) unstable; urgency=medium + + * New upstream version 6.1.36-dfsg + * Refresh patches, drop patches now upstream: + - 020-linux518.patch + - ffreestanding.patch + - python3.10.patch + + -- Gianfranco Costamagna Thu, 21 Jul 2022 19:03:38 +0200 + +virtualbox (6.1.34-dfsg-3) unstable; urgency=medium + + * Add patch from archlinux to fix a build failure with kernel 5.18 + (Closes: #1012122) + + -- Gianfranco Costamagna Tue, 07 Jun 2022 14:12:22 +0200 + +virtualbox (6.1.34-dfsg-2) unstable; urgency=medium + + * Add ffreestanding.patch proposed from upstream to fix a empty memset code + generation with newer gccs + + -- Gianfranco Costamagna Thu, 21 Apr 2022 11:42:03 +0200 + +virtualbox (6.1.34-dfsg-1) unstable; urgency=medium + + * Fix udev rule: + - If there is a NAME, the line "Only network interfaces can be renamed, + ignoring NAME" is written in the log + + [ Gianfranco Costamagna ] + * New upstream version 6.1.34-dfsg + + -- Gianfranco Costamagna Wed, 20 Apr 2022 11:27:31 +0200 + +virtualbox (6.1.32-dfsg-1) unstable; urgency=medium + + * New upstream version 6.1.32-dfsg + * Refresh patches + + -- Gianfranco Costamagna Mon, 24 Jan 2022 11:49:42 +0100 + +virtualbox (6.1.30-dfsg-2) unstable; urgency=medium + + * Add support for Python3.10 + + -- Gianfranco Costamagna Sun, 16 Jan 2022 16:10:45 +0100 + +virtualbox (6.1.30-dfsg-1) unstable; urgency=medium + + * New upstream version 6.1.30-dfsg + + -- Gianfranco Costamagna Wed, 01 Dec 2021 10:34:31 +0100 + +virtualbox (6.1.28-dfsg-1) unstable; urgency=medium + + * New upstream version 6.1.28-dfsg + * Bump std-version to 4.6.0 + * Drop patches now upstream or coming from upstream: new-mesa, 90377, no-vboxrem + * Patch refresh + + -- Gianfranco Costamagna Wed, 20 Oct 2021 10:01:31 +0200 + +virtualbox (6.1.26-dfsg-4) unstable; urgency=medium + + * debian/patches/new-mesa.patch + - add new upstream proposed patch to fix whitescreens with new + mesa. (thanks Klaus for the patch) + + -- Gianfranco Costamagna Fri, 03 Sep 2021 08:29:58 +0200 + +virtualbox (6.1.26-dfsg-3) unstable; urgency=medium + + * Drop broken old symlinks (Closes: #991901) + + -- Gianfranco Costamagna Thu, 05 Aug 2021 16:14:08 +0200 + +virtualbox (6.1.26-dfsg-2) unstable; urgency=medium + + * debian/patches/90377.patch: + - cherry-pick upstream build fix + + -- Gianfranco Costamagna Thu, 29 Jul 2021 11:31:03 +0200 + +virtualbox (6.1.26-dfsg-1) unstable; urgency=medium + + * New upstream version 6.1.26-dfsg + * Delete 40-linux-5.13-support.patch + * Patch refresh + + -- Gianfranco Costamagna Thu, 29 Jul 2021 11:26:57 +0200 + +virtualbox (6.1.22-dfsg-4) unstable; urgency=medium + + * Set R^3 to binary-target, chmod +s in dh_fixperms needs root to work + Note: Debian implementation seems to set +s during build regardless + of the set, while Ubuntu implementation failed to set +s leading to + non-working virtualbox binary + + -- Gianfranco Costamagna Fri, 23 Jul 2021 10:21:57 +0200 + +virtualbox (6.1.22-dfsg-3) unstable; urgency=medium + + [ Dimitri John Ledkov ] + * Drop virtualbox guest modules dkms and sources, as in Ubuntu and Debian, + all kernel flavours provide those from the upstream kernel since at least + focal 20.04 LTS. + Thus these are not needed anymore. LP: #1933248 + + -- Gianfranco Costamagna Mon, 19 Jul 2021 09:28:22 +0200 + +virtualbox (6.1.22-dfsg-2) unstable; urgency=medium + + [ Andrea Righi ] + * Support linux 5.13 with vbox-guest drivers (LP: #1929193): + - debian/patches/40-linux-5.13-support.patch + + -- Gianfranco Costamagna Wed, 23 Jun 2021 15:45:42 +0200 + +virtualbox (6.1.22-dfsg-1) unstable; urgency=medium + + * New upstream version 6.1.22-dfsg + + -- Gianfranco Costamagna Tue, 11 May 2021 12:07:27 +0200 + +virtualbox (6.1.20-dfsg-1) unstable; urgency=medium + + * New upstream version 6.1.20-dfsg + * Drop patches now upstream: + - 50-kernel-5.11.patch + - 88207-kernel-5.11.patch + - 88212-kernel-5.11.patch + - kernel-5.10.patch + - python3.9.patch + * Refresh patches: + - 27-hide-host-cache-warning.patch + - 36-fix-vnc-version-string.patch + - no-vboxrem.patch + * Add R^3: no + + -- Gianfranco Costamagna Tue, 20 Apr 2021 22:38:00 +0200 + +virtualbox (6.1.18-dfsg-5) unstable; urgency=medium + + * Drop vboxweb.service too + + -- Gianfranco Costamagna Thu, 08 Apr 2021 20:43:02 +0200 + +virtualbox (6.1.18-dfsg-4) unstable; urgency=medium + + * Drop vboxweb.service. + Never worked, needs too much customization (LP: #1894862) + * Add patches from upstream to fix build with kernel 5.11 + - upstream changeset: 88207 and 88212 + + -- Gianfranco Costamagna Thu, 08 Apr 2021 19:25:43 +0200 + +virtualbox (6.1.18-dfsg-3) unstable; urgency=medium + + * Support linux 5.11 (LP: #1915900): + - debian/patches/50-kernel-5.11.patch + + -- Andrea Righi Thu, 25 Feb 2021 15:32:11 +0000 + +virtualbox (6.1.18-dfsg-2) unstable; urgency=medium + + * debian/patches/kernel-5.10.patch: + - fix 32bit guest kernel builds + + -- Gianfranco Costamagna Thu, 21 Jan 2021 12:01:33 +0100 + +virtualbox (6.1.18-dfsg-1) unstable; urgency=medium + + [ Andrea Righi ] + * debian/patches/linux-5.10-TASK_SIZE_MAX-replaces-USER_DS.patch: + - upstream patch to fix build failure of virtualbox-guest-dkms with linux + 5.10 (LP: #1908734) + * debian/patches/linux-5.10-drm-fixes.patch: + - upstream patch to fix multiple virtualbox-guest-dkms drm's build + failures with linux 5.10 (LP: #1908734) + + [ Gianfranco Costamagna ] + * New upstream version 6.1.18-dfsg + * Drop kernel 5.10 build fixes, upstream + * Refresh patches + + -- Gianfranco Costamagna Wed, 20 Jan 2021 13:52:19 +0100 virtualbox (6.1.16-dfsg-6) unstable; urgency=medium diff -Nru virtualbox-6.1.16-dfsg/debian/control virtualbox-6.1.38-dfsg/debian/control --- virtualbox-6.1.16-dfsg/debian/control 2021-04-29 15:42:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/control 2022-09-07 11:18:57.000000000 +0000 @@ -1,8 +1,7 @@ Source: virtualbox Section: contrib/misc Priority: optional -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Debian Virtualbox Team +Maintainer: Debian Virtualbox Team Uploaders: Ritesh Raj Sarraf , Gianfranco Costamagna Build-Depends: bzip2, @@ -18,7 +17,6 @@ genisoimage, gsoap, acpica-tools, - imagemagick, kbuild (>= 1:0.1.9998svn3098~), libasound2-dev, libcap-dev, @@ -75,7 +73,8 @@ yasm, zlib1g-dev X-Python-Version: >= 2.5 -Standards-Version: 4.5.0 +Standards-Version: 4.6.0 +Rules-Requires-Root: binary-targets Homepage: https://www.virtualbox.org Vcs-Browser: https://salsa.debian.org/pkg-virtualbox-team/virtualbox Vcs-Git: https://salsa.debian.org/pkg-virtualbox-team/virtualbox.git @@ -244,7 +243,3 @@ utilities are meant to be run inside the virtual machine. They provide closer integration and allow to share data through shared folders between the host system and the virtual machine. - . - Either the virtualbox-guest-dkms or the virtualbox-guest-source package is - also required in order to compile the kernel modules needed for - virtualbox-guest-utils. diff -Nru virtualbox-6.1.16-dfsg/debian/patches/01-build-arch.patch virtualbox-6.1.38-dfsg/debian/patches/01-build-arch.patch --- virtualbox-6.1.16-dfsg/debian/patches/01-build-arch.patch 2020-09-07 16:46:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/01-build-arch.patch 2021-07-29 09:24:07.000000000 +0000 @@ -3,7 +3,7 @@ --- a/configure +++ b/configure -@@ -356,7 +356,7 @@ test_execute_path() +@@ -355,7 +355,7 @@ test_execute_path() check_environment() { test_header environment diff -Nru virtualbox-6.1.16-dfsg/debian/patches/02-gsoap-build-fix.patch virtualbox-6.1.38-dfsg/debian/patches/02-gsoap-build-fix.patch --- virtualbox-6.1.16-dfsg/debian/patches/02-gsoap-build-fix.patch 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/02-gsoap-build-fix.patch 2022-07-21 17:00:18.000000000 +0000 @@ -3,7 +3,7 @@ =================================================================== --- virtualbox-git-orig.orig/src/VBox/Main/webservice/Makefile.kmk 2013-12-02 12:32:51.211124975 -0500 +++ virtualbox-git-orig/src/VBox/Main/webservice/Makefile.kmk 2013-12-02 12:36:04.531133954 -0500 -@@ -771,7 +771,7 @@ +@@ -772,7 +772,7 @@ $(RECOMPILE_ON_MAKEFILE_CURRENT) | $$(dir $$@) $(call MSG_GENERATE,,lots of files,$(GSOAPH_RELEVANT)) $(RM) -f $@ diff -Nru virtualbox-6.1.16-dfsg/debian/patches/04-vboxdrv-references.patch virtualbox-6.1.38-dfsg/debian/patches/04-vboxdrv-references.patch --- virtualbox-6.1.16-dfsg/debian/patches/04-vboxdrv-references.patch 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/04-vboxdrv-references.patch 2021-10-20 07:54:31.000000000 +0000 @@ -5,7 +5,7 @@ =================================================================== --- virtualbox.orig/src/VBox/VMM/VMMR3/VM.cpp +++ virtualbox/src/VBox/VMM/VMMR3/VM.cpp -@@ -261,12 +261,8 @@ +@@ -264,12 +264,8 @@ #ifdef RT_OS_LINUX case VERR_SUPDRV_COMPONENT_NOT_FOUND: @@ -20,7 +20,7 @@ break; #endif -@@ -329,12 +325,8 @@ +@@ -332,12 +328,8 @@ { case VERR_VM_DRIVER_LOAD_ERROR: #ifdef RT_OS_LINUX @@ -35,7 +35,7 @@ #else pszError = N_("VirtualBox kernel driver not loaded"); #endif -@@ -373,12 +365,8 @@ +@@ -376,12 +368,8 @@ case VERR_INVALID_HANDLE: /** @todo track down and fix this error. */ case VERR_VM_DRIVER_NOT_INSTALLED: #ifdef RT_OS_LINUX @@ -54,7 +54,7 @@ =================================================================== --- virtualbox.orig/src/VBox/Frontends/VirtualBox/src/main.cpp +++ virtualbox/src/VBox/Frontends/VirtualBox/src/main.cpp -@@ -94,8 +94,9 @@ +@@ -90,8 +90,9 @@ QString g_QStrHintLinuxNoDriver = QApplication::tr( "The VirtualBox Linux kernel driver is either not loaded or not set " @@ -66,7 +66,7 @@ "as root.

" "If your system has EFI Secure Boot enabled you may also need to sign " "the kernel modules (vboxdrv, vboxnetflt, vboxnetadp, vboxpci) before " -@@ -114,7 +115,7 @@ +@@ -110,7 +111,7 @@ "The VirtualBox kernel modules do not match this version of " "VirtualBox. The installation of VirtualBox was apparently not " "successful. Executing

" diff -Nru virtualbox-6.1.16-dfsg/debian/patches/12-make-module.patch virtualbox-6.1.38-dfsg/debian/patches/12-make-module.patch --- virtualbox-6.1.16-dfsg/debian/patches/12-make-module.patch 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/12-make-module.patch 2022-07-21 17:01:57.000000000 +0000 @@ -3,12 +3,12 @@ --- a/src/VBox/Installer/linux/Makefile-header.gmk +++ b/src/VBox/Installer/linux/Makefile-header.gmk -@@ -45,7 +45,7 @@ - # (We have to support basic cross building (ARCH=i386|x86_64).) - # While at it, warn about BUILD_* vars found to help with user problems. +@@ -50,7 +50,7 @@ # + + # VBOX_KBUILD_TARGET_ARCH = amd64|x86 -ifeq ($(filter-out x86_64 amd64 AMD64,$(shell uname -m)),) +ifeq ($(filter-out x86_64 amd64 AMD64,$(shell dpkg-architecture -qDEB_HOST_GNU_CPU)),) - BUILD_TARGET_ARCH_DEF := amd64 + VBOX_KBUILD_TARGET_ARCH_DEFAULT := amd64 else - BUILD_TARGET_ARCH_DEF := x86 + VBOX_KBUILD_TARGET_ARCH_DEFAULT := x86 diff -Nru virtualbox-6.1.16-dfsg/debian/patches/13-module-mismatch.patch virtualbox-6.1.38-dfsg/debian/patches/13-module-mismatch.patch --- virtualbox-6.1.16-dfsg/debian/patches/13-module-mismatch.patch 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/13-module-mismatch.patch 2021-10-20 07:54:44.000000000 +0000 @@ -5,7 +5,7 @@ =================================================================== --- virtualbox.orig/src/VBox/VMM/VMMR3/VM.cpp +++ virtualbox/src/VBox/VMM/VMMR3/VM.cpp -@@ -376,9 +376,11 @@ +@@ -379,9 +379,11 @@ break; case VERR_VERSION_MISMATCH: case VERR_VM_DRIVER_VERSION_MISMATCH: diff -Nru virtualbox-6.1.16-dfsg/debian/patches/16-no-update.patch virtualbox-6.1.38-dfsg/debian/patches/16-no-update.patch --- virtualbox-6.1.16-dfsg/debian/patches/16-no-update.patch 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/16-no-update.patch 2021-05-11 09:57:20.000000000 +0000 @@ -6,7 +6,7 @@ =================================================================== --- virtualbox.orig/doc/manual/en_US/user_Introduction.xml +++ virtualbox/doc/manual/en_US/user_Introduction.xml -@@ -3793,14 +3793,14 @@ +@@ -4405,14 +4405,14 @@ . diff -Nru virtualbox-6.1.16-dfsg/debian/patches/23-remove-invalid-chars-check.patch virtualbox-6.1.38-dfsg/debian/patches/23-remove-invalid-chars-check.patch --- virtualbox-6.1.16-dfsg/debian/patches/23-remove-invalid-chars-check.patch 2020-09-07 16:46:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/23-remove-invalid-chars-check.patch 2021-07-29 09:24:20.000000000 +0000 @@ -4,7 +4,7 @@ --- a/configure +++ b/configure -@@ -181,11 +181,6 @@ BUILD_TYPE="release" +@@ -180,11 +180,6 @@ BUILD_TYPE="release" # the restricting tool is ar (mri mode). INVALID_CHARS="[^A-Za-z0-9/\\$:._-]" diff -Nru virtualbox-6.1.16-dfsg/debian/patches/27-hide-host-cache-warning.patch virtualbox-6.1.38-dfsg/debian/patches/27-hide-host-cache-warning.patch --- virtualbox-6.1.16-dfsg/debian/patches/27-hide-host-cache-warning.patch 2020-10-20 22:30:32.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/27-hide-host-cache-warning.patch 2022-01-24 10:49:25.000000000 +0000 @@ -6,7 +6,7 @@ =================================================================== --- virtualbox.orig/src/VBox/Main/src-client/ConsoleImpl2.cpp +++ virtualbox/src/VBox/Main/src-client/ConsoleImpl2.cpp -@@ -4093,34 +4093,12 @@ +@@ -4166,34 +4166,12 @@ if ( enmFsTypeFile == RTFSTYPE_EXT4 || enmFsTypeFile == RTFSTYPE_XFS) { diff -Nru virtualbox-6.1.16-dfsg/debian/patches/36-fix-vnc-version-string.patch virtualbox-6.1.38-dfsg/debian/patches/36-fix-vnc-version-string.patch --- virtualbox-6.1.16-dfsg/debian/patches/36-fix-vnc-version-string.patch 2020-10-26 16:08:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/36-fix-vnc-version-string.patch 2022-07-21 17:02:18.000000000 +0000 @@ -1,7 +1,7 @@ Description: fix version string for VNC plugin module. --- a/Config.kmk +++ b/Config.kmk -@@ -3237,6 +3237,7 @@ VBOX_EDIT_VERSION_AND_BUILD_CMD_FN = $(S +@@ -3261,6 +3261,7 @@ VBOX_EDIT_VERSION_AND_BUILD_CMD_FN = $(S -e 's/@VBOX_VERSION_MINOR@/$(VBOX_VERSION_MINOR)/g' \ -e 's/@VBOX_VERSION_BUILD@/$(VBOX_VERSION_BUILD)/g' \ -e 's/@VBOX_VERSION_STRING@/$(VBOX_VERSION_STRING)/g' \ diff -Nru virtualbox-6.1.16-dfsg/debian/patches/latex.patch virtualbox-6.1.38-dfsg/debian/patches/latex.patch --- virtualbox-6.1.16-dfsg/debian/patches/latex.patch 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/latex.patch 2022-09-06 20:00:55.000000000 +0000 @@ -0,0 +1,46 @@ +Description: Cherry-pick upstream changes for LaTeX build fixes. + +Origin: https://www.virtualbox.org/changeset?reponame=vbox&new=96615%40trunk%2Fdoc%2Fmanual%2Fdocbook2latex.xsl&old=96407%40trunk%2Fdoc%2Fmanual%2Fdocbook2latex.xsl&format=diff + +Index: doc/manual/docbook2latex.xsl +=================================================================== +--- a/doc/manual/docbook2latex.xsl (revision 96407) ++++ b/doc/manual/docbook2latex.xsl (revision 96615) +@@ -94,6 +94,4 @@ + \usepackage{geometry} + \geometry{top=3cm,bottom=4cm} +-\usepackage{ucs} +-\usepackage[utf8x]{inputenc} + \usepackage[T1]{fontenc} + \usepackage{tabulary} +@@ -1246,5 +1244,29 @@ + + +- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + diff -Nru virtualbox-6.1.16-dfsg/debian/patches/linux-5.10-r0drv-memobj-fix-r0.patch virtualbox-6.1.38-dfsg/debian/patches/linux-5.10-r0drv-memobj-fix-r0.patch --- virtualbox-6.1.16-dfsg/debian/patches/linux-5.10-r0drv-memobj-fix-r0.patch 2020-12-04 22:58:18.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/linux-5.10-r0drv-memobj-fix-r0.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,96 +0,0 @@ -Index: src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c -=================================================================== ---- a/src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c (Revision 141658) -+++ b/src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c (Arbeitskopie) -@@ -56,9 +56,12 @@ - * Whether we use alloc_vm_area (3.2+) for executable memory. - * This is a must for 5.8+, but we enable it all the way back to 3.2.x for - * better W^R compliance (fExecutable flag). */ --#if RTLNX_VER_MIN(3,2,0) || defined(DOXYGEN_RUNNING) -+#if RTLNX_VER_RANGE(3,2,0, 5,10,0) || defined(DOXYGEN_RUNNING) - # define IPRT_USE_ALLOC_VM_AREA_FOR_EXEC - #endif -+#if RTLNX_VER_MIN(5,10,0) || defined(DOXYGEN_RUNNING) -+# define IPRT_USE_APPLY_TO_PAGE_RANGE_FOR_EXEC -+#endif - - /* - * 2.6.29+ kernels don't work with remap_pfn_range() anymore because -@@ -502,7 +505,43 @@ - } - - -+#ifdef IPRT_USE_APPLY_TO_PAGE_RANGE_FOR_EXEC - /** -+ * User data passed to the apply_to_page_range() callback. -+ */ -+typedef struct LNXAPPLYPGRANGE -+{ -+ /** Pointer to the memory object. */ -+ PRTR0MEMOBJLNX pMemLnx; -+ /** The page protection flags to apply. */ -+ pgprot_t fPg; -+} LNXAPPLYPGRANGE; -+/** Pointer to the user data. */ -+typedef LNXAPPLYPGRANGE *PLNXAPPLYPGRANGE; -+/** Pointer to the const user data. */ -+typedef const LNXAPPLYPGRANGE *PCLNXAPPLYPGRANGE; -+ -+/** -+ * Callback called in apply_to_page_range(). -+ * -+ * @returns Linux status code. -+ * @param pPte Pointer to the page table entry for the given address. -+ * @param uAddr The address to apply the new protection to. -+ * @param pvUser The opaque user data. -+ */ -+static DECLCALLBACK(int) rtR0MemObjLinuxApplyPageRange(pte_t *pPte, unsigned long uAddr, void *pvUser) -+{ -+ PCLNXAPPLYPGRANGE pArgs = (PCLNXAPPLYPGRANGE)pvUser; -+ PRTR0MEMOBJLNX pMemLnx = pArgs->pMemLnx; -+ uint32_t idxPg = (uAddr - (unsigned long)pMemLnx->Core.pv) >> PAGE_SHIFT; -+ -+ set_pte(pPte, mk_pte(pMemLnx->apPages[idxPg], pArgs->fPg)); -+ return 0; -+} -+#endif -+ -+ -+/** - * Maps the allocation into ring-0. - * - * This will update the RTR0MEMOBJLNX::Core.pv and RTR0MEMOBJ::fMappedToRing0 members. -@@ -584,6 +623,11 @@ - else - # endif - { -+# if defined(IPRT_USE_APPLY_TO_PAGE_RANGE_FOR_EXEC) -+ if (fExecutable) -+ pgprot_val(fPg) |= _PAGE_NX; /* Uses RTR0MemObjProtect to clear NX when memory ready, W^X fashion. */ -+# endif -+ - # ifdef VM_MAP - pMemLnx->Core.pv = vmap(&pMemLnx->apPages[0], pMemLnx->cPages, VM_MAP, fPg); - # else -@@ -1851,6 +1895,21 @@ - preempt_enable(); - return VINF_SUCCESS; - } -+# elif defined(IPRT_USE_APPLY_TO_PAGE_RANGE_FOR_EXEC) -+ PRTR0MEMOBJLNX pMemLnx = (PRTR0MEMOBJLNX)pMem; -+ if ( pMemLnx->fExecutable -+ && pMemLnx->fMappedToRing0) -+ { -+ LNXAPPLYPGRANGE Args; -+ Args.pMemLnx = pMemLnx; -+ Args.fPg = rtR0MemObjLinuxConvertProt(fProt, true /*fKernel*/); -+ int rcLnx = apply_to_page_range(current->active_mm, (unsigned long)pMemLnx->Core.pv + offSub, cbSub, -+ rtR0MemObjLinuxApplyPageRange, (void *)&Args); -+ if (rcLnx) -+ return VERR_NOT_SUPPORTED; -+ -+ return VINF_SUCCESS; -+ } - # endif - - NOREF(pMem); diff -Nru virtualbox-6.1.16-dfsg/debian/patches/no-vboxrem.patch virtualbox-6.1.38-dfsg/debian/patches/no-vboxrem.patch --- virtualbox-6.1.16-dfsg/debian/patches/no-vboxrem.patch 2020-09-08 08:06:11.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/no-vboxrem.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,44 +0,0 @@ -Description: don't add VBoxREM symlink -Forwarded: irc -Author: Gianfranco Costamagna -Bug-Debian: https://bugs.debian.org/969793 -Last-Update: 2020-09-08 - ---- virtualbox-6.1.14-dfsg.orig/src/VBox/Installer/linux/deffiles -+++ virtualbox-6.1.14-dfsg/src/VBox/Installer/linux/deffiles -@@ -32,7 +32,6 @@ DEFAULT_FILE_NAMES=" \ - VBoxManage \ - VBoxNetDHCP \ - VBoxNetDHCP.so \ -- VBoxREM.so \ - VBoxRT.so \ - VBoxSDL \ - VBoxSDL.so \ -@@ -82,7 +81,6 @@ DEFAULT_FILE_NAMES=" \ - components/xpti.dat \ - components/VBoxC.so \ - components/VBoxDDU.so \ -- components/VBoxREM.so \ - components/VBoxRT.so \ - components/VBoxVMM.so \ - components/VBoxXPCOM.so \ ---- virtualbox-6.1.14-dfsg.orig/src/VBox/Installer/linux/uninstall.sh -+++ virtualbox-6.1.14-dfsg/src/VBox/Installer/linux/uninstall.sh -@@ -96,7 +96,6 @@ rm -f \ - /usr/bin/vboxdtrace \ - /usr/bin/vboxbugreport \ - $PREV_INSTALLATION/components/VBoxVMM.so \ -- $PREV_INSTALLATION/components/VBoxREM.so \ - $PREV_INSTALLATION/components/VBoxRT.so \ - $PREV_INSTALLATION/components/VBoxDDU.so \ - $PREV_INSTALLATION/components/VBoxXPCOM.so \ ---- virtualbox-6.1.14-dfsg.orig/src/VBox/Main/Makefile.kmk -+++ virtualbox-6.1.14-dfsg/src/VBox/Main/Makefile.kmk -@@ -1292,7 +1292,6 @@ if defined(VBOX_WITH_HARDENING) && "$(KB - VBoxMain-hardening-inst_INST = $(INST_BIN)components/ - VBoxMain-hardening-inst_SYMLINKS = \ - VBoxDDU.so=>../VBoxDDU.so \ -- VBoxREM.so=>../VBoxREM.so \ - VBoxRT.so=>../VBoxRT.so \ - VBoxVMM.so=>../VBoxVMM.so \ - VBoxXPCOM.so=>../VBoxXPCOM.so diff -Nru virtualbox-6.1.16-dfsg/debian/patches/python3.9.patch virtualbox-6.1.38-dfsg/debian/patches/python3.9.patch --- virtualbox-6.1.16-dfsg/debian/patches/python3.9.patch 2020-10-27 07:27:51.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/python3.9.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -Description: Support Python3.9 -Author: Gianfranco Costamagna -Bug-Debian: https://bugs.debian.org/972918 -Forwarded: irc -Last-Update: 2020-10-27 - ---- virtualbox-6.1.16-dfsg.orig/configure -+++ virtualbox-6.1.16-dfsg/configure -@@ -2023,7 +2023,7 @@ extern "C" int main(void) - } - EOF - found= -- SUPPYTHONLIBS="python2.7 python2.6 python3.1 python3.2 python3.3 python3.4 python3.4m python3.5 python3.5m python3.6 python3.6m python3.7 python3.7m python3.8 python3.8m" -+ SUPPYTHONLIBS="python2.7 python2.6 python3.1 python3.2 python3.3 python3.4 python3.4m python3.5 python3.5m python3.6 python3.6m python3.7 python3.7m python3.8 python3.8m python3.9 python3.9m" - for p in $PYTHONDIR; do - for d in $SUPPYTHONLIBS; do - for b in lib/x86_64-linux-gnu lib/i386-linux-gnu lib64 lib/64 lib; do diff -Nru virtualbox-6.1.16-dfsg/debian/patches/series virtualbox-6.1.38-dfsg/debian/patches/series --- virtualbox-6.1.16-dfsg/debian/patches/series 2020-12-04 22:58:18.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/patches/series 2022-09-06 13:11:06.000000000 +0000 @@ -14,6 +14,4 @@ 35-libvdeplug-soname.patch 36-fix-vnc-version-string.patch 37-do-not-run-if-not-in-vm.patch -no-vboxrem.patch -python3.9.patch -linux-5.10-r0drv-memobj-fix-r0.patch +latex.patch diff -Nru virtualbox-6.1.16-dfsg/debian/rules virtualbox-6.1.38-dfsg/debian/rules --- virtualbox-6.1.16-dfsg/debian/rules 2020-09-07 16:47:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/rules 2022-09-07 11:18:57.000000000 +0000 @@ -116,11 +116,6 @@ # install -m 644 -p -D out/bin/additions/vboxvideo_drv_system.so \ # debian/$(uxname)/usr/lib/xorg/modules/drivers/vboxvideo_drv.so #endif -# check if arch-any packages are being built -ifneq (,$(filter $(sxname), $(shell dh_listpackages))) - convert debian/$(sxname)/usr/share/icons/hicolor/32x32/apps/virtualbox.png \ - debian/$(sxname)/usr/share/pixmaps/virtualbox.xpm -endif ifeq ($(DIST_NAME),Ubuntu) install -m 644 -p -D debian/apport-hook.py \ @@ -188,9 +183,6 @@ dh_installinit --remaining-packages --no-start override_dh_systemd_enable: -ifneq (,$(filter $(sname), $(shell dh_listpackages))) - dh_systemd_enable -p$(sname) --name vboxweb --no-enable debian/vboxweb.service -endif dh_systemd_enable -p$(uname) debian/$(uname).service override_dh_dkms: @@ -234,4 +226,3 @@ rm -f debian/$(gdkms).links find . -name "*.pyc" -exec rm -f {} \; - diff -Nru virtualbox-6.1.16-dfsg/debian/vboxweb.service virtualbox-6.1.38-dfsg/debian/vboxweb.service --- virtualbox-6.1.16-dfsg/debian/vboxweb.service 2020-12-04 21:11:17.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/vboxweb.service 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -[Unit] -Description=VirtualBox Web Service -After=network.target - -[Service] -Type=forking -ExecStart=/usr/lib/virtualbox/vboxweb-service.sh start -PIDFile=/run/vboxweb-service.sh - -[Install] -WantedBy=multi-user.target diff -Nru virtualbox-6.1.16-dfsg/debian/virtualbox.dirs virtualbox-6.1.38-dfsg/debian/virtualbox.dirs --- virtualbox-6.1.16-dfsg/debian/virtualbox.dirs 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/virtualbox.dirs 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -lib/systemd/system/ diff -Nru virtualbox-6.1.16-dfsg/debian/virtualbox-dkms.udev virtualbox-6.1.38-dfsg/debian/virtualbox-dkms.udev --- virtualbox-6.1.16-dfsg/debian/virtualbox-dkms.udev 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/virtualbox-dkms.udev 2022-03-06 16:05:24.000000000 +0000 @@ -1,3 +1,3 @@ -KERNEL=="vboxdrv", NAME="vboxdrv", OWNER="root", GROUP="root", MODE="0600" -KERNEL=="vboxdrvu", NAME="vboxdrvu", OWNER="root", GROUP="root", MODE="0666" -KERNEL=="vboxnetctl", NAME="vboxnetctl", OWNER="root", GROUP="root", MODE="0600" +KERNEL=="vboxdrv", OWNER="root", GROUP="root", MODE="0600" +KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666" +KERNEL=="vboxnetctl", OWNER="root", GROUP="root", MODE="0600" diff -Nru virtualbox-6.1.16-dfsg/debian/virtualbox-guest-utils.udev virtualbox-6.1.38-dfsg/debian/virtualbox-guest-utils.udev --- virtualbox-6.1.16-dfsg/debian/virtualbox-guest-utils.udev 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/virtualbox-guest-utils.udev 2022-09-05 15:20:25.000000000 +0000 @@ -1,2 +1,2 @@ -KERNEL=="vboxguest", NAME="vboxguest", OWNER="root", MODE="0660" -KERNEL=="vboxuser", NAME="vboxuser", OWNER="root", MODE="0666" +KERNEL=="vboxguest", OWNER="root", MODE="0660" +KERNEL=="vboxuser", OWNER="root", MODE="0666" diff -Nru virtualbox-6.1.16-dfsg/debian/virtualbox-guest-x11.links virtualbox-6.1.38-dfsg/debian/virtualbox-guest-x11.links --- virtualbox-6.1.16-dfsg/debian/virtualbox-guest-x11.links 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/virtualbox-guest-x11.links 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -/usr/lib/VBoxOGL.so /usr/lib/virtualbox/additions/libGL.so.1 -/usr/lib/VBoxOGL.so /usr/lib/virtualbox/additions/libGL.so -/usr/lib/VBoxEGL.so /usr/lib/virtualbox/additions/libEGL.so.1 -/usr/lib/VBoxEGL.so /usr/lib/virtualbox/additions/libEGL.so diff -Nru virtualbox-6.1.16-dfsg/debian/virtualbox.install virtualbox-6.1.38-dfsg/debian/virtualbox.install --- virtualbox-6.1.16-dfsg/debian/virtualbox.install 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/virtualbox.install 2021-05-11 09:57:20.000000000 +0000 @@ -38,7 +38,5 @@ out/bin/VBoxSysInfo.sh /usr/share/virtualbox out/bin/UnattendedTemplates /usr/share/virtualbox -debian/vboxweb.service /lib/systemd/system/ - out/bin/rdesktop-vrdp /usr/bin out/bin/rdesktop-vrdp-keymaps /usr/share/virtualbox diff -Nru virtualbox-6.1.16-dfsg/debian/virtualbox-qt.menu virtualbox-6.1.38-dfsg/debian/virtualbox-qt.menu --- virtualbox-6.1.16-dfsg/debian/virtualbox-qt.menu 2020-09-07 16:43:43.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/virtualbox-qt.menu 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -?package(virtualbox-qt):needs="X11" section="Applications/Emulators"\ - title="VirtualBox" longtitle="PC virtualization solution"\ - description="Run several virtual systems on a single host computer"\ - command="/usr/bin/virtualbox" icon="/usr/share/pixmaps/virtualbox.xpm" diff -Nru virtualbox-6.1.16-dfsg/debian/virtualbox-source.files/control.modules.in virtualbox-6.1.38-dfsg/debian/virtualbox-source.files/control.modules.in --- virtualbox-6.1.16-dfsg/debian/virtualbox-source.files/control.modules.in 2020-09-16 09:58:23.000000000 +0000 +++ virtualbox-6.1.38-dfsg/debian/virtualbox-source.files/control.modules.in 2021-10-20 07:53:56.000000000 +0000 @@ -5,7 +5,7 @@ Uploaders: Ritesh Raj Sarraf , Gianfranco Costamagna Build-Depends: debhelper-compat (= 12), kbuild -Standards-Version: 4.5.0 +Standards-Version: 4.6.0 Homepage: https://www.virtualbox.org Vcs-Browser: https://salsa.debian.org/pkg-virtualbox-team/virtualbox Vcs-Git: https://salsa.debian.org/pkg-virtualbox-team/virtualbox.git diff -Nru virtualbox-6.1.16-dfsg/doc/manual/Config.kmk virtualbox-6.1.38-dfsg/doc/manual/Config.kmk --- virtualbox-6.1.16-dfsg/doc/manual/Config.kmk 2020-10-16 16:27:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/Config.kmk 2022-09-01 13:17:42.000000000 +0000 @@ -69,6 +69,7 @@ user_PrivacyPolicy.xml \ user_Glossary.xml \ oracle-accessibility-en.xml \ + oracle-diversity.xml \ oracle-support-en.xml ## List of user manual XML files common for all languages. @@ -96,7 +97,7 @@ # one of them is enough, they show the same information). ifdef VBOX_XML_CATALOG VBOX_XSLTPROC_WITH_CAT = $(REDIRECT) -E "XML_CATALOG_FILES=$(VBOX_XML_CATALOG)" -E "XML_DEBUG_CATALOG=" $1 -- \ - $(VBOX_XSLTPROC) --nonet --xinclude $(VBOX_XSLTPROC_OPTS) --path "$(VBOX_PATH_MANUAL_OUTBASE)" + $(VBOX_XSLTPROC) --nonet --xinclude $(VBOX_XSLTPROC_OPTS) --path "$(VBOX_PATH_MANUAL_OUTBASE)" VBOX_XMLLINT_WITH_CAT = $(REDIRECT) -E "XML_CATALOG_FILES=$(VBOX_XML_CATALOG)" -E "XML_DEBUG_CATALOG=" -- \ $(VBOX_XMLLINT) --nonet --xinclude --noout $(VBOX_XMLLINT_OPTS) --path "$(VBOX_PATH_MANUAL_OUTBASE)" else @@ -144,7 +145,7 @@ # # @param 1 Destination file. # @param 2 Full source file path. -# @param 3 Help infix. +# @param 3 Help infix. define def_vbox_single_refentry_to_h $(1).ts +| $(1): \ $$(VBOX_DOCBOOK_REFENTRY_TO_C_HELP) \ @@ -205,7 +206,9 @@ ' ' \ ' ' \ ' ' \ + ' ' \ ' ' \ + ' ' \ ' ' \ ' ' \ ' ' \ @@ -230,7 +233,9 @@ ' ' \ ' ' \ ' ' \ + ' ' \ ' ' \ + ' ' \ ' ' \ ' ' \ ' ' \ @@ -260,11 +265,12 @@ '' \ '' \ ' ' \ + ' ' \ ' ' \ ' ' \ ' ' \ $(foreach x,user_VBoxManage_CommandsOverview.xml user_isomakercmd-man.xml $(addprefix user_,$(VBOX_MANUAL_XML_REFENTRY_FILES) man_VBoxHeadless.xml man_vboximg-mount.xml)\ - ,' ') \ + ,' ') \ ' ' \ ' ' \ ' ' \ @@ -289,7 +295,7 @@ '' \ '' \ '' \ - '' \ + '' \ '' \ '' \ '' \ diff -Nru virtualbox-6.1.16-dfsg/doc/manual/docbook2latex.xsl virtualbox-6.1.38-dfsg/doc/manual/docbook2latex.xsl --- virtualbox-6.1.16-dfsg/doc/manual/docbook2latex.xsl 2020-10-16 16:27:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/docbook2latex.xsl 2022-09-01 13:17:43.000000000 +0000 @@ -557,7 +557,7 @@ - + @@ -1069,6 +1069,7 @@ or (name(..) = 'command') or (name(../..) = 'command') or (name(..) = 'cmdsynopsis') or (name(../..) = 'cmdsynopsis') or (name(..) = 'replaceable') or (name(../..) = 'replaceable') + or (name(..) = 'entry') or (name(../..) = 'entry') "> diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/Accessibility.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/Accessibility.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/Accessibility.xml 2020-10-16 16:27:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/Accessibility.xml 2022-09-01 13:17:43.000000000 +0000 @@ -27,7 +27,7 @@ Introduction - Welcome to the VirtualBox Accessibility Support documentation! This document is primarily + Welcome to the VirtualBox Accessibility Support documentation! This document is primarily a reference to help people who are interested in our project accessibility support and will describe how to use VirtualBox user interface step-by-step. Since whole the application navigation will be explained here, this document will also be helpful for those who are not familiar with our product user interface and wish to learn more. It will be written in a bit @@ -52,7 +52,7 @@ - First of them is VirtualBox Manager user interface, the main application window + First of them is VirtualBox Manager user interface, the main application window which allows to manage and configure virtual machines and their groups. Besides that, this window provides user with access to various global and machine related tools allowing to administrate some of VirtualBox objects and their settings. @@ -60,14 +60,14 @@ - Second application mode is Virtual Machine user interface, which allows to control + Second application mode is Virtual Machine user interface, which allows to control virtual machine guest screens as separate application windows. Besides that, this interface allows to access some of machine tools and adjust guest screens up to your needs, by changing their resolution and toggling full-screen, seamless and scaled modes. - But first of all we should start from the General Concept which is related to whole the + But first of all we should start from the General Concept which is related to whole the GUI and summarizes the navigation and accessibility aspects we are using for whole application. Binary files /tmp/tmp09q2yl6v/sV07raLuHN/virtualbox-6.1.16-dfsg/doc/manual/en_US/images/upload-key.png and /tmp/tmp09q2yl6v/7fbhyDgHOT/virtualbox-6.1.38-dfsg/doc/manual/en_US/images/upload-key.png differ diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/man_VBoxManage-bandwidthctl.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/man_VBoxManage-bandwidthctl.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/man_VBoxManage-bandwidthctl.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/man_VBoxManage-bandwidthctl.xml 2022-09-01 13:17:43.000000000 +0000 @@ -1,6 +1,6 @@ @@ -21,7 +21,7 @@ - http://www.oracle.com/pls/topic/lookup?ctx=acc&id=docacc. + . diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/oracle-diversity.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/oracle-diversity.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/oracle-diversity.xml 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/oracle-diversity.xml 2022-09-01 13:17:43.000000000 +0000 @@ -0,0 +1,13 @@ + + + + Diversity and Inclusion + Oracle is fully committed to diversity and inclusion. Oracle recognizes the influence of + ethnic and cultural values and is working to remove language from our products and + documentation that might be considered insensitive. While doing so, we are also mindful of + the necessity to maintain compatibility with our customers' existing technologies and the + need to ensure continuity of service as Oracle's offerings and industry standards evolve. + Because of these technical constraints, our effort to remove insensitive terms is an + ongoing, long-term process. + diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/oracle-support-en.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/oracle-support-en.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/oracle-support-en.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/oracle-support-en.xml 2022-09-01 13:17:43.000000000 +0000 @@ -6,7 +6,7 @@ %all.entities; ]> - + Access to Oracle Support @@ -17,11 +17,7 @@ - http://www.oracle.com/pls/topic/lookup?ctx=acc&id=info - or visit - http://www.oracle.com/pls/topic/lookup?ctx=acc&id=trs - if you are hearing impaired. + . diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_AdvancedTopics.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_AdvancedTopics.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_AdvancedTopics.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_AdvancedTopics.xml 2022-09-01 13:17:43.000000000 +0000 @@ -20,7 +20,7 @@ When a guest operating system is running in a virtual machine, it might be desirable to perform coordinated and automated logins - using credentials from a master login system. Credentials are user + using credentials passed from the host. Credentials are user name, password, and domain name, where each value might be empty. @@ -1421,7 +1421,7 @@ $ VBoxManage internalcommands createrawvmdk -filename \ -/path/to/file.vmdk -rawdisk /dev/sda +path-to-file.vmdk -rawdisk /dev/sda This creates the @@ -1454,7 +1454,7 @@ $ VBoxManage storageattach WindowsXP --storagectl "IDE Controller" \ - --port 0 --device 0 --type hdd --medium /path/to/file.vmdk + --port 0 --device 0 --type hdd --medium path-to-file.vmdk When this is done the selected virtual machine will boot from @@ -1488,7 +1488,7 @@ $ VBoxManage internalcommands createrawvmdk -filename \ -/path/to/file.vmdk -rawdisk /dev/sda -partitions 1,5 +path-to-file.vmdk -rawdisk /dev/sda -partitions 1,5 The command is identical to the one for full hard disk access, @@ -1548,7 +1548,7 @@ $ VBoxManage internalcommands createrawvmdk -filename \ -/path/to/file.vmdk -rawdisk /dev/sda -partitions 1,5 -relative +path-to-file.vmdk -rawdisk /dev/sda -partitions 1,5 -relative When used from a virtual machine, the image will then refer @@ -1575,7 +1575,7 @@ $ VBoxManage internalcommands createrawvmdk -filename -/path/to/file.vmdk -rawdisk /dev/sda -partitions 1,5 -mbr winxp.mbr +path-to-file.vmdk -rawdisk /dev/sda -partitions 1,5 -mbr winxp.mbr The modified MBR will be stored inside the image, not on the @@ -2147,7 +2147,7 @@ $ VBoxManage setextradata VM-name \ -"VBoxInternal/Devices/acpi/0/Config/CustomTable0" "/path/to/table.bin" +"VBoxInternal/Devices/acpi/0/Config/CustomTable0" "/path-to-table.bin" Configuring custom ACPI tables can for example avoid the need for @@ -2869,8 +2869,8 @@ Configure VM Selector Menu Entries - You can disable, or blacklist, certain entries in the global - settings page of the VM selector: + You can disable certain entries in the global settings page of + the VM selector: $ VBoxManage setextradata global GUI/RestrictedGlobalSettingsPages property[,property...] @@ -3002,8 +3002,7 @@ Configure VM Window Menu Entries - You can disable, or blacklist, certain menu actions in the VM - window: + You can disable certain menu actions in the VM window: VBoxManage setextradata "VM name"|global GUI/RestrictedRuntimeMenus OPTION[,OPTION...] @@ -3132,10 +3131,10 @@ VBoxManage setextradata "VM name"|global GUI/RestrictedRuntimeMenus - You can also disable, or blacklist, certain menu actions of - certain menus. Use the following command to disable certain - actions of the Application - menu. This is only available on Mac OS X hosts. + You can also disable certain menu actions of certain menus. Use + the following command to disable certain actions of the + Application menu. This is only + available on Mac OS X hosts. VBoxManage setextradata "VM name"|global GUI/RestrictedRuntimeApplicationMenuActions OPTION[,OPTION...] @@ -4162,7 +4161,7 @@ Configure VM Window Status Bar Entries - You can disable, or blacklist, certain status bar items: + You can disable certain status bar items: VBoxManage setextradata "VM name"|global GUI/RestrictedStatusBarIndicators OPTION[,OPTION...] @@ -4321,7 +4320,7 @@ Configure VM Window Visual Modes - You can disable, or blacklist, certain VM visual modes: + You can disable certain VM visual modes: $ VBoxManage setextradata VM-name GUI/RestrictedVisualStates property[,property...] @@ -4671,8 +4670,8 @@ Action when Terminating the VM - You can disallow, or blacklist, certain actions when terminating - a VM. To disallow specific actions, use the following command: + You can disallow certain actions when terminating a VM. To + disallow specific actions, use the following command: $ VBoxManage setextradata VM-name GUI/RestrictedCloseActions property[,property...] @@ -6081,52 +6080,54 @@ On Windows, autostart functionality consist of two components. - First one is configuration file where the administrator can both - set delayed start of the VMs and temporary disable autostarting - for the particular user. The configuration file should be located - in the folder accessible by all required users but it should have - permissions allowing the only reading by everyone but - administrators. The configuration file contains several options. - One is default_policy which controls whether the - autostart service allows or denies to start a VM for users which - are not in the exception list. The exception list starts with + The first component is a configuration file where the + administrator can both set a delayed start for the VMs and + temporarily disable autostarting for a particular user. The + configuration file should be located in a folder accessible by + all required users but it should have permissions allowing only + reading by everyone but administrators. The configuration file + contains several options. The + default_policy controls whether the autostart + service allows or denies starting of a VM for users that are not + in the exception list. The exception list starts with exception_list and contains a comma separated - list with usernames. Furthermore a separate startup delay can be - configured for every user to avoid overloading the host. A + list with usernames. Furthermore, a separate startup delay can + be configured for every user to avoid overloading the host. A sample configuration is given below: -# Default policy is to deny starting a VM, the other option is "allow". -default_policy = deny - -# Bob is allowed to start virtual machines but starting them -# will be delayed for 10 seconds -bob = { - allow = true - startup_delay = 10 -} + # Default policy is to deny starting a VM, the other option is "allow". + default_policy = deny -# Alice is not allowed to start virtual machines, useful to exclude certain users -# if the default policy is set to allow. -alice = { - allow = false -} + # Bob is allowed to start virtual machines but starting them + # will be delayed for 10 seconds + bob = { + allow = true + startup_delay = 10 + } + + # Alice is not allowed to start virtual machines, useful to exclude certain users + # if the default policy is set to allow. + alice = { + allow = false + } - The user name can be specified using the following forms: "user", - "domain\user", ".\user" and "user@domain". Administrator must add - the VBOXAUTOSTART_CONFIG environment variable into - system variables containing the path to the configuration file - described above. The environment variable tells the autostart services - what configuration file is used. + The user name can be specified using the following forms: + "user", "domain\user", ".\user" and "user@domain". An + administrator must add the + VBOXAUTOSTART_CONFIG environment variable + into system variables containing the path to the configuration + file described above. The environment variable tells the + autostart services which configuration file is used. - Second component of autostart functionality is Windows service, every - instance of it works on behalf of particular user using its own - credentials. + The second component of autostart functionality is a Windows + service. Every instance of this works on behalf of a particular + user using their credentials. @@ -6134,7 +6135,7 @@ administrators group must run the following command: - VBoxAutostartSvc install --user=user [--password-file=password_file] +VBoxAutostartSvc install --user=user [--password-file=password_file] The password file should contain the password followed by a line @@ -6147,7 +6148,7 @@ administrators group must run the following command: - VBoxAutostartSvc delete --user=user +VBoxAutostartSvc delete --user=user If a user has changed their password then a member of the @@ -6158,19 +6159,19 @@ - Finally, the particular user should define which VM should be - started at boot or not. The user should run the following command - for every VM it desired to start at boot: + Finally, the user should define which VMs should be started at + boot. The user should run the following command for every VM + they wish to start at boot: - VBoxManage modifyvm VM name or UUID --autostart-enabled on +VBoxManage modifyvm VM name or UUID --autostart-enabled on - The user can remove the particular VM from the VMs starting at boot - by running the following command: + The user can remove a particular VM from the VMs starting at + boot by running the following command: - VBoxManage modifyvm VM name or UUID --autostart-enabled off +VBoxManage modifyvm VM name or UUID --autostart-enabled off @@ -7223,7 +7224,7 @@ devices: -VBoxManage usbdevsource add unique-name --backend USB-IP --address device-server[:port] +VBoxManage usbdevsource add unique-name --backend USBIP --address device-server[:port] USB devices exported on the device server are then accessible diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_BasicConcepts.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_BasicConcepts.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_BasicConcepts.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_BasicConcepts.xml 2022-09-01 13:17:43.000000000 +0000 @@ -53,320 +53,227 @@ Because &product-name; is designed to provide a generic - virtualization environment for x86 systems, it can run operating - systems (OSes) of any kind. However, &product-name; focuses on the - following guest systems: + virtualization environment for x86 systems, it can run guest + operating systems (OSes) of any kind. - - - - - Windows NT 4.0: - - - - - - - Fully supports all versions, editions, and service packs. - Note that you might encounter issues with some older - service packs, so install at least service pack 6a. - - - - - - Guest Additions are available with a limited feature set. - - - - - - - - - Windows 2000, Windows XP, Windows Server - 2003, Windows Vista, Windows Server 2008, Windows 7, Windows - Server 2008 R2, Windows 8, Windows Server 2012, Windows 8.1, - Windows Server 2012 R2, Windows 10 (non-Insider Preview - releases), Windows Server 2016, Windows Server - 2019: - - - - - - - Fully supports all versions, editions, and service packs, - including 64-bit versions. - - - - - - Note that you must enable hardware virtualization when - running at least Windows 8. - - - - - - Guest Additions are available. - - - - - - - - - MS-DOS, Windows 3.x, Windows 95, Windows - 98, Windows ME: - - - - - - - Limited testing has been performed. - - - - - - Use beyond legacy installation mechanisms is not - recommended. - - - - - - Guest Additions are not available. - - - - - - - - - Linux 2.4: - - - - Limited support. - - - - - - Linux 2.6: - - - - - - - Fully supports all versions and editions, both 32-bit and - 64-bit. - - - - - - For best performance, use at least Linux kernel version - 2.6.13. - - - - - - Guest Additions are available. - - - - - - - - Certain Linux kernel releases have bugs that prevent them - from executing in a virtual environment. See - . - - - - - - - Linux 3.x and later: - - - - - - - Fully supports all versions and editions, both 32-bit and - 64-bit. - - - - - - Guest Additions are available. - - - - - - - - - Oracle Solaris 10 and Oracle Solaris - 11: - - - - - - - Fully supports all versions starting with Oracle Solaris - 10 8/08 and Oracle Solaris 11. - - - - - - Supports 64-bit prior to Oracle Solaris 11 11/11, and - 32-bit. - - - - - - Guest Additions are available. - - - - - - - - - FreeBSD: - - - - - - - Limited support. - - - - - - Note that you must enable hardware virtualization when - running FreeBSD. - - - - - - Guest Additions are not available. - - - - - - - - - OpenBSD: - - - - - - - Supports at least version 3.7. - - - - - - Note that you must enable hardware virtualization when - running OpenBSD. - - - - - - Guest Additions are not available. - - + + The following guest OS platforms are supported: + - - + - OS/2 Warp 4.5: + Platforms With Full Support. + These guest OS platforms qualify for Oracle Premier Support. + See . - - - - - - Only MCP2 is supported. Other OS/2 versions might not - work. - - - - - - Note that you must enable hardware virtualization when - running OS/2 Warp 4.5. - - - - - - Guest Additions are available with a limited feature set. - See . - - - - - Mac OS X: + Platforms With Limited + Support. These legacy guest OS platforms can be + used with &product-name;, but only qualify for best + effort support. Therefore, resolution of customer + issues is not guaranteed. See + . - - - - - - &product-name; 3.2 added experimental support for Mac OS X - guests, with restrictions. See - and - . - - - - - - Guest Additions are not available. - - - - + + Guest Operating Systems With Full Support + + + + + Operating System + + + Comments + + + + + + + Windows 10 (32-bit and 64-bit) + + + Insider preview builds are not supported + + + + + Windows 8 and 8.1 (32-bit and 64-bit) + + + + + + Windows Server 2019 (64-bit) + + + + + + Windows Server 2016 (64-bit) + + + + + + Windows Server 2012 and 2012 R2 (64-bit) + + + + + + Solaris 11 (32-bit and 64-bit) + + + + + + Solaris 10 8/11 Update 10 and later (32-bit and 64-bit) + + + + + + Oracle Linux 8 (64-bit) + + + Includes Red Hat Enterprise Linux 8, CentOS 8 + + + + + Oracle Linux 7 (64-bit) + + + Includes Red Hat Enterprise Linux 7, CentOS 7 + + + + + Oracle Linux 6 (32-bit and 64-bit) + + + Includes Red Hat Enterprise Linux 6, CentOS 6 + + + + + Ubuntu 16.04 LTS (Xenial Xerus) (32-bit and 64-bit) + + + + + + Ubuntu 18.04 LTS (Bionic Beaver) (64-bit) + + + + + + Ubuntu 20.04 LTS (Focal Fossa) (64-bit) + + + + + + SUSE Linux Enterprise Server 15 (64-bit) + + + + + + SUSE Linux Enterprise Server 12 (64-bit) + + + + + +
+ + + Legacy Guest Operating Systems With Limited Support + + + + + Operating System + + + Comments + + + + + + + Windows 7 (32-bit and 64-bit) + + + + + + Windows Vista SP2 and later (32-bit and 64-bit) + + + + + + Windows XP (32-bit) + + + + + + Windows Vista (32-bit) + + + + + + Windows Server 2008 and 2008 R2 (32-bit and 64-bit) + + + + + + Windows Server 2003 (32-bit and 64-bit) + + + + + + Oracle Linux 5 (32-bit and 64-bit) + + + Includes Red Hat Enterprise Linux 5, CentOS 5 + + + + + Ubuntu 14.04.5 LTS (Trusty Tahr) (32-bit and 64-bit) + + + + + + OS/2 Warp 4.5 + + + + + +
+ Mac OS X Guests @@ -1808,7 +1715,7 @@ width="10cm" /> - + Depending on the guest OS type that you selected when you created @@ -1820,8 +1727,8 @@ IDE controller. A virtual - CD/DVD drive is attached to the secondary master port of the - IDE controller. + CD/DVD drive is attached to device 0 on the secondary channel + of the IDE controller. @@ -1918,11 +1825,10 @@ The device slot of the controller that the virtual disk is connected to. IDE - controllers have four slots which have traditionally been - called primary master, primary slave, secondary master, - and secondary slave. By contrast, SATA and SCSI - controllers offer you up to 30 slots for attaching virtual - devices. + controllers have four slots: primary device 0, primary + device 1, secondary device 0, and secondary device 1. By + contrast, SATA and SCSI controllers offer you up to 30 + slots for attaching virtual devices. diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Glossary.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Glossary.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Glossary.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Glossary.xml 2022-09-01 13:17:43.000000000 +0000 @@ -5,7 +5,6 @@ %all.entities; ]> - A diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_GuestAdditions.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_GuestAdditions.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_GuestAdditions.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_GuestAdditions.xml 2022-09-01 13:17:43.000000000 +0000 @@ -199,8 +199,7 @@ virtual machine, set the value of its /VirtualBox/GuestAdd/CheckHostVersion guest property to 0. See - . + . @@ -985,9 +984,7 @@ &product-name; also ships with a set of drivers that improve running OS/2 in a virtual machine. Due to restrictions of OS/2 itself, this variant of the Guest Additions has a limited - feature set. See for - details. + feature set. See for details.
@@ -1094,7 +1091,7 @@ Transient shares, that are added at runtime and disappear when - the VM is powered off. These can be created using a checkbox + the VM is powered off. These can be created using a check box in the VirtualBox Manager, or by using the option of the VBoxManage sharedfolder add command. @@ -1107,7 +1104,7 @@ Shared folders can either be read-write or read-only. This means that the guest is either allowed to both read and write, or just read files on the host. By default, shared folders are read-write. - Read-only folders can be created using a checkbox in the + Read-only folders can be created using a check box in the VirtualBox Manager, or with the option of the VBoxManage sharedfolder add command. @@ -1164,7 +1161,7 @@ therefore visible in Windows Explorer. To attach the host's shared folder to your Windows guest, open Windows Explorer and look for the folder in My - Networking Places, Entire + Networking Places, Entire Network, &product-name; Shared Folders. By right-clicking on a shared folder and selecting Map Network @@ -1395,10 +1392,11 @@ In the context of using drag and drop, the origin of the data is called the source. That is, where the actual - data comes from and is specified. The destination - specifies where the data from the source should go to. - Transferring data from the source to the destination can be done in - various ways, such as copying, moving, or linking. + data comes from and is specified. The + destination specifies where the data from the + source should go to. Transferring data from the source to the + destination can be done in various ways, such as copying, moving, + or linking. @@ -1412,7 +1410,8 @@ When transferring data from the host to the guest OS, the host in this case is the source, whereas the guest OS is the destination. However, when transferring data from the guest OS to the host, the - guest OS this time became the source and the host is the destination. + guest OS this time became the source and the host is the + destination. @@ -1495,9 +1494,9 @@ As &product-name; can run on a variety of host operating systems and also supports a wide range of guests, certain data formats - must be translated after transfer. This is so that the destination - operating system, which receiving the data, is able to handle - them in an appropriate manner. + must be translated after transfer. This is so that the + destination operating system, which receives the data, is able + to handle them in an appropriate manner. @@ -1561,11 +1560,10 @@ On Linux hosts and guests, programs can query for drag and drop - data while the drag operation still is in progress (e.g. on LXDE - using the PCManFM file manager). This currently is not supported. - - As a workaround, a different file manager (e.g. Nautilus) can - be used instead. + data while the drag operation is still in progress. For example, + on LXDE using the PCManFM file manager. This currently is not + supported. As a workaround, a different file manager, such as + Nautilus, can be used instead. @@ -2391,6 +2389,90 @@ + + + + + Controlling Virtual Monitor Topology + + + + X11/Wayland Desktop Environments + + + The Guest Additions provide services for controlling the guest + system's monitor topology. Monitor topology means the resolution + of each virtual monitor and its state (disabled/enabled). The + resolution of a virtual monitor can be modified from the host + side either by resizing the window that hosts the virtual monitor, + through the view menu or through + VBoxManage controlvm "vmname" setscreenlayout. + On guest operating systems with X11/Wayland desktops this is + put into effect by either of two following services: + + + + VBoxClient --vmsvga + VBoxDRMClient + + + + Here are some details about guest screen resolution control + functionality: + + + + + + + On X11/Wayland desktops the resizing service is started during + desktop session initialization, that is desktop login. On X11 + desktops VBoxClient --vmsvga handles screen + topology through the RandR extension. + On Wayland clients VBoxDRMClient is used. The + decision is made automatically at each desktop session start. + + + + + On 32 bit guest operating systems VBoxDRMClient + is always used, in order to work around bugs. + + + + + Since the mentioned monitor topology control services are + initialized during the desktop session start, it is impossible + to control the monitor resolution of display managers such as + gdm, lightdm. This default behavior can be changed by setting + the guest property /VirtualBox/GuestAdd/DRMResize + of the virtual machine to any value. Please refer to + for updating guest + properties. When this guest property is set then + VBoxDRMClient is started during the guest OS boot + and stays active all the time, for both ithe display manager + login screen and the desktop session. + + + + + + + + Known Limitations + + VBoxDRMClient is not able to handle arbitrary guest + monitor topologies. Specifically, disabling a guest monitor + (except the last one) invalidates the monitor topology due to + limitations in the Linux kernel module vmwgfx.ko. + iFor example, when the guest is configured to have 4 monitors + it is not recommended to disable the 2nd or 3rd monitor. + + + + + + diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Introduction.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Introduction.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Introduction.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Introduction.xml 2022-09-01 13:17:43.000000000 +0000 @@ -164,7 +164,7 @@ - Host operating system (host + Host operating system (host OS). This is the OS of the physical computer on which &product-name; was installed. There are versions of &product-name; for Windows, Mac OS X, Linux, and Oracle @@ -180,7 +180,7 @@ - Guest operating system (guest + Guest operating system (guest OS). This is the OS that is running inside the virtual machine. Theoretically, &product-name; can run any x86 OS such as DOS, Windows, OS/2, FreeBSD, and OpenBSD. But to @@ -199,14 +199,13 @@ - Virtual machine (VM). This - is the special environment that &product-name; creates for - your guest OS while it is running. In other words, you run - your guest OS in a VM. Normally, a VM is - shown as a window on your computer's desktop. Depending on - which of the various frontends of &product-name; you use, the - VM might be shown in full screen mode or remotely on another - computer. + Virtual machine (VM). This is + the special environment that &product-name; creates for your + guest OS while it is running. In other words, you run your + guest OS in a VM. Normally, a VM is shown + as a window on your computer's desktop. Depending on which of + the various frontends of &product-name; you use, the VM might + be shown in full screen mode or remotely on another computer. @@ -227,8 +226,8 @@ - Guest Additions. This - refers to special software packages which are shipped with + Guest Additions. This refers + to special software packages which are shipped with &product-name; but designed to be installed inside a VM to improve performance of the guest OS and to add extra features. See @@ -671,7 +670,7 @@ - Red Hat Enterprise Linux 6, 7 and 8 + CentOS/Red Hat Enterprise Linux 6, 7 and 8 @@ -736,9 +735,7 @@ - Note that the above list is informal. Oracle support for customers - who have a support contract is limited to a subset of the listed - host OSes. Also, any feature which is marked as + Note that any feature which is marked as experimental is not supported. Feedback and suggestions about such features are welcome. @@ -790,12 +787,11 @@ extension packs can be downloaded which extend the functionality of the &product-name; base package. Currently, Oracle provides a single extension pack, available from: - . The - extension pack provides the following added functionality: + . The extension pack + provides the following added functionality: - + @@ -807,8 +803,7 @@ The virtual USB 3.0 (xHCI) device. See - . + . @@ -846,7 +841,13 @@ - + + + Cloud integration features. See . + + + + &product-name; extension packages have a @@ -929,7 +930,7 @@
VirtualBox Manager Window, After Initial Startup - + @@ -964,7 +965,7 @@
VirtualBox Manager Window, After Creating Virtual Machines - + @@ -986,7 +987,7 @@
Creating a New Virtual Machine: Name and Operating System - + @@ -1110,7 +1111,7 @@
Creating a New Virtual Machine: Hard Disk - + @@ -1215,7 +1216,7 @@
Creating a New Virtual Machine: File Location and Size - + @@ -1428,6 +1429,7 @@ width="7cm" /> +
@@ -1876,6 +1878,7 @@ width="10cm" />
+
@@ -2566,35 +2569,7 @@ Cloud service formats. Export to and import from cloud services such as &oci; is supported. - See the following topics: - - - - - - - - - - - - - - - - - - - - Before using &product-name; with &oci; there are some initial - configuration steps you need to consider. See - . - - - - &product-name; can also be used to create new instances from a - custom image stored on &oci;. See - + See . @@ -2724,6 +2699,7 @@ width="12cm" />
+
@@ -2937,216 +2913,237 @@ + + + + + Integrating with &oci; + + + This section describes how to use the features of &product-name; + to integrate with &oci;. + + + + Integrating with &oci; involves the following steps: + + + + + + + Prepare for &oci; + Integration. Before using &product-name; with &oci; + there are some initial configuration steps you may need to do. + See . + + + + + + Use &product-name; with + &oci;. + describes how you can use &product-name; with &oci;. + + + + + Preparing for &oci; Integration - There are some common configuration steps you need to take - before using &product-name; to integrate with your &oci; - account. + Perform the following configuration steps before using + &product-name; to integrate with your &oci; account. - + + + + + Install the Extension Pack. + Cloud integration features are only available when you + install the &product-name; Extension Pack. See + . + + Create a key pair. Generate an API signing key pair that is used for API requests to - &oci;. + &oci;. See . - + + Upload the public key of the key pair from your client + device to the cloud service. See + . + + - - - The key pair is usually installed in the - .oci folder in your home directory. - For example, ~/.oci on a Linux - system. - - + + + Create a cloud profile. The + cloud profile contains resource identifiers for your cloud + account, such as your user OCID, and details of your key + pair. See . + + - - - Upload the public key of the key pair to the cloud - service. - - + - + + + + + Creating an API Signing Key Pair + + + + + To use the cloud integration features of &product-name;, you + must generate an API signing key pair that is used for API + requests to &oci;. + + + + Your API requests are signed with your private key, and &oci; + uses the public key to verify the authenticity of the request. + You must upload the public key to the &oci; Console. + + + + + This key pair is not the same SSH key that you use to access + compute instances on &oci;. + + + + + - For step-by-step instructions for creating and uploading an - API signing key for &oci;, see: + (Optional) Create a .oci directory to + store the key pair. +$ mkdir ~/.oci + - + The key pair is usually installed in the + .oci folder in your home directory. For + example, ~/.oci on a Linux system. - Create a cloud profile. The - cloud profile contains resource identifiers for your cloud - account, such as your user OCID, and the fingerprint for - your public key. You can create a cloud profile in the - following ways: + Generate the private key. - + + Use the openssl command. + - - - Automatically, by using the Cloud - Profile Manager. See - . - - + - Automatically, by using the VBoxManage - cloudprofile command. See - . + To generate a private key with a passphrase: - - - - Manually, by creating an oci_config - file in your &product-name; global configuration - directory. For example, this is - $HOME/.config/VirtualBox/oci_config - on a Linux host. - +$ openssl genrsa -out ~/.oci/oci_api_key.pem -aes128 2048 - Manually, by creating a config file - in your &oci; configuration directory. For example, this - is $HOME/.oci/config on a Linux - host. + To generate a private key without a passphrase: - - This is the same file that is used by the &oci; command - line interface. - - - - &product-name; automatically uses the - config file if no cloud profile - file is present in your global configuration directory. - Alternatively, you can import this file manually into - the Cloud Profile Manager. - +$ openssl genrsa -out ~/.oci/oci_api_key.pem 2048 - - - For more information about the cloud profile settings used - by &oci; see: - - - - - - Custom Linux images. To - export a custom Linux image, prepare the VM as described - here: + Change permissions for the private key. - - - - +$ chmod 600 ~/.oci/oci_api_key.pem - - Subnets. When exporting a - VM to cloud, ensure that the subnets that are used by source - VMs are available in the target compartment on the cloud - service. + Generate the public key. + +$ openssl rsa -pubout -in ~/.oci/oci_api_key.pem -out ~/.oci/oci_api_key_public.pem - + - - - Exporting an Appliance to &oci; - - - &product-name; supports the export of VMs to an &oci; service. - The exported VM is stored on &oci; as a custom image. You can - configure whether a cloud instance is created and started after - the export process has completed. - + - - Before you can export a VM to &oci; ensure that you have done - the required preconfiguration tasks, as described in - . - + Uploading the Public Key to &oci; - Perform the following steps to export a VM to &oci;: + Use the following steps to upload your public key to &oci;. - Select File, - Export Appliance to open - the Export Virtual - Appliance wizard. + Log in to the &oci; Console. + + + + + + Display the User Settings + page. - Select a VM to export and click - Next to open the - Appliance Settings screen. + Click Profile, + User Settings. - From the Format drop-down - list, select &oci;. + Display your current API signing keys. - In the Account drop-down - list, select your &oci; account. + Click Resources, + API Keys. + + - You can set up &oci; accounts by using the Cloud Profile - Manager. + Upload the public key. - The list after the Account - field shows the profile settings for your cloud account. + Click Add Public Key. -
- Appliance Settings Screen, Showing Cloud Profile and Machine Creation - Settings + + The Add Public Key dialog + is displayed. + + +
+ Upload Public Key Dialog in &oci; Console - @@ -3154,310 +3151,206 @@
- In the Machine Creation - field, select an option to configure settings for a cloud - instance created when you export to &oci;. The options - enable you to do one of the following: + Select one of the following options: - Configure settings for the cloud instance - after you have finished exporting - the VM. - - - - - - Configure settings for the cloud instance - before you start to export the VM. + Choose Public Key File. + This option enables you to browse to the public key file + on your local hard disk. - Do not create a cloud instance when you export the VM. + Paste Public Keys. This + option enables you to paste the contents of the public + key file into the window in the dialog box. - Click Next to make an API - request to the &oci; service and open the - Virtual System Settings - screen. + Click Add to upload the + public key. - - - Optionally edit storage settings used for the exported - virtual machine in &oci;. You can change the following - settings: - - - - - - - The name of the bucket used to store the exported files. - - - - - - Whether to store the custom image in &oci;. - - + - - - The name for the custom image in &oci;. - - + - - - The launch mode for the custom image. - + - - Paravirtualized mode - gives improved performance and should be suitable for - most &product-name; VMs. - + Creating a Cloud Profile - - Emulated mode is - suitable for legacy OS images. - - + + &product-name; uses a cloud profile to + connect to &oci;. A cloud profile is a text file that contains + details of your key files and Oracle Cloud Identifier (OCID) + resource identifiers for your cloud account, such as the + following: + - + + - Click Export to export the - VM to &oci;. + Fingerprint of the public + key. To obtain the fingerprint, you can use the + openssl command: - - Depending on the selection in the - Machine Creation field, the - Cloud Virtual Machine - Settings screen may be displayed before or after - export. This screen enables you to configure settings for - the cloud instance, such as Shape and Disk Size. - +$ openssl rsa -pubout -outform DER -in ~/.oci/oci_api_key.pem | openssl md5 -c + + - Click Create. The VM is - exported to &oci;. + Location of the private key on the + client device. Specify the full path to the + private key. + + - Depending on the Machine - Creation setting, a cloud instance may be started - after upload to &oci; is completed. + (Optional) Passphrase for the private + key.. This is only required if the key is + encrypted. - Monitor the export process by using the &oci; Console. + Region. Shown on the &oci; + Console. Click + Administration, + Tenancy Details. - - - - You can also use the VBoxManage export - command to export a VM to &oci;. See - . - - - - - - - Importing an Instance from &oci; - - - &product-name; supports the import of cloud instances from an - &oci; service. - - - - Before you can import an instance from &oci; ensure that you - have done the required preconfiguration tasks, as described in - . - - - - Perform the following steps to import an instance from &oci;: - - - - - Select File, - Import Appliance to open - the Import Virtual - Appliance wizard. - - - - In the Source drop-down - list, select &oci;. + Tenancy OCID. Shown on the + &oci; Console. Click + Administration, + Tenancy Details. - In the Account drop-down - list, select your &oci; account. + A link enables you to copy the Tenancy OCID. + + - You can set up &oci; accounts by using the Cloud Profile - Manager. + Compartment OCID. Shown on + the &oci; Console. Click + Identity, + Compartments. - The list after the Account - field shows the profile settings for your cloud account. + A link enables you to copy the Compartment OCID. + + - Choose the required cloud instance from the list in the - Machines field. + User OCID. Shown on the + &oci; Console. Click + Profile, + User Settings. - Click Next to make an API - request to the &oci; service and open the - Appliance Settings screen. + A link enables you to copy the User OCID. + + + + You can create a cloud profile in the following ways: + + + + - Optionally edit settings for the new local virtual machine. + Automatically, by using the Cloud + Profile Manager. See + . - For example, you can edit the VM name and description. + The Cloud Profile Manager is a component of &product-name; + that enables you to create, edit, and manage cloud profiles + for your cloud service accounts. + -
- Import Cloud Instance Screen, Showing Profile Settings and VM Settings - - - - - -
- + - Click Import to import the - instance from the cloud service. + Automatically, by using the VBoxManage + cloudprofile command. See + . - Monitor the import process by using the &oci; Console. + Manually, by creating an oci_config + file in your &product-name; global configuration directory. + For example, this is + $HOME/.config/VirtualBox/oci_config on + a Linux host. - - - - You can also use the VBoxManage import - command to import an instance from &oci;. See - . - - - - - Importing an Instance: Overview of Events - - - The following describes the sequence of events when you import - an instance from &oci;. - - - - - - - A custom image is created from the boot volume of the - instance. - - - - - - The custom image is exported to an &oci; object and is - stored using Object Storage in the bucket specified by the - user. - - - - - - The &oci; object is downloaded to the local host. The - object is a TAR archive which contains a boot volume of - the instance in QCOW2 format and a JSON file containing - metadata related to the instance. - - - - - - The boot volume of the instance is extracted from the - archive and a new VMDK image is created by converting the - boot volume into the VMDK format. The VMDK image is - registered with &product-name;. - - - - - - A new VM is created using the VMDK image for the cloud - instance. - - - - By default, the new VM is not started after import from - &oci;. - - + + + Manually, by creating a config file in + your &oci; configuration directory. For example, this is + $HOME/.oci/config on a Linux host. + - - - The downloaded TAR archive is deleted after a successful - import. - - + + This is the same file that is used by the &oci; command line + interface. + - + + &product-name; automatically uses the + config file if no cloud profile file is + present in your global configuration directory. + Alternatively, you can import this file manually into the + Cloud Profile Manager. + +
- + - + - The Cloud Profile Manager + Using the Cloud Profile Manager + + + This section describes how to use the Cloud Profile Manager to + create a cloud profile. + - The Cloud Profile Manager is a component of &product-name; that - enables you to create, edit, and manage cloud profiles for your - cloud service accounts. + To open the Cloud Profile Manager click + File, + Cloud Profile Manager in the + VirtualBox Manager window.
@@ -3471,21 +3364,29 @@
- To open the Cloud Profile Manager select - File, - Cloud Profile Manager from the - VirtualBox Manager window. + You can use the Cloud Profile Manager in the following ways: - - You can use the Cloud Profile Manager to create a new cloud - profile automatically or to create a cloud profile by importing - settings from your &oci; configuration file. - + + + + + To create a new cloud profile automatically + + + + + + To create a cloud profile by importing settings from your + &oci; configuration file. + + + + Perform the following steps to create a new cloud profile - automatically: + automatically, using the Cloud Profile Manager: @@ -3524,12 +3425,12 @@ - + @@ -3574,7 +3475,7 @@ Perform the following steps to import an existing &oci; - configuration file: + configuration file into the Cloud Profile Manager: @@ -3636,32 +3537,70 @@
- + - Creating New Cloud Instances from a Custom Image + Using &product-name; With &oci; - You can use &product-name; to create new instances from a custom - image on your cloud service. + This section describes how you can use &product-name; with &oci; + to do the following tasks: - - describes how to create a - custom image when you are exporting a VM to &oci;. Using a - custom image means that you can quickly create cloud instances - without having to upload your image to the cloud service every - time. - + + + + + Export an &product-name; VM to &oci;. See + . + + + + + + Import a cloud instance into &product-name;. See + . + + + + + + Create a new cloud instance from a custom image stored on + &oci;. See . + + + + + + Use the VBoxManage commands to integrate + with &oci; and perform cloud operations. See + . + + + + + + + + + + Exporting an Appliance to &oci; - Before you can create a new cloud instance in &oci; ensure that - you have done the required preconfiguration tasks, as described - in . + &product-name; supports the export of VMs to an &oci; service. + The exported VM is stored on &oci; as a custom Linux image. You + can configure whether a cloud instance is created and started + after the export process has completed. + + + Before you export a VM to &oci;, you must prepare the VM as + described in . + + + - Perform the following steps to create a new cloud instance on - &oci;: + Use the following steps to export a VM to &oci;: @@ -3669,27 +3608,27 @@ Select File, - New Cloud VM to open the - Create Cloud Virtual - Machine wizard. + Export Appliance to open + the Export Virtual + Appliance wizard. - - - From the Destination - drop-down list, select - &oci;. + Select a VM to export and click + Next to open the + Appliance Settings screen. + + - In the Account drop-down - list, select your &oci; account. + From the Format drop-down + list, select &oci;. - You can set up &oci; accounts by using the Cloud Profile - Manager. + In the Account drop-down + list, select the cloud profile for your &oci; account. @@ -3697,23 +3636,591 @@ field shows the profile settings for your cloud account. - - In the Images list, select - from the custom images available on &oci;. - - -
- New Cloud VM Wizard, Showing List of Custom Images +
+ Appliance Settings Screen, Showing Cloud Profile and Machine Creation + Settings -
- Click Next to make an API + In the Machine Creation + field, select an option to configure settings for a cloud + instance created when you export to &oci;. The options + enable you to do one of the following: + + + + + + + Configure settings for the cloud instance + after you have finished exporting + the VM. + + + + + + Configure settings for the cloud instance + before you start to export the VM. + + + + + + Do not create a cloud instance when you export the VM. + + + + + + + Click Next to make an API + request to the &oci; service and open the + Virtual System Settings + screen. + + + + + + (Optional) Edit storage settings used for the exported + virtual machine in &oci;. You can change the following + settings: + + + + + + + The name of the bucket used to store the exported files. + + + + + + Whether to store the custom image in &oci;. + + + + + + The name for the custom image in &oci;. + + + + + + The launch mode for the custom image. + + + + Paravirtualized mode + gives improved performance and should be suitable for + most &product-name; VMs. + + + + Emulated mode is + suitable for legacy OS images. + + + + + + + Click Export to continue. + + + + + + Depending on the selection in the + Machine Creation field, the + Cloud Virtual Machine + Settings screen may be displayed before or after + export. This screen enables you to configure settings for + the cloud instance, such as Shape and Disk Size. + + + + Click Create. The VM is + exported to &oci;. + + + + Depending on the Machine + Creation setting, a cloud instance may be started + after upload to &oci; is completed. + + + + + + Monitor the export process by using the &oci; Console. + + + + + + + You can also use the VBoxManage export + command to export a VM to &oci;. See + . + + + + + Preparing a VM for Export to &oci; + + + &oci; provides the option to import a custom Linux image. + Before an &product-name; image can be exported to &oci;, the + custom image needs to be prepared to ensure that instances + launched from the custom image can boot correctly and that + network connections will work. This section provides advice on + how to prepare a Linux image for export from &product-name;. + + + + The following list shows some tasks to consider when preparing + an Oracle Linux VM for export: + + + + + + + Use DHCP for network + addresses. Configure the VM to use a DHCP + server to allocate network addresses, rather than using a + static IP address. The &oci; instance will then be + allocated an IP address automatically. + + + + + + Do not specify a MAC + address. The network interface configuration + for the VM must not specify the MAC address. + + + + Remove the HWADDR setting from the + /etc/sysconfig/ifcfg-devicename + network script. + + + + + + Disable persistent network device + naming rules. This means that the &oci; + instance will use the same network device names as the VM. + + + + + + + Change the GRUB kernel parameters. + + + + Add net.ifnames=0 and + biosdevname=0 as kernel parameter + values to the GRUB_CMDLINE_LINUX + variable. + + + + + + Update the GRUB configuration. + + +# grub2-mkconfig -o /boot/grub2/grub.cfg + + + + + Disable any udev rules for network + device naming. + + + + For example, if an automated udev + rule exists for net-persistence: + + +# cd /etc/udev/rules.d +# rm -f 70-persistent-net.rules +# ln -s /dev/null /etc/udev/rules.d/70-persistent-net.rules + + + + + + + + Enable the serial + console. This enables you to troubleshoot the + instance when it is running on &oci;. + + + + + + + Edit the /etc/default/grub file, + as follows: + + + + + + + Remove the resume setting from + the kernel parameters. This setting slows down + boot time significantly. + + + + + + Replace GRUB_TERMINAL="gfxterm" + with GRUB_TERMINAL="console + serial". This configures use of the + serial console instead of a graphical terminal. + + + + + + Add GRUB_SERIAL_COMMAND="serial --unit=0 + --speed=115200". This configures the + serial connection. + + + + + + Add console=tty0 + console=ttyS0,115200 to the + GRUB_CMDLINE_LINUX variable. + This adds the serial console to the Linux kernel + boot parameters. + + + + + + + + + Regenerate the GRUB configuration. + + +# grub2-mkconfig -o /boot/grub2/grub.cfg + + + + + To verify the changes, reboot the machine and run the + dmesg command to look for the + updated kernel parameters. + + +# dmesg |grep console=ttyS0 + + + + + + + + Enable paravirtualized device + support. You do this by adding the + virtio drivers to the + initrd for the VM. + + + + + + + This procedure works only on machines with a Linux + kernel of version 3.4 or later. Check that the VM is + running a supported kernel: + + +# uname -a + + + + + Use the dracut tool to rebuild + initrd. Add the + qemu module, as follows: + + +# dracut –-logfile /var/log/Dracut.log –-force –-add qemu + + + + + Verify that the virtio drivers are + now present in initrd. + + + # lsinitrd |grep virtio + + + + + + + + + For more information about importing a custom Linux image into + &oci;, see also: + + + + + + + + + + + + + Importing an Instance from &oci; + + + Perform the following steps to import a cloud instance from + &oci; into &product-name;: + + + + + + + Select File, + Import Appliance to open + the Import Virtual + Appliance wizard. + + + + In the Source drop-down + list, select &oci;. + + + + In the Account drop-down + list, select the cloud profile for your &oci; account. + + + + The list after the Account + field shows the profile settings for your cloud account. + + + + Choose the required cloud instance from the list in the + Machines field. + + + + Click Next to make an API + request to the &oci; service and display the + Appliance Settings screen. + + + + + + (Optional) Edit settings for the new local virtual machine. + + + + For example, you can edit the VM name and description. + + +
+ Import Cloud Instance Screen, Showing Profile Settings and VM Settings + + + + + +
+ + + Click Import to import the + instance from &oci;. + +
+ + + + Monitor the import process by using the &oci; Console. + + + +
+ + + You can also use the VBoxManage import + command to import an instance from &oci;. See + . + + + + + Importing an Instance: Overview of Events + + + The following describes the sequence of events when you import + an instance from &oci;. + + + + + + + A custom image is created from the boot volume of the + instance. + + + + + + The custom image is exported to an &oci; object and is + stored using Object Storage in the bucket specified by the + user. + + + + + + The &oci; object is downloaded to the local host. The + object is a TAR archive which contains a boot volume of + the instance in QCOW2 format and a JSON file containing + metadata related to the instance. + + + + + + The boot volume of the instance is extracted from the + archive and a new VMDK image is created by converting the + boot volume into the VMDK format. The VMDK image is + registered with &product-name;. + + + + + + A new VM is created using the VMDK image for the cloud + instance. + + + + By default, the new VM is not started after import from + &oci;. + + + + + + The downloaded TAR archive is deleted after a successful + import. + + + + + + + +
+ + + + Creating New Cloud Instances from a Custom Image + + + You can use &product-name; to create new instances from a custom + image on your cloud service. + + + + describes how to create a + custom image when you are exporting a VM to &oci;. Using a + custom image means that you can quickly create cloud instances + without having to upload your image to the cloud service every + time. + + + + Perform the following steps to create a new cloud instance on + &oci;: + + + + + + + Select File, + New Cloud VM to open the + Create Cloud Virtual + Machine wizard. + + + + + + From the Destination + drop-down list, select + &oci;. + + + + In the Account drop-down + list, select the cloud profile for your &oci; account. + + + + The list after the Account + field shows the profile settings for your cloud account. + + + + In the Images list, select + from the custom images available on &oci;. + + +
+ New Cloud VM Wizard, Showing List of Custom Images + + + + + +
+ + + Click Next to make an API request to the &oci; service and open the Cloud Virtual Machine Settings screen. @@ -3722,7 +4229,7 @@ - Optionally edit settings used for the instance on &oci;. + (Optional) Edit settings used for the new instance on &oci;. @@ -3753,6 +4260,111 @@
+ + + Using VBoxManage Commands With &oci; + + + This section includes some examples of how + VBoxManage commands can be used to integrate + with &oci; and perform common cloud operations. + + + + Creating a Cloud Profile + + + + To create a cloud profile called vbox-oci: + + +VBoxManage cloudprofile --provider "OCI" --profile="vbox-oci" add \ +--clouduser="ocid1.user.oc1..." --keyfile="/home/username/.oci/oci_api_key.pem" \ +--tenancy="ocid1.tenancy.oc1..." --compartment="ocid1.compartment.oc1..." --region="us-ashburn-1" + + + + The new cloud profile is added to the + oci_config file in your &product-name; + global configuration directory. For example, this is + $HOME/.VirtualBox/oci_config on a Windows + host. + + + + Listing Cloud Instances + + + + To list the instances in your &oci; compartment: + + +VBoxManage cloud --provider="OCI" --profile="vbox-oci" list instances + + + + Exporting an &product-name; VM to the + Cloud + + + + To export a VM called myVM and create a cloud + instance called myVM_Cloud: + + +VBoxManage export myVM --output OCI:// --cloud 0 --vmname myVM_Cloud \ +--cloudprofile "vbox-oci" --cloudbucket myBucket \ +--cloudshape VM.Standard2.1 --clouddomain US-ASHBURN-AD-1 --clouddisksize 50 \ +--cloudocivcn ocid1.vcn.oc1... --cloudocisubnet ocid1.subnet.oc1... \ +--cloudkeepobject true --cloudlaunchinstance true --cloudpublicip true + + + + Importing a Cloud Instance Into + &product-name; + + + + To import a cloud instance and create an &product-name; VM + called oci_Import: + + +VBoxManage import OCI:// --cloud --vmname oci_Import --memory 4000 +--cpus 3 --ostype FreeBSD_64 --cloudprofile "vbox-oci" +--cloudinstanceid ocid1.instance.oc1... --cloudbucket myBucket + + + + Creating a New Cloud Instance From a + Custom Image + + + + To create a new cloud instance from a custom image on &oci;: + + +VBoxManage cloud --provider="OCI" --profile="vbox-oci" instance create \ +--domain-name="oraclecloud.com" --image-id="ocid1.image.oc1..." --display-name="myInstance" \ +--shape="VM.Standard2.1" --subnet="ocid1.subnet.oc1..." + + + Terminating a Cloud Instance + + + + To terminate an instance in your compartment on &oci;: + + +VBoxManage cloud --provider="OCI" --profile="vbox-oci" instance terminate \ +--id="ocid1.instance.oc1..." + + + For more details about the available commands for cloud + operations, see . + + + + @@ -3819,7 +4431,8 @@ Network. Enables the user to - configure the details of Host Only Networks. + configure the details of NAT networks. See + . diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_KnownIssues.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_KnownIssues.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_KnownIssues.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_KnownIssues.xml 2022-09-01 13:17:43.000000000 +0000 @@ -96,24 +96,29 @@ - Poor performance when using &product-name; and Hyper-V on the same host. To fix this, certain - Windows features like "Hyper-V Platform", "Virtual Machine Platform" and - "Windows Hypervisor Platform" have to be turned off, followed by a host - reboot. + Poor performance when using &product-name; and + Hyper-V on the same host. To + fix this, certain Windows features like "Hyper-V Platform", + "Virtual Machine Platform" and "Windows Hypervisor Platform" + must be turned off, followed by a host reboot. + - Additionally, the "Microsoft Device Guard and Credential Guard hardware - readiness tool" might be used in order to turn off more features, - for example with .\DG_Readiness_Tool_vX.X.ps1 -Disable -AutoReboot - - - Disabling Device Guard and Credential Guard features will have an - impact on the overall security of the host. Please contact your Administrator - beforehand regarding this. - - + Additionally, the Microsoft Device Guard and Credential Guard + hardware readiness tool might have to be used in order to turn + off more features. For example, by running the following + command: + +.\DG_Readiness_Tool_vX.X.ps1 -Disable -AutoReboot + + + + Disabling Device Guard and Credential Guard features will + have an impact on the overall security of the host. Please + contact your Administrator beforehand regarding this. + + diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Networking.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Networking.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Networking.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Networking.xml 2022-09-01 13:17:43.000000000 +0000 @@ -474,13 +474,14 @@ - Even though the NAT engine separates the VM from the host, - the VM has access to the host's loopback interface and the - network services running on it. The host's loopback interface - is accessible as IP address 10.0.2.2. This access to the host's + Even though the NAT engine separates the VM from the host, the + VM has access to the host's loopback interface and the network + services running on it. The host's loopback interface is + accessible as IP address 10.0.2.2. This access to the host's loopback interface can be extremely useful in some cases, for example when running a web application under development in the - VM and the database server on the loopback interface on the host. + VM and the database server on the loopback interface on the + host. @@ -810,12 +811,20 @@ VBoxManage list natnetworks + + NAT networks can also be created, deleted, and configured using + the VirtualBox Manager. Click + File, + Preferences and select the + Network page. + + - Even though the NAT service separates the VM from the host, - the VM has access to the host's loopback interface and the - network services running on it. The host's loopback interface - is accessible as IP address 10.0.2.2 (assuming the default + Even though the NAT service separates the VM from the host, the + VM has access to the host's loopback interface and the network + services running on it. The host's loopback interface is + accessible as IP address 10.0.2.2 (assuming the default configuration, in other configurations it's the respective address in the configured IPv4 or IPv6 network range). This access to the host's loopback interface can be extremely useful @@ -1003,10 +1012,10 @@ Use the VM's Settings dialog - in the &product-name; graphical user interface. In the - Networking category of the + in the VirtualBox Manager. In the + Network category of the settings dialog, select Internal - Networking from the drop-down list of networking + Network from the drop-down list of networking modes. Select the name of an existing internal network from the drop-down list below, or enter a new name into the Name field. @@ -1092,8 +1101,8 @@ - To change a virtual machine's virtual network interface to Host - Only mode, do either of the following: + To enable a host-only network interface for a virtual machine, do + either of the following: @@ -1102,74 +1111,52 @@ Go to the Network page in the virtual machine's Settings - dialog and select Host-Only - Networking. + dialog and select an Adapter + tab. Ensure that the Enable Network + Adapter check box is selected and choose + Host-Only Adapter for the + Attached To field. - On the command line, enter VBoxManage modifyvm + On the command line, use VBoxManage modifyvm "vmname --nicx hostonly. See - . + . - Before you can attach a VM to a host-only network you have to - create at least one host-only interface. You can use the - VirtualBox Manager for this. Choose - File, - Preferences, - Network, - Host-Only Network, - (+)Add Host-Only Network. - - - - Alternatively, you can use the command line: - - -VBoxManage hostonlyif create - - - See . - - - For host-only networking, as with internal networking, you may find the DHCP server useful that is built into &product-name;. - This can be enabled to then manage the IP addresses in the - host-only network since otherwise you would need to configure all - IP addresses statically. + This is enabled by default and manages the IP addresses in the + host-only network. Without the DHCP server you would need to + configure all IP addresses statically. - In the &product-name; graphical user interface, you can - configure all these items in the global settings by choosing - File, - Preferences, - Network. This lists all - host-only networks which are presently in use. Click on the - network name and then on - Edit. You can then modify the - adapter and DHCP settings. + In the VirtualBox Manager you can configure the DHCP server by + choosing File, + Host Network Manager. The + Host Network Manager lists all host-only networks which are + presently in use. Select the network name and then use the + DHCP Server tab to configure + DHCP server settings. - Alternatively, you can use VBoxManage - dhcpserver on the command line. See - . + Alternatively, you can use the VBoxManage + dhcpserver command. See + . @@ -1183,6 +1170,32 @@ + + On Linux, Mac OS X and Solaris &product-name; will only allow IP + addresses in 192.168.56.0/21 range to be assigned to host-only + adapters. For IPv6 only link-local addresses are allowed. If other + ranges are desired, they can be enabled by creating + /etc/vbox/networks.conf and specifying allowed + ranges there. For example, to allow 10.0.0.0/8 and 192.168.0.0/16 + IPv4 ranges as well as 2001::/64 range put the following lines into + /etc/vbox/networks.conf: + + * 10.0.0.0/8 192.168.0.0/16 + * 2001::/64 + + Lines starting with the hash # are ignored. Next + example allows any addresses, effectively disabling range control: + + * 0.0.0.0/0 ::/0 + + If the file exists, but no ranges are specified in it, no addresses + will be assigned to host-only adapters. The following example + effectively disables all ranges: + + # No addresses are allowed for host-only adapters + + + @@ -1377,8 +1390,7 @@ For more information on setting up VDE networks, please see the documentation accompanying the software. See also - . + . diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Preface.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Preface.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Preface.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Preface.xml 2022-09-01 13:17:43.000000000 +0000 @@ -87,4 +87,7 @@ + + diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Security.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Security.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Security.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Security.xml 2022-09-01 13:17:43.000000000 +0000 @@ -21,8 +21,8 @@ - Keep software up to date. - One of the principles of good security practise is to keep all + Keep software up to date. One + of the principles of good security practise is to keep all software versions and patches up to date. Activate the &product-name; update notification to get notified when a new &product-name; release is available. When updating @@ -34,7 +34,7 @@ - Restrict network access to critical + Restrict network access to critical services. Use proper means, for instance a firewall, to protect your computer and your guests from accesses from the outside. Choosing the proper networking mode @@ -45,7 +45,7 @@ - Follow the principle of least + Follow the principle of least privilege. The principle of least privilege states that users should be given the least amount of privilege necessary to perform their jobs. Always execute &product-name; @@ -62,7 +62,7 @@ - Monitor system activity. + Monitor system activity. System security builds on three pillars: good security protocols, proper system configuration and system monitoring. Auditing and reviewing audit records address the third @@ -74,7 +74,7 @@ - Keep up to date on latest security + Keep up to date on latest security information. Oracle continually improves its software and documentation. Check this note yearly for revisions. @@ -96,10 +96,9 @@ The &product-name; base package should be downloaded only from a trusted source, for instance the official website - . - The integrity of the package should be verified with the - provided SHA256 checksum which can be found on the official - website. + . The integrity of the + package should be verified with the provided SHA256 checksum + which can be found on the official website. @@ -507,13 +506,13 @@ When using the &product-name; Extension Pack provided by - Oracle for disk encryption, the data stored in disk images can - optionally be encrypted. See . - This feature covers disk image content only. All other data - for a virtual machine is stored unencrypted, including the VM's - memory and device state which is stored as part of a saved - state, both when created explicitly or part of a snapshot of a - running VM. + Oracle for disk encryption, the data stored in disk images + can optionally be encrypted. See + . This feature covers disk + image content only. All other data for a virtual machine is + stored unencrypted, including the VM's memory and device + state which is stored as part of a saved state, both when + created explicitly or part of a snapshot of a running VM. @@ -537,8 +536,8 @@ This section contains security recommendations for specific issues. By default VirtualBox will configure the VMs to run in a secure manner, however this may not always be possible without - additional user actions (for example host OS / firmware configuration - changes). + additional user actions such as host OS or firmware configuration + changes. @@ -629,18 +628,18 @@ A more aggressive flushing option is provided via the VBoxManage modifyvm - option. When - enabled the level 1 data cache will be flushed on every VM - entry. The performance impact is greater than with the default - option, though this of course depends on the workload. - Workloads producing a lot of VM exits (like networking, VGA - access, and similiar) will probably be most impacted. + option. When enabled + the level 1 data cache will be flushed on every VM entry. The + performance impact is greater than with the default option, + though this of course depends on the workload. Workloads + producing a lot of VM exits (like networking, VGA access, and + similiar) will probably be most impacted. For users not concerned by this security issue, the default - mitigation can be disabled using the VBoxManage modifyvm - name --l1d-flush-on-sched off command. + mitigation can be disabled using the VBoxManage + modifyvm name --l1d-flush-on-sched off command. @@ -705,20 +704,20 @@ - The VBoxManage modifyvm command provides a more - aggressive flushing option is provided by means of the - option. When - enabled the affected buffers will be cleared on every VM - entry. The performance impact is greater than with the default - option, though this of course depends on the workload. - Workloads producing a lot of VM exits (like networking, VGA - access, and similiar) will probably be most impacted. + The VBoxManage modifyvm command provides a + more aggressive flushing option is provided by means of the + option. When enabled + the affected buffers will be cleared on every VM entry. The + performance impact is greater than with the default option, + though this of course depends on the workload. Workloads + producing a lot of VM exits (like networking, VGA access, and + similiar) will probably be most impacted. For users not concerned by this security issue, the default - mitigation can be disabled using the VBoxManage modifyvm - name --mds-clear-on-sched off command. + mitigation can be disabled using the VBoxManage + modifyvm name --mds-clear-on-sched off command. diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Storage.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Storage.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Storage.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Storage.xml 2022-09-01 13:17:43.000000000 +0000 @@ -55,7 +55,7 @@ In a computing device, hard disks and CD/DVD drives are connected - to a device called hard disk controller which drives hard disk + to a device called a hard disk controller, which drives hard disk operation and data transfers. &product-name; can emulate the most common types of hard disk controllers typically found in computing devices: IDE, SATA (AHCI), SCSI, SAS, USB-based, NVMe and @@ -72,20 +72,20 @@ worked only with hard disks, but was later extended to also support CD-ROM drives and other types of removable media. In physical PCs, this standard uses flat ribbon parallel cables - with 40 or 80 wires. Each such cable can connect two devices - to a controller, which have traditionally been called - master and slave. - Typical PCs had two connectors for such cables. As a result, - support for up to four IDE devices was most common. + with 40 or 80 wires. Each such cable can connect two devices, + called device 0 and device 1, to a controller. Typical PCs had + two connectors for such cables. As a result, support for up to + four IDE devices was most common: primary device 0, primary + device 1, secondary device 0, and secondary device 1. In &product-name;, each virtual machine may have one IDE controller enabled, which gives you up to four virtual storage devices that you can attach to the machine. By default, one of - these virtual storage devices, the secondary master, is - preconfigured to be the virtual machine's virtual CD/DVD - drive. However, you can change the default setting. + these virtual storage devices, device 0 on the secondary + channel, is preconfigured to be the virtual machine's virtual + CD/DVD drive. However, you can change the default setting. @@ -105,7 +105,7 @@ After you have created a new virtual machine with the New Virtual Machine wizard of - the graphical user interface, you will typically see one IDE + the VirtualBox Manager, you will typically see one IDE controller in the machine's Storage settings. The virtual CD/DVD drive will be attached to one of the four ports of this @@ -313,16 +313,16 @@ Virtual I/O Device SCSI is a standard to connect virtual storage devices like hard disks or optical drives to a VM. Recent Linux and Windows versions - support these devices (Windows needs additional drivers). - Currently the virtio-scsi controller is experimental. + support these devices, but Windows needs additional drivers. + Currently virtio-scsi controller support is experimental. The virtio-scsi controller will only be seen by OSes with - device support for it. In particular, there is - no built-in support in Windows. So Windows will - not see such disks unless you install additional drivers. + device support for it. In particular, there is no + built-in support in Windows. So Windows will not + see such disks unless you install additional drivers. @@ -360,8 +360,8 @@ - Up to 255 slots attached to the SAS controller, if enabled - and supported by the guest OS. + Up to 255 slots attached to the SAS controller, if enabled and + supported by the guest OS. @@ -381,8 +381,8 @@ - Up to 256 slots attached to the virtio-scsi controller, if enabled - and supported by the guest OS. + Up to 256 slots attached to the virtio-scsi controller, if + enabled and supported by the guest OS. @@ -543,7 +543,7 @@
The Virtual Media Manager - + diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Troubleshooting.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Troubleshooting.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_Troubleshooting.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_Troubleshooting.xml 2022-09-01 13:17:44.000000000 +0000 @@ -762,13 +762,13 @@ [x] specifies the - disk for IDE. 0 represents the master device - on the first channel, 1 represents the slave - device on the first channel, 2 represents the - master device on the second channel, and 3 - represents the slave device on the second channel. For SATA, use - values between 0 and 29. - This configuration option applies to disks only. Do not use this + disk. For IDE, 0 represents device 0 on the + primary channel, 1 represents device 1 on the + primary channel, 2 represents device 0 on the + secondary channel, and 3 represents device 1 + on the secondary channel. For SATA, use values between + 0 and 29. This + configuration option applies to disks only. Do not use this option for CD or DVD drives. @@ -821,9 +821,10 @@ [x] specifies the - disk. is 0 for the master device on the first channel, 1 for the - slave device on the first channel, 2 for the master device on - the second channel or 3 for the master device on the second + disk. Enter 0 for device 0 on the primary + channel, 1 for device 1 on the primary + channel, 2 for device 0 on the secondary + channel, or 3 for device 1 on the secondary channel. diff -Nru virtualbox-6.1.16-dfsg/doc/manual/en_US/user_VBoxManage.xml virtualbox-6.1.38-dfsg/doc/manual/en_US/user_VBoxManage.xml --- virtualbox-6.1.16-dfsg/doc/manual/en_US/user_VBoxManage.xml 2020-10-16 16:27:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/en_US/user_VBoxManage.xml 2022-09-01 13:17:44.000000000 +0000 @@ -687,8 +687,8 @@ This command changes the properties of a registered virtual machine which is not running. Most of the properties that this - command makes available correspond to the VM settings that - &product-name; graphical user interface displays in each VM's + command makes available correspond to the VM settings that the + VirtualBox Manager displays in each VM's Settings dialog. These are described in . However, some of the more advanced settings are only available through the @@ -2753,21 +2753,23 @@ (change with "--vsys 0 --cpus <n>") 7: Guest memory: 956 MB (change with "--vsys 0 --memory <MB>") 8: Sound card (appliance expects "ensoniq1371", can change on import) - (disable with "--vsys 0 --unit 5 --ignore") + (disable with "--vsys 0 --unit 8 --ignore") 9: USB controller - (disable with "--vsys 0 --unit 6 --ignore") + (disable with "--vsys 0 --unit 9 --ignore") 10: Network adapter: orig bridged, config 2, extra type=bridged 11: Floppy - (disable with "--vsys 0 --unit 8 --ignore") + (disable with "--vsys 0 --unit 11 --ignore") 12: SCSI controller, type BusLogic - (change with "--vsys 0 --unit 9 --scsitype {BusLogic|LsiLogic}"; - disable with "--vsys 0 --unit 9 --ignore") + (change with "--vsys 0 --unit 12 --scsitype {BusLogic|LsiLogic}"; + disable with "--vsys 0 --unit 12 --ignore") 13: IDE controller, type PIIX4 - (disable with "--vsys 0 --unit 10 --ignore") + (disable with "--vsys 0 --unit 13 --ignore") 14: Hard disk image: source image=WindowsXp.vmdk, - target path=/home/user/disks/WindowsXp.vmdk, controller=9;channel=0 - (change controller with "--vsys 0 --unit 11 --controller <id>"; - disable with "--vsys 0 --unit 11 --ignore") + target path=/home/user/disks/WindowsXp.vmdk, controller=12;channel=0 + (change target path with "--vsys 0 --unit 14 --disk <path>"; + change controller with "--vsys 0 --unit 14 --controller <index>"; + change controller port with "--vsys 0 --unit 14 --port <n>"; + disable with "--vsys 0 --unit 14 --ignore") The individual configuration items are numbered, and depending @@ -2798,7 +2800,7 @@ controller, use the following command: -VBoxManage import WindowsXp.ovf +VBoxManage import WindowsXp.ovf \ --vsys 0 --unit 8 --ignore --unit 9 --ignore --unit 14 --controller 13 @@ -2945,8 +2947,8 @@ The import options , , , - , are not - valid for cloud import. + , , + are not valid for cloud import. @@ -2954,8 +2956,8 @@ an instance from &oci;: -# VBoxManage import OCI:// --cloud --vmname import_from_oci --memory 4000 - --cpus 3 --ostype FreeBSD_64 --cloudprofile "standard user" +# VBoxManage import OCI:// --cloud --vmname import_from_oci --memory 4000 \ + --cpus 3 --ostype FreeBSD_64 --cloudprofile "standard user" \ --cloudinstanceid ocid1.instance.oc1.iad.abuwc... --cloudbucket myBucket @@ -6021,9 +6023,9 @@ This command is used to change global settings which affect the entire &product-name; installation. Some of these correspond to - the settings in the Global - Settings dialog in the graphical user interface. The - following properties are available: + the settings in the Preferences + dialog in the VirtualBox Manager. The following properties are + available: @@ -6902,10 +6904,10 @@ VBoxManage --nologo guestcontrol "My VM" run --exe "/bin/ls" - --username foo --passwordfile bar.txt --wait-exit --wait-stdout -- -l /usr + --username foo --passwordfile bar.txt --wait-stdout -- -l /usr VBoxManage --nologo guestcontrol "My VM" run --exe "c:\\windows\\system32\\ipconfig.exe" - --username foo --passwordfile bar.txt --wait-exit --wait-stdout + --username foo --passwordfile bar.txt --wait-stdout Note that the double backslashes in the second example are @@ -7119,10 +7121,10 @@ VBoxManage --nologo guestcontrol "My VM" start --exe "/bin/ls" - --username foo --passwordfile bar.txt --wait-exit --wait-stdout -- -l /usr + --username foo --passwordfile bar.txt -- -l /usr VBoxManage --nologo guestcontrol "My VM" start --exe "c:\\windows\\system32\\ipconfig.exe" - --username foo --passwordfile bar.txt --wait-exit --wait-stdout + --username foo --passwordfile bar.txt Note that the double backslashes in the second example are diff -Nru virtualbox-6.1.16-dfsg/doc/manual/user_ChangeLogImpl.xml virtualbox-6.1.38-dfsg/doc/manual/user_ChangeLogImpl.xml --- virtualbox-6.1.16-dfsg/doc/manual/user_ChangeLogImpl.xml 2020-10-16 16:27:49.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/manual/user_ChangeLogImpl.xml 2022-09-01 13:17:44.000000000 +0000 @@ -28,11 +28,961 @@ users, e.g. audio before serial ports and generally Windows before Linux. Please also try to describe the user impact, not the technical details, and only use technical terms if no non-technical ones are clear enough. + +Rules for adding a changelog entry to make them look more uniform: + + 1. Begin the entry with an UPPERCASE letter, e.g. "Foo: Fixed" vs. "Foo: fixed" + 2. Use the past form of something, e.g. "Fixed ..." vs. "Fix ..." + 3. No dot (.) after the entry, e.g. "Foo: Bar" vs. "Foo: Bar." + + Full example: + + + Foo: Fixed something really important + --> - + + + + Version 6.1.38 (2022-09-02) + + This is a maintenance release. The following items were fixed + and/or added: + + + + + GUI: Improvements in Native Language Support area + + + + Main: OVF Export: Added support for exporting VMs containing + Virtio-SCSI controllers + + + + Recording settings: Fixed a regression which could cause not starting the COM + server (VBoxSVC) under certain circumstances (bug #21034) + + + + Recording: More deterministic naming for recorded files (will now + overwrite old .webm files if present) + + + + Linux Host and Guest Additions installer: Improved check for systemd presence + in the system (bug #19033) + + + + Linux Guest Additions: Introduced initial support for kernel 6.0 + + + + Linux Guest Additions: Additional fixes for kernel + RHEL 9.1 (bug #21065) + + + + Windows Guest Additions: Improvements in Drag and Drop area + + + + + + + Version 6.1.36 (2022-07-19) + + This is a maintenance release. The following items were fixed + and/or added: + + + + + VMM: Fixed possible Linux guest kernel crash when configuring + Speculative Store Bypass for a single vCPU VM + + + + GUI: In the storage page of the virtual machine settings dialog, + fixed a bug which disrupted mouse interaction with the native file + selector on KDE + + + + NAT: Prevent issue when host resolver incorrectly returned NXDOMAIN + for unsupported queries (bug #20977) + + + + Audio: General improvements in saved state area + + + + Recording: Various fixes for settings handling + + + + VGA: Performance improvements for screen updates when VBE + banking is used + + + + USB: Fixed rare crashes when detaching a USB device + + + + ATA: Fixed NT4 guests taking a minute to eject CDs + + + + vboximg-mount: Fixed broken write support (bug #20896) + + + + SDK: Fixed Python bindings incorrectly trying to convert + arbitrary byte data into unicode objects with Python 3, causing + exceptions (bug #19740) + + + + API: Fixed an issue when virtual USB mass storage devices or + virtual USB DVD drives are added while the VM is not running are + by default not marked as hot-pluggable + + + + API: Initial support for Python 3.10 + + + + API: Solaris OS types cleanup + + + + Windows host: Fixed regression in 6.1.32 leading to guest hangs + when Hyper-V is used (bug #20787) + + + + Windows host: Fixed possible issues with saving and restoring + VM state when using Hyper-V/NEM mode + + + + Linux and Solaris hosts: Allow to mount shared folder if it is + represented as a symlink on a host side (bug #17491) + + + + Linux Host and Guest drivers: Introduced initial support + for kernels 5.18, 5.19 and RHEL 9.1 (bugs #20914, #20941) + + + + Linux Host and Guest drivers: Better support for kernels built + with clang compiler (bugs #20425 and #20998) + + + + Solaris Guest Additions: General improvements in + installer area + + + + Solaris Guest Additions: Fixed guest screen resize in VMSVGA + graphics configuration + + + + Linux and Solaris Guest Additions: Fixed multi-screen handling in + VBoxVGA and VBoxSVGA graphics configuration + + + + Linux and Solaris Guest Additions: Added support for setting + primary screen via VBoxManage + + + + Linux and Solaris Guest Additions: Fixed X11 resources leak + when resizing guest screens + + + + Linux and Solaris Guest Additions: Fixed file descriptor leak when + starting a process using guest control (bug #20902) + + + + Linux and Solaris Guest Additions: Fixed guest control executing + processes as root + + + + Linux Guest Additions: Improved guests booting time by preventing + kernel modules from being rebuilt when it is not necessary (bug #20502) + + + + Windows Guest Additions: Fixed VBoxTray crash on + startup in NT4 guests on rare circumstances + + + + + + + Version 6.1.34 (2022-03-22) + + This is a maintenance release. The following items were fixed + and/or added: + + + + + VMM: Fix instruction emulation for "cmpxchg16b" + + + + GUI: Improved GUI behavior on macOS Big Sur and later when kernel + extensions are not loaded + + + + EHCI: Addressed an issue with handling short packets + (bug #20726) + + + + Storage: Fixed a potential hang during disk I/O when the host I/O + cache is disabled (bug #20875) + + + + NVMe: Fixed loading saved state when nothing is attached to it + (bug #20791) + + + + DevPcBios: Addressed an issue which resulted in rejecting the + detected LCHS geometry when the head count was above 16 + + + + virtio-scsi: Improvements + + + + E1000: Improve descriptor handling + + + + VBoxManage: Fixed handling of command line arguments with + incomplete quotes (bug #20740) + + + + VBoxManage: Improved 'natnetwork list' output + + + + VBoxManage: NATNetwork: Provide an option (--ipv6-prefix) to set + IPv6 prefix + + + + VBoxManage: NATNetwork: Provide an option (--ipv6-default) to + advertise default IPv6 route (bug #20714) + + + + VBoxManage: Fix documentation of "usbdevsource add" + (bug #20849) + + + + Networking: General improvements in IPv4 and IPv6 area + (bug #20714) + + + + OVF Import: Allow users to specify a different storage + controller and/or controller port for hard disks when importing + a VM + + + + Unattended install: Improvements + + + + Shared Clipboard: Improved HTML clipboard handling for Windows + host + + + + MacOS host: Fix handling of non-ASCII characters in the guest + control functionality (bug #20792) + + + + Linux host and guest: Introduced initial support for kernel 5.17 + + + + Solaris package: Fixes for API access from Python + + + + Solaris IPS package: Suppress dependency on libpython2.7.so.* + + + + Linux host and guest: Fixes for Linux kernel 5.14 + + + + Linux Guest Additions: Fixed guest screen resize for older guests + which are running libXrandr older than version 1.4 + + + + Linux Guest Additions: Introduced initial support for RHEL 8.6 + kernels (bug #20877) + + + + Windows guest: Make driver install smarter + + + + Solaris guest: Addressed an issue which prevented VBox GAs + 6.1.30 or 6.1.32 from being removed in Solaris 10 guests + (bug #20780) + + + + EFI: Fixed booting from FreeBSD ISO images (bug #19910) + + + + + + + Version 6.1.32 (2022-01-18) + + This is a maintenance release. The following items were fixed + and/or added: + + + + VMM: Changed the guest RAM management when using Hyper-V to be + more compatible with HVCI (bug #20627 and #20694) + + + + VMM: Workaround for OS/2 guest unstability on newer AMD CPUs + due to a missing TLB flush in OS/2 (bug #20625) + + + + GUI: Fixed keyboard focus loss in rare circumstances when using + the mini toolbar in fullscreen mode + + + + Audio: Fixed accidental creation of empty debug log file when + the OSS audio backend was configured + + + + E1000: Fix link status reporting for certain Linux kernels + (some Oracle Linux ones, probably more) + + + + Unattended installation: Fixed regression introduced in 6.1.28, + causing partitioning failure for Windows XP to 10 (bug #20769) + + + + Solaris host: Fixed regression in installer, failed on Solaris 10 + + + + Solaris host: Fix packaging regression, make vboxshell.py executable + + + + Linux host: Fix access to some USB devices, device class was not correctly + handled (bug #20721) + + + + Guest: Fixed wrong mouse position if guest is in text mode + + + + Guest Control: Fixed folders copying from host to guest and + from guest to host + + + + Guest Control: Fixes for UNICODE handling + + + + Shared Clipboard: Improved HTML content exchange between X11 and Windows + guests and hosts + + + + OS/2 Additions: Fixed some issues with extended attributes in the + shared folders (bug #19453) + + + + + + + Version 6.1.30 (2021-11-22) + + This is a maintenance release. The following items were fixed + and/or added: + + + + VMM: Fixed 6.1.28 regression preventing VMs starting when using Hyper-V mode + on Windows 10 + + + + GUI: Fixed inability to complete First Run wizard after browsing for an + external image + + + + GUI: Fixed crash on macOS Big Sur while browsing for an external image from + First Run wizard + + + + GUI: Fixed bug on Windows with inability to save taken screenshot under a folder with + native name (bug #15561) + + + + GUI: Fixed bug on X11 with drag and drop initiated on single mouse click in VM + storage settings + + + + GUI: Fixed settings check on machines not supporting hardware + virtualization + + + + GUI: Non critical media related errors should not cause modal pop-up error + messages + + + + Host-only networking: Fixed crash parsing + /etc/vbox/networks.conf + + + + DVD: Fixed drive lock handling across VM reset + + + + VBoxHeadless: Fixed crash when running on macOS Monterey (bug #20636) + + + + VBoxManage: Fixed incorrect help text for "hostonlyif" + + + + vboximg-mount: Error message if no image is specified + + + + macOS host: Fix multiple bugs specific to macOS Monterey in + installer and startup of kernel extensions + + + + macOS host: Show message indicating the unsupported CPU on M1 + based Macs and abort installation + + + + Linux host: For all distribution specific packages (deb/rpm + format) fix the packaging so that the feature for unattended + installation of guest OSes works + + + + Linux host and guest: Introduced initial support for kernel 5.16 + + + + Shared Clipboard: Improved communication between guest and host + when guest has no clipboard data to report + + + + Linux Guest Additions: Allow running only one VBoxDRMClient instance (bug #19373) + + + + + + Version 6.1.28 (2021-10-19) + + This is a maintenance release. The following items were fixed + and/or added: + + + + VMM: Fixed guru meditation while booting nested-guests accessing + debug registers under certain conditions + + + + UI: Bug fixes for touchpad-based scrolling + + + + VMSVGA: Fixed VM black screen issue on first resize after restoring from + saved state (bug #20067) + + + + VMSVGA: Fixed display corruption on Linux Mint (bug #20513) + + + + Storage: Fixed a possible write error under certain circumstances + when using VHD images (bug #20512) + + + + Network: Multiple updates in virtio-net device support + + + + Network: Disconnecting cable in saved VM state now is handled properly + by virtio-net + + + + Network: More administrative control over host-only network ranges to prevent trouble due to misconfiguration, see . Check updated documentation or your VMs may stop working! + + + + + NAT: Fixed not rejecting TFTP requests with absolute pathnames + (bug #20589) + + + + Audio: Fixed VM session aborting after PC hibernation (bug #20516) + + + + Audio: Fixed setting the line-in volume of the HDA emulation on + modern Linux guests + + + + Audio: Fixed resuming playback of the AC'97 emulation while a + snapshot has been taken + + + + API: Added bindings support for Python 3.9 (bug #20252) + + + + API: Fixed rare hang of VM when changing settings at runtime + + + + Linux host: Improved kernel modules installation detection which + prevents unnecessary modules rebuild + + + + Windows Host: Sped up large page allocations on Windows 8 and later + + + + Windows Host: Fixed VBoxHeadless process sticking around after + VM is closed (bug #20574) + + + + Host Services: Shared Clipboard: Prevent guest clipboard reset when + clipboard sharing is disabled (bug #20487) + + + + Host Services: Shared Clipboard over VRDP: Fixed to continue working + when guest service reconnects to host (bug #20366) + + + + Host Services: Shared Clipboard over VRDP: Fixed preventing remote + RDP client to hang when guest has no clipboard data to report + + + + Linux Host and Guest: Introduced initial support for kernels 5.14 and 5.15 + + + + Linux Host and Guest: Introduced initial support for RHEL 8.5 kernel + + + + Windows Guest: Introduced Windows 11 guest support, including + unattended installation + + + + + + + Version 6.1.26 (2021-07-28) + + This is a maintenance release. The following items were fixed + and/or added: + + + + + VMSVGA: Fixed VM screen artifacts after restoring from + saved state (bug #20067) + + + + Storage: Fixed audio endianness for certain CUE sheet CD/DVD images + + + + VBoxHeadless: Running VM will save its state on host shutdown + + + + VBoxManage: Fixed OS detection for Ubuntu 20.10 ISO with unattended install + + + + Linux Additions: Fixed mouse pointer offsetting issue for VMSVGA + graphics adapter in multi-monitor VM setup (6.1.24 regression) + + + + + + + Version 6.1.24 (2021-07-20) + + This is a maintenance release. The following items were fixed + and/or added: + + + + + Storage: Fixed starting a VM if a device is attached to a VirtIO SCSI port higher than 30 (bug #20213) + + + + Storage: Improvement to DVD medium change signaling + + + + Serial: Fixed a the guest missing interrupts under certain circumstances (6.0 regression, bug #18668) + + + + Audio: Multiple fixes and enhancements + + + + Network: Fixed connectivity issue with virtio-net after resuming VM with disconnected link + + + + Network: Fixed UDP GSO fragmentation issue with missing 8 bytes of payload at the end of the first fragment + + + + API: Fixed VM configuration for recent Windows Server versions + + + + Extension Pack: Fixed issues with USB webcam pass-through on Linux + + + + Host and guest driver: Fix small memory leak (bug #20280) + + + + Linux host: Support Split Lock Detection feature of recent Intel CPUs (bug #20180) + + + + Linux host and guest: Support kernel version 5.13 (bug #20456) + + + + Linux host and guest: Introduce support for SUSE SLES/SLED 15 SP3 kernels (bug #20396) + + + + Linux host: Installer will not attempt to build kernel modules if + system already has them installed and modules versions match current version + + + + Windows host: Fix DLL signature validation to work better with an invalid certificate + + + + Guest Additions: Fixed crash on using shared clipboard (bug #19165) + + + + Linux Guest Additions: Introduce support for Ubuntu specific kernels (bug #20325) + + + + Solaris guest: Increased default memory and disk sizes + + + + EFI: Support network booting with the E1000 network controller emulation + + + + EFI: Stability improvements (bug #20090) + + + + + + + Version 6.1.22 (2021-04-29) + + This is a maintenance release. The following items were fixed + and/or added: + + + + VMM: Improved performance of 64-bit Windows and Solaris guests when Hyper-V + is used on recent Windows 10 hosts + + + + VMM: Fixed frequent crashes of 64-bit Windows Vista and Server 2003 guests when Hyper-V + is used + + + + GUI: Fixed regression where user was not able to save unset default shortcuts (bug #20305) + + + + Storage: Fixed regression in LsiLogic SAS controller emulation caused VM crash (bug #20323) + + + + Linux Guest Additions: Fixed issue when it was not possible to run executables from mounted share (bug #20320) + + + + + + Version 6.1.20 (2021-04-20) + + This is a maintenance release. The following items were fixed + and/or added: + + + + VMM: Fixed extremely poor VM performance depending on the timing of various actions (regression in 6.1.0) + + + + VMM: Fixed guest OS hanging under certain circumstances when Hyper-V is present (bug #20141) + + + + VMM: Fixed Guru Meditation error when using a nested hypervisor under certain circumstances (bug #20175) + + + + VMM: Fixed a SMAP related host panic affecting Solaris 11.4 systems with Intel Haswell CPUs or later (bug #16068) + + + + OCI: Add cloud-init support for export to OCI and for OCI instance creation + + + + GUI: Fixed "Delete all files" leaving behind Logs/VBoxUI.log (bug #20235) + + + + Audio: Multiple fixes and enhancements + + + + Audio: Fixed detection of duplex audio devices on macOS (5.0 regression; bug #20171) + + + + Network: Fixed link status reporting for "not attached" adapters + + + + Network: Fixed connectivity issues with e1000 in OS/2 guests (6.1.18 regression; bug #20148) + + + + Network: Fixed VxWorks e1000 driver compatibility issue (bug #20182) + + + + Network: Fixed GUI checks for port forwarding rules rejecting IPv6 with "Nat Network" (bug #14847) + + + + DHCP: Don't crash in the presence of fixed address assignments (bug #20128) + + + + Serial: Fixed possible VM hang when using the a serial port in disconnected mode (bug #19854) + + + + Webcam: Fixed interoperability with v4l2loopback and fixed a crash under certain circumstances (bug #20176) + + + + NVMe: Fixed sporadic Windows VM hang or reboot on high CPU load + + + + VBoxManage: Allow changing network adapter attachment of a saved VM with "modifyvm" + + + + vboximg-mount: Fix for argument processing to honor the '--root' option (6.0 regression; bug #20073) + + + + Linux host and guest: Support kernel versions 5.11 (bug #20198) and 5.12 + + + + Linux host: Maximum MTU size increased to 16110 for host-only adapters on Linux kernels 4.10+ (bug #19122) + + + + Linux Guest Additions: Fix vboxvideo module compilation for kernel version 5.10.x + + + + Linux Guest Additions: Fixed kernel module build for RHEL 8.4 beta and CentOS Stream (bug #20289) + + + + + + + Version 6.1.18 (2021-01-19) + + This is a maintenance release. The following items were fixed + and/or added: + + + + Nested VM: Fixed hangs when executing SMP nested-guests under certain + conditions on Intel hosts (bug #19315, #19561) + + + + OCI integration: Cloud Instance parameters parsing is improved + on import (bug #19156) + + + + Network: UDP checksum offloading in e1000 no longer produces + zero checksums (bug #19930) + + + + Network: Fixed Host-Only Ethernet Adapter DHCP, guest os can not + get IP on host resume (bug #19620) + + + + NAT: Fixed mss parameter handing (bug #15256) + + + + macOS host: Multiple optimizations for BigSur + + + + Audio: Fixed issues with audio playback after host goes to + sleep (bug #18594) + + + + Documentation: Some content touch-up and table formatting fixes + + + + Linux host and guest: Support kernel version 5.10 (bug + #20055) + + + + Solaris host: Fix regression breaking VGA text mode since version 6.1.0 + + + + Guest Additions: Fixed a build failure affecting CentOS + 8.2-2004 and later (bug #20091) + + + + Guest Additions: Fixed a build failure affecting Linux + kernels 3.2.0 through 3.2.50 (bug #20006) + + + + Guest Additions: Fixed a VM segfault on copy with shared clipboard + with X11 (bug #19226) + + + + Shared Folder: Fixed error with remounting on Linux guests + + + + Version 6.1.16 (2020-10-16) @@ -141,7 +1091,7 @@ - Audio: fix regression in HDA emulation introduced in 6.1.0 + Audio: Fix regression in HDA emulation introduced in 6.1.0 @@ -262,7 +1212,7 @@ GUI: Fixed crash when using Qt on Xwayland sessions - (bug #19583) + (bug #19583) @@ -272,12 +1222,12 @@ VBoxManage: Fixed crash of 'VBoxManage internalcommands repairhd' - when processing invalid input (bug #19579) + when processing invalid input (bug #19579) - Settings: disable audio input and audio output by default for new VMs - (bug #19527) + Settings: Disable audio input and audio output by default for new VMs + (bug #19527) @@ -336,7 +1286,7 @@ Guest Additions: Build problems fix with Oracle Linux 8.2 - (Red Hat compatible kernel) / Red Hat Enterprise Linux 8.2 / CentOS 8.2 (bug #19391) + (Red Hat compatible kernel) / Red Hat Enterprise Linux 8.2 / CentOS 8.2 (bug #19391) diff -Nru virtualbox-6.1.16-dfsg/doc/ReadMe-Solaris11.txt virtualbox-6.1.38-dfsg/doc/ReadMe-Solaris11.txt --- virtualbox-6.1.16-dfsg/doc/ReadMe-Solaris11.txt 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/ReadMe-Solaris11.txt 2022-09-01 13:17:42.000000000 +0000 @@ -0,0 +1,55 @@ + +@VBOX_PRODUCT@ for Oracle Solaris 11 (TM) Operating System +---------------------------------------------------------------- + +Installing: +----------- + +After extracting the contents of the tar.xz file install the VirtualBox +package with the following command: + + $ sudo pkg install -g VirtualBox-@VBOX_VERSION_STRING@-SunOS-@KBUILD_TARGET_ARCH@-r@VBOX_SVN_REV@.p5p virtualbox + +Of course you can add options for performing the install in a different boot +environment or in a separate Solaris install. + +Normally you need to reboot the system to load the drivers which have been +added by the VirtualBox package. + +If you want to have VirtualBox immediately usable on your system you can run +the script /opt/VirtualBox/ipsinstall.sh which sets up everything immediately. + +At this point, all the required files should be installed on your system. +You can launch VirtualBox by running 'VirtualBox' from the terminal. + + +Upgrading: +---------- + +If you want to upgrade from an older to a newer version of the VirtualBox IPS +package you can use the following command after extracting the contents of the +tar.xz file: + + $ sudo pkg update -g VirtualBox-@VBOX_VERSION_STRING@-SunOS-@KBUILD_TARGET_ARCH@-r@VBOX_SVN_REV@.p5p virtualbox + +If you want to upgrade from the SysV package of VirtualBox to the IPS one, +please uninstall the previous package before installing the IPS one. Please +refer to the "Uninstalling" and "Installing" sections of this document for +details. + +It is your responsibility to ensure that no VirtualBox VMs or other related +activities are running. One possible way is using the command pgrep VBoxSVC. If +this shows no output then it is safe to upgrade VirtualBox. + + +Uninstalling: +------------- + +To remove VirtualBox from your system, run the following command: + + $ sudo pkg uninstall virtualbox + +It is your responsibility to ensure that no VirtualBox VMs or other related +activities are running. One possible way is using the command pgrep VBoxSVC. If +this shows no output then it is safe to uninstall VirtualBox. + diff -Nru virtualbox-6.1.16-dfsg/doc/ReadMe-Solaris.txt virtualbox-6.1.38-dfsg/doc/ReadMe-Solaris.txt --- virtualbox-6.1.16-dfsg/doc/ReadMe-Solaris.txt 2020-10-16 16:27:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/ReadMe-Solaris.txt 2022-09-01 13:17:42.000000000 +0000 @@ -1,5 +1,5 @@ -@VBOX_PRODUCT@ for Solaris (TM) Operating System +@VBOX_PRODUCT@ for Oracle Solaris (TM) Operating System -------------------------------------------------------- Upgrading: @@ -45,7 +45,3 @@ 2. To remove VirtualBox, run the command: pkgrm SUNWvbox -3. To remove the VirtualBox kernel interface module, run the command: - pkgrm SUNWvboxkern - * Only required if you're uninstalling VirtualBox versions 3.0.x or lower. - diff -Nru virtualbox-6.1.16-dfsg/doc/VBox-doc.c virtualbox-6.1.38-dfsg/doc/VBox-doc.c --- virtualbox-6.1.16-dfsg/doc/VBox-doc.c 2020-10-16 16:27:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/doc/VBox-doc.c 2022-09-01 13:17:42.000000000 +0000 @@ -71,6 +71,21 @@ * - DevPCI - Peripheral Component Interface (Bus). * - VBoxDev - Special PCI Device which serves as an interface between * the VMM and the guest OS for the additions. + * - @ref pg_pdm_audio "Audio": + * - DevHda - Intel High Definition Audio Device Emulation. + * - DevIchAc97 - ICH AC'97 Device Emulation. + * - DevSB16 - SoundBlaster 16 Device Emulation. + * - DrvAudio - Intermediate driver. + * - DrvHostAudioAlsa - ALSA Host Audio Driver (Linux). + * - DrvHostAudioCoreAudio - Core Audio Host Audio Driver (macOS). + * - DrvHostAudioDebug - Debug Backend Driver. + * - DrvHostAudioDSound - DirectSound Host Audio Driver (Windows). + * - DrvHostAudioNull - NULL Backend Driver. + * - DrvHostAudioOss - Open Sound System Host Audio Driver (Linux, + * Solaris, ++). + * - DrvHostAudioPulseAudio - PulseAudio Host Audio Driver (Linux). + * - DrvHostAudioValidationKit - Validation Kit Test Driver. + * - DrvHostAudioWasApi - Windows Audio Session API Host Audio Driver. * - Networking: * - DevPCNet - AMD PCNet Device Emulation. * - DevE1000 - Intel E1000 Device Emulation. diff -Nru virtualbox-6.1.16-dfsg/Doxyfile.Core virtualbox-6.1.38-dfsg/Doxyfile.Core --- virtualbox-6.1.16-dfsg/Doxyfile.Core 2020-10-16 16:27:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/Doxyfile.Core 2022-09-01 13:17:42.000000000 +0000 @@ -130,39 +130,39 @@ # The following is for device configuration options. ALIASES += \ - devcfgm{6}="\xrefitem lst_cfgm \"Device Config: \1\" \"VM Configuration Values\" \ -Name: [Device Instance]/\1
\ -Type: \2
\ -Unit: \6
\ -Min: \3
\ -Max: \4
\ -Default: \5
\ -" -ALIASES += \ - devcfgm{5}="\xrefitem lst_cfgm \"Device Config: \1\" \"VM Configuration Values\" \ -Name: [Device Instance]/\1
\ -Type: \2
\ -Min: \3
\ -Max: \4
\ -Default: \5
\ -" -ALIASES += \ - devcfgm{4}="\xrefitem lst_cfgm \"Device Config: \1\" \"VM Configuration Values\" \ -Name: [Device Instance]/\1
\ -Type: \2
\ -Min: \3
\ -Max: \4
\ -" -ALIASES += \ - devcfgm{3}="\xrefitem lst_cfgm \"Device Config: \1\" \"VM Configuration Values\" \ -Name: [Device Instance]/\1
\ -Type: \2
\ -Default: \3
\ -" -ALIASES += \ - devcfgm{2}="\xrefitem lst_cfgm \"Device Config: \1\" \"VM Configuration Values\" \ -Name: [Device Instance]/\1
\ -Type: \2
\ + devcfgm{7}="\xrefitem lst_cfgm \"Device Config: \1/\2\" \"VM Configuration Values\" \ +Name: /Devices/\1/#/Config/\2
\ +Type: \3
\ +Unit: \7
\ +Min: \4
\ +Max: \5
\ +Default: \6
\ +" +ALIASES += \ + devcfgm{6}="\xrefitem lst_cfgm \"Device Config: \1/\2\" \"VM Configuration Values\" \ +Name: /Devices/\1/#1/Config/\2
\ +Type: \3
\ +Min: \4
\ +Max: \5
\ +Default: \6
\ +" +ALIASES += \ + devcfgm{5}="\xrefitem lst_cfgm \"Device Config: \1/\2\" \"VM Configuration Values\" \ +Name: /Devices\1/#1/Config/\2
\ +Type: \3
\ +Min: \4
\ +Max: \5
\ +" +ALIASES += \ + devcfgm{4}="\xrefitem lst_cfgm \"Device Config: \1/\2\" \"VM Configuration Values\" \ +Name: /Devices\1/#1/Config/\2
\ +Type: \3
\ +Default: \4
\ +" +ALIASES += \ + devcfgm{3}="\xrefitem lst_cfgm \"Device Config: \1/\2\" \"VM Configuration Values\" \ +Name: /Devices\1/#1/Config/\2
\ +Type: \3
\ " # The following is for driver configuration options. @@ -575,6 +575,13 @@ DECL_FORCE_INLINE(type)=DECLINLINE(type) \ DECL_NO_INLINE(type)=type \ DECLCALLBACK(type)=type \ + "DECLCALLBACKTYPE(type,name,args)=type name args" \ + "DECLCALLBACKPTR(type,name,args)=type (* name) args" \ + "DECLCALLBACKMEMBER(type,name)=type (* name) " \ + "DECLRCCALLBACKMEMBER(type,name,args)=type (* name) args" \ + "DECLR3CALLBACKMEMBER(type,name,args)=type (* name) args" \ + "DECLR0CALLBACKMEMBER(type,name,args)=type (* name) args" \ + "DECLRGCALLBACKMEMBER(type,name,args)=type (* name) args" \ DECLEXPORT(type)=type \ DECLIMPORT(type)=type \ DECLHIDDEN(type)=type \ @@ -757,33 +764,13 @@ VBOX_WITH_HGCM EXPAND_AS_DEFINED = \ - RCPTRTYPE \ - R3PTRTYPE \ - R0PTRTYPE \ - HCPTRTYPE \ - R3R0PTRTYPE \ - \ ARCH_BITS \ R3_ARCH_BITS \ R0_ARCH_BITS \ GC_ARCH_BITS \ HC_ARCH_BITS \ \ - DECLEXPORT \ - DECLIMPORT \ - DECLASM \ DECLASMTYPE \ - DECLCALLBACK \ - DECLCALLBACKMEMBER \ - DECLCALLBACKPTR \ - DECLHCCALLBACKMEMBER \ - DECLRCCALLBACKMEMBER \ - DECLR3CALLBACKMEMBER \ - DECLR0CALLBACKMEMBER \ - DECLRGCALLBACKMEMBER \ - DECLINLINE \ - DECL_FORCE_INLINE \ - DECLHIDDEN \ CTXSUFF \ OTHERCTXSUFF \ CTXMID \ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/asn1-generator-pass.h virtualbox-6.1.38-dfsg/include/iprt/asn1-generator-pass.h --- virtualbox-6.1.16-dfsg/include/iprt/asn1-generator-pass.h 2020-10-16 16:27:54.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/asn1-generator-pass.h 2022-09-01 13:17:50.000000000 +0000 @@ -116,8 +116,8 @@ # define RTASN1TMPL_MEMBER_OPT_XTAG_EX(a_TnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_uTag, a_Constraints) \ extern "C" DECLHIDDEN(RTASN1COREVTABLE const) RT_CONCAT5(g_,RTASN1TMPL_INT_NAME,_XTAG_,a_Name,_Vtable) -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_END_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() @@ -151,8 +151,8 @@ # define RTASN1TMPL_BEGIN_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_BEGIN_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_MEMBER_OPT_XTAG_EX(a_TnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_uTag, a_Constraints) \ /* This is the method we need to make it work. */ \ static DECLCALLBACK(int) RT_CONCAT4(RTASN1TMPL_INT_NAME,_XTAG_,a_Name,_Enum)(PRTASN1CORE pThisCore, \ @@ -291,8 +291,8 @@ AssertCompileMemberOffset(RTASN1TMPL_TYPE, SetCore, 0); \ RTASN1TMPL_BEGIN_COMMON(ASN1_TAG_SET, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED) # define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_END_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() @@ -339,12 +339,12 @@ if (RT_SUCCESS(rc)) \ rc = RT_CONCAT(a_Api,_Init)(&pThis->a_Name, pAllocator) -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator); \ pThis->a_enmMembNm = RT_CONCAT(a_enmType,_NOT_PRESENT) # define RTASN1TMPL_MEMBER_DYN_COMMON(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_enmMembNm, a_enmValue, a_IfStmt) \ do { } while (0) -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) do { } while (0) +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) do { } while (0) # define RTASN1TMPL_MEMBER_DEF_ITAG_EX(a_Name, a_Type, a_Api, a_uTag, a_fClue, a_DefVal, a_Constraints) \ if (RT_SUCCESS(rc)) \ @@ -362,12 +362,15 @@ # define RTASN1TMPL_END_SETCORE() RTASN1TMPL_END_SEQCORE() /* No choice, just an empty, non-present structure. */ -# define RTASN1TMPL_BEGIN_PCHOICE() RTASN1TMPL_BEGIN_COMMON(); int rc = VINF_SUCCESS +# define RTASN1TMPL_BEGIN_PCHOICE() \ + RTASN1TMPL_BEGIN_COMMON(); \ + RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator); \ + int rc = VINF_SUCCESS # define RTASN1TMPL_PCHOICE_ITAG_EX(a_uTag, a_enmChoice, a_PtrName, a_Name, a_Type, a_Api, a_fClue, a_Constraints) \ do { } while (0) # define RTASN1TMPL_PCHOICE_XTAG_EX(a_uTag, a_enmChoice, a_PtrTnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_Constraints) \ do { } while (0) -# define RTASN1TMPL_END_PCHOICE() RTASN1TMPL_END_COMMON() +# define RTASN1TMPL_END_PCHOICE() RTASN1TMPL_END_COMMON() # define RTASN1TMPL_SET_SEQ_OF_COMMON(a_ItemType, a_ItemApi, a_OfApi, a_OfMember) \ @@ -420,7 +423,7 @@ if (RT_SUCCESS(rc)) \ rc = RT_CONCAT(a_Api,_DecodeAsn1)(pCursor, 0, &pThis->a_Name, #a_Name) -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ if (RT_SUCCESS(rc)) \ { \ int rc2; /* not initialized! */ \ @@ -438,7 +441,7 @@ rc2 = RT_CONCAT(a_Api,_DecodeAsn1)(pCursor, 0, pThis->a_UnionNm.a_PtrName, #a_UnionNm "." #a_PtrName); \ } \ } while (0) -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ rc = rc2; /* Should trigger warning if a _DEFAULT is missing. */ \ } @@ -623,7 +626,7 @@ # define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) \ if (rc == VINF_SUCCESS) \ rc = pfnCallback(RT_CONCAT(a_Api,_GetAsn1Core)(&pThis->a_Name), #a_Name, uDepth, pvUser) -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ if (rc == VINF_SUCCESS) \ switch (pThis->a_enmMembNm) \ { \ @@ -633,7 +636,7 @@ rc = pfnCallback(RT_CONCAT(a_Api,_GetAsn1Core)(pThis->a_UnionNm.a_PtrName), #a_UnionNm "." #a_PtrName, \ uDepth, pvUser); \ break -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ case RT_CONCAT(a_enmType,_NOT_PRESENT): break; \ } # define RTASN1TMPL_MEMBER_OPT_EX(a_Name, a_Type, a_Api, a_Constraints) \ @@ -701,7 +704,7 @@ if (RT_SUCCESS(rc)) \ rc = RT_CONCAT(a_Api,_Clone)(&pThis->a_Name, &pSrc->a_Name, pAllocator); \ -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ if (RT_SUCCESS(rc)) \ { \ RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator); \ @@ -716,7 +719,7 @@ if (RT_SUCCESS(rc)) \ rc = RT_CONCAT(a_Api,_Clone)(pThis->a_UnionNm.a_PtrName, pSrc->a_UnionNm.a_PtrName, pAllocator); \ break -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ case RT_CONCAT(a_enmType,_NOT_PRESENT): break; \ } \ } @@ -753,7 +756,8 @@ case a_enmChoice: \ rc = RTAsn1MemAllocZ(&pThis->Allocation, (void **)&pThis->a_PtrName, sizeof(*pThis->a_PtrName)); \ if (RT_SUCCESS(rc)) \ - rc = RT_CONCAT(a_Api,_Clone)(pThis->a_PtrName, pSrc->a_PtrName, pAllocator); break + rc = RT_CONCAT(a_Api,_Clone)(pThis->a_PtrName, pSrc->a_PtrName, pAllocator); \ + break # define RTASN1TMPL_PCHOICE_XTAG_EX(a_uTag, a_enmChoice, a_PtrTnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_Constraints) \ case a_enmChoice: /* A bit of presence paranoia here, but better safe than sorry... */ \ rc = RTAsn1MemAllocZ(&pThis->Allocation, (void **)&pThis->a_PtrTnNm, sizeof(*pThis->a_PtrTnNm)); \ @@ -829,20 +833,20 @@ } RTASN1TMPL_SEMICOLON_DUMMY() #endif -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_END_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_END_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_BEGIN_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_BEGIN_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_PCHOICE_ITAG_EX(a_uTag, a_enmChoice, a_PtrName, a_Name, a_Type, a_Api, a_fClue, a_Constraints) RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_PCHOICE_XTAG_EX(a_uTag, a_enmChoice, a_PtrTnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_Constraints) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_END_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_END_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_SEQ_OF(a_ItemType, a_ItemApi) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_SET_OF(a_ItemType, a_ItemApi) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_SEQ_OF(a_ItemType, a_ItemApi) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_SET_OF(a_ItemType, a_ItemApi) RTASN1TMPL_SEMICOLON_DUMMY() @@ -852,22 +856,100 @@ * Member setters. * */ -# define RTASN1TMPL_BEGIN_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_BEGIN_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_END_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_BEGIN_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_BEGIN_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() + +# define RTASN1TMPL_MEMBER_DYN(a_UnionNm, a_PtrName, a_Name, a_Type, a_Api, a_Allocation, a_ObjIdMembNm, a_enmMembNm, a_enmValue, a_szObjId) \ +RTASN1TMPL_DECL(int) RT_CONCAT3(RTASN1TMPL_EXT_NAME,_Set,a_Name)(RT_CONCAT(P,RTASN1TMPL_TYPE) pThis, \ + RT_CONCAT(PC,a_Type) pToClone,\ + PCRTASN1ALLOCATORVTABLE pAllocator) \ +{ \ + AssertPtr(pThis); AssertPtrNull(pToClone); Assert(!pToClone || RT_CONCAT(a_Api,_IsPresent)(pToClone)); \ + AssertReturn(pThis->a_UnionNm.a_PtrName == NULL, VERR_INVALID_STATE); /* for now */ \ + /* Set the type */ \ + if (RTAsn1ObjId_IsPresent(&pThis->a_ObjIdMembNm)) \ + RTAsn1ObjId_Delete(&pThis->a_ObjIdMembNm); \ + int rc = RTAsn1ObjId_InitFromString(&pThis->a_ObjIdMembNm, a_szObjId, pAllocator); \ + if (RT_SUCCESS(rc)) \ + { \ + pThis->a_enmMembNm = a_enmValue; \ + \ + /* Allocate memory for the structure we're targeting. */ \ + rc = RTAsn1MemAllocZ(&pThis->a_Allocation, (void **)&pThis->a_UnionNm.a_PtrName, sizeof(*pThis->a_UnionNm.a_PtrName)); \ + if (RT_SUCCESS(rc)) \ + { \ + if (pToClone) /* If nothing to clone, just initialize the structure. */ \ + rc = RT_CONCAT(a_Api,_Clone)(pThis->a_UnionNm.a_PtrName, pToClone, pAllocator); \ + else \ + rc = RT_CONCAT(a_Api,_Init)(pThis->a_UnionNm.a_PtrName, pAllocator); \ + } \ + } \ + return rc; \ +} RTASN1TMPL_SEMICOLON_DUMMY() + +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_END_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() + +# define RTASN1TMPL_MEMBER_OPT_ITAG_EX(a_Name, a_Type, a_Api, a_uTag, a_fClue, a_Constraints) \ +RTASN1TMPL_DECL(int) RT_CONCAT3(RTASN1TMPL_EXT_NAME,_Set,a_Name)(RT_CONCAT(P,RTASN1TMPL_TYPE) pThis, \ + RT_CONCAT(PC,a_Type) pToClone,\ + PCRTASN1ALLOCATORVTABLE pAllocator) \ +{ \ + AssertPtr(pThis); AssertPtrNull(pToClone); Assert(!pToClone || RT_CONCAT(a_Api,_IsPresent)(pToClone)); \ + if (RT_CONCAT(a_Api,_IsPresent)(&pThis->a_Name)) \ + RT_CONCAT(a_Api,_Delete)(&pThis->a_Name); \ + \ + int rc; \ + if (pToClone) \ + rc = RT_CONCAT(a_Api,_Clone)(&pThis->a_Name, pToClone, pAllocator); \ + else \ + rc = RT_CONCAT(a_Api,_Init)(&pThis->a_Name, pAllocator); \ + if (RT_SUCCESS(rc)) \ + { \ + RTAsn1Core_ResetImplict(RT_CONCAT(a_Api,_GetAsn1Core)(&pThis->a_Name)); /* probably not needed */ \ + rc = RTAsn1Core_SetTagAndFlags(RT_CONCAT(a_Api,_GetAsn1Core)(&pThis->a_Name), \ + a_uTag, RTASN1TMPL_ITAG_F_EXPAND(a_fClue)); \ + } \ + return rc; \ +} RTASN1TMPL_SEMICOLON_DUMMY() + +# define RTASN1TMPL_MEMBER_OPT_XTAG_EX(a_TnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_uTag, a_Constraints) \ +RTASN1TMPL_DECL(int) RT_CONCAT3(RTASN1TMPL_EXT_NAME,_Set,a_Name)(RT_CONCAT(P,RTASN1TMPL_TYPE) pThis, \ + RT_CONCAT(PC,a_Type) pToClone,\ + PCRTASN1ALLOCATORVTABLE pAllocator) \ +{ \ + AssertPtr(pThis); AssertPtrNull(pToClone); Assert(!pToClone || RT_CONCAT(a_Api,_IsPresent)(pToClone)); \ + if (RTASN1CORE_IS_PRESENT(&pThis->a_TnNm.a_CtxTagN.Asn1Core)) \ + RT_CONCAT(a_Api,_Delete)(&pThis->a_TnNm.a_Name); \ + \ + int rc = RT_CONCAT3(RTAsn1ContextTag,a_uTag,_Init)(&pThis->a_TnNm.a_CtxTagN, \ + &RT_CONCAT5(g_,RTASN1TMPL_INT_NAME,_XTAG_,a_Name,_Vtable), \ + pAllocator); \ + if (RT_SUCCESS(rc)) \ + { \ + if (pToClone) \ + rc = RT_CONCAT(a_Api,_Clone)(&pThis->a_TnNm.a_Name, pToClone, pAllocator); \ + else \ + rc = RT_CONCAT(a_Api,_Init)(&pThis->a_TnNm.a_Name, pAllocator); \ + if (RT_SUCCESS(rc) && pToClone) \ + RTAsn1Core_ResetImplict(RT_CONCAT(a_Api,_GetAsn1Core)(&pThis->a_TnNm.a_Name)); \ + } \ + return rc; \ +} RTASN1TMPL_SEMICOLON_DUMMY() + # define RTASN1TMPL_BEGIN_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_PCHOICE_ITAG_EX(a_uTag, a_enmChoice, a_PtrName, a_Name, a_Type, a_Api, a_fClue, a_Constraints) \ -RTASN1TMPL_DECL(int) RT_CONCAT3(RTASN1TMPL_EXT_NAME,_Set,a_Name)(RT_CONCAT(P,RTASN1TMPL_TYPE) pThis, RT_CONCAT(PC,a_Type) pSrc,\ +RTASN1TMPL_DECL(int) RT_CONCAT3(RTASN1TMPL_EXT_NAME,_Set,a_Name)(RT_CONCAT(P,RTASN1TMPL_TYPE) pThis, \ + RT_CONCAT(PC,a_Type) pToClone,\ PCRTASN1ALLOCATORVTABLE pAllocator) \ { \ - AssertPtr(pSrc); AssertPtr(pThis); \ + AssertPtrNull(pToClone); AssertPtr(pThis); \ RT_CONCAT(RTASN1TMPL_EXT_NAME,_Delete)(pThis); /* See _Init. */ \ RTAsn1Dummy_InitEx(&pThis->Dummy); \ pThis->Dummy.Asn1Core.pOps = &RT_CONCAT3(g_,RTASN1TMPL_INT_NAME,_Vtable); \ @@ -876,10 +958,14 @@ int rc = RTAsn1MemAllocZ(&pThis->Allocation, (void **)&pThis->a_PtrName, sizeof(*pThis->a_PtrName)); \ if (RT_SUCCESS(rc)) \ { \ - rc = RT_CONCAT(a_Api,_Clone)(pThis->a_PtrName, pSrc, pAllocator); \ + if (pToClone) \ + rc = RT_CONCAT(a_Api,_Clone)(pThis->a_PtrName, pToClone, pAllocator); \ + else \ + rc = RT_CONCAT(a_Api,_Init)(pThis->a_PtrName, pAllocator); \ if (RT_SUCCESS(rc)) \ { \ - RTAsn1Core_ResetImplict(RT_CONCAT(a_Api,_GetAsn1Core)(pThis->a_PtrName)); \ + if (pToClone) \ + RTAsn1Core_ResetImplict(RT_CONCAT(a_Api,_GetAsn1Core)(pThis->a_PtrName)); \ rc = RTAsn1Core_SetTagAndFlags(RT_CONCAT(a_Api,_GetAsn1Core)(pThis->a_PtrName), \ a_uTag, RTASN1TMPL_ITAG_F_EXPAND(a_fClue)); \ } \ @@ -888,10 +974,11 @@ } RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_PCHOICE_XTAG_EX(a_uTag, a_enmChoice, a_PtrTnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_Constraints) \ -RTASN1TMPL_DECL(int) RT_CONCAT3(RTASN1TMPL_EXT_NAME,_Set,a_Name)(RT_CONCAT(P,RTASN1TMPL_TYPE) pThis, RT_CONCAT(PC,a_Type) pSrc,\ +RTASN1TMPL_DECL(int) RT_CONCAT3(RTASN1TMPL_EXT_NAME,_Set,a_Name)(RT_CONCAT(P,RTASN1TMPL_TYPE) pThis, \ + RT_CONCAT(PC,a_Type) pToClone,\ PCRTASN1ALLOCATORVTABLE pAllocator) \ { \ - AssertPtr(pThis); AssertPtr(pSrc); Assert(RT_CONCAT(a_Api,_IsPresent)(pSrc)); \ + AssertPtr(pThis); AssertPtrNull(pToClone); Assert(!pToClone || RT_CONCAT(a_Api,_IsPresent)(pToClone)); \ RT_CONCAT(RTASN1TMPL_EXT_NAME,_Delete)(pThis); /* See _Init. */ \ RTAsn1Dummy_InitEx(&pThis->Dummy); \ pThis->Dummy.Asn1Core.pOps = &RT_CONCAT3(g_,RTASN1TMPL_INT_NAME,_Vtable); \ @@ -905,8 +992,11 @@ pAllocator); \ if (RT_SUCCESS(rc)) \ { \ - rc = RT_CONCAT(a_Api,_Clone)(&pThis->a_PtrTnNm->a_Name, pSrc, pAllocator); \ - if (RT_SUCCESS(rc)) \ + if (pToClone) \ + rc = RT_CONCAT(a_Api,_Clone)(&pThis->a_PtrTnNm->a_Name, pToClone, pAllocator); \ + else \ + rc = RT_CONCAT(a_Api,_Init)(&pThis->a_PtrTnNm->a_Name, pAllocator); \ + if (RT_SUCCESS(rc) && pToClone) \ RTAsn1Core_ResetImplict(RT_CONCAT(a_Api,_GetAsn1Core)(&pThis->a_PtrTnNm->a_Name)); \ } \ } \ @@ -926,19 +1016,19 @@ * Array operations. * */ -# define RTASN1TMPL_BEGIN_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_BEGIN_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_END_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_BEGIN_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_BEGIN_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_BEGIN_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_END_SETCORE() RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_BEGIN_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_PCHOICE_ITAG_EX(a_uTag, a_enmChoice, a_PtrName, a_Name, a_Type, a_Api, a_fClue, a_Constraints) \ - RTASN1TMPL_SEMICOLON_DUMMY() + RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_PCHOICE_XTAG_EX(a_uTag, a_enmChoice, a_PtrTnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_Constraints) \ - RTASN1TMPL_SEMICOLON_DUMMY() -# define RTASN1TMPL_END_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() + RTASN1TMPL_SEMICOLON_DUMMY() +# define RTASN1TMPL_END_PCHOICE() RTASN1TMPL_SEMICOLON_DUMMY() # define RTASN1TMPL_SET_SEQ_OF_COMMON(a_ItemType, a_ItemApi) \ RTASN1TMPL_DECL(int) RT_CONCAT(RTASN1TMPL_EXT_NAME,_Erase)(RT_CONCAT(P,RTASN1TMPL_TYPE) pThis, uint32_t iPosition) \ @@ -1047,7 +1137,7 @@ # define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) \ if (!iDiff) \ iDiff = RT_CONCAT(a_Api,_Compare)(&pLeft->a_Name, &pRight->a_Name) -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ if (!iDiff && pLeft->a_enmMembNm != pRight->a_enmMembNm) \ iDiff = pLeft->a_enmMembNm < pRight->a_enmMembNm ? -1 : 1; \ else if (!iDiff) \ @@ -1056,7 +1146,7 @@ default: break # define RTASN1TMPL_MEMBER_DYN_COMMON(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_enmMembNm, a_enmValue, a_IfStmt) \ case a_enmValue: iDiff = RT_CONCAT(a_Api,_Compare)(pLeft->a_UnionNm.a_PtrName, pRight->a_UnionNm.a_PtrName); break -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ case RT_CONCAT(a_enmType,_NOT_PRESENT): break; \ } # define RTASN1TMPL_MEMBER_OPT_XTAG_EX(a_TnNm, a_CtxTagN, a_Name, a_Type, a_Api, a_uTag, a_Constraints) \ @@ -1145,7 +1235,7 @@ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "%s: Missing member %s (%s).", \ pszErrorTag, #a_Name, RT_XSTR(RTASN1TMPL_TYPE)); \ } do {} while (0) -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ if (RT_SUCCESS(rc)) \ switch (pThis->a_enmMembNm) \ { \ @@ -1158,7 +1248,7 @@ rc = RT_CONCAT(a_Api,_CheckSanity)(pThis->a_UnionNm.a_PtrName, fFlags & RTASN1_CHECK_SANITY_F_COMMON_MASK, \ pErrInfo, RT_XSTR(RTASN1TMPL_TYPE) "::" #a_UnionNm "." #a_PtrName); \ break -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ case RT_CONCAT(a_enmType,_NOT_PRESENT): \ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, \ "%s: Invalid " #a_enmMembNm " value: " #a_enmType "_NOT_PRESENT", pszErrorTag); \ @@ -1324,7 +1414,7 @@ # define RTASN1TMPL_BEGIN_SEQCORE() RTASN1TMPL_BEGIN_COMMON() # define RTASN1TMPL_BEGIN_SETCORE() RTASN1TMPL_BEGIN_COMMON() # define RTASN1TMPL_MEMBER_EX(a_Name, a_Type, a_Api, a_Constraints) RT_CONCAT(a_Api,_Delete)(&pThis->a_Name) -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ switch (pThis->a_enmMembNm) \ { \ default: break @@ -1337,7 +1427,7 @@ pThis->a_UnionNm.a_PtrName = NULL; \ } \ break -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) \ +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) \ } # define RTASN1TMPL_END_SEQCORE() RTASN1TMPL_END_COMMON() # define RTASN1TMPL_END_SETCORE() RTASN1TMPL_END_COMMON() @@ -1424,21 +1514,21 @@ /* Any/dynamic members. */ #ifndef RTASN1TMPL_MEMBER_DYN_BEGIN -# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_enmType, a_enmMembNm, a_Allocation) do { } while (0) +# define RTASN1TMPL_MEMBER_DYN_BEGIN(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) do { } while (0) #endif #ifndef RTASN1TMPL_MEMBER_DYN_END -# define RTASN1TMPL_MEMBER_DYN_END(a_enmType, a_enmMembNm, a_Allocation) do { } while (0) +# define RTASN1TMPL_MEMBER_DYN_END(a_ObjIdMembNm, a_enmType, a_enmMembNm, a_Allocation) do { } while (0) #endif #ifndef RTASN1TMPL_MEMBER_DYN_COMMON # define RTASN1TMPL_MEMBER_DYN_COMMON(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_enmMembNm, a_enmValue, a_IfStmt) \ RTASN1TMPL_MEMBER(a_UnionNm.a_PtrName, a_Type, a_Api) #endif #ifndef RTASN1TMPL_MEMBER_DYN -# define RTASN1TMPL_MEMBER_DYN(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_enmMembNm, a_enmValue, a_WhenExpr) \ - RTASN1TMPL_MEMBER_DYN_COMMON(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_enmMembNm, a_enmValue, if (a_WhenExpr)) +# define RTASN1TMPL_MEMBER_DYN(a_UnionNm, a_PtrName, a_Name, a_Type, a_Api, a_Allocation, a_ObjIdMembNm, a_enmMembNm, a_enmValue, a_szObjId) \ + RTASN1TMPL_MEMBER_DYN_COMMON(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_enmMembNm, a_enmValue, if (RTAsn1ObjId_CompareWithString(&pThis->a_ObjIdMembNm, a_szObjId) == 0)) #endif #ifndef RTASN1TMPL_MEMBER_DYN_DEFAULT -# define RTASN1TMPL_MEMBER_DYN_DEFAULT(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_enmMembNm, a_enmValue) \ +# define RTASN1TMPL_MEMBER_DYN_DEFAULT(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_ObjIdMembNm, a_enmMembNm, a_enmValue) \ RTASN1TMPL_MEMBER_DYN_COMMON(a_UnionNm, a_PtrName, a_Type, a_Api, a_Allocation, a_enmMembNm, a_enmValue, RT_NOTHING) #endif diff -Nru virtualbox-6.1.16-dfsg/include/iprt/asn1.h virtualbox-6.1.38-dfsg/include/iprt/asn1.h --- virtualbox-6.1.16-dfsg/include/iprt/asn1.h 2020-10-16 16:27:54.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/asn1.h 2022-09-01 13:17:50.000000000 +0000 @@ -949,7 +949,7 @@ * * @remarks Currently assume unsigned number. */ -RTDECL(int) RTAsn1Integer_ToString(PRTASN1INTEGER pThis, char *pszBuf, size_t cbBuf, uint32_t fFlags, size_t *pcbActual); +RTDECL(int) RTAsn1Integer_ToString(PCRTASN1INTEGER pThis, char *pszBuf, size_t cbBuf, uint32_t fFlags, size_t *pcbActual); RTASN1_IMPL_GEN_SEQ_OF_TYPEDEFS_AND_PROTOS(RTASN1SEQOFINTEGERS, RTASN1INTEGER, RTDECL, RTAsn1SeqOfIntegers); RTASN1_IMPL_GEN_SET_OF_TYPEDEFS_AND_PROTOS(RTASN1SETOFINTEGERS, RTASN1INTEGER, RTDECL, RTAsn1SetOfIntegers); @@ -1025,8 +1025,37 @@ */ RTDECL(int) RTAsn1Time_CompareWithTimeSpec(PCRTASN1TIME pLeft, PCRTTIMESPEC pTsRight); +/** + * Extended init function that lets you select the kind of time object (UTC or + * generalized). + */ RTDECL(int) RTAsn1Time_InitEx(PRTASN1TIME pThis, uint32_t uTag, PCRTASN1ALLOCATORVTABLE pAllocator); +/** + * Combines RTAsn1Time_InitEx() and RTAsn1Time_SetTime(). + */ +RTDECL(int) RTAsn1Time_InitWithTime(PRTASN1TIME pThis, uint32_t uTag, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIME pTime); + +/** + * Sets the ASN.1 time value to @a pTime. + * + * @returns IPRT status code. + * @param pThis The ASN.1 time object to modify. + * @param pAllocator The allocator to use. + * @param pTime The time to set. + */ +RTDECL(int) RTAsn1Time_SetTime(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIME pTime); + +/** + * Sets the ASN.1 time value to @a pTimeSpec. + * + * @returns IPRT status code. + * @param pThis The ASN.1 time object to modify. + * @param pAllocator The allocator to use. + * @param pTimeSpec The time to set. + */ +RTDECL(int) RTAsn1Time_SetTimeSpec(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIMESPEC pTimeSpec); + /** @name Predicate macros for determing the exact type of RTASN1TIME. * @{ */ /** True if UTC time. */ @@ -1075,6 +1104,7 @@ RTASN1TYPE_STANDARD_PROTOTYPES(RTASN1OBJID, RTDECL, RTAsn1ObjId, Asn1Core); RTDECL(int) RTAsn1ObjId_InitFromString(PRTASN1OBJID pThis, const char *pszObjId, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTAsn1ObjId_SetFromString(PRTASN1OBJID pThis, const char *pszObjId, PCRTASN1ALLOCATORVTABLE pAllocator); /** * Compares an ASN.1 object identifier with a dotted object identifier string. @@ -1153,6 +1183,8 @@ */ #define RTASN1BITSTRING_GET_BYTE_SIZE(a_pBitString) ( ((a_pBitString)->cBits + 7U) >> 3 ) +RTDECL(int) RTAsn1BitString_InitWithData(PRTASN1BITSTRING pThis, void const *pvSrc, uint32_t cSrcBits, + PCRTASN1ALLOCATORVTABLE pAllocator); RTDECL(int) RTAsn1BitString_DecodeAsn1Ex(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t cMaxBits, PRTASN1BITSTRING pThis, const char *pszErrorTag); RTDECL(uint64_t) RTAsn1BitString_GetAsUInt64(PCRTASN1BITSTRING pThis); @@ -1190,6 +1222,10 @@ RTASN1TYPE_STANDARD_PROTOTYPES(RTASN1OCTETSTRING, RTDECL, RTAsn1OctetString, Asn1Core); +RTDECL(int) RTAsn1OctetString_AllocContent(PRTASN1OCTETSTRING pThis, void const *pvSrc, size_t cb, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTAsn1OctetString_SetContent(PRTASN1OCTETSTRING pThis, void const *pvSrc, size_t cbSrc, + PCRTASN1ALLOCATORVTABLE pAllocator); RTDECL(bool) RTAsn1OctetString_AreContentBytesValid(PCRTASN1OCTETSTRING pThis, uint32_t fFlags); RTDECL(int) RTAsn1OctetString_RefreshContent(PRTASN1OCTETSTRING pThis, uint32_t fFlags, PCRTASN1ALLOCATORVTABLE pAllocator, PRTERRINFO pErrInfo); @@ -1473,6 +1509,7 @@ /** Pointer to a const ASN.1 dynamic type record. */ typedef RTASN1DYNTYPE const *PCRTASN1DYNTYPE; RTASN1TYPE_STANDARD_PROTOTYPES(RTASN1DYNTYPE, RTDECL, RTAsn1DynType, u.Core); +RTDECL(int) RTAsn1DynType_SetToNull(PRTASN1DYNTYPE pThis); /** @name Virtual Method Table Based API @@ -1630,6 +1667,28 @@ */ RTDECL(int) RTAsn1EncodeToBuffer(PCRTASN1CORE pRoot, uint32_t fFlags, void *pvBuf, size_t cbBuf, PRTERRINFO pErrInfo); +/** + * Helper for when DER encoded ASN.1 is needed for something. + * + * Handy when interfacing with OpenSSL and the many d2i_Xxxxx OpenSSL functions, + * but also handy when structures needs to be digested or similar during signing + * or verification. + * + * We sometimes can use the data we've decoded directly, but often we have + * encode it into a temporary heap buffer. + * + * @returns IPRT status code, details in @a pErrInfo if present. + * @param pRoot The ASN.1 root of the structure to be passed to OpenSSL. + * @param ppbRaw Where to return the pointer to raw encoded data. + * @param pcbRaw Where to return the size of the raw encoded data. + * @param ppvFree Where to return what to pass to RTMemTmpFree, i.e. NULL + * if we use the previously decoded data directly and + * non-NULL if we had to allocate heap and encode it. + * @param pErrInfo Where to return details about encoding issues. Optional. + */ +RTDECL(int) RTAsn1EncodeQueryRawBits(PRTASN1CORE pRoot, const uint8_t **ppbRaw, uint32_t *pcbRaw, + void **ppvFree, PRTERRINFO pErrInfo); + /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/base64.h virtualbox-6.1.38-dfsg/include/iprt/base64.h --- virtualbox-6.1.16-dfsg/include/iprt/base64.h 2020-10-16 16:27:54.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/base64.h 2022-09-01 13:17:50.000000000 +0000 @@ -52,7 +52,11 @@ /** Insert line breaks into encoded string. * The size of the end-of-line marker is that that of the host platform. */ -#define RTBASE64_FLAGS_NO_LINE_BREAKS RT_BIT_32(0) +#define RTBASE64_FLAGS_EOL_NATIVE UINT32_C(0) /**< Use native newlines. */ +#define RTBASE64_FLAGS_NO_LINE_BREAKS UINT32_C(1) /**< No newlines. */ +#define RTBASE64_FLAGS_EOL_LF UINT32_C(2) /**< Use UNIX-style newlines. */ +#define RTBASE64_FLAGS_EOL_CRLF UINT32_C(3) /**< Use DOS-style newlines. */ +#define RTBASE64_FLAGS_EOL_STYLE_MASK UINT32_C(3) /**< End-of-line style mask. */ /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/cdefs.h virtualbox-6.1.38-dfsg/include/iprt/cdefs.h --- virtualbox-6.1.16-dfsg/include/iprt/cdefs.h 2020-10-16 16:27:54.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/cdefs.h 2022-09-01 13:17:50.000000000 +0000 @@ -155,6 +155,17 @@ # error "Exactly one RT_ARCH_XXX macro shall be defined" #endif +/** @def RT_CPLUSPLUS_PREREQ + * Require a minimum __cplusplus value, simplifying dealing with non-C++ code. + * + * @param a_Min The minimum version, e.g. 201100. + */ +#ifdef __cplusplus +# define RT_CPLUSPLUS_PREREQ(a_Min) (__cplusplus >= (a_Min)) +#else +# define RT_CPLUSPLUS_PREREQ(a_Min) (0) +#endif + /** @def RT_GNUC_PREREQ * Shorter than fiddling with __GNUC__ and __GNUC_MINOR__. * @@ -1280,6 +1291,17 @@ # define DECLHIDDEN(type) __attribute__((visibility("hidden"))) type #endif +/** @def DECL_HIDDEN_DATA + * How to declare a non-exported variable. + * @param a_Type The data type of the variable. + * @sa DECL_HIDDEN_CONST + */ +#if !defined(RT_GCC_SUPPORTS_VISIBILITY_HIDDEN) || defined(RT_NO_VISIBILITY_HIDDEN) +# define DECL_HIDDEN_DATA(a_Type) a_Type +#else +# define DECL_HIDDEN_DATA(a_Type) __attribute__((visibility("hidden"))) a_Type +#endif + /** @def DECL_HIDDEN_CONST * Workaround for g++ warnings when applying the hidden attribute to a const * definition. Use DECLHIDDEN for the declaration. @@ -1392,16 +1414,41 @@ */ #define DECLCALLBACK(type) type RT_FAR_CODE RTCALL -/** @def DECLCALLBACKPTR +/** @def DECLCALLBACKTYPE_EX + * How to declare an call back function type. + * @param a_RetType The return type of the function declaration. + * @param a_CallConv Calling convention. + * @param a_Name The name of the typedef + * @param a_Args The argument list enclosed in parentheses. + */ +#define DECLCALLBACKTYPE_EX(a_RetType, a_CallConv, a_Name, a_Args) a_RetType a_CallConv a_Name a_Args +/** @def DECLCALLBACKTYPE + * How to declare an call back function type. + * @param a_RetType The return type of the function declaration. + * @param a_Name The name of the typedef + * @param a_Args The argument list enclosed in parentheses. + */ +#define DECLCALLBACKTYPE(a_RetType, a_Name, a_Args) DECLCALLBACKTYPE_EX(a_RetType, RT_FAR_CODE RTCALL, a_Name, a_Args) + +/** @def DECLCALLBACKPTR_EX * How to declare an call back function pointer. - * @param type The return type of the function declaration. - * @param name The name of the variable member. + * @param a_RetType The return type of the function declaration. + * @param a_CallConv Calling convention. + * @param a_Name The name of the variable member. + * @param a_Args The argument list enclosed in parentheses. */ #if defined(__IBMC__) || defined(__IBMCPP__) -# define DECLCALLBACKPTR(type, name) type (* RTCALL name) +# define DECLCALLBACKPTR_EX(a_RetType, a_CallConv, a_Name, a_Args) a_RetType (* a_CallConv a_Name) a_Args #else -# define DECLCALLBACKPTR(type, name) type (RT_FAR_CODE RTCALL * name) +# define DECLCALLBACKPTR_EX(a_RetType, a_CallConv, a_Name, a_Args) a_RetType (a_CallConv * a_Name) a_Args #endif +/** @def DECLCALLBACKPTR + * How to declare an call back function pointer. + * @param a_RetType The return type of the function declaration. + * @param a_Name The name of the variable member. + * @param a_Args The argument list enclosed in parentheses. + */ +#define DECLCALLBACKPTR(a_RetType, a_Name, a_Args) DECLCALLBACKPTR_EX(a_RetType, RT_FAR_CODE RTCALL, a_Name, a_Args) /** @def DECLCALLBACKMEMBER * How to declare an call back function pointer member. @@ -2533,6 +2580,9 @@ * What to up inside the square brackets when declaring a structure member * with a flexible size. * + * @note RT_FLEXIBLE_ARRAY_EXTENSION must always preceed the type, unless + * it's C-only code. + * * @note Use RT_UOFFSETOF() to calculate the structure size. * * @note Never to a sizeof() on the structure or member! @@ -2548,7 +2598,9 @@ * @sa RT_FLEXIBLE_ARRAY_NESTED, RT_FLEXIBLE_ARRAY_IN_UNION */ #if RT_MSC_PREREQ(RT_MSC_VER_VS2005) /** @todo Probably much much earlier. */ \ - || (defined(__cplusplus) && RT_GNUC_PREREQ(6, 1) && !RT_GNUC_PREREQ(7, 0)) /* gcc-7 warns again */\ + || (defined(__cplusplus) && ( RT_GNUC_PREREQ(6, 1) \ + && ( !RT_GNUC_PREREQ(7, 0) /* gcc-7 warns again */ \ + || RT_GNUC_PREREQ(10, 0) /* gcc-10 works with __extension__, the ones in between needs testing */ ))) \ || defined(__WATCOMC__) /* openwatcom 1.9 supports it, we don't care about older atm. */ \ || RT_CLANG_PREREQ_EX(3, 4, 0) /* Only tested clang v3.4, support is probably older. */ # define RT_FLEXIBLE_ARRAY @@ -2565,6 +2617,16 @@ # define RT_FLEXIBLE_ARRAY 1 #endif +/** @def RT_FLEXIBLE_ARRAY_EXTENSION + * A trick to make GNU C++ quietly accept flexible arrays in C++ code when + * pedantic warnings are enabled. Put this on the line before the flexible + * array. */ +#if (RT_GNUC_PREREQ(10, 0) && defined(__cplusplus)) || defined(DOXGYEN_RUNNING) +# define RT_FLEXIBLE_ARRAY_EXTENSION RT_GCC_EXTENSION +#else +# define RT_FLEXIBLE_ARRAY_EXTENSION +#endif + /** @def RT_FLEXIBLE_ARRAY_NESTED * Variant of RT_FLEXIBLE_ARRAY for use in structures that are nested. * @@ -3737,14 +3799,23 @@ # define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) + 0x1000U >= 0x2000U \ && ( ((uintptr_t)(ptr) & 0xffff800000000000ULL) == 0xffff800000000000ULL \ || ((uintptr_t)(ptr) & 0xffff800000000000ULL) == 0) ) +# elif defined(RT_OS_LINUX) /* May use 5-level paging (see Documentation/x86/x86_64/mm.rst). */ +# define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) >= 0x1000U /* one invalid page at the bottom */ \ + && !((uintptr_t)(ptr) & 0xff00000000000000ULL) ) # else -# define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) + 0x1000U >= 0x2000U \ +# define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) >= 0x1000U \ && !((uintptr_t)(ptr) & 0xffff800000000000ULL) ) # endif # else /* !IN_RING3 */ -# define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) + 0x1000U >= 0x2000U \ +# if defined(RT_OS_LINUX) /* May use 5-level paging (see Documentation/x86/x86_64/mm.rst). */ +# define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) + 0x200000 >= 0x201000U /* one invalid page at the bottom and 2MB at the top */ \ + && ( ((uintptr_t)(ptr) & 0xff00000000000000ULL) == 0xff00000000000000ULL \ + || ((uintptr_t)(ptr) & 0xff00000000000000ULL) == 0) ) +# else +# define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) + 0x1000U >= 0x2000U \ && ( ((uintptr_t)(ptr) & 0xffff800000000000ULL) == 0xffff800000000000ULL \ || ((uintptr_t)(ptr) & 0xffff800000000000ULL) == 0) ) +# endif # endif /* !IN_RING3 */ #elif defined(RT_ARCH_X86) diff -Nru virtualbox-6.1.16-dfsg/include/iprt/cpp/ministring.h virtualbox-6.1.38-dfsg/include/iprt/cpp/ministring.h --- virtualbox-6.1.16-dfsg/include/iprt/cpp/ministring.h 2020-10-16 16:27:54.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/cpp/ministring.h 2022-09-01 13:17:50.000000000 +0000 @@ -852,6 +852,18 @@ int replaceNoThrow(size_t offStart, size_t cchLength, const char *pszReplacement, size_t cchReplacement) RT_NOEXCEPT; /** + * Truncates the string to a max length of @a cchMax. + * + * If the string is shorter than @a cchMax characters, no change is made. + * + * If the @a cchMax is not at the start of a UTF-8 sequence, it will be adjusted + * down to the start of the UTF-8 sequence. Thus, after a truncation, the + * length() may be smaller than @a cchMax. + * + */ + RTCString &truncate(size_t cchMax) RT_NOEXCEPT; + + /** * Index operator. * * Returns the byte at the given index, or a null byte if the index is not diff -Nru virtualbox-6.1.16-dfsg/include/iprt/crypto/applecodesign.h virtualbox-6.1.38-dfsg/include/iprt/crypto/applecodesign.h --- virtualbox-6.1.16-dfsg/include/iprt/crypto/applecodesign.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/crypto/applecodesign.h 2022-09-01 13:17:50.000000000 +0000 @@ -147,6 +147,7 @@ /** Number of slots. Big endian. */ uint32_t cSlots; /** Slots. */ + RT_FLEXIBLE_ARRAY_EXTENSION RTCRAPLCSBLOBSLOT aSlots[RT_FLEXIBLE_ARRAY]; } RTCRAPLCSSUPERBLOB; AssertCompileMemberOffset(RTCRAPLCSSUPERBLOB, aSlots, 12); diff -Nru virtualbox-6.1.16-dfsg/include/iprt/crypto/pem.h virtualbox-6.1.38-dfsg/include/iprt/crypto/pem.h --- virtualbox-6.1.16-dfsg/include/iprt/crypto/pem.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/crypto/pem.h 2022-09-01 13:17:50.000000000 +0000 @@ -30,6 +30,8 @@ #endif #include +#include /* PRTASN1CORE */ +#include /* PFNRTSTROUTPUT */ RT_C_DECLS_BEGIN @@ -87,6 +89,7 @@ /** The field name length. */ size_t cchName; /** The field name. */ + RT_FLEXIBLE_ARRAY_EXTENSION char szName[RT_FLEXIBLE_ARRAY]; } RTCRPEMFIELD; /** Pointer to a PEM field. */ @@ -204,6 +207,85 @@ RTDECL(const char *) RTCrPemFindFirstSectionInContent(void const *pvContent, size_t cbContent, PCRTCRPEMMARKER paMarkers, size_t cMarkers); + +/** + * PEM formatter for a binary data blob. + * + * @returns Number of output bytes (sum of @a pfnOutput return values). + * @param pfnOutput The output callback function. + * @param pvUser The user argument to the output callback. + * @param pvContent The binary blob to output. + * @param cbContent Size of the binary blob. + * @param pszMarker The PEM marker, .e.g "PRIVATE KEY", "CERTIFICATE" or + * similar. + * @sa RTCrPemWriteAsn1, RTCrPemWriteAsn1ToVfsFile, + * RTCrPemWriteAsn1ToVfsFile + */ +RTDECL(size_t) RTCrPemWriteBlob(PFNRTSTROUTPUT pfnOutput, void *pvUser, + const void *pvContent, size_t cbContent, const char *pszMarker); + +RTDECL(ssize_t) RTCrPemWriteBlobToVfsIoStrm(RTVFSIOSTREAM hVfsIos, const void *pvContent, size_t cbContent, const char *pszMarker); +RTDECL(ssize_t) RTCrPemWriteBlobToVfsFile(RTVFSFILE hVfsFile, const void *pvContent, size_t cbContent, const char *pszMarker); + +/** + * PEM formatter for a generic ASN.1 structure. + * + * This will call both RTAsn1EncodePrepare() and RTAsn1EncodeWrite() on + * @a pRoot. Uses DER encoding. + * + * @returns Number of outputted chars (sum of @a pfnOutput return values), + * negative values are error status codes from the ASN.1 encoding. + * @param pfnOutput The output callback function. + * @param pvUser The user argument to the output callback. + * @param fFlags Reserved, MBZ. + * @param pRoot The root of the ASN.1 to encode and format as PEM. + * @param pszMarker The PEM marker, .e.g "PRIVATE KEY", "CERTIFICATE" or + * similar. + * @param pErrInfo For encoding errors. Optional. + * @sa RTCrPemWriteAsn1ToVfsFile, RTCrPemWriteAsn1ToVfsFile, + * RTCrPemWriteBlob + */ +RTDECL(ssize_t) RTCrPemWriteAsn1(PFNRTSTROUTPUT pfnOutput, void *pvUser, PRTASN1CORE pRoot, + uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo); + +/** + * PEM formatter for a generic ASN.1 structure and output it to @a hVfsIos. + * + * This will call both RTAsn1EncodePrepare() and RTAsn1EncodeWrite() on + * @a pRoot. Uses DER encoding. + * + * @returns Number of chars written, negative values are error status codes from + * the ASN.1 encoding or from RTVfsIoStrmWrite(). + * @param hVfsIos Handle to the I/O stream to write it to. + * @param fFlags Reserved, MBZ. + * @param pRoot The root of the ASN.1 to encode and format as PEM. + * @param pszMarker The PEM marker, .e.g "PRIVATE KEY", "CERTIFICATE" or + * similar. + * @param pErrInfo For encoding errors. Optional. + * @sa RTCrPemWriteAsn1ToVfsFile, RTCrPemWriteAsn1, RTCrPemWriteBlob + */ +RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsIoStrm(RTVFSIOSTREAM hVfsIos, PRTASN1CORE pRoot, + uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo); + +/** + * PEM formatter for a generic ASN.1 structure and output it to @a hVfsFile. + * + * This will call both RTAsn1EncodePrepare() and RTAsn1EncodeWrite() on + * @a pRoot. Uses DER encoding. + * + * @returns Number of chars written, negative values are error status codes from + * the ASN.1 encoding or from RTVfsIoStrmWrite(). + * @param hVfsFile Handle to the file to write it to. + * @param fFlags Reserved, MBZ. + * @param pRoot The root of the ASN.1 to encode and format as PEM. + * @param pszMarker The PEM marker, .e.g "PRIVATE KEY", "CERTIFICATE" or + * similar. + * @param pErrInfo For encoding errors. Optional. + * @sa RTCrPemWriteAsn1ToVfsIoStrm, RTCrPemWriteAsn1, RTCrPemWriteBlob + */ +RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsFile(RTVFSFILE hVfsFile, PRTASN1CORE pRoot, + uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo); + /** @} */ RT_C_DECLS_END diff -Nru virtualbox-6.1.16-dfsg/include/iprt/crypto/pkcs7.h virtualbox-6.1.38-dfsg/include/iprt/crypto/pkcs7.h --- virtualbox-6.1.16-dfsg/include/iprt/crypto/pkcs7.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/crypto/pkcs7.h 2022-09-01 13:17:51.000000000 +0000 @@ -154,8 +154,27 @@ typedef RTCRPKCS7ATTRIBUTE const *PCRTCRPKCS7ATTRIBUTE; RTASN1TYPE_STANDARD_PROTOTYPES(RTCRPKCS7ATTRIBUTE, RTDECL, RTCrPkcs7Attribute, SeqCore.Asn1Core); +RTDECL(int) RTCrPkcs7Attribute_SetAppleMultiCdPlist(PRTCRPKCS7ATTRIBUTE pThis, PCRTASN1SETOFOCTETSTRINGS pToClone, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Attribute_SetContentType(PRTCRPKCS7ATTRIBUTE pThis, PCRTASN1SETOFOBJIDS pToClone, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Attribute_SetCounterSignatures(PRTCRPKCS7ATTRIBUTE pThis, PCRTCRPKCS7SIGNERINFOS pToClone, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Attribute_SetMessageDigest(PRTCRPKCS7ATTRIBUTE pThis, PCRTASN1SETOFOCTETSTRINGS pToClone, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Attribute_SetMsStatementType(PRTCRPKCS7ATTRIBUTE pThis, PCRTASN1SETOFOBJIDSEQS pToClone, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Attribute_SetMsNestedSignature(PRTCRPKCS7ATTRIBUTE pThis, struct RTCRPKCS7SETOFCONTENTINFOS const *pToClone, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Attribute_SetMsTimestamp(PRTCRPKCS7ATTRIBUTE pThis, struct RTCRPKCS7SETOFCONTENTINFOS const *pToClone, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Attribute_SetSigningTime(PRTCRPKCS7ATTRIBUTE pThis, PCRTASN1SETOFTIMES pToClone, + PCRTASN1ALLOCATORVTABLE pAllocator); + RTASN1_IMPL_GEN_SET_OF_TYPEDEFS_AND_PROTOS(RTCRPKCS7ATTRIBUTES, RTCRPKCS7ATTRIBUTE, RTDECL, RTCrPkcs7Attributes); +RTDECL(int) RTCrPkcs7Attributes_HashAttributes(PRTCRPKCS7ATTRIBUTES pAttributes, RTCRDIGEST hDigest, PRTERRINFO pErrInfo); + /** * One PKCS \#7 SignerInfo (IPRT representation). @@ -187,6 +206,11 @@ } RTCRPKCS7SIGNERINFO; RTASN1TYPE_STANDARD_PROTOTYPES(RTCRPKCS7SIGNERINFO, RTDECL, RTCrPkcs7SignerInfo, SeqCore.Asn1Core); +RTDECL(int) RTCrPkcs7SignerInfo_SetAuthenticatedAttributes(PRTCRPKCS7SIGNERINFO pThis, PCRTCRPKCS7ATTRIBUTES pAttributes, + PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7SignerInfo_SetUnauthenticatedAttributes(PRTCRPKCS7SIGNERINFO pThis, PCRTCRPKCS7ATTRIBUTES pAttributes, + PCRTASN1ALLOCATORVTABLE pAllocator); + /** RTCRPKCS7SIGNERINFO::Version value. */ #define RTCRPKCS7SIGNERINFO_V1 1 @@ -366,6 +390,12 @@ RTASN1TYPE_STANDARD_PROTOTYPES(RTCRPKCS7CERT, RTDECL, RTCrPkcs7Cert, Dummy.Asn1Core); RTASN1_IMPL_GEN_SET_OF_TYPEDEFS_AND_PROTOS(RTCRPKCS7SETOFCERTS, RTCRPKCS7CERT, RTDECL, RTCrPkcs7SetOfCerts); +RTDECL(int) RTCrPkcs7Cert_SetX509Cert(PRTCRPKCS7CERT pThis, PCRTCRX509CERTIFICATE pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Cert_SetExtendedCert(PRTCRPKCS7CERT pThis, PCRTASN1CORE pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Cert_SetAcV1(PRTCRPKCS7CERT pThis, PCRTASN1CORE pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Cert_SetAcV2(PRTCRPKCS7CERT pThis, PCRTASN1CORE pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7Cert_SetOtherCert(PRTCRPKCS7CERT pThis, PCRTASN1CORE pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); + RTDECL(PCRTCRX509CERTIFICATE) RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(PCRTCRPKCS7SETOFCERTS pCertificates, PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNumber); @@ -419,6 +449,8 @@ * OTHER type present (RFC-5652, section 5.1). */ #define RTCRPKCS7SIGNEDDATA_V5 5 +RTDECL(int) RTCrPkcs7SignedData_SetCertificates(PRTCRPKCS7SIGNEDDATA pThis, PCRTCRPKCS7SETOFCERTS pCerts, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrPkcs7SignedData_SetCrls(PRTCRPKCS7SIGNEDDATA pThis, PCRTASN1CORE pCerts, PCRTASN1ALLOCATORVTABLE pAllocator); /** @name RTCRPKCS7SIGNEDDATA_SANITY_F_XXX - Flags for RTPkcs7SignedDataCheckSantiy. * @{ */ @@ -430,6 +462,22 @@ #define RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT RT_BIT_32(2) /** @} */ +/** PKCS\#7/CMS (content info) markers. */ +extern RTDATADECL(RTCRPEMMARKER const) g_aRTCrPkcs7Markers[]; +/** Number of entries in g_aRTCrPkcs7Markers. */ +extern RTDATADECL(uint32_t const) g_cRTCrPkcs7Markers; + +/** @name Flags for RTCrPkcs7ContentInfo_ReadFromBuffer + * @{ */ +/** Only allow PEM certificates, not binary ones. + * @sa RTCRPEMREADFILE_F_ONLY_PEM */ +#define RTCRPKCS7_READ_F_PEM_ONLY RT_BIT(1) +/** @} */ + +RTDECL(int) RTCrPkcs7_ReadFromBuffer(PRTCRPKCS7CONTENTINFO pContentInfo, const void *pvBuf, size_t cbBuf, + uint32_t fFlags, PCRTASN1ALLOCATORVTABLE pAllocator, + bool *pfCmsLabeled, PRTERRINFO pErrInfo, const char *pszErrorTag); + /** * PKCS \#7 DigestInfo (IPRT representation). @@ -551,16 +599,17 @@ PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, void *pvUser, void const *pvData, size_t cbData, PRTERRINFO pErrInfo); -/** @name RTCRPKCS7VERIFY_SD_F_XXX - Flags for RTCrPkcs7VerifySignedData +/** @name RTCRPKCS7VERIFY_SD_F_XXX - Flags for RTCrPkcs7VerifySignedData and + * RTCrPkcs7VerifySignedDataWithExternalData * @{ */ /** Always use the signing time attribute if present, requiring it to be * verified as valid. The default behavior is to ignore unverifiable * signing time attributes and use the @a pValidationTime instead. */ #define RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT RT_BIT_32(0) /** Same as RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT for the MS - * timestamp counter sigantures. */ + * timestamp counter signatures. */ #define RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT RT_BIT_32(1) -/** Only use signging time attributes from counter signatures. */ +/** Only use signing time attributes from counter signatures. */ #define RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY RT_BIT_32(2) /** Don't validate the counter signature containing the signing time, just use * it unverified. This is useful if we don't necessarily have the root @@ -576,6 +625,9 @@ * usage bit present. This is used for recursivly verifying MS timestamp * signatures. */ #define RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING RT_BIT_32(6) +/** Skip the verification of the certificate trust paths, taking all + * certificates to be trustworthy. */ +#define RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS RT_BIT_32(7) /** Indicates internally that we're validating a counter signature and should * use different rules when checking out the authenticated attributes. @@ -583,6 +635,29 @@ #define RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE RT_BIT_32(31) /** @} */ + +RTDECL(int) RTCrPkcs7SimpleSignSignedData(uint32_t fFlags, PCRTCRX509CERTIFICATE pSigner, RTCRKEY hPrivateKey, + void const *pvData, size_t cbData, RTDIGESTTYPE enmDigestType, + RTCRSTORE hAdditionalCerts, PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs, + void *pvResult, size_t *pcbResult, PRTERRINFO pErrInfo); + +/** @name RTCRPKCS7SIGN_SD_F_XXX - Flags for RTCrPkcs7SimpleSign. + * @{ */ +/** Detached data. */ +#define RTCRPKCS7SIGN_SD_F_DEATCHED RT_BIT_32(0) +/** No SMIME capabilities attribute. */ +#define RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP RT_BIT_32(1) +/** Produce version 1 output (PKCS\#7), rather than version 3 (CMS). */ +#define RTCRPKCS7SIGN_SD_F_USE_V1 RT_BIT_32(2) +/** Avoid extra OCTET STRING encapsulation around the data blob. + * This is needed for Authenticode signatures. This requires that the + * content type is supplied via the additional authenticated attributes. + * @note Currently only works with RTCRPKCS7SIGN_SD_F_USE_V1. */ +#define RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP RT_BIT_32(3) +/** Valid flag mask. */ +#define RTCRPKCS7SIGN_SD_F_VALID_MASK UINT32_C(0x0000000f) +/** @} */ + /** @} */ RT_C_DECLS_END diff -Nru virtualbox-6.1.16-dfsg/include/iprt/crypto/pkix.h virtualbox-6.1.38-dfsg/include/iprt/crypto/pkix.h --- virtualbox-6.1.16-dfsg/include/iprt/crypto/pkix.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/crypto/pkix.h 2022-09-01 13:17:51.000000000 +0000 @@ -34,6 +34,7 @@ RT_C_DECLS_BEGIN +struct RTCRX509CERTIFICATE; struct RTCRX509SUBJECTPUBLICKEYINFO; /** @defgroup grp_rt_crpkix RTCrPkix - Public Key Infrastructure APIs @@ -92,6 +93,17 @@ void const *pvSignedDigest, size_t cbSignedDigest, RTCRDIGEST hDigest, PRTERRINFO pErrInfo); +/** + * Checks if the hash size can be handled by the given public key. + */ +RTDECL(bool) RTCrPkixPubKeyCanHandleDigestType(struct RTCRX509SUBJECTPUBLICKEYINFO const *pPublicKeyInfo, + RTDIGESTTYPE enmDigestType, PRTERRINFO pErrInfo); + +/** + * Checks if the hash size can be handled by the given certificate's public key. + */ +RTDECL(bool) RTCrPkixCanCertHandleDigestType(struct RTCRX509CERTIFICATE const *pCertificate, + RTDIGESTTYPE enmDigestType, PRTERRINFO pErrInfo); /** * Signs a digest (@a hDigest) using the specified private key (@a pPrivateKey) and algorithm. @@ -145,6 +157,8 @@ #define RTCR_PKCS1_SHA384_WITH_RSA_OID "1.2.840.113549.1.1.12" #define RTCR_PKCS1_SHA512_WITH_RSA_OID "1.2.840.113549.1.1.13" #define RTCR_PKCS1_SHA224_WITH_RSA_OID "1.2.840.113549.1.1.14" +#define RTCR_PKCS1_SHA512T224_WITH_RSA_OID "1.2.840.113549.1.1.15" +#define RTCR_PKCS1_SHA512T256_WITH_RSA_OID "1.2.840.113549.1.1.16" /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/crypto/rsa.h virtualbox-6.1.38-dfsg/include/iprt/crypto/rsa.h --- virtualbox-6.1.16-dfsg/include/iprt/crypto/rsa.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/crypto/rsa.h 2022-09-01 13:17:51.000000000 +0000 @@ -61,6 +61,9 @@ typedef RTCRRSAPUBLICKEY const *PCRTCRRSAPUBLICKEY; RTASN1TYPE_STANDARD_PROTOTYPES(RTCRRSAPUBLICKEY, RTDECL, RTCrRsaPublicKey, SeqCore.Asn1Core); +RTDECL(bool) RTCrRsaPublicKey_CanHandleDigestType(PCRTCRRSAPUBLICKEY pRsaPublicKey, RTDIGESTTYPE enmDigestType, + PRTERRINFO pErrInfo); + /** * RSA other prime info (ASN.1 IPRT representation). @@ -123,6 +126,9 @@ #define RTCRRSAPRIVATEKEY_VERSION_MULTI 1 /** @} */ +RTDECL(bool) RTCrRsaPrivateKey_CanHandleDigestType(PCRTCRRSAPRIVATEKEY pRsaPrivateKey, RTDIGESTTYPE enmDigestType, + PRTERRINFO pErrInfo); + /** * RSA DigestInfo used by the EMSA-PKCS1-v1_5 encoding method. diff -Nru virtualbox-6.1.16-dfsg/include/iprt/crypto/spc.h virtualbox-6.1.38-dfsg/include/iprt/crypto/spc.h --- virtualbox-6.1.16-dfsg/include/iprt/crypto/spc.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/crypto/spc.h 2022-09-01 13:17:51.000000000 +0000 @@ -43,6 +43,9 @@ * @{ */ +/** Value for RTCR_PKCS9_ID_MS_STATEMENT_TYPE. */ +#define RTCRSPC_STMT_TYPE_INDIVIDUAL_CODE_SIGNING "1.3.6.1.4.1.311.2.1.21" + /** * PE Image page hash table, generic union. * @@ -180,6 +183,11 @@ typedef RTCRSPCSERIALIZEDOBJECTATTRIBUTE const *PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE; RTASN1TYPE_STANDARD_PROTOTYPES(RTCRSPCSERIALIZEDOBJECTATTRIBUTE, RTDECL, RTCrSpcSerializedObjectAttribute, SeqCore.Asn1Core); +RTDECL(int) RTCrSpcSerializedObjectAttribute_SetV1Hashes(PRTCRSPCSERIALIZEDOBJECTATTRIBUTE pThis, + PCRTCRSPCSERIALIZEDPAGEHASHES, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrSpcSerializedObjectAttribute_SetV2Hashes(PRTCRSPCSERIALIZEDOBJECTATTRIBUTE pThis, + PCRTCRSPCSERIALIZEDPAGEHASHES, PCRTASN1ALLOCATORVTABLE pAllocator); + /** @name RTCRSPCSERIALIZEDOBJECTATTRIBUTE::Type values * @{ */ /** Serialized object attribute type for page hashes version 1. */ @@ -290,6 +298,9 @@ typedef RTCRSPCSTRING const *PCRTCRSPCSTRING; RTASN1TYPE_STANDARD_PROTOTYPES(RTCRSPCSTRING, RTDECL, RTCrSpcString, Dummy.Asn1Core); +RTDECL(int) RTCrSpcString_SetUcs2(PRTCRSPCSTRING pThis, PCRTASN1STRING pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrSpcString_SetAscii(PRTCRSPCSTRING pThis, PCRTASN1STRING pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); + /** * RTCRSPCSTRING choices. @@ -347,6 +358,10 @@ typedef RTCRSPCLINK const *PCRTCRSPCLINK; RTASN1TYPE_STANDARD_PROTOTYPES(RTCRSPCLINK, RTDECL, RTCrSpcLink, Dummy.Asn1Core); +RTDECL(int) RTCrSpcLink_SetUrl(PRTCRSPCLINK pThis, PCRTASN1STRING pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrSpcLink_SetMoniker(PRTCRSPCLINK pThis, PCRTCRSPCSERIALIZEDOBJECT pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrSpcLink_SetFile(PRTCRSPCLINK pThis, PCRTCRSPCSTRING pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); + #if 0 /** @todo Might not be the correct bit order. */ /** @@ -386,6 +401,9 @@ typedef RTCRSPCPEIMAGEDATA const *PCRTCRSPCPEIMAGEDATA; RTASN1TYPE_STANDARD_PROTOTYPES(RTCRSPCPEIMAGEDATA, RTDECL, RTCrSpcPeImageData, SeqCore.Asn1Core); +RTDECL(int) RTCrSpcPeImageData_SetFlags(PRTCRSPCPEIMAGEDATA pThis, PCRTASN1BITSTRING pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); +RTDECL(int) RTCrSpcPeImageData_SetFile(PRTCRSPCPEIMAGEDATA pThis, PCRTCRSPCLINK pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); + /** The object ID for SpcPeImageData. */ #define RTCRSPCPEIMAGEDATA_OID "1.3.6.1.4.1.311.2.1.15" @@ -442,6 +460,8 @@ typedef RTCRSPCATTRIBUTETYPEANDOPTIONALVALUE const *PCRTCRSPCATTRIBUTETYPEANDOPTIONALVALUE; RTASN1TYPE_STANDARD_PROTOTYPES(RTCRSPCATTRIBUTETYPEANDOPTIONALVALUE, RTDECL, RTCrSpcAttributeTypeAndOptionalValue, SeqCore.Asn1Core); +RTDECL(int) RTCrSpcAttributeTypeAndOptionalValue_SetPeImage(PRTCRSPCATTRIBUTETYPEANDOPTIONALVALUE pThis, + PCRTCRSPCPEIMAGEDATA pToClone, PCRTASN1ALLOCATORVTABLE pAllocator); /** * Authenticode indirect data content. diff -Nru virtualbox-6.1.16-dfsg/include/iprt/crypto/store.h virtualbox-6.1.38-dfsg/include/iprt/crypto/store.h --- virtualbox-6.1.16-dfsg/include/iprt/crypto/store.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/crypto/store.h 2022-09-01 13:17:51.000000000 +0000 @@ -132,6 +132,12 @@ /** Open the certificate store of the system containg trusted CAs * and certificates. */ RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, + /** Open the certificate store of the current user containing intermediate CAs. + * @remarks This may or may not include all the certificates in the system + * store, that's host dependent. So, you better look in both. */ + RTCRSTOREID_USER_INTERMEDIATE_CAS, + /** Open the certificate store of the system containg intermediate CAs. */ + RTCRSTOREID_SYSTEM_INTERMEDIATE_CAS, /** End of valid values. */ RTCRSTOREID_END, /** Traditional enum type compression prevention hack. */ @@ -145,8 +151,6 @@ * There will be no duplicates in this one. * * @returns IPRT status code. - * @retval VWRN_ALREADY_EXISTS if the certificate is already present and - * RTCRCERTCTX_F_ADD_IF_NOT_FOUND was specified. * @param phStore Where to return the store handle. Use * RTCrStoreRelease to release it. * @param enmStoreId The store to snapshot. @@ -158,6 +162,7 @@ RTDECL(int) RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(PRTCRSTORE phStore, PRTERRINFO pErrInfo); RTDECL(int) RTCrStoreCreateInMem(PRTCRSTORE phStore, uint32_t cSizeHint); +RTDECL(int) RTCrStoreCreateInMemEx(PRTCRSTORE phStore, uint32_t cSizeHint, RTCRSTORE hParentStore); RTDECL(uint32_t) RTCrStoreRetain(RTCRSTORE hStore); RTDECL(uint32_t) RTCrStoreRelease(RTCRSTORE hStore); @@ -181,6 +186,24 @@ RTDECL(int) RTCrStoreCertAddEncoded(RTCRSTORE hStore, uint32_t fFlags, void const *pvSrc, size_t cbSrc, PRTERRINFO pErrInfo); /** + * Add an X.509 packaged certificate to the store. + * + * @returns IPRT status code. + * @retval VWRN_ALREADY_EXISTS if the certificate is already present and + * RTCRCERTCTX_F_ADD_IF_NOT_FOUND was specified. + * @retval VERR_WRITE_PROTECT if the store doesn't support adding. + * @param hStore The store to add the certificate to. + * @param fFlags RTCRCERTCTX_F_XXX. Encoding must is optional, + * but must be RTCRCERTCTX_F_ENC_X509_DER if given. + * RTCRCERTCTX_F_ADD_IF_NOT_FOUND is supported. + * @param pCertificate The certificate to add. We may have to encode + * it, thus not const. + * @param pErrInfo Where to return additional error/warning info. + * Optional. + */ +RTDECL(int) RTCrStoreCertAddX509(RTCRSTORE hStore, uint32_t fFlags, PRTCRX509CERTIFICATE pCertificate, PRTERRINFO pErrInfo); + +/** * Adds certificates from files in the specified directory. * * @returns IPRT status code. Even when RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR is @@ -309,8 +332,8 @@ RTDECL(PCRTCRCERTCTX) RTCrStoreCertSearchNext(RTCRSTORE hStore, PRTCRSTORECERTSEARCH pSearch); RTDECL(int) RTCrStoreCertSearchDestroy(RTCRSTORE hStore, PRTCRSTORECERTSEARCH pSearch); -RTDECL(int) RTCrStoreConvertToOpenSslCertStore(RTCRSTORE hStore, uint32_t fFlags, void **ppvOpenSslStore); -RTDECL(int) RTCrStoreConvertToOpenSslCertStack(RTCRSTORE hStore, uint32_t fFlags, void **ppvOpenSslStack); +RTDECL(int) RTCrStoreConvertToOpenSslCertStore(RTCRSTORE hStore, uint32_t fFlags, void **ppvOpenSslStore, PRTERRINFO pErrInfo); +RTDECL(int) RTCrStoreConvertToOpenSslCertStack(RTCRSTORE hStore, uint32_t fFlags, void **ppvOpenSslStack, PRTERRINFO pErrInfo); /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/crypto/x509.h virtualbox-6.1.38-dfsg/include/iprt/crypto/x509.h --- virtualbox-6.1.16-dfsg/include/iprt/crypto/x509.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/crypto/x509.h 2022-09-01 13:17:51.000000000 +0000 @@ -151,6 +151,10 @@ #define RTCRX509ALGORITHMIDENTIFIERID_SHA224 "2.16.840.1.101.3.4.2.4" #define RTCRX509ALGORITHMIDENTIFIERID_SHA512T224 "2.16.840.1.101.3.4.2.5" #define RTCRX509ALGORITHMIDENTIFIERID_SHA512T256 "2.16.840.1.101.3.4.2.6" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA3_224 "2.16.840.1.101.3.4.2.7" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA3_256 "2.16.840.1.101.3.4.2.8" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA3_384 "2.16.840.1.101.3.4.2.9" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA3_512 "2.16.840.1.101.3.4.2.10" #define RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL "1.0.10118.3.0.55" /** @} */ @@ -158,15 +162,21 @@ * @remarks The PKCS variants are the default ones, alternative OID are marked * as such. * @{ */ -#define RTCRX509ALGORITHMIDENTIFIERID_RSA "1.2.840.113549.1.1.1" -#define RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA "1.2.840.113549.1.1.2" -#define RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA "1.2.840.113549.1.1.3" -#define RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA "1.2.840.113549.1.1.4" -#define RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA "1.2.840.113549.1.1.5" -#define RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA "1.2.840.113549.1.1.11" -#define RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA "1.2.840.113549.1.1.12" -#define RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA "1.2.840.113549.1.1.13" -#define RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA "1.2.840.113549.1.1.14" +#define RTCRX509ALGORITHMIDENTIFIERID_RSA "1.2.840.113549.1.1.1" +#define RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA "1.2.840.113549.1.1.2" +#define RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA "1.2.840.113549.1.1.3" +#define RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA "1.2.840.113549.1.1.4" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA "1.2.840.113549.1.1.5" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA "1.2.840.113549.1.1.11" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA "1.2.840.113549.1.1.12" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA "1.2.840.113549.1.1.13" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA "1.2.840.113549.1.1.14" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA "1.2.840.113549.1.1.15" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA "1.2.840.113549.1.1.16" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA3_224_WITH_RSA "2.16.840.1.101.3.4.3.13" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA3_256_WITH_RSA "2.16.840.1.101.3.4.3.14" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA3_384_WITH_RSA "2.16.840.1.101.3.4.3.15" +#define RTCRX509ALGORITHMIDENTIFIERID_SHA3_512_WITH_RSA "2.16.840.1.101.3.4.3.16" /** @} */ @@ -1036,6 +1046,21 @@ extern RTDATADECL(uint32_t const) g_cRTCrX509CertificateMarkers; +/** Wrapper around RTCrPemWriteAsn1ToVfsIoStrm(). */ +DECLINLINE(ssize_t) RTCrX509Certificate_WriteToVfsIoStrm(RTVFSIOSTREAM hVfsIos, PRTCRX509CERTIFICATE pCertificate, + PRTERRINFO pErrInfo) +{ + return RTCrPemWriteAsn1ToVfsIoStrm(hVfsIos, &pCertificate->SeqCore.Asn1Core, 0 /*fFlags*/, + g_aRTCrX509CertificateMarkers[0].paWords[0].pszWord, pErrInfo); +} + +/** Wrapper around RTCrPemWriteAsn1ToVfsFile(). */ +DECLINLINE(ssize_t) RTCrX509Certificate_WriteToVfsFile(RTVFSFILE hVfsFile, PRTCRX509CERTIFICATE pCertificate, + PRTERRINFO pErrInfo) +{ + return RTCrPemWriteAsn1ToVfsFile(hVfsFile, &pCertificate->SeqCore.Asn1Core, 0 /*fFlags*/, + g_aRTCrX509CertificateMarkers[0].paWords[0].pszWord, pErrInfo); +} /** @name X.509 Certificate Extensions * @{ */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/dbg.h virtualbox-6.1.38-dfsg/include/iprt/dbg.h --- virtualbox-6.1.16-dfsg/include/iprt/dbg.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/dbg.h 2022-09-01 13:17:51.000000000 +0000 @@ -1957,8 +1957,13 @@ * @retval VERR_INVALID_POINTER if any of the pointers are bad. * * @param hKrnlInfo The kernel info handle. - * @param pszModule Reserved for future extensions. Pass NULL. + * @param pszModule The name of the module to search, pass NULL to + * search the default kernel module(s). * @param pszSymbol The C name of the symbol. + * On Windows NT there are the following special symbols: + * - __ImageBase: The base address of the module. + * - __ImageSize: The size of the module. + * - __ImageNtHdrs: Address of the NT headers. * @param ppvSymbol Where to return the symbol value, passing NULL is * OK. This may be modified even on failure, in * particular, it will be set to NULL when @@ -1975,8 +1980,13 @@ * @return Symbol address if found, NULL if not found or some invalid parameter * or something. * @param hKrnlInfo The kernel info handle. - * @param pszModule Reserved for future extensions. Pass NULL. + * @param pszModule The name of the module to search, pass NULL to + * search the default kernel module(s). * @param pszSymbol The C name of the symbol. + * On Windows NT there are the following special symbols: + * - __ImageBase: The base address of the module. + * - __ImageSize: The size of the module. + * - __ImageNtHdrs: Address of the NT headers. * @sa RTR0DbgKrnlInfoQuerySymbol, RTLdrGetSymbol */ RTR0DECL(void *) RTR0DbgKrnlInfoGetSymbol(RTDBGKRNLINFO hKrnlInfo, const char *pszModule, const char *pszSymbol); diff -Nru virtualbox-6.1.16-dfsg/include/iprt/err.h virtualbox-6.1.38-dfsg/include/iprt/err.h --- virtualbox-6.1.16-dfsg/include/iprt/err.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/err.h 2022-09-01 13:17:51.000000000 +0000 @@ -379,6 +379,9 @@ #define VERR_EMPTY_STRING (-22422) /** Too many references to an object. */ #define VERR_TOO_MANY_REFERENCES (-22423) +/** Unable to translate one or more of the arguments to the codeset the child + * process is expected to use. */ +#define VERR_PROC_NO_ARG_TRANSLATION (-22425) /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/file.h virtualbox-6.1.38-dfsg/include/iprt/file.h --- virtualbox-6.1.16-dfsg/include/iprt/file.h 2020-10-16 16:27:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/file.h 2022-09-01 13:17:51.000000000 +0000 @@ -950,6 +950,32 @@ /** + * Creates a new file with a unique name using the given template, returning a + * handle to it. + * + * One or more trailing X'es in the template will be replaced by random alpha + * numeric characters until a RTFileOpen with RTFILE_O_CREATE succeeds or we + * run out of patience. + * For instance: + * "/tmp/myprog-XXXXXX" + * + * As an alternative to trailing X'es, it is possible to put 3 or more X'es + * somewhere inside the file name. In the following string only the last + * bunch of X'es will be modified: + * "/tmp/myprog-XXX-XXX.tmp" + * + * @returns IPRT status code. + * @param phFile Where to return the file handle on success. Set to + * NIL on failure. + * @param pszTemplate The file name template on input. The actual file + * name on success. Empty string on failure. + * @param fOpen The RTFILE_O_XXX flags to open the file with. + * RTFILE_O_CREATE is mandatory. + * @see RTFileCreateTemp + */ +RTDECL(int) RTFileCreateUnique(PRTFILE phFile, char *pszTemplate, uint64_t fOpen); + +/** * Creates a new file with a unique name using the given template. * * One or more trailing X'es in the template will be replaced by random alpha @@ -968,6 +994,7 @@ * name on success. Empty string on failure. * @param fMode The mode to create the file with. Use 0600 unless * you have reason not to. + * @see RTFileCreateUnique */ RTDECL(int) RTFileCreateTemp(char *pszTemplate, RTFMODE fMode); @@ -990,6 +1017,7 @@ * @returns VERR_INSECURE if the file could not be created securely. * @param pszTemplate The file name template on input. The actual * file name on success. Empty string on failure. + * @see RTFileCreateUnique */ RTDECL(int) RTFileCreateTempSecure(char *pszTemplate); diff -Nru virtualbox-6.1.16-dfsg/include/iprt/formats/iso9660.h virtualbox-6.1.38-dfsg/include/iprt/formats/iso9660.h --- virtualbox-6.1.16-dfsg/include/iprt/formats/iso9660.h 2020-10-16 16:27:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/formats/iso9660.h 2022-09-01 13:17:51.000000000 +0000 @@ -238,6 +238,7 @@ * @note Endianess depends on table. */ uint16_t idParentRec; /** 0x08: Directory identifier (d-characters or d1-characters). */ + RT_FLEXIBLE_ARRAY_EXTENSION char achDirId[RT_FLEXIBLE_ARRAY]; /* There will be a zero padding byte following if the directory identifier length is odd. */ } ISO9660PATHREC; @@ -290,6 +291,7 @@ /** 0x0f6: Length of the application use field. */ ISO9660U16 cbAppUse; /** 0x0fa: Variable sized application use field. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abAppUse[RT_FLEXIBLE_ARRAY]; /* This is followed by escape sequences with length given by cbEscapeSequnces. */ } ISO9660EXATTRREC; diff -Nru virtualbox-6.1.16-dfsg/include/iprt/formats/mach-o.h virtualbox-6.1.38-dfsg/include/iprt/formats/mach-o.h --- virtualbox-6.1.16-dfsg/include/iprt/formats/mach-o.h 2020-10-16 16:27:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/formats/mach-o.h 2022-09-01 13:17:51.000000000 +0000 @@ -632,6 +632,7 @@ uint32_t minos; /**< Minimum OS version: 31..16=major, 15..8=minor, 7..0=patch */ uint32_t sdk; /**< SDK version: 31..16=major, 15..8=minor, 7..0=patch */ uint32_t ntools; /**< Number of build_tool_version entries following in aTools. */ + RT_FLEXIBLE_ARRAY_EXTENSION build_tool_version_t aTools[RT_FLEXIBLE_ARRAY]; } build_version_command_t; AssertCompileMemberOffset(build_version_command_t, aTools, 24); diff -Nru virtualbox-6.1.16-dfsg/include/iprt/formats/ntfs.h virtualbox-6.1.38-dfsg/include/iprt/formats/ntfs.h --- virtualbox-6.1.16-dfsg/include/iprt/formats/ntfs.h 2020-10-16 16:27:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/formats/ntfs.h 2022-09-01 13:17:52.000000000 +0000 @@ -380,6 +380,7 @@ /** 0x18: Attribute instance number. Unique within the MFT record. */ uint16_t idAttrib; /** 0x1a: Maybe where the attribute name starts. */ + RT_FLEXIBLE_ARRAY_EXTENSION RTUTF16 wszName[RT_FLEXIBLE_ARRAY]; } NTFSATLISTENTRY; AssertCompileMemberOffset(NTFSATLISTENTRY, idAttrib, 0x18); @@ -498,6 +499,7 @@ /** 0x41: Filename type (NTFS_FILENAME_T_XXX). */ uint8_t fFilenameType; /** 0x42: The filename. */ + RT_FLEXIBLE_ARRAY_EXTENSION RTUTF16 wszFilename[RT_FLEXIBLE_ARRAY]; } NTFSATFILENAME; AssertCompileMemberOffset(NTFSATFILENAME, cbData, 0x30); diff -Nru virtualbox-6.1.16-dfsg/include/iprt/formats/pecoff.h virtualbox-6.1.38-dfsg/include/iprt/formats/pecoff.h --- virtualbox-6.1.16-dfsg/include/iprt/formats/pecoff.h 2020-10-16 16:27:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/formats/pecoff.h 2022-09-01 13:17:52.000000000 +0000 @@ -720,7 +720,8 @@ /** Scaled frame register offset. */ RT_GCC_EXTENSION uint8_t FrameOffset : 4; /** Unwind opcodes. */ - IMAGE_UNWIND_CODE aOpcodes[RT_FLEXIBLE_ARRAY]; + RT_FLEXIBLE_ARRAY_EXTENSION + IMAGE_UNWIND_CODE aOpcodes[RT_FLEXIBLE_ARRAY]; } IMAGE_UNWIND_INFO; AssertCompileMemberOffset(IMAGE_UNWIND_INFO, aOpcodes, 4); typedef IMAGE_UNWIND_INFO *PIMAGE_UNWIND_INFO; diff -Nru virtualbox-6.1.16-dfsg/include/iprt/formats/riff.h virtualbox-6.1.38-dfsg/include/iprt/formats/riff.h --- virtualbox-6.1.16-dfsg/include/iprt/formats/riff.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/formats/riff.h 2022-09-01 13:17:52.000000000 +0000 @@ -0,0 +1,237 @@ +/* $Id: riff.h $ */ +/** @file + * IPRT - Resource Interchange File Format (RIFF), WAVE, ++. + */ + +/* + * Copyright (C) 2021 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef IPRT_INCLUDED_formats_riff_h +#define IPRT_INCLUDED_formats_riff_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include + + +/** @defgroup grp_rt_formats_riff RIFF & WAVE structures and definitions + * @ingroup grp_rt_formats + * @{ + */ + +/** + * Resource interchange file format (RIFF) file header. + */ +typedef struct RTRIFFHDR +{ + /** The 'RIFF' magic (RTRIFFHDR_MAGIC). */ + uint32_t uMagic; + /** The file size. */ + uint32_t cbFile; + /** The file type. */ + uint32_t uFileType; +} RTRIFFHDR; +AssertCompileSize(RTRIFFHDR, 12); +/** Pointer to a RIFF file header. */ +typedef RTRIFFHDR *PRTRIFFHDR; + +/** Magic value for RTRIFFHDR::uMagic ('RIFF'). */ +#define RTRIFFHDR_MAGIC RT_BE2H_U32_C(0x52494646) + +/** @name RIFF file types (RTRIFFHDR::uFileType) + * @{ */ +/** RIFF file type: WAVE (audio) */ +#define RTRIFF_FILE_TYPE_WAVE RT_BE2H_U32_C(0x57415645) +/** RIFF file type: AVI (video) */ +#define RTRIFF_FILE_TYPE_AVI RT_BE2H_U32_C(0x41564920) +/** @} */ + +/** + * A RIFF chunk. + */ +typedef struct RTRIFFCHUNK +{ + /** The chunk magic (four character code). */ + uint32_t uMagic; + /** The size of the chunk minus this header. */ + uint32_t cbChunk; +} RTRIFFCHUNK; +AssertCompileSize(RTRIFFCHUNK, 8); +/** Pointer to a RIFF chunk. */ +typedef RTRIFFCHUNK *PRTRIFFCHUNK; + +/** + * A RIFF list. + */ +typedef struct RTRIFFLIST +{ + /** The list indicator (RTRIFFLIST_MAGIC). */ + uint32_t uMagic; + /** The size of the chunk minus this header. */ + uint32_t cbChunk; + /** The list type (four character code). */ + uint32_t uListType; +} RTRIFFLIST; +AssertCompileSize(RTRIFFLIST, 12); +/** Pointer to a RIFF list. */ +typedef RTRIFFLIST *PRTRIFFLIST; +/** Magic value for RTRIFFLIST::uMagic ('LIST'). */ +#define RTRIFFLIST_MAGIC RT_BE2H_U32_C(0x4c495354) + +/** Generic 'INFO' list type. */ +#define RTRIFFLIST_TYPE_INFO RT_BE2H_U32_C(0x494e464f) + + +/** + * Wave file format (WAVEFORMATEX w/o cbSize). + * @see RTRIFFWAVEFMTCHUNK. + */ +typedef struct RTRIFFWAVEFMT +{ + /** Audio format tag. */ + uint16_t uFormatTag; + /** Number of channels. */ + uint16_t cChannels; + /** Sample rate. */ + uint32_t uHz; + /** Byte rate (= uHz * cChannels * cBitsPerSample / 8) */ + uint32_t cbRate; + /** Frame size (aka block alignment). */ + uint16_t cbFrame; + /** Number of bits per sample. */ + uint16_t cBitsPerSample; +} RTRIFFWAVEFMT; +AssertCompileSize(RTRIFFWAVEFMT, 16); +/** Pointer to a wave file format structure. */ +typedef RTRIFFWAVEFMT *PRTRIFFWAVEFMT; + +/** + * Extensible wave file format (WAVEFORMATEXTENSIBLE). + * @see RTRIFFWAVEFMTEXTCHUNK. + */ +#pragma pack(4) /* Override the uint64_t effect from RTUUID, so we can safely put it after RTRIFFHDR in a structure. */ +typedef struct RTRIFFWAVEFMTEXT +{ + /** The coreformat structure. */ + RTRIFFWAVEFMT Core; + /** Number of bytes of extra information after the core. */ + uint16_t cbExtra; + /** Number of valid bits per sample. */ + uint16_t cValidBitsPerSample; + /** The channel mask. */ + uint32_t fChannelMask; + /** The GUID of the sub-format. */ + RTUUID SubFormat; +} RTRIFFWAVEFMTEXT; +#pragma pack() +AssertCompileSize(RTRIFFWAVEFMTEXT, 16+2+22); +/** Pointer to an extensible wave file format structure. */ +typedef RTRIFFWAVEFMTEXT *PRTRIFFWAVEFMTEXT; + +/** RTRIFFWAVEFMT::uFormatTag value for PCM (WDK: WAVE_FORMAT_PCM). */ +#define RTRIFFWAVEFMT_TAG_PCM UINT16_C(0x0001) +/** RTRIFFWAVEFMT::uFormatTag value for extensible wave files (WDK: WAVE_FORMAT_EXTENSIBLE). */ +#define RTRIFFWAVEFMT_TAG_EXTENSIBLE UINT16_C(0xfffe) + +/** Typical RTRIFFWAVEFMTEXT::cbExtra value (min). */ +#define RTRIFFWAVEFMTEXT_EXTRA_SIZE UINT16_C(22) + +/** @name Channel IDs for RTRIFFWAVEFMTEXT::fChannelMask. + * @{ */ +#define RTRIFFWAVEFMTEXT_CH_ID_FL RT_BIT_32(0) /**< Front left. */ +#define RTRIFFWAVEFMTEXT_CH_ID_FR RT_BIT_32(1) /**< Front right. */ +#define RTRIFFWAVEFMTEXT_CH_ID_FC RT_BIT_32(2) /**< Front center */ +#define RTRIFFWAVEFMTEXT_CH_ID_LFE RT_BIT_32(3) /**< Low frequency */ +#define RTRIFFWAVEFMTEXT_CH_ID_BL RT_BIT_32(4) /**< Back left. */ +#define RTRIFFWAVEFMTEXT_CH_ID_BR RT_BIT_32(5) /**< Back right. */ +#define RTRIFFWAVEFMTEXT_CH_ID_FLC RT_BIT_32(6) /**< Front left of center. */ +#define RTRIFFWAVEFMTEXT_CH_ID_FLR RT_BIT_32(7) /**< Front right of center. */ +#define RTRIFFWAVEFMTEXT_CH_ID_BC RT_BIT_32(8) /**< Back center. */ +#define RTRIFFWAVEFMTEXT_CH_ID_SL RT_BIT_32(9) /**< Side left. */ +#define RTRIFFWAVEFMTEXT_CH_ID_SR RT_BIT_32(10) /**< Side right. */ +#define RTRIFFWAVEFMTEXT_CH_ID_TC RT_BIT_32(11) /**< Top center. */ +#define RTRIFFWAVEFMTEXT_CH_ID_TFL RT_BIT_32(12) /**< Top front left. */ +#define RTRIFFWAVEFMTEXT_CH_ID_TFC RT_BIT_32(13) /**< Top front center. */ +#define RTRIFFWAVEFMTEXT_CH_ID_TFR RT_BIT_32(14) /**< Top front right. */ +#define RTRIFFWAVEFMTEXT_CH_ID_TBL RT_BIT_32(15) /**< Top back left. */ +#define RTRIFFWAVEFMTEXT_CH_ID_TBC RT_BIT_32(16) /**< Top back center. */ +#define RTRIFFWAVEFMTEXT_CH_ID_TBR RT_BIT_32(17) /**< Top back right. */ +/** @} */ + +/** RTRIFFWAVEFMTEXT::SubFormat UUID string for PCM. */ +#define RTRIFFWAVEFMTEXT_SUBTYPE_PCM "00000001-0000-0010-8000-00aa00389b71" + + +/** + * Wave file format chunk. + */ +typedef struct RTRIFFWAVEFMTCHUNK +{ + /** Chunk header with RTRIFFWAVEFMT_MAGIC as magic. */ + RTRIFFCHUNK Chunk; + /** The wave file format. */ + RTRIFFWAVEFMT Data; +} RTRIFFWAVEFMTCHUNK; +AssertCompileSize(RTRIFFWAVEFMTCHUNK, 8+16); +/** Pointer to a wave file format chunk. */ +typedef RTRIFFWAVEFMTCHUNK *PRTRIFFWAVEFMTCHUNK; +/** Magic value for RTRIFFWAVEFMTCHUNK and RTRIFFWAVEFMTEXTCHUNK ('fmt '). */ +#define RTRIFFWAVEFMT_MAGIC RT_BE2H_U32_C(0x666d7420) + +/** + * Extensible wave file format chunk. + */ +typedef struct RTRIFFWAVEFMTEXTCHUNK +{ + /** Chunk header with RTRIFFWAVEFMT_MAGIC as magic. */ + RTRIFFCHUNK Chunk; + /** The wave file format. */ + RTRIFFWAVEFMTEXT Data; +} RTRIFFWAVEFMTEXTCHUNK; +AssertCompileSize(RTRIFFWAVEFMTEXTCHUNK, 8+16+2+22); +/** Pointer to a wave file format chunk. */ +typedef RTRIFFWAVEFMTEXTCHUNK *PRTRIFFWAVEFMTEXTCHUNK; + + +/** + * Wave file data chunk. + */ +typedef struct RTRIFFWAVEDATACHUNK +{ + /** Chunk header with RTRIFFWAVEFMT_MAGIC as magic. */ + RTRIFFCHUNK Chunk; + /** Variable sized sample data. */ + uint8_t abData[RT_FLEXIBLE_ARRAY_IN_NESTED_UNION]; +} RTRIFFWAVEDATACHUNK; + +/** Magic value for RTRIFFWAVEFMT::uMagic ('data'). */ +#define RTRIFFWAVEDATACHUNK_MAGIC RT_BE2H_U32_C(0x64617461) + + +/** Magic value padding chunks ('PAD '). */ +#define RTRIFFPADCHUNK_MAGIC RT_BE2H_U32_C(0x50414420) + +/** @} */ + +#endif /* !IPRT_INCLUDED_formats_riff_h */ + diff -Nru virtualbox-6.1.16-dfsg/include/iprt/formats/udf.h virtualbox-6.1.38-dfsg/include/iprt/formats/udf.h --- virtualbox-6.1.16-dfsg/include/iprt/formats/udf.h 2020-10-16 16:27:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/formats/udf.h 2022-09-01 13:17:52.000000000 +0000 @@ -791,6 +791,7 @@ UDFEXTENTAD IntegritySeqExtent; /** 0x1b8: Partition maps (length given by @a cbMapTable), data format is * defined by UDFPARTMAPHDR, UDFPARTMAPTYPE1 and UDFPARTMAPTYPE2. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abPartitionMaps[RT_FLEXIBLE_ARRAY]; } UDFLOGICALVOLUMEDESC; AssertCompileMemberOffset(UDFLOGICALVOLUMEDESC, abPartitionMaps, 0x1b8); @@ -924,6 +925,7 @@ /** 0x14: Number of allocation descriptors in the array below. */ uint32_t cAllocationDescriptors; /** 0x18: Allocation descriptors (variable length). */ + RT_FLEXIBLE_ARRAY_EXTENSION UDFEXTENTAD aAllocationDescriptors[RT_FLEXIBLE_ARRAY]; } UDFUNALLOCATEDSPACEDESC; AssertCompileMemberOffset(UDFUNALLOCATEDSPACEDESC, aAllocationDescriptors, 0x18); @@ -974,6 +976,7 @@ * Following these tables there are @a cbImplementationUse bytes of space for * the implementation to use. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint32_t aTables[RT_FLEXIBLE_ARRAY]; } UDFLOGICALVOLINTEGRITYDESC; AssertCompileMemberOffset(UDFLOGICALVOLINTEGRITYDESC, cbImplementationUse, 0x2c); @@ -1074,6 +1077,7 @@ * implementation use field with length given by @a cbImplementationUse. * After that is a d-string field with the name of the file, length * specified by @a cbName. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abImplementationUse[RT_FLEXIBLE_ARRAY]; } UDFFILEIDDESC; AssertCompileMemberOffset(UDFFILEIDDESC, fFlags, 0x12); @@ -1357,6 +1361,7 @@ uint32_t cbAllocDescs; /** 0xb0: Two variable sized fields. First @a cbExtAttribs bytes of extended * attributes, then @a cbAllocDescs bytes of allocation descriptors. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abExtAttribs[RT_FLEXIBLE_ARRAY]; } UDFFILEENTRY; AssertCompileMemberOffset(UDFFILEENTRY, abExtAttribs, 0xb0); @@ -1970,6 +1975,7 @@ /** 0x14: The bitmap size in bytes. */ uint32_t cbBitmap; /** 0x18: The bitmap. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abBitmap[RT_FLEXIBLE_ARRAY]; } UDFSPACEBITMAPDESC; AssertCompileMemberOffset(UDFSPACEBITMAPDESC, abBitmap, 0x18); @@ -1999,6 +2005,7 @@ /** 0x0e0: Implementation identifier. */ UDFENTITYID idImplementation; /** 0x100: Implementation use data. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abImplementationUse[RT_FLEXIBLE_ARRAY]; } UDFPARTITIONINTEGRITYDESC; AssertCompileMemberOffset(UDFPARTITIONINTEGRITYDESC, abImplementationUse, 0x100); @@ -2066,6 +2073,7 @@ uint32_t cbAllocDescs; /** 0xd8: Two variable sized fields. First @a cbExtAttribs bytes of extended * attributes, then @a cbAllocDescs bytes of allocation descriptors. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abExtAttribs[RT_FLEXIBLE_ARRAY]; } UDFEXFILEENTRY; AssertCompileMemberOffset(UDFEXFILEENTRY, abExtAttribs, 0xd8); diff -Nru virtualbox-6.1.16-dfsg/include/iprt/fsisomaker.h virtualbox-6.1.38-dfsg/include/iprt/fsisomaker.h --- virtualbox-6.1.16-dfsg/include/iprt/fsisomaker.h 2020-10-16 16:27:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/fsisomaker.h 2022-09-01 13:17:52.000000000 +0000 @@ -130,6 +130,24 @@ RTDECL(int) RTFsIsoMakerSetJolietRockRidgeLevel(RTFSISOMAKER hIsoMaker, uint8_t uLevel); /** + * Gets the rock ridge support level (on the primary ISO-9660 namespace). + * + * @returns 0 if disabled, 1 just enabled, 2 if enabled with ER tag, and + * UINT8_MAX if the handle is invalid. + * @param hIsoMaker The ISO maker handle. + */ +RTDECL(uint8_t) RTFsIsoMakerGetRockRidgeLevel(RTFSISOMAKER hIsoMaker); + +/** + * Gets the rock ridge support level on the joliet namespace (experimental). + * + * @returns 0 if disabled, 1 just enabled, 2 if enabled with ER tag, and + * UINT8_MAX if the handle is invalid. + * @param hIsoMaker The ISO maker handle. + */ +RTDECL(uint8_t) RTFsIsoMakerGetJolietRockRidgeLevel(RTFSISOMAKER hIsoMaker); + +/** * Changes the file attribute (mode, owner, group) inherit style (from source). * * The strict style will use the exact attributes from the source, where as the @@ -256,6 +274,14 @@ RTDECL(int) RTFsIsoMakerSetImagePadding(RTFSISOMAKER hIsoMaker, uint32_t cSectors); /** + * Gets currently populated namespaces. + * + * @returns Set of namespaces (RTFSISOMAKER_NAMESPACE_XXX), UINT32_MAX on error. + * @param hIsoMaker The ISO maker handle. + */ +RTDECL(uint32_t) RTFsIsoMakerGetPopulatedNamespaces(RTFSISOMAKER hIsoMaker); + +/** * Resolves a path into a object ID. * * This will be doing the looking up using the specified object names rather diff -Nru virtualbox-6.1.16-dfsg/include/iprt/ldr.h virtualbox-6.1.38-dfsg/include/iprt/ldr.h --- virtualbox-6.1.16-dfsg/include/iprt/ldr.h 2020-10-16 16:27:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/ldr.h 2022-09-01 13:17:52.000000000 +0000 @@ -1104,6 +1104,18 @@ * @remarks This generally starts with a PKCS \#7 Content structure, the * SignedData bit is found a few levels down into this as per RFC. */ RTLDRPROP_PKCS7_SIGNED_DATA, + /** Query the number of pages that needs hashing. + * This is for RTLDRPROP_SHA1_PAGE_HASHES and RTLDRPROP_SHA256_PAGE_HASHES + * buffer size calculations. */ + RTLDRPROP_HASHABLE_PAGES, + /** Query the SHA-1 page hashes. + * Returns an array with entries made of a 32-bit file offset and a SHA-1 + * digest. Use RTLDRPROP_HASHABLE_PAGES to calculate the buffer size. */ + RTLDRPROP_SHA1_PAGE_HASHES, + /** Query the SHA-256 page hashes. + * Returns an array with entries made of a 32-bit file offset and a SHA-256 + * digest. Use RTLDRPROP_HASHABLE_PAGES to calculate the buffer size. */ + RTLDRPROP_SHA256_PAGE_HASHES, /** Query whether code signature checks are enabled. */ RTLDRPROP_SIGNATURE_CHECKS_ENFORCED, @@ -1277,10 +1289,12 @@ * @returns IPRT status code. * @param hLdrMod The module handle. * @param enmDigest Which kind of digest. - * @param pszDigest Where to store the image digest. - * @param cbDigest Size of the buffer @a pszDigest points at. + * @param pabHash Where to store the image hash. + * @param cbHash Size of the buffer @a pabHash points at. The + * required and returned size can be derived from the + * digest type (@a enmDigest). */ -RTDECL(int) RTLdrHashImage(RTLDRMOD hLdrMod, RTDIGESTTYPE enmDigest, char *pszDigest, size_t cbDigest); +RTDECL(int) RTLdrHashImage(RTLDRMOD hLdrMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash); /** * Try use unwind information to unwind one frame. diff -Nru virtualbox-6.1.16-dfsg/include/iprt/linux/version.h virtualbox-6.1.38-dfsg/include/iprt/linux/version.h --- virtualbox-6.1.16-dfsg/include/iprt/linux/version.h 2020-10-16 16:27:57.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/linux/version.h 2022-09-01 13:17:52.000000000 +0000 @@ -32,6 +32,15 @@ #include +/* We need utsrelease.h in order to detect Ubuntu kernel, + * i.e. check if UTS_UBUNTU_RELEASE_ABI is defined. Support kernels + * starting from Ubuntu 14.04 Trusty which is based on upstream + * kernel 3.13.x. */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0)) +# include +# include +#endif + /** @def RTLNX_VER_MIN * Evaluates to true if the linux kernel version is equal or higher to the * one specfied. */ @@ -115,5 +124,72 @@ #endif +#if defined(UTS_UBUNTU_RELEASE_ABI) || defined(DOXYGEN_RUNNING) + +/** Hack to make the UTS_UBUNTU_RELEASE_ABI palatable by the C preprocesor. + * + * While the Ubuntu kernel ABI version looks like a decimal number, some + * kernels has a leading zero (e.g. 050818) that makes the preprocessor think + * it's an octal number. To work around that, we turn it into an hexadecimal + * number by prefixing it with '0x'. */ +# define RTLNX_UBUNTU_ABI(a_iAbi) (RT_CONCAT(0x,a_iAbi)) + +/** @def RTLNX_UBUNTU_ABI_MIN + * Require Ubuntu release ABI to be equal or newer than specified version. + * + * The kernel version should exactly match the specified @a a_iMajor, @a + * a_iMinor and @a a_iPatch. The @a a_iAbi number should be equal to or greater + * than the current ABI version. + * + * @param a_iMajor The major kernel version number. + * @param a_iMinor The minor kernel version number. + * @param a_iPatch The kernel patch level. + * @param a_iAbi Ubuntu kernel ABI version number (inclusive). + */ +# define RTLNX_UBUNTU_ABI_MIN(a_iMajor, a_iMinor, a_iPatch, a_iAbi) \ + ( KERNEL_VERSION(a_iMajor, a_iMinor, a_iPatch) == LINUX_VERSION_CODE \ + && RTLNX_UBUNTU_ABI(UTS_UBUNTU_RELEASE_ABI) >= RTLNX_UBUNTU_ABI(a_iAbi)) + +/** @def RTLNX_UBUNTU_ABI_MAX + * Require Ubuntu release ABI to be older than specified version. + * + * The kernel version should exactly match the specified @a a_iMajor, @a + * a_iMinor and @a a_iPatch. The @a a_iAbi number should be less than the + * current ABI version. + * + * @param a_iMajor The major kernel version number. + * @param a_iMinor The minor kernel version number. + * @param a_iPatch The kernel patch level. + * @param a_iAbi Ubuntu kernel ABI version number (exclusive). + */ +# define RTLNX_UBUNTU_ABI_MAX(a_iMajor, a_iMinor, a_iPatch, a_iAbi) \ + ( KERNEL_VERSION(a_iMajor, a_iMinor, a_iPatch) == LINUX_VERSION_CODE \ + && RTLNX_UBUNTU_ABI(UTS_UBUNTU_RELEASE_ABI) < RTLNX_UBUNTU_ABI(a_iAbi)) + +/** @def RTLNX_UBUNTU_ABI_RANGE + * Require Ubuntu release ABI to be in specified range. + * + * The kernel version should exactly match the specified @a a_iMajor, @a + * a_iMinor and @a a_iPatch. The numbers @a a_iAbiMin and @a a_iAbiMax specify + * ABI versions range. The max ABI version is exclusive, the minimum inclusive. + * + * @param a_iMajor The major kernel version number. + * @param a_iMinor The minor kernel version number. + * @param a_iPatch The kernel patch level. + * @param a_iAbiMin The minimum Ubuntu kernel ABI version number (inclusive). + * @param a_iAbiMax The maximum Ubuntu kernel ABI version number (exclusive). + */ +# define RTLNX_UBUNTU_ABI_RANGE(a_iMajor, a_iMinor, a_iPatch, a_iAbiMin, a_iAbiMax) \ + ( RTLNX_UBUNTU_ABI_MIN(a_iMajor, a_iMinor, a_iPatch, a_iAbiMin) \ + && RTLNX_UBUNTU_ABI_MAX(a_iMajor, a_iMinor, a_iPatch, a_iAbiMax)) + +#else /* !UTS_UBUNTU_RELEASE_ABI */ + +# define RTLNX_UBUNTU_ABI_MIN(a_iMajor, a_iMinor, a_iPatch, a_iAbi) (0) +# define RTLNX_UBUNTU_ABI_MAX(a_iMajor, a_iMinor, a_iPatch, a_iAbi) (0) +# define RTLNX_UBUNTU_ABI_RANGE(a_iMajorMin, a_iMinorMin, a_iPatchMin, a_iAbiMin, a_iAbiMax) (0) + +#endif /* !UTS_UBUNTU_RELEASE_ABI */ + #endif /* !IPRT_INCLUDED_linux_version_h */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/mangling.h virtualbox-6.1.38-dfsg/include/iprt/mangling.h --- virtualbox-6.1.16-dfsg/include/iprt/mangling.h 2020-10-16 16:27:57.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/mangling.h 2022-09-01 13:17:52.000000000 +0000 @@ -926,6 +926,7 @@ # define RTFileCopyPartCleanup RT_MANGLER(RTFileCopyPartCleanup) # define RTFileCopyPartEx RT_MANGLER(RTFileCopyPartEx) # define RTFileCopyPartPrep RT_MANGLER(RTFileCopyPartPrep) +# define RTFileCreateUnique RT_MANGLER(RTFileCreateUnique) # define RTFileCreateTemp RT_MANGLER(RTFileCreateTemp) # define RTFileCreateTempSecure RT_MANGLER(RTFileCreateTempSecure) # define RTFileDelete RT_MANGLER(RTFileDelete) @@ -995,7 +996,10 @@ # define RTFsIsoMakerBootCatSetSectionEntry RT_MANGLER(RTFsIsoMakerBootCatSetSectionEntry) # define RTFsIsoMakerBootCatSetSectionHeaderEntry RT_MANGLER(RTFsIsoMakerBootCatSetSectionHeaderEntry) # define RTFsIsoMakerQueryObjIdxForBootCatalog RT_MANGLER(RTFsIsoMakerQueryObjIdxForBootCatalog) +# define RTFsIsoMakerGetPopulatedNamespaces RT_MANGLER(RTFsIsoMakerGetPopulatedNamespaces) # define RTFsIsoMakerGetIso9660Level RT_MANGLER(RTFsIsoMakerGetIso9660Level) +# define RTFsIsoMakerGetRockRidgeLevel RT_MANGLER(RTFsIsoMakerGetRockRidgeLevel) +# define RTFsIsoMakerGetJolietRockRidgeLevel RT_MANGLER(RTFsIsoMakerGetJolietRockRidgeLevel) # define RTFsIsoMakerSetImagePadding RT_MANGLER(RTFsIsoMakerSetImagePadding) # define RTFsIsoMakerSetIso9660Level RT_MANGLER(RTFsIsoMakerSetIso9660Level) # define RTFsIsoMakerSetJolietUcs2Level RT_MANGLER(RTFsIsoMakerSetJolietUcs2Level) @@ -1401,6 +1405,7 @@ # define RTLogSetDefaultInstance RT_MANGLER(RTLogSetDefaultInstance) # define RTLogSetDefaultInstanceThread RT_MANGLER(RTLogSetDefaultInstanceThread) /* r0drv */ # define RTLogSetGroupLimit RT_MANGLER(RTLogSetGroupLimit) +# define RTLogSetR0ThreadNameV RT_MANGLER(RTLogSetR0ThreadNameV) /* r0drv */ # define RTLogWriteCom RT_MANGLER(RTLogWriteCom) # define RTLogWriteCom RT_MANGLER(RTLogWriteCom) # define RTLogWriteDebugger RT_MANGLER(RTLogWriteDebugger) @@ -1607,22 +1612,23 @@ # define RTNetIPv6PseudoChecksum RT_MANGLER(RTNetIPv6PseudoChecksum) # define RTNetIPv6PseudoChecksumBits RT_MANGLER(RTNetIPv6PseudoChecksumBits) # define RTNetIPv6PseudoChecksumEx RT_MANGLER(RTNetIPv6PseudoChecksumEx) +# define RTNetIsIPv4AddrStr RT_MANGLER(RTNetIsIPv4AddrStr) +# define RTNetIsIPv6AddrStr RT_MANGLER(RTNetIsIPv6AddrStr) # define RTNetMaskToPrefixIPv4 RT_MANGLER(RTNetMaskToPrefixIPv4) +# define RTNetMaskToPrefixIPv6 RT_MANGLER(RTNetMaskToPrefixIPv6) # define RTNetPrefixToMaskIPv4 RT_MANGLER(RTNetPrefixToMaskIPv4) -# define RTNetTCPChecksum RT_MANGLER(RTNetTCPChecksum) -# define RTNetUDPChecksum RT_MANGLER(RTNetUDPChecksum) -# define RTNetStrToMacAddr RT_MANGLER(RTNetStrToMacAddr) -# define RTNetIsIPv4AddrStr RT_MANGLER(RTNetIsIPv4AddrStr) +# define RTNetPrefixToMaskIPv6 RT_MANGLER(RTNetPrefixToMaskIPv6) # define RTNetStrIsIPv4AddrAny RT_MANGLER(RTNetStrIsIPv4AddrAny) -# define RTNetStrToIPv4AddrEx RT_MANGLER(RTNetStrToIPv4AddrEx) +# define RTNetStrIsIPv6AddrAny RT_MANGLER(RTNetStrIsIPv6AddrAny) # define RTNetStrToIPv4Addr RT_MANGLER(RTNetStrToIPv4Addr) +# define RTNetStrToIPv4AddrEx RT_MANGLER(RTNetStrToIPv4AddrEx) # define RTNetStrToIPv4Cidr RT_MANGLER(RTNetStrToIPv4Cidr) -# define RTNetIsIPv6AddrStr RT_MANGLER(RTNetIsIPv6AddrStr) -# define RTNetStrIsIPv6AddrAny RT_MANGLER(RTNetStrIsIPv6AddrAny) -# define RTNetStrToIPv6AddrEx RT_MANGLER(RTNetStrToIPv6AddrEx) # define RTNetStrToIPv6Addr RT_MANGLER(RTNetStrToIPv6Addr) -# define RTNetMaskToPrefixIPv6 RT_MANGLER(RTNetMaskToPrefixIPv6) -# define RTNetPrefixToMaskIPv6 RT_MANGLER(RTNetPrefixToMaskIPv6) +# define RTNetStrToIPv6AddrEx RT_MANGLER(RTNetStrToIPv6AddrEx) +# define RTNetStrToIPv6Cidr RT_MANGLER(RTNetStrToIPv6Cidr) +# define RTNetStrToMacAddr RT_MANGLER(RTNetStrToMacAddr) +# define RTNetTCPChecksum RT_MANGLER(RTNetTCPChecksum) +# define RTNetUDPChecksum RT_MANGLER(RTNetUDPChecksum) # define RTOnceSlow RT_MANGLER(RTOnceSlow) # define RTOnceReset RT_MANGLER(RTOnceReset) # define RTPathAbs RT_MANGLER(RTPathAbs) @@ -1760,6 +1766,7 @@ # define RTR0MemKernelCopyFrom RT_MANGLER(RTR0MemKernelCopyFrom) /* r0drv */ # define RTR0MemKernelCopyTo RT_MANGLER(RTR0MemKernelCopyTo) /* r0drv */ # define RTR0MemObjAllocContTag RT_MANGLER(RTR0MemObjAllocContTag) /* r0drv */ +# define RTR0MemObjAllocLargeTag RT_MANGLER(RTR0MemObjAllocLargeTag) /* r0drv */ # define RTR0MemObjAllocLowTag RT_MANGLER(RTR0MemObjAllocLowTag) /* r0drv */ # define RTR0MemObjAllocPageTag RT_MANGLER(RTR0MemObjAllocPageTag) /* r0drv */ # define RTR0MemObjAllocPhysExTag RT_MANGLER(RTR0MemObjAllocPhysExTag) /* r0drv */ @@ -1844,9 +1851,10 @@ # define RTReqQueueDestroy RT_MANGLER(RTReqQueueDestroy) # define RTReqQueueIsBusy RT_MANGLER(RTReqQueueIsBusy) # define RTReqQueueProcess RT_MANGLER(RTReqQueueProcess) -# define RTReqSubmit RT_MANGLER(RTReqSubmit) +# define RTReqCancel RT_MANGLER(RTReqCancel) # define RTReqRelease RT_MANGLER(RTReqRelease) # define RTReqRetain RT_MANGLER(RTReqRetain) +# define RTReqSubmit RT_MANGLER(RTReqSubmit) # define RTReqWait RT_MANGLER(RTReqWait) # define RTReqGetStatus RT_MANGLER(RTReqGetStatus) # define RTS3BucketsDestroy RT_MANGLER(RTS3BucketsDestroy) @@ -2020,6 +2028,42 @@ # define RTSha512t256Init RT_MANGLER(RTSha512t256Init) # define RTSha512t256ToString RT_MANGLER(RTSha512t256ToString) # define RTSha512t256Update RT_MANGLER(RTSha512t256Update) +# define RTSha3t224 RT_MANGLER(RTSha3t224) +# define RTSha3t224Check RT_MANGLER(RTSha3t224Check) +# define RTSha3t224Cleanup RT_MANGLER(RTSha3t224Cleanup) +# define RTSha3t224Clone RT_MANGLER(RTSha3t224Clone) +# define RTSha3t224Init RT_MANGLER(RTSha3t224Init) +# define RTSha3t224Final RT_MANGLER(RTSha3t224Final) +# define RTSha3t224FromString RT_MANGLER(RTSha3t224FromString) +# define RTSha3t224ToString RT_MANGLER(RTSha3t224ToString) +# define RTSha3t224Update RT_MANGLER(RTSha3t224Update) +# define RTSha3t256 RT_MANGLER(RTSha3t256) +# define RTSha3t256Check RT_MANGLER(RTSha3t256Check) +# define RTSha3t256Cleanup RT_MANGLER(RTSha3t256Cleanup) +# define RTSha3t256Clone RT_MANGLER(RTSha3t256Clone) +# define RTSha3t256Init RT_MANGLER(RTSha3t256Init) +# define RTSha3t256Final RT_MANGLER(RTSha3t256Final) +# define RTSha3t256FromString RT_MANGLER(RTSha3t256FromString) +# define RTSha3t256ToString RT_MANGLER(RTSha3t256ToString) +# define RTSha3t256Update RT_MANGLER(RTSha3t256Update) +# define RTSha3t384 RT_MANGLER(RTSha3t384) +# define RTSha3t384Check RT_MANGLER(RTSha3t384Check) +# define RTSha3t384Cleanup RT_MANGLER(RTSha3t384Cleanup) +# define RTSha3t384Clone RT_MANGLER(RTSha3t384Clone) +# define RTSha3t384Init RT_MANGLER(RTSha3t384Init) +# define RTSha3t384Final RT_MANGLER(RTSha3t384Final) +# define RTSha3t384FromString RT_MANGLER(RTSha3t384FromString) +# define RTSha3t384ToString RT_MANGLER(RTSha3t384ToString) +# define RTSha3t384Update RT_MANGLER(RTSha3t384Update) +# define RTSha3t512 RT_MANGLER(RTSha3t512) +# define RTSha3t512Check RT_MANGLER(RTSha3t512Check) +# define RTSha3t512Cleanup RT_MANGLER(RTSha3t512Cleanup) +# define RTSha3t512Clone RT_MANGLER(RTSha3t512Clone) +# define RTSha3t512Init RT_MANGLER(RTSha3t512Init) +# define RTSha3t512Final RT_MANGLER(RTSha3t512Final) +# define RTSha3t512FromString RT_MANGLER(RTSha3t512FromString) +# define RTSha3t512ToString RT_MANGLER(RTSha3t512ToString) +# define RTSha3t512Update RT_MANGLER(RTSha3t512Update) # define RTShMemClose RT_MANGLER(RTShMemClose) # define RTShMemDelete RT_MANGLER(RTShMemDelete) # define RTShMemMapRegion RT_MANGLER(RTShMemMapRegion) @@ -2150,6 +2194,8 @@ # define RTStrmOpenFV RT_MANGLER(RTStrmOpenFV) # define RTStrmPrintf RT_MANGLER(RTStrmPrintf) # define RTStrmPrintfV RT_MANGLER(RTStrmPrintfV) +# define RTStrmWrappedPrintf RT_MANGLER(RTStrmWrappedPrintf) +# define RTStrmWrappedPrintfV RT_MANGLER(RTStrmWrappedPrintfV) # define RTStrmDumpPrintfV RT_MANGLER(RTStrmDumpPrintfV) # define RTStrmPutCh RT_MANGLER(RTStrmPutCh) # define RTStrmPutStr RT_MANGLER(RTStrmPutStr) @@ -2299,6 +2345,8 @@ # define RTTestCreateEx RT_MANGLER(RTTestCreateEx) # define RTTestDestroy RT_MANGLER(RTTestDestroy) # define RTTestDisableAssertions RT_MANGLER(RTTestDisableAssertions) +# define RTTestErrContext RT_MANGLER(RTTestErrContext) +# define RTTestErrContextV RT_MANGLER(RTTestErrContextV) # define RTTestErrorCount RT_MANGLER(RTTestErrorCount) # define RTTestErrorInc RT_MANGLER(RTTestErrorInc) # define RTTestFailed RT_MANGLER(RTTestFailed) @@ -2310,6 +2358,8 @@ # define RTTestGuardedAllocTail RT_MANGLER(RTTestGuardedAllocTail) # define RTTestGuardedFree RT_MANGLER(RTTestGuardedFree) # define RTTestIDisableAssertions RT_MANGLER(RTTestIDisableAssertions) +# define RTTestIErrContext RT_MANGLER(RTTestIErrContext) +# define RTTestIErrContextV RT_MANGLER(RTTestIErrContextV) # define RTTestIErrorCount RT_MANGLER(RTTestIErrorCount) # define RTTestIErrorInc RT_MANGLER(RTTestIErrorInc) # define RTTestIFailed RT_MANGLER(RTTestIFailed) @@ -2688,6 +2738,8 @@ # define RTVfsFileOpen RT_MANGLER(RTVfsFileOpen) # define RTVfsFileOpenNormal RT_MANGLER(RTVfsFileOpenNormal) # define RTVfsFilePoll RT_MANGLER(RTVfsFilePoll) +# define RTVfsFilePrintf RT_MANGLER(RTVfsFilePrintf) +# define RTVfsFilePrintfV RT_MANGLER(RTVfsFilePrintfV) # define RTVfsFileQueryInfo RT_MANGLER(RTVfsFileQueryInfo) # define RTVfsFileQueryMaxSize RT_MANGLER(RTVfsFileQueryMaxSize) # define RTVfsFileRead RT_MANGLER(RTVfsFileRead) @@ -2725,6 +2777,8 @@ # define RTVfsIoStrmIsAtEnd RT_MANGLER(RTVfsIoStrmIsAtEnd) # define RTVfsIoStrmOpenNormal RT_MANGLER(RTVfsIoStrmOpenNormal) # define RTVfsIoStrmPoll RT_MANGLER(RTVfsIoStrmPoll) +# define RTVfsIoStrmPrintf RT_MANGLER(RTVfsIoStrmPrintf) +# define RTVfsIoStrmPrintfV RT_MANGLER(RTVfsIoStrmPrintfV) # define RTVfsIoStrmQueryInfo RT_MANGLER(RTVfsIoStrmQueryInfo) # define RTVfsIoStrmRead RT_MANGLER(RTVfsIoStrmRead) # define RTVfsIoStrmReadAt RT_MANGLER(RTVfsIoStrmReadAt) @@ -2736,6 +2790,7 @@ # define RTVfsIoStrmSgRead RT_MANGLER(RTVfsIoStrmSgRead) # define RTVfsIoStrmSgWrite RT_MANGLER(RTVfsIoStrmSgWrite) # define RTVfsIoStrmSkip RT_MANGLER(RTVfsIoStrmSkip) +# define RTVfsIoStrmStrOutputCallback RT_MANGLER(RTVfsIoStrmStrOutputCallback) # define RTVfsIoStrmTell RT_MANGLER(RTVfsIoStrmTell) # define RTVfsIoStrmToFile RT_MANGLER(RTVfsIoStrmToFile) # define RTVfsIoStrmValidateUtf8Encoding RT_MANGLER(RTVfsIoStrmValidateUtf8Encoding) @@ -2897,6 +2952,7 @@ # define RTAsn1EncodePrepare RT_MANGLER(RTAsn1EncodePrepare) # define RTAsn1EncodeRecalcHdrSize RT_MANGLER(RTAsn1EncodeRecalcHdrSize) # define RTAsn1EncodeToBuffer RT_MANGLER(RTAsn1EncodeToBuffer) +# define RTAsn1EncodeQueryRawBits RT_MANGLER(RTAsn1EncodeQueryRawBits) # define RTAsn1EncodeWrite RT_MANGLER(RTAsn1EncodeWrite) # define RTAsn1EncodeWriteHeader RT_MANGLER(RTAsn1EncodeWriteHeader) # define RTAsn1BitString_CheckSanity RT_MANGLER(RTAsn1BitString_CheckSanity) @@ -2906,6 +2962,7 @@ # define RTAsn1BitString_Enum RT_MANGLER(RTAsn1BitString_Enum) # define RTAsn1BitString_GetAsUInt64 RT_MANGLER(RTAsn1BitString_GetAsUInt64) # define RTAsn1BitString_Init RT_MANGLER(RTAsn1BitString_Init) +# define RTAsn1BitString_InitWithData RT_MANGLER(RTAsn1BitString_InitWithData) # define RTAsn1BitString_AreContentBitsValid RT_MANGLER(RTAsn1BitString_AreContentBitsValid) # define RTAsn1BitString_RefreshContent RT_MANGLER(RTAsn1BitString_RefreshContent) # define RTAsn1SeqOfBitStrings_CheckSanity RT_MANGLER(RTAsn1SeqOfBitStrings_CheckSanity) @@ -2976,6 +3033,7 @@ # define RTAsn1Core_DecodeAsn1 RT_MANGLER(RTAsn1Core_DecodeAsn1) # define RTAsn1SeqOfCores_DecodeAsn1 RT_MANGLER(RTAsn1SeqOfCores_DecodeAsn1) # define RTAsn1SetOfCores_DecodeAsn1 RT_MANGLER(RTAsn1SetOfCores_DecodeAsn1) +# define RTAsn1DynType_SetToNull RT_MANGLER(RTAsn1DynType_SetToNull) # define RTAsn1DynType_CheckSanity RT_MANGLER(RTAsn1DynType_CheckSanity) # define RTAsn1DynType_Clone RT_MANGLER(RTAsn1DynType_Clone) # define RTAsn1DynType_Compare RT_MANGLER(RTAsn1DynType_Compare) @@ -3031,6 +3089,7 @@ # define RTAsn1ObjId_Enum RT_MANGLER(RTAsn1ObjId_Enum) # define RTAsn1ObjId_Init RT_MANGLER(RTAsn1ObjId_Init) # define RTAsn1ObjId_InitFromString RT_MANGLER(RTAsn1ObjId_InitFromString) +# define RTAsn1ObjId_SetFromString RT_MANGLER(RTAsn1ObjId_SetFromString) # define RTAsn1ObjId_StartsWith RT_MANGLER(RTAsn1ObjId_StartsWith) # define RTAsn1SeqOfObjIds_CheckSanity RT_MANGLER(RTAsn1SeqOfObjIds_CheckSanity) # define RTAsn1SeqOfObjIds_Clone RT_MANGLER(RTAsn1SeqOfObjIds_Clone) @@ -3060,6 +3119,8 @@ # define RTAsn1ObjId_DecodeAsn1 RT_MANGLER(RTAsn1ObjId_DecodeAsn1) # define RTAsn1SeqOfObjIds_DecodeAsn1 RT_MANGLER(RTAsn1SeqOfObjIds_DecodeAsn1) # define RTAsn1SetOfObjIds_DecodeAsn1 RT_MANGLER(RTAsn1SetOfObjIds_DecodeAsn1) +# define RTAsn1OctetString_AllocContent RT_MANGLER(RTAsn1OctetString_AllocContent) +# define RTAsn1OctetString_SetContent RT_MANGLER(RTAsn1OctetString_SetContent) # define RTAsn1OctetString_CheckSanity RT_MANGLER(RTAsn1OctetString_CheckSanity) # define RTAsn1OctetString_Clone RT_MANGLER(RTAsn1OctetString_Clone) # define RTAsn1OctetString_Compare RT_MANGLER(RTAsn1OctetString_Compare) @@ -3208,6 +3269,9 @@ # define RTAsn1Time_Enum RT_MANGLER(RTAsn1Time_Enum) # define RTAsn1Time_Init RT_MANGLER(RTAsn1Time_Init) # define RTAsn1Time_InitEx RT_MANGLER(RTAsn1Time_InitEx) +# define RTAsn1Time_InitWithTime RT_MANGLER(RTAsn1Time_InitWithTime) +# define RTAsn1Time_SetTime RT_MANGLER(RTAsn1Time_SetTime) +# define RTAsn1Time_SetTimeSpec RT_MANGLER(RTAsn1Time_SetTimeSpec) # define RTAsn1UtcTime_CheckSanity RT_MANGLER(RTAsn1UtcTime_CheckSanity) # define RTAsn1UtcTime_Clone RT_MANGLER(RTAsn1UtcTime_Clone) # define RTAsn1UtcTime_Compare RT_MANGLER(RTAsn1UtcTime_Compare) @@ -3308,18 +3372,40 @@ # define RTCrRsaOtherPrimeInfo_CheckSanity RT_MANGLER(RTCrRsaOtherPrimeInfo_CheckSanity) # define RTCrRsaOtherPrimeInfos_CheckSanity RT_MANGLER(RTCrRsaOtherPrimeInfos_CheckSanity) # define RTCrRsaPrivateKey_CheckSanity RT_MANGLER(RTCrRsaPrivateKey_CheckSanity) +# define RTCrRsaPrivateKey_CanHandleDigestType RT_MANGLER(RTCrRsaPrivateKey_CanHandleDigestType) # define RTCrRsaPublicKey_CheckSanity RT_MANGLER(RTCrRsaPublicKey_CheckSanity) +# define RTCrRsaPublicKey_CanHandleDigestType RT_MANGLER(RTCrRsaPublicKey_CanHandleDigestType) # define RTCrPemFindFirstSectionInContent RT_MANGLER(RTCrPemFindFirstSectionInContent) # define RTCrPemFreeSections RT_MANGLER(RTCrPemFreeSections) # define RTCrPemParseContent RT_MANGLER(RTCrPemParseContent) # define RTCrPemReadFile RT_MANGLER(RTCrPemReadFile) +# define RTCrPemWriteBlob RT_MANGLER(RTCrPemWriteBlob) +# define RTCrPemWriteBlobToVfsIoStrm RT_MANGLER(RTCrPemWriteBlobToVfsIoStrm) +# define RTCrPemWriteBlobToVfsFile RT_MANGLER(RTCrPemWriteBlobToVfsFile) +# define RTCrPemWriteAsn1 RT_MANGLER(RTCrPemWriteAsn1) +# define RTCrPemWriteAsn1ToVfsIoStrm RT_MANGLER(RTCrPemWriteAsn1ToVfsIoStrm) +# define RTCrPemWriteAsn1ToVfsFile RT_MANGLER(RTCrPemWriteAsn1ToVfsFile) # define RTCrPkcs5Pbkdf2Hmac RT_MANGLER(RTCrPkcs5Pbkdf2Hmac) +# define RTCrPkcs7_ReadFromBuffer RT_MANGLER(RTCrPkcs7_ReadFromBuffer) +# define RTCrPkcs7Attribute_SetAppleMultiCdPlist RT_MANGLER(RTCrPkcs7Attribute_SetAppleMultiCdPlist) +# define RTCrPkcs7Attribute_SetContentType RT_MANGLER(RTCrPkcs7Attribute_SetContentType) +# define RTCrPkcs7Attribute_SetCounterSignatures RT_MANGLER(RTCrPkcs7Attribute_SetCounterSignatures) +# define RTCrPkcs7Attribute_SetMessageDigest RT_MANGLER(RTCrPkcs7Attribute_SetMessageDigest) +# define RTCrPkcs7Attribute_SetMsStatementType RT_MANGLER(RTCrPkcs7Attribute_SetMsStatementType) +# define RTCrPkcs7Attribute_SetMsNestedSignature RT_MANGLER(RTCrPkcs7Attribute_SetMsNestedSignature) +# define RTCrPkcs7Attribute_SetMsTimestamp RT_MANGLER(RTCrPkcs7Attribute_SetMsTimestamp) +# define RTCrPkcs7Attribute_SetSigningTime RT_MANGLER(RTCrPkcs7Attribute_SetSigningTime) +# define RTCrPkcs7Attributes_HashAttributes RT_MANGLER(RTCrPkcs7Attributes_HashAttributes) # define RTCrPkcs7Attribute_DecodeAsn1 RT_MANGLER(RTCrPkcs7Attribute_DecodeAsn1) # define RTCrPkcs7Attributes_DecodeAsn1 RT_MANGLER(RTCrPkcs7Attributes_DecodeAsn1) # define RTCrPkcs7ContentInfo_DecodeAsn1 RT_MANGLER(RTCrPkcs7ContentInfo_DecodeAsn1) # define RTCrPkcs7DigestInfo_DecodeAsn1 RT_MANGLER(RTCrPkcs7DigestInfo_DecodeAsn1) # define RTCrPkcs7IssuerAndSerialNumber_DecodeAsn1 RT_MANGLER(RTCrPkcs7IssuerAndSerialNumber_DecodeAsn1) +# define RTCrPkcs7SignedData_SetCertificates RT_MANGLER(RTCrPkcs7SignedData_SetCertificates) +# define RTCrPkcs7SignedData_SetCrls RT_MANGLER(RTCrPkcs7SignedData_SetCrls) # define RTCrPkcs7SignedData_DecodeAsn1 RT_MANGLER(RTCrPkcs7SignedData_DecodeAsn1) +# define RTCrPkcs7SignerInfo_SetAuthenticatedAttributes RT_MANGLER(RTCrPkcs7SignerInfo_SetAuthenticatedAttributes) +# define RTCrPkcs7SignerInfo_SetUnauthenticatedAttributes RT_MANGLER(RTCrPkcs7SignerInfo_SetUnauthenticatedAttributes) # define RTCrPkcs7SignerInfo_DecodeAsn1 RT_MANGLER(RTCrPkcs7SignerInfo_DecodeAsn1) # define RTCrPkcs7SignerInfos_DecodeAsn1 RT_MANGLER(RTCrPkcs7SignerInfos_DecodeAsn1) # define RTCrPkcs7Attribute_Compare RT_MANGLER(RTCrPkcs7Attribute_Compare) @@ -3373,10 +3459,16 @@ # define RTCrPkcs7SignedData_CheckSanity RT_MANGLER(RTCrPkcs7SignedData_CheckSanity) # define RTCrPkcs7SignerInfo_CheckSanity RT_MANGLER(RTCrPkcs7SignerInfo_CheckSanity) # define RTCrPkcs7SignerInfos_CheckSanity RT_MANGLER(RTCrPkcs7SignerInfos_CheckSanity) +# define RTCrPkcs7SimpleSignSignedData RT_MANGLER(RTCrPkcs7SimpleSignSignedData) # define RTCrPkcs7VerifyCertCallbackCodeSigning RT_MANGLER(RTCrPkcs7VerifyCertCallbackCodeSigning) # define RTCrPkcs7VerifyCertCallbackDefault RT_MANGLER(RTCrPkcs7VerifyCertCallbackDefault) # define RTCrPkcs7VerifySignedData RT_MANGLER(RTCrPkcs7VerifySignedData) # define RTCrPkcs7VerifySignedDataWithExternalData RT_MANGLER(RTCrPkcs7VerifySignedDataWithExternalData) +# define RTCrPkcs7Cert_SetX509Cert RT_MANGLER(RTCrPkcs7Cert_SetX509Cert) +# define RTCrPkcs7Cert_SetExtendedCert RT_MANGLER(RTCrPkcs7Cert_SetExtendedCert) +# define RTCrPkcs7Cert_SetAcV1 RT_MANGLER(RTCrPkcs7Cert_SetAcV1) +# define RTCrPkcs7Cert_SetAcV2 RT_MANGLER(RTCrPkcs7Cert_SetAcV2) +# define RTCrPkcs7Cert_SetOtherCert RT_MANGLER(RTCrPkcs7Cert_SetOtherCert) # define RTCrPkcs7Cert_CheckSanity RT_MANGLER(RTCrPkcs7Cert_CheckSanity) # define RTCrPkcs7Cert_Clone RT_MANGLER(RTCrPkcs7Cert_Clone) # define RTCrPkcs7Cert_Compare RT_MANGLER(RTCrPkcs7Cert_Compare) @@ -3422,11 +3514,18 @@ # define RTCrPkixPubKeyVerifySignature RT_MANGLER(RTCrPkixPubKeyVerifySignature) # define RTCrPkixPubKeyVerifySignedDigest RT_MANGLER(RTCrPkixPubKeyVerifySignedDigest) # define RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo RT_MANGLER(RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo) +# define RTCrPkixPubKeyCanHandleDigestType RT_MANGLER(RTCrPkixPubKeyCanHandleDigestType) +# define RTCrPkixCanCertHandleDigestType RT_MANGLER(RTCrPkixCanCertHandleDigestType) # define RTCrRandBytes RT_MANGLER(RTCrRandBytes) +# define RTCrSpcAttributeTypeAndOptionalValue_SetPeImage RT_MANGLER(RTCrSpcAttributeTypeAndOptionalValue_SetPeImage) # define RTCrSpcAttributeTypeAndOptionalValue_DecodeAsn1 RT_MANGLER(RTCrSpcAttributeTypeAndOptionalValue_DecodeAsn1) # define RTCrSpcIndirectDataContent_DecodeAsn1 RT_MANGLER(RTCrSpcIndirectDataContent_DecodeAsn1) # define RTCrSpcLink_DecodeAsn1 RT_MANGLER(RTCrSpcLink_DecodeAsn1) +# define RTCrSpcPeImageData_SetFile RT_MANGLER(RTCrSpcPeImageData_SetFile) +# define RTCrSpcPeImageData_SetFlags RT_MANGLER(RTCrSpcPeImageData_SetFlags) # define RTCrSpcPeImageData_DecodeAsn1 RT_MANGLER(RTCrSpcPeImageData_DecodeAsn1) +# define RTCrSpcSerializedObjectAttribute_SetV1Hashes RT_MANGLER(RTCrSpcSerializedObjectAttribute_SetV1Hashes) +# define RTCrSpcSerializedObjectAttribute_SetV2Hashes RT_MANGLER(RTCrSpcSerializedObjectAttribute_SetV2Hashes) # define RTCrSpcSerializedObjectAttribute_DecodeAsn1 RT_MANGLER(RTCrSpcSerializedObjectAttribute_DecodeAsn1) # define RTCrSpcSerializedObjectAttributes_DecodeAsn1 RT_MANGLER(RTCrSpcSerializedObjectAttributes_DecodeAsn1) # define RTCrSpcSerializedObject_DecodeAsn1 RT_MANGLER(RTCrSpcSerializedObject_DecodeAsn1) @@ -3439,6 +3538,9 @@ # define RTCrSpcIndirectDataContent_Delete RT_MANGLER(RTCrSpcIndirectDataContent_Delete) # define RTCrSpcIndirectDataContent_Enum RT_MANGLER(RTCrSpcIndirectDataContent_Enum) # define RTCrSpcIndirectDataContent_GetPeImageObjAttrib RT_MANGLER(RTCrSpcIndirectDataContent_GetPeImageObjAttrib) +# define RTCrSpcLink_SetFile RT_MANGLER(RTCrSpcLink_SetFile) +# define RTCrSpcLink_SetMoniker RT_MANGLER(RTCrSpcLink_SetMoniker) +# define RTCrSpcLink_SetUrl RT_MANGLER(RTCrSpcLink_SetUrl) # define RTCrSpcLink_Compare RT_MANGLER(RTCrSpcLink_Compare) # define RTCrSpcLink_Delete RT_MANGLER(RTCrSpcLink_Delete) # define RTCrSpcLink_Enum RT_MANGLER(RTCrSpcLink_Enum) @@ -3465,6 +3567,8 @@ # define RTCrSpcAttributeTypeAndOptionalValue_Init RT_MANGLER(RTCrSpcAttributeTypeAndOptionalValue_Init) # define RTCrSpcIndirectDataContent_Clone RT_MANGLER(RTCrSpcIndirectDataContent_Clone) # define RTCrSpcIndirectDataContent_Init RT_MANGLER(RTCrSpcIndirectDataContent_Init) +# define RTCrSpcString_SetAscii RT_MANGLER(RTCrSpcString_SetAscii) +# define RTCrSpcString_SetUcs2 RT_MANGLER(RTCrSpcString_SetUcs2) # define RTCrSpcLink_Clone RT_MANGLER(RTCrSpcLink_Clone) # define RTCrSpcLink_Init RT_MANGLER(RTCrSpcLink_Init) # define RTCrSpcPeImageData_Clone RT_MANGLER(RTCrSpcPeImageData_Clone) @@ -3804,6 +3908,7 @@ # define RTCrCertCtxRelease RT_MANGLER(RTCrCertCtxRelease) # define RTCrCertCtxRetain RT_MANGLER(RTCrCertCtxRetain) # define RTCrStoreCertAddEncoded RT_MANGLER(RTCrStoreCertAddEncoded) +# define RTCrStoreCertAddX509 RT_MANGLER(RTCrStoreCertAddX509) # define RTCrStoreCertByIssuerAndSerialNo RT_MANGLER(RTCrStoreCertByIssuerAndSerialNo) # define RTCrStoreCertCount RT_MANGLER(RTCrStoreCertCount) # define RTCrStoreCertFindAll RT_MANGLER(RTCrStoreCertFindAll) @@ -3815,6 +3920,7 @@ # define RTCrStoreRelease RT_MANGLER(RTCrStoreRelease) # define RTCrStoreRetain RT_MANGLER(RTCrStoreRetain) # define RTCrStoreCreateInMem RT_MANGLER(RTCrStoreCreateInMem) +# define RTCrStoreCreateInMemEx RT_MANGLER(RTCrStoreCreateInMemEx) # define RTCrStoreCreateSnapshotById RT_MANGLER(RTCrStoreCreateSnapshotById) # define RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts RT_MANGLER(RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts) # define RTCrStoreCertAddFromDir RT_MANGLER(RTCrStoreCertAddFromDir) @@ -3993,6 +4099,8 @@ # define g_RTAsn1DefaultAllocator RT_MANGLER(g_RTAsn1DefaultAllocator) # define g_RTAsn1EFenceAllocator RT_MANGLER(g_RTAsn1EFenceAllocator) # define g_RTAsn1SaferAllocator RT_MANGLER(g_RTAsn1SaferAllocator) +# define g_aRTCrPkcs7Markers RT_MANGLER(g_aRTCrPkcs7Markers) +# define g_cRTCrPkcs7Markers RT_MANGLER(g_cRTCrPkcs7Markers) # define g_aRTCrX509CertificateMarkers RT_MANGLER(g_aRTCrX509CertificateMarkers) # define g_cRTCrX509CertificateMarkers RT_MANGLER(g_cRTCrX509CertificateMarkers) # define g_aRTCrKeyPublicMarkers RT_MANGLER(g_aRTCrKeyPublicMarkers) diff -Nru virtualbox-6.1.16-dfsg/include/iprt/memobj.h virtualbox-6.1.38-dfsg/include/iprt/memobj.h --- virtualbox-6.1.16-dfsg/include/iprt/memobj.h 2020-10-16 16:27:57.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/memobj.h 2022-09-01 13:17:53.000000000 +0000 @@ -152,6 +152,72 @@ RTR0DECL(int) RTR0MemObjAllocPageTag(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag); /** + * Allocates large page aligned virtual kernel memory (default tag). + * + * Each large page in the allocation is backed by a contiguous chunk of physical + * memory aligned to the page size. The memory is taken from a non paged (= + * fixed physical memory backing) pool. + * + * On some hosts we only support allocating a single large page at a time, they + * will return VERR_NOT_SUPPORTED if @a cb is larger than @a cbLargePage. + * + * @returns IPRT status code. + * @retval VERR_TRY_AGAIN instead of VERR_NO_MEMORY when + * RTMEMOBJ_ALLOC_LARGE_F_FAST is set and supported. + * @param pMemObj Where to store the ring-0 memory object handle. + * @param cb Number of bytes to allocate. This is rounded up to + * nearest large page. + * @param cbLargePage The large page size. The allowed values varies from + * architecture to architecture and the paging mode + * used by the OS. + * @param fFlags Flags, RTMEMOBJ_ALLOC_LARGE_F_XXX. + * + * @note The implicit kernel mapping of this allocation does not necessarily + * have to be aligned on a @a cbLargePage boundrary. + */ +#define RTR0MemObjAllocLarge(pMemObj, cb, cbLargePage, fFlags) \ + RTR0MemObjAllocLargeTag((pMemObj), (cb), (cbLargePage), (fFlags), RTMEM_TAG) + +/** + * Allocates large page aligned virtual kernel memory (custom tag). + * + * Each large page in the allocation is backed by a contiguous chunk of physical + * memory aligned to the page size. The memory is taken from a non paged (= + * fixed physical memory backing) pool. + * + * On some hosts we only support allocating a single large page at a time, they + * will return VERR_NOT_SUPPORTED if @a cb is larger than @a cbLargePage. + * + * @returns IPRT status code. + * @retval VERR_TRY_AGAIN instead of VERR_NO_MEMORY when + * RTMEMOBJ_ALLOC_LARGE_F_FAST is set and supported. + * @param pMemObj Where to store the ring-0 memory object handle. + * @param cb Number of bytes to allocate. This is rounded up to + * nearest large page. + * @param cbLargePage The large page size. The allowed values varies from + * architecture to architecture and the paging mode + * used by the OS. + * @param fFlags Flags, RTMEMOBJ_ALLOC_LARGE_F_XXX. + * @param pszTag Allocation tag used for statistics and such. + * + * @note The implicit kernel mapping of this allocation does not necessarily + * have to be aligned on a @a cbLargePage boundrary. + */ +RTR0DECL(int) RTR0MemObjAllocLargeTag(PRTR0MEMOBJ pMemObj, size_t cb, size_t cbLargePage, uint32_t fFlags, const char *pszTag); + +/** @name RTMEMOBJ_ALLOC_LARGE_F_XXX + * @{ */ +/** Indicates that it is okay to fail if there aren't enough large pages handy, + * cancelling any expensive search and reshuffling of memory (when supported). + * @note This flag can't be realized on all OSes. (Those who do support it + * will return VERR_TRY_AGAIN instead of VERR_NO_MEMORY if they + * cannot satisfy the request.) */ +#define RTMEMOBJ_ALLOC_LARGE_F_FAST RT_BIT_32(0) +/** Mask with valid bits. */ +#define RTMEMOBJ_ALLOC_LARGE_F_VALID_MASK UINT32_C(0x00000001) +/** @} */ + +/** * Allocates page aligned virtual kernel memory with physical backing below 4GB * (default tag). * diff -Nru virtualbox-6.1.16-dfsg/include/iprt/net.h virtualbox-6.1.38-dfsg/include/iprt/net.h --- virtualbox-6.1.16-dfsg/include/iprt/net.h 2020-10-16 16:27:57.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/net.h 2022-09-01 13:17:53.000000000 +0000 @@ -221,6 +221,25 @@ */ RTDECL(int) RTNetPrefixToMaskIPv6(int iPrefix, PRTNETADDRIPV6 pMask); +/** + * Parses IPv6 prefix notation into RTNETADDRIPV6 representation and + * prefix length. Missing prefix specification is treated as exact + * address specification (prefix length 128). Leading and trailing + * whitespace is ignored. + * + * "CIDR" in the name is a misnomer as IPv6 doesn't have network + * classes, but is parallel to the IPv4 name (and naming things is + * hard). + * + * @returns VINF_SUCCESS on success, VERR_INVALID_PARAMETER on + * failure. + * + * @param pcszAddr The value to convert. + * @param pAddr Where to store the address. + * @param piPrefix Where to store the prefix length; + */ +RTDECL(int) RTNetStrToIPv6Cidr(const char *pcszAddr, PRTNETADDRIPV6 pAddr, int *piPrefix); + /** * IPX address. diff -Nru virtualbox-6.1.16-dfsg/include/iprt/nt/hyperv.h virtualbox-6.1.38-dfsg/include/iprt/nt/hyperv.h --- virtualbox-6.1.16-dfsg/include/iprt/nt/hyperv.h 2020-10-16 16:27:57.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/nt/hyperv.h 2022-09-01 13:17:53.000000000 +0000 @@ -35,6 +35,7 @@ # include #else # define RT_FLEXIBLE_ARRAY +# define RT_FLEXIBLE_ARRAY_EXTENSION # define AssertCompile(expr) # define AssertCompileSize(type, size) # define AssertCompileMemberOffset(type, member, off) @@ -349,6 +350,7 @@ typedef enum { HvPartitionPropertyPrivilegeFlags = 0x00010000, + HvPartitionPropertySyntheticProcessorFeaturesBanks, /**< Read by WHvApi::Capabilities::GetSyntheticProcessorFeaturesBanks (build 22000) */ HvPartitionPropertyCpuReserve = 0x00020001, HvPartitionPropertyCpuCap, @@ -358,6 +360,9 @@ HvPartitionPropertyEmulatedTimerPeriod = 0x00030000, /**< @note Fails on exo partition (build 17134). */ HvPartitionPropertyEmulatedTimerControl, /**< @note Fails on exo partition (build 17134). */ HvPartitionPropertyPmTimerAssist, /**< @note Fails on exo partition (build 17134). */ + HvPartitionPropertyUnknown30003, /**< @note WHvSetupPartition writes this (build 22000). */ + HvPartitionPropertyUnknown30004, /**< ? */ + HvPartitionPropertyUnknown30005, /**< WHvPartitionPropertyCodeReferenceTime maps to this (build 22000) */ HvPartitionPropertyDebugChannelId = 0x00040000, /**< @note Hangs system on exo partition hangs (build 17134). */ @@ -367,7 +372,24 @@ HvPartitionPropertyUnknown50003, /**< On exo partition (build 17134), initial value zero. */ HvPartitionPropertyUnknown50004, /**< On exo partition (build 17134), initial value zero. */ HvPartitionPropertyUnknown50005, /**< On exo partition (build 17134), initial value one. */ - HvPartitionPropertyUnknown50006, /**< On exo partition (build 17134), initial value zero. */ + HvPartitionPropertyUnknown50006, /**< On exo partition (build 17134), initial value zero. + * @note build 22000/w11-ga fends this off in VID.SYS. */ + HvPartitionPropertyUnknown50007, + HvPartitionPropertyUnknown50008, + HvPartitionPropertyUnknown50009, + HvPartitionPropertyUnknown5000a, + HvPartitionPropertyUnknown5000b, + HvPartitionPropertyUnknown5000c, + HvPartitionPropertyUnknown5000d, + HvPartitionPropertyUnknown5000e, + HvPartitionPropertyUnknown5000f, + HvPartitionPropertyUnknown50010, + HvPartitionPropertyUnknown50012, + HvPartitionPropertyUnknown50013, /**< Set by WHvSetupPartition (build 22000) */ + HvPartitionPropertyUnknown50014, + HvPartitionPropertyUnknown50015, + HvPartitionPropertyUnknown50016, + HvPartitionPropertyUnknown50017, /**< Set by WHvSetupPartition (build 22000) */ HvPartitionPropertyProcessorVendor = 0x00060000, HvPartitionPropertyProcessorFeatures, /**< On exo/17134/threadripper: 0x6cb26f39fbf */ @@ -376,6 +398,11 @@ HvPartitionPropertyUnknown60004, /**< On exo partition (build 17134), initial value zero. */ HvPartitionPropertyUnknown60005, /**< On exo partition (build 17134), initial value 0x603. */ HvPartitionPropertyUnknown60006, /**< On exo partition (build 17134), initial value 0x2c. */ + HvPartitionPropertyUnknown60007, /**< WHvSetupPartition reads this (build 22000). */ + HvPartitionPropertyUnknown60008, /**< WHvSetupPartition reads this (build 22000). */ + HvPartitionPropertyProcessorClockFrequency, /**< Read by WHvApi::Capabilities::GetProcessorClockFrequency (build 22000). */ + HvPartitionPropertyProcessorFeaturesBank0, /**< Read by WHvApi::Capabilities::GetProcessorFeaturesBanks (build 22000). */ + HvPartitionPropertyProcessorFeaturesBank1, /**< Read by WHvApi::Capabilities::GetProcessorFeaturesBanks (build 22000). */ HvPartitionPropertyGuestOsId = 0x00070000, /**< @since v4 */ @@ -497,6 +524,7 @@ HV_MAP_GPA_FLAGS MapFlags; uint32_t u32ExplicitPadding; /* The repeating part: */ + RT_FLEXIBLE_ARRAY_EXTENSION HV_SPA_PAGE_NUMBER PageList[RT_FLEXIBLE_ARRAY]; } HV_INPUT_MAP_GPA_PAGES; AssertCompileMemberOffset(HV_INPUT_MAP_GPA_PAGES, PageList, 24); @@ -520,6 +548,7 @@ HV_MAP_GPA_FLAGS MapFlags; uint32_t u32ExplicitPadding; /* The repeating part: */ + RT_FLEXIBLE_ARRAY_EXTENSION HV_GPA_MAPPING PageList[RT_FLEXIBLE_ARRAY]; } HV_INPUT_MAP_SPARSE_GPA_PAGES; AssertCompileMemberOffset(HV_INPUT_MAP_SPARSE_GPA_PAGES, PageList, 16); @@ -1233,6 +1262,7 @@ /** Was this introduced after v2? Dunno what it it really is. */ uint32_t fFlags; /* The repeating part: */ + RT_FLEXIBLE_ARRAY_EXTENSION HV_REGISTER_NAME Names[RT_FLEXIBLE_ARRAY]; } HV_INPUT_GET_VP_REGISTERS; AssertCompileMemberOffset(HV_INPUT_GET_VP_REGISTERS, Names, 16); @@ -1267,6 +1297,7 @@ HV_VP_INDEX VpIndex; uint32_t RsvdZ; /* The repeating part: */ + RT_FLEXIBLE_ARRAY_EXTENSION HV_REGISTER_ASSOC Elements[RT_FLEXIBLE_ARRAY]; } HV_INPUT_SET_VP_REGISTERS; AssertCompileMemberOffset(HV_INPUT_SET_VP_REGISTERS, Elements, 16); diff -Nru virtualbox-6.1.16-dfsg/include/iprt/nt/nt.h virtualbox-6.1.38-dfsg/include/iprt/nt/nt.h --- virtualbox-6.1.16-dfsg/include/iprt/nt/nt.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/nt/nt.h 2022-09-01 13:17:53.000000000 +0000 @@ -3020,7 +3020,7 @@ SystemExtendedHandleInformation, /* 64 */ SystemInformation_Unknown_65, SystemInformation_Unknown_66, - SystemInformation_Unknown_67, + SystemInformation_Unknown_67, /**< See https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/codeintegrity.htm */ SystemInformation_Unknown_68, SystemInformation_HotPatchInfo, /* 69 */ SystemInformation_Unknown_70, diff -Nru virtualbox-6.1.16-dfsg/include/iprt/nt/vid.h virtualbox-6.1.38-dfsg/include/iprt/nt/vid.h --- virtualbox-6.1.16-dfsg/include/iprt/nt/vid.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/nt/vid.h 2022-09-01 13:17:53.000000000 +0000 @@ -123,6 +123,9 @@ #define VID_MSHAGN_F_CANCEL RT_BIT_32(2) /** @} */ +/** A 64-bit version of HV_PARTITION_PROPERTY_CODE. */ +typedef int64_t VID_PARTITION_PROPERTY_CODE; + #ifdef IN_RING3 RT_C_DECLS_BEGIN @@ -146,10 +149,37 @@ * * The partition ID is the numeric identifier used when making hypercalls to the * hypervisor. + * + * @note Starting with Windows 11 (or possibly earlier), this does not work on + * Exo partition as created by WHvCreatePartition. It returns a + * STATUS_NOT_IMPLEMENTED as the I/O control code is not allowed through. + * All partitions has an ID though, so just pure annoying blockheadedness + * sprung upon us w/o any chance of doing a memory managment rewrite in + * time. */ DECLIMPORT(BOOL) VIDAPI VidGetHvPartitionId(VID_PARTITION_HANDLE hPartition, HV_PARTITION_ID *pidPartition); /** + * Get a partition property. + * + * @returns Success indicator (details in LastErrorValue). + * @param hPartition The partition handle. + * @param enmProperty The property to get. Is a HV_PARTITION_PROPERTY_CODE + * type, but seems to be passed around as a 64-bit integer + * for some reason. + * @param puValue Where to return the property value. + */ +DECLIMPORT(BOOL) VIDAPI VidGetPartitionProperty(VID_PARTITION_HANDLE hPartition, VID_PARTITION_PROPERTY_CODE enmProperty, + PHV_PARTITION_PROPERTY puValue); + +/** + * @copydoc VidGetPartitionProperty + * @note Currently (Windows 11 GA) identical to VidGetPartitionProperty. + */ +DECLIMPORT(BOOL) VIDAPI VidGetExoPartitionProperty(VID_PARTITION_HANDLE hPartition, VID_PARTITION_PROPERTY_CODE enmProperty, + PHV_PARTITION_PROPERTY puValue); + +/** * Starts asynchronous execution of a virtual CPU. */ DECLIMPORT(BOOL) VIDAPI VidStartVirtualProcessor(VID_PARTITION_HANDLE hPartition, HV_VP_INDEX iCpu); diff -Nru virtualbox-6.1.16-dfsg/include/iprt/path.h virtualbox-6.1.38-dfsg/include/iprt/path.h --- virtualbox-6.1.16-dfsg/include/iprt/path.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/path.h 2022-09-01 13:17:53.000000000 +0000 @@ -777,6 +777,7 @@ * @note Don't try figure the end of the input path by adding up off and cch * of the last component. If RTPATH_PROP_DIR_SLASH is set, there may * be one or more trailing slashes that are unaccounted for! */ + RT_FLEXIBLE_ARRAY_EXTENSION struct { /** The offset of the component. */ @@ -869,6 +870,7 @@ * present. */ const char *pszSuffix; /** Array of component strings (variable size). */ + RT_FLEXIBLE_ARRAY_EXTENSION char *apszComps[RT_FLEXIBLE_ARRAY]; } RTPATHSPLIT; /** Pointer to a split path buffer. */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/process.h virtualbox-6.1.38-dfsg/include/iprt/process.h --- virtualbox-6.1.16-dfsg/include/iprt/process.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/process.h 2022-09-01 13:17:54.000000000 +0000 @@ -205,11 +205,8 @@ * the more frequently used case. */ #define RTPROC_FLAGS_SAME_CONTRACT RT_BIT(3) /** Load user profile data when executing a process. - * This redefines the meaning of RTENV_DEFAULT to the profile environment. - * @remarks On non-windows platforms, the resulting environment maybe very - * different from what you see in your shell. Among other reasons, - * we cannot run shell profile scripts which typically sets up the - * environment. */ + * This redefines the meaning of RTENV_DEFAULT to the profile environment. See + * also RTPROC_FLAGS_ONLY_BASIC_PROFILE */ #define RTPROC_FLAGS_PROFILE RT_BIT(4) /** Create process without a console window. * This is a Windows (and OS/2) concept, do not use on other platforms. */ @@ -234,8 +231,14 @@ * (Windows only, ignored elsewhere). The @a pvExtraData argument points to * a uint32_t containing the session ID, UINT32_MAX means any session. */ #define RTPROC_FLAGS_DESIRED_SESSION_ID RT_BIT(11) +/** This is a modifier to RTPROC_FLAGS_PROFILE on unix systems that makes it + * skip trying to dump the environment of a login shell. */ +#define RTPROC_FLAGS_ONLY_BASIC_PROFILE RT_BIT(12) +/** Don't translate arguments to the (guessed) child process codeset. + * This is ignored on Windows as it is using UTF-16. */ +#define RTPROC_FLAGS_UTF8_ARGV RT_BIT_32(13) /** Valid flag mask. */ -#define RTPROC_FLAGS_VALID_MASK UINT32_C(0xfff) +#define RTPROC_FLAGS_VALID_MASK UINT32_C(0x3fff) /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/req.h virtualbox-6.1.38-dfsg/include/iprt/req.h --- virtualbox-6.1.16-dfsg/include/iprt/req.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/req.h 2022-09-01 13:17:54.000000000 +0000 @@ -80,7 +80,7 @@ RTREQFLAGS_VOID = 1, /** Return type mask. */ RTREQFLAGS_RETURN_MASK = 1, - /** Caller does not wait on the packet, Queue process thread will free it. */ + /** Caller does not wait on the packet. */ RTREQFLAGS_NO_WAIT = 2 } RTREQFLAGS; @@ -201,9 +201,10 @@ * @returns VERR_TIMEOUT if cMillies was reached without the packet being completed. * * @param hQueue The request queue. - * @param ppReq Where to store the pointer to the request. - * This will be NULL or a valid request pointer not matter what happens, unless fFlags - * contains RTREQFLAGS_NO_WAIT when it will be optional and always NULL. + * @param ppReq Where to store the pointer to the request. Optional + * when RTREQFLAGS_NO_WAIT is used. + * This variable will be set to NIL or a valid request + * handle not matter what happens. * @param cMillies Number of milliseconds to wait for the request to * be completed. Use RT_INDEFINITE_WAIT to only * wait till it's completed. @@ -229,9 +230,10 @@ * @returns VERR_TIMEOUT if cMillies was reached without the packet being completed. * * @param hQueue The request queue. - * @param ppReq Where to store the pointer to the request. - * This will be NULL or a valid request pointer not matter what happens, unless fFlags - * contains RTREQFLAGS_NO_WAIT when it will be optional and always NULL. + * @param ppReq Where to store the pointer to the request. Optional + * when RTREQFLAGS_NO_WAIT is used. + * This variable will be set to NIL or a valid request + * handle not matter what happens. * @param cMillies Number of milliseconds to wait for the request to * be completed. Use RT_INDEFINITE_WAIT to only * wait till it's completed. @@ -331,6 +333,8 @@ RTREQPOOLCFGVAR_INVALID = 0, /** The desired RTTHREADTYPE of the worker threads. */ RTREQPOOLCFGVAR_THREAD_TYPE, + /** The RTTHREADFLAGS mask for the worker threads (not waitable). */ + RTREQPOOLCFGVAR_THREAD_FLAGS, /** The minimum number of threads to keep handy once spawned. */ RTREQPOOLCFGVAR_MIN_THREADS, /** The maximum number of thread to start. */ @@ -396,6 +400,8 @@ RTREQPOOLSTAT_REQUESTS_PROCESSED, /** The total number of requests that have been submitted. */ RTREQPOOLSTAT_REQUESTS_SUBMITTED, + /** The total number of requests that have been cancelled. */ + RTREQPOOLSTAT_REQUESTS_CANCELLED, /** the current number of pending (waiting) requests. */ RTREQPOOLSTAT_REQUESTS_PENDING, /** The current number of active (executing) requests. */ @@ -447,8 +453,10 @@ * @param hPool The request thread pool handle. * @param cMillies The number of milliseconds to wait for the request * to be processed. - * @param phReq Where to return the request. Can be NULL if the - * RTREQFLAGS_NO_WAIT flag is used. + * @param phReq Where to store the pointer to the request. Optional + * when RTREQFLAGS_NO_WAIT is used. + * This variable will be set to NIL or a valid request + * handle not matter what happens. * @param fFlags A combination of RTREQFLAGS values. * @param pfnFunction The function to be called. Must be declared by a * DECL macro because of calling conventions. @@ -469,8 +477,10 @@ * @param hPool The request thread pool handle. * @param cMillies The number of milliseconds to wait for the request * to be processed. - * @param phReq Where to return the request. Can be NULL if the - * RTREQFLAGS_NO_WAIT flag is used. + * @param phReq Where to store the pointer to the request. Optional + * when RTREQFLAGS_NO_WAIT is used. + * This variable will be set to NIL or a valid request + * handle not matter what happens. * @param fFlags A combination of RTREQFLAGS values. * @param pfnFunction The function to be called. Must be declared by a * DECL macro because of calling conventions. @@ -580,6 +590,15 @@ */ RTDECL(int) RTReqSubmit(PRTREQ pReq, RTMSINTERVAL cMillies); +/** + * Cancels a pending request. + * + * @returns IPRT status code. + * @retval VERR_RT_REQUEST_STATE if the request is not cancellable. + * + * @param hReq The request to cancel. + */ +RTDECL(int) RTReqCancel(PRTREQ hReq); /** * Waits for a request to be completed. diff -Nru virtualbox-6.1.16-dfsg/include/iprt/sha.h virtualbox-6.1.38-dfsg/include/iprt/sha.h --- virtualbox-6.1.16-dfsg/include/iprt/sha.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/sha.h 2022-09-01 13:17:54.000000000 +0000 @@ -513,6 +513,68 @@ RTSHA512_DECLARE_VARIANT(512t256,512T256); +/** + * SHA3 context. + */ +typedef union RTSHA3CONTEXT +{ + uint64_t a64Padding[26]; + uint8_t abPadding[208]; +#ifdef RT_SHA3_PRIVATE_CONTEXT + RTSHA3PRIVATECTX Private; +#endif +#ifdef RT_SHA3_PRIVATE_ALT_CONTEXT + RTSHA3ALTPRIVATECTX AltPrivate; +#endif +} RTSHA3CONTEXT; +/** Pointer to an SHA3 context. */ +typedef RTSHA3CONTEXT *PRTSHA3CONTEXT; + +/** Macro for declaring the interface for a SHA3 variation. + * + * @note The interface differes slightly from the older checksums: + * - Must call Final and/or Cleanup method. + * - Must use Clone instead of memcpy'ing the context. + * - Status codes are returned, Init may really fail. + * + * @internal */ +#define RTSHA3_DECLARE_VARIANT(a_Bits) \ + typedef struct RT_CONCAT3(RTSHA3T,a_Bits,CONTEXT) { RTSHA3CONTEXT Sha3; } RT_CONCAT3(RTSHA3T,a_Bits,CONTEXT); \ + typedef RT_CONCAT3(RTSHA3T,a_Bits,CONTEXT) *RT_CONCAT3(PRTSHA3T,a_Bits,CONTEXT); \ + RTDECL(int) RT_CONCAT(RTSha3t,a_Bits)(const void *pvBuf, size_t cbBuf, uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_Bits,_HASH_SIZE)]); \ + RTDECL(bool) RT_CONCAT3(RTSha3t,a_Bits,Check)(const void *pvBuf, size_t cbBuf, uint8_t const pabHash[RT_CONCAT3(RTSHA3_,a_Bits,_HASH_SIZE)]); \ + RTDECL(int) RT_CONCAT3(RTSha3t,a_Bits,Init)(RT_CONCAT3(PRTSHA3T,a_Bits,CONTEXT) pCtx); \ + RTDECL(int) RT_CONCAT3(RTSha3t,a_Bits,Update)(RT_CONCAT3(PRTSHA3T,a_Bits,CONTEXT) pCtx, const void *pvBuf, size_t cbBuf); \ + RTDECL(int) RT_CONCAT3(RTSha3t,a_Bits,Final)(RT_CONCAT3(PRTSHA3T,a_Bits,CONTEXT) pCtx, uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_Bits,_HASH_SIZE)]); \ + RTDECL(int) RT_CONCAT3(RTSha3t,a_Bits,Cleanup)(RT_CONCAT3(PRTSHA3T,a_Bits,CONTEXT) pCtx); \ + RTDECL(int) RT_CONCAT3(RTSha3t,a_Bits,Clone)(RT_CONCAT3(PRTSHA3T,a_Bits,CONTEXT) pCtx, RT_CONCAT3(RTSHA3T,a_Bits,CONTEXT) const *pCtxSrc); \ + RTDECL(int) RT_CONCAT3(RTSha3t,a_Bits,ToString)(uint8_t const pabHash[RT_CONCAT3(RTSHA3_,a_Bits,_HASH_SIZE)], char *pszDigest, size_t cchDigest); \ + RTDECL(int) RT_CONCAT3(RTSha3t,a_Bits,FromString)(char const *pszDigest, uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_Bits,_HASH_SIZE)]) + +/** The size of a SHA-224 hash. */ +#define RTSHA3_224_HASH_SIZE 28 +/** The length of a SHA-224 digest string. The terminator is not included. */ +#define RTSHA3_224_DIGEST_LEN 56 +RTSHA3_DECLARE_VARIANT(224); + +/** The size of a SHA-256 hash. */ +#define RTSHA3_256_HASH_SIZE 32 +/** The length of a SHA-256 digest string. The terminator is not included. */ +#define RTSHA3_256_DIGEST_LEN 64 +RTSHA3_DECLARE_VARIANT(256); + +/** The size of a SHA-384 hash. */ +#define RTSHA3_384_HASH_SIZE 48 +/** The length of a SHA-384 digest string. The terminator is not included. */ +#define RTSHA3_384_DIGEST_LEN 96 +RTSHA3_DECLARE_VARIANT(384); + +/** The size of a SHA-512 hash. */ +#define RTSHA3_512_HASH_SIZE 64 +/** The length of a SHA-512 digest string. The terminator is not included. */ +#define RTSHA3_512_DIGEST_LEN 128 +RTSHA3_DECLARE_VARIANT(512); + /** @} */ RT_C_DECLS_END diff -Nru virtualbox-6.1.16-dfsg/include/iprt/stdarg.h virtualbox-6.1.38-dfsg/include/iprt/stdarg.h --- virtualbox-6.1.16-dfsg/include/iprt/stdarg.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/stdarg.h 2022-09-01 13:17:54.000000000 +0000 @@ -43,6 +43,13 @@ # if __GNUC__ >= 4 /* System headers refers to __builtin_stdarg_start. */ # define __builtin_stdarg_start __builtin_va_start # endif +# elif defined(RT_OS_LINUX) && defined(IN_RING0) +# include "linux/version.h" +# if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_MAJ_PREREQ(9,1) +# include +# else +# include +# endif # else # include # endif diff -Nru virtualbox-6.1.16-dfsg/include/iprt/stream.h virtualbox-6.1.38-dfsg/include/iprt/stream.h --- virtualbox-6.1.16-dfsg/include/iprt/stream.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/stream.h 2022-09-01 13:17:54.000000000 +0000 @@ -310,6 +310,72 @@ RTR3DECL(int) RTStrmPrintfV(PRTSTREAM pStream, const char *pszFormat, va_list args) RT_IPRT_FORMAT_ATTR(2, 0); /** + * Prints a formatted string to the specified stream, performing wrapping of + * lines considered too long. + * + * If the stream is to a terminal, the terminal width is used as the max line + * width. Otherwise, the width is taken from @a fFlags + * (RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK / + * RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_SHIFT), defaulting to 80 if zero. + * + * @returns Low 16 bits is the line offset, high 16 bits the number of lines + * outputted. Apply RTSTRMWRAPPED_F_LINE_OFFSET_MASK to the value and + * it can be passed via @a fFlags to the next invocation (not necessary + * if all format strings ends with a newline). + * Negative values are IPRT error status codes. + * @param pStream The stream to print to. + * @param fFlags RTSTRMWRAPPED_F_XXX - flags, configuration and state. + * @param pszFormat Runtime format string. + * @param ... Arguments specified by pszFormat. + * @sa RTStrmWrappedPrintfV, RTStrmPrintf, RTStrmPrintfV + */ +RTDECL(int32_t) RTStrmWrappedPrintf(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(3, 4); + +/** + * Prints a formatted string to the specified stream, performing wrapping of + * lines considered too long. + * + * If the stream is to a terminal, the terminal width is used as the max line + * width. Otherwise, the width is taken from @a fFlags + * (RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK / + * RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_SHIFT), defaulting to 80 if zero. + * + * @returns Low 16 bits is the line offset, high 16 bits the number of lines + * outputted. Apply RTSTRMWRAPPED_F_LINE_OFFSET_MASK to the value and + * it can be passed via @a fFlags to the next invocation (not necessary + * if all format strings ends with a newline). + * Negative values are IPRT error status codes. + * @param pStream The stream to print to. + * @param fFlags RTSTRMWRAPPED_F_XXX - flags, configuration and state. + * @param pszFormat Runtime format string. + * @param va Arguments specified by pszFormat. + * @sa RTStrmWrappedPrintf, RTStrmPrintf, RTStrmPrintfV + */ +RTDECL(int32_t) RTStrmWrappedPrintfV(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, + va_list va) RT_IPRT_FORMAT_ATTR(3, 0); + +/** @name RTSTRMWRAPPED_F_XXX - Flags for RTStrmWrappedPrintf & + * RTStrmWrappedPrintfV. + * @{ */ +/** The current line offset mask. + * This should be used to passed the line off state from one call to the next + * when printing incomplete lines. If all format strings ends with a newline, + * this is not necessary. */ +#define RTSTRMWRAPPED_F_LINE_OFFSET_MASK UINT32_C(0x00000fff) +/** The non-terminal width mask. Defaults to 80 if not specified (zero). */ +#define RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK UINT32_C(0x000ff000) +/** The non-terminal width shift. */ +#define RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_SHIFT 12 +/** The hanging indent level mask - defaults to 4 if zero. + * Used when RTSTRMWRAPPED_F_HANGING_INDENT is set. */ +#define RTSTRMWRAPPED_F_HANGING_INDENT_MASK UINT32_C(0x01f00000) +/** The hanging indent level shift. */ +#define RTSTRMWRAPPED_F_HANGING_INDENT_SHIFT 20 +/** Hanging indent. Used for command synopsis and such. */ +#define RTSTRMWRAPPED_F_HANGING_INDENT UINT32_C(0x80000000) +/** @} */ + +/** * Dumper vprintf-like function outputting to a stream. * * @param pvUser The stream to print to. NULL means standard output. diff -Nru virtualbox-6.1.16-dfsg/include/iprt/string.h virtualbox-6.1.38-dfsg/include/iprt/string.h --- virtualbox-6.1.16-dfsg/include/iprt/string.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/string.h 2022-09-01 13:17:54.000000000 +0000 @@ -60,7 +60,9 @@ * ffs = find first set bit. */ # define ffs ffs_string_h +# define fls fls_string_h # include +# undef fls # undef ffs # undef strpbrk diff -Nru virtualbox-6.1.16-dfsg/include/iprt/system.h virtualbox-6.1.38-dfsg/include/iprt/system.h --- virtualbox-6.1.16-dfsg/include/iprt/system.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/system.h 2022-09-01 13:17:54.000000000 +0000 @@ -354,6 +354,11 @@ */ RTDECL(uint64_t) RTSystemGetNtVersion(void); +/** + * Get the Windows NT product type (OSVERSIONINFOW::wProductType). + */ +RTDECL(uint8_t) RTSystemGetNtProductType(void); + #endif /* RT_OS_WINDOWS */ /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/test.h virtualbox-6.1.38-dfsg/include/iprt/test.h --- virtualbox-6.1.16-dfsg/include/iprt/test.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/test.h 2022-09-01 13:17:54.000000000 +0000 @@ -470,12 +470,12 @@ * older versions of this header and other components using the same * contant values. * @remarks When adding a new item: - * - Always add at the end of the list - do NOT group it. + * - Always add at the end of the list. * - Add it to rtTestUnitName in r3/test.cpp. - * - include/VBox/VMMDevTesting.h (VMMDEV_TESTING_UNIT_XXX). + * - Add it as VMMDEV_TESTING_UNIT_ in include/VBox/VMMDevTesting.h. * - Add it to g_aszBs2TestUnitNames in - * TestSuite/bootsectors/bootsector2-common-routines.mac. - * + * ValidationKit/bootsectors/bootsector2-common-routines.mac. + * - Add it to g_aszBs3TestUnitNames in bs3kit/bs3-cmn-TestData.c. */ typedef enum RTTESTUNIT { @@ -513,12 +513,20 @@ RTTESTUNIT_PP10K, /**< Parts per ten thousand (10^-4). */ RTTESTUNIT_PPM, /**< Parts per million (10^-6). */ RTTESTUNIT_PPB, /**< Parts per billion (10^-9). */ + RTTESTUNIT_TICKS, /**< CPU ticks. */ + RTTESTUNIT_TICKS_PER_CALL, /**< CPU ticks per call. */ + RTTESTUNIT_TICKS_PER_OCCURENCE, /**< CPU ticks per occurence. */ + RTTESTUNIT_PAGES, /**< Page count. */ + RTTESTUNIT_PAGES_PER_SEC, /**< Pages per second. */ + RTTESTUNIT_TICKS_PER_PAGE, /**< CPU ticks per page. */ + RTTESTUNIT_NS_PER_PAGE, /**< Nanoseconds per page. */ /** The end of valid units. */ RTTESTUNIT_END } RTTESTUNIT; -AssertCompile(RTTESTUNIT_INSTRS == 0x19); -AssertCompile(RTTESTUNIT_NONE == 0x1b); +AssertCompile(RTTESTUNIT_INSTRS == 0x19); +AssertCompile(RTTESTUNIT_NONE == 0x1b); +AssertCompile(RTTESTUNIT_NS_PER_PAGE == 0x26); /** * Report a named test result value. @@ -639,6 +647,30 @@ RTR3DECL(int) RTTestFailureDetails(RTTEST hTest, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(2, 3); /** + * Sets error context info to be printed with the first failure. + * + * @returns IPRT status code. + * @param hTest The test handle. If NIL_RTTEST we'll use the one + * associated with the calling thread. + * @param pszFormat The message, no trailing newline. NULL to clear the + * context message. + * @param va The arguments. + */ +RTR3DECL(int) RTTestErrContextV(RTTEST hTest, const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(2, 0); + +/** + * Sets error context info to be printed with the first failure. + * + * @returns IPRT status code. + * @param hTest The test handle. If NIL_RTTEST we'll use the one + * associated with the calling thread. + * @param pszFormat The message, no trailing newline. NULL to clear the + * context message. + * @param ... The arguments. + */ +RTR3DECL(int) RTTestErrContext(RTTEST hTest, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(2, 3); + +/** * Disables and shuts up assertions. * * Max 8 nestings. @@ -1117,6 +1149,26 @@ RTR3DECL(int) RTTestIFailureDetails(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); /** + * Sets error context info to be printed with the first failure. + * + * @returns IPRT status code. + * @param pszFormat The message, no trailing newline. NULL to clear the + * context message. + * @param va The arguments. + */ +RTR3DECL(int) RTTestIErrContextV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0); + +/** + * Sets error context info to be printed with the first failure. + * + * @returns IPRT status code. + * @param pszFormat The message, no trailing newline. NULL to clear the + * context message. + * @param ... The arguments. + */ +RTR3DECL(int) RTTestIErrContext(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); + +/** * Disables and shuts up assertions. * * Max 8 nestings. diff -Nru virtualbox-6.1.16-dfsg/include/iprt/thread.h virtualbox-6.1.38-dfsg/include/iprt/thread.h --- virtualbox-6.1.16-dfsg/include/iprt/thread.h 2020-10-16 16:27:58.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/thread.h 2022-09-01 13:17:54.000000000 +0000 @@ -254,8 +254,15 @@ /** The bit number corresponding to the RTTHREADFLAGS_WAITABLE mask. */ RTTHREADFLAGS_WAITABLE_BIT = 0, + /** Call CoInitializeEx w/ COINIT_MULTITHREADED, COINIT_DISABLE_OLE1DDE and + * COINIT_SPEED_OVER_MEMORY. Ignored on non-windows platforms. */ + RTTHREADFLAGS_COM_MTA = RT_BIT(1), + /** Call CoInitializeEx w/ COINIT_APARTMENTTHREADED and + * COINIT_SPEED_OVER_MEMORY. Ignored on non-windows platforms. */ + RTTHREADFLAGS_COM_STA = RT_BIT(2), + /** Mask of valid flags, use for validation. */ - RTTHREADFLAGS_MASK = RT_BIT(0) + RTTHREADFLAGS_MASK = UINT32_C(0x7) } RTTHREADFLAGS; @@ -280,8 +287,8 @@ RTTHREADTYPE enmType, unsigned fFlags, const char *pszName); #ifndef RT_OS_LINUX /* XXX crashes genksyms at least on 32-bit Linux hosts */ /** Pointer to a RTThreadCreate function. */ -typedef DECLCALLBACKPTR(int, PFNRTTHREADCREATE)(PRTTHREAD pThread, PFNRTTHREAD pfnThread, void *pvUser, size_t cbStack, - RTTHREADTYPE enmType, unsigned fFlags, const char *pszName); +typedef DECLCALLBACKPTR(int, PFNRTTHREADCREATE,(PRTTHREAD pThread, PFNRTTHREAD pfnThread, void *pvUser, size_t cbStack, + RTTHREADTYPE enmType, unsigned fFlags, const char *pszName)); #endif diff -Nru virtualbox-6.1.16-dfsg/include/iprt/types.h virtualbox-6.1.38-dfsg/include/iprt/types.h --- virtualbox-6.1.16-dfsg/include/iprt/types.h 2020-10-16 16:27:59.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/types.h 2022-09-01 13:17:54.000000000 +0000 @@ -131,7 +131,6 @@ * 3. Starting with 2.6.24, linux/types.h typedefs uintptr_t. * We work around these issues here and nowhere else. */ -# include # if defined(__cplusplus) typedef bool _Bool; # endif @@ -2967,6 +2966,20 @@ RTDIGESTTYPE_SHA512T224, /** SHA-512/256 checksum. */ RTDIGESTTYPE_SHA512T256, + /** SHA3-224 checksum. */ + RTDIGESTTYPE_SHA3_224, + /** SHA3-256 checksum. */ + RTDIGESTTYPE_SHA3_256, + /** SHA3-384 checksum. */ + RTDIGESTTYPE_SHA3_384, + /** SHA3-512 checksum. */ + RTDIGESTTYPE_SHA3_512, +#if 0 + /** SHAKE128 checksum. */ + RTDIGESTTYPE_SHAKE128, + /** SHAKE256 checksum. */ + RTDIGESTTYPE_SHAKE256, +#endif /** End of valid types. */ RTDIGESTTYPE_END, /** Usual 32-bit type blowup. */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/vfs.h virtualbox-6.1.38-dfsg/include/iprt/vfs.h --- virtualbox-6.1.16-dfsg/include/iprt/vfs.h 2020-10-16 16:27:59.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/vfs.h 2022-09-01 13:17:55.000000000 +0000 @@ -1206,6 +1206,68 @@ #define RTVFS_VALIDATE_UTF8_VALID_MASK UINT32_C(0x00000003) /** @} */ +/** + * Printf-like write function. + * + * @returns Number of characters written on success, negative error status on + * failure. + * @param hVfsIos The VFS I/O stream handle to write to. + * @param pszFormat The format string. + * @param ... Format arguments. + */ +RTDECL(ssize_t) RTVfsIoStrmPrintf(RTVFSIOSTREAM hVfsIos, const char *pszFormat, ...); + +/** + * Printf-like write function. + * + * @returns Number of characters written on success, negative error status on + * failure. + * @param hVfsIos The VFS I/O stream handle to write to. + * @param pszFormat The format string. + * @param va Format arguments. + */ +RTDECL(ssize_t) RTVfsIoStrmPrintfV(RTVFSIOSTREAM hVfsIos, const char *pszFormat, va_list va); + +/** + * VFS I/O stream output buffer structure to use with + * RTVfsIoStrmStrOutputCallback(). + */ +typedef struct VFSIOSTRMOUTBUF +{ + /** The I/O stream handle. */ + RTVFSIOSTREAM hVfsIos; + /** Size of this structure (for sanity). */ + size_t cbSelf; + /** Status code of the operation. */ + int rc; + /** Current offset into szBuf (number of output bytes pending). */ + size_t offBuf; + /** Modest output buffer. */ + char szBuf[256]; +} VFSIOSTRMOUTBUF; +/** Pointer to an VFS I/O stream output buffer for use with + * RTVfsIoStrmStrOutputCallback() */ +typedef VFSIOSTRMOUTBUF *PVFSIOSTRMOUTBUF; + +/** Initializer for a VFS I/O stream output buffer. */ +#define VFSIOSTRMOUTBUF_INIT(a_pOutBuf, a_hVfsIos) \ + do { \ + (a_pOutBuf)->hVfsIos = a_hVfsIos; \ + (a_pOutBuf)->cbSelf = sizeof(*(a_pOutBuf)); \ + (a_pOutBuf)->rc = VINF_SUCCESS; \ + (a_pOutBuf)->offBuf = 0; \ + (a_pOutBuf)->szBuf[0] = '\0'; \ + } while (0) + +/** + * @callback_method_impl{FNRTSTROUTPUT, + * For use with VFSIOSTRMOUTBUF. + * + * Users must use VFSIOSTRMOUTBUF_INIT to initialize a VFSIOSTRMOUTBUF and pass + * that as the outputter argument to the function this callback is handed to.} + */ +RTDECL(size_t) RTVfsIoStrmStrOutputCallback(void *pvArg, const char *pachChars, size_t cbChars); + /** @} */ @@ -1483,6 +1545,28 @@ */ RTDECL(uint64_t) RTVfsFileGetOpenFlags(RTVFSFILE hVfsFile); +/** + * Printf-like write function. + * + * @returns Number of characters written on success, negative error status on + * failure. + * @param hVfsFile The VFS file handle to write to. + * @param pszFormat The format string. + * @param ... Format arguments. + */ +RTDECL(ssize_t) RTVfsFilePrintf(RTVFSFILE hVfsFile, const char *pszFormat, ...); + +/** + * Printf-like write function. + * + * @returns Number of characters written on success, negative error status on + * failure. + * @param hVfsFile The VFS file handle to write to. + * @param pszFormat The format string. + * @param va Format arguments. + */ +RTDECL(ssize_t) RTVfsFilePrintfV(RTVFSFILE hVfsFile, const char *pszFormat, va_list va); + /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/iprt/win/audioclient.h virtualbox-6.1.38-dfsg/include/iprt/win/audioclient.h --- virtualbox-6.1.16-dfsg/include/iprt/win/audioclient.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/win/audioclient.h 2022-09-01 13:17:55.000000000 +0000 @@ -0,0 +1,49 @@ +/** @file + * Safe way to include audioclient.h. + */ + +/* + * Copyright (C) 2021 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef IPRT_INCLUDED_win_audioclient_h +#define IPRT_INCLUDED_win_audioclient_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4668) /* ks.h(1978): warning C4668: '_WIN64' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' [SDK 7.1] */ +# if _MSC_VER >= 1900 /*RT_MSC_VER_VC140*/ +# ifdef __cplusplus +# pragma warning(disable:4091) /* ksmedia.h(4356): warning C4091: 'typedef ': ignored on left of '' when no variable is declared [SDK 7.1] */ +# endif +# endif +#endif + +#include + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif /* !IPRT_INCLUDED_win_audioclient_h */ + diff -Nru virtualbox-6.1.16-dfsg/include/iprt/win/d3dkmthk.h virtualbox-6.1.38-dfsg/include/iprt/win/d3dkmthk.h --- virtualbox-6.1.16-dfsg/include/iprt/win/d3dkmthk.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/win/d3dkmthk.h 2022-09-01 13:17:55.000000000 +0000 @@ -0,0 +1,46 @@ +/** @file + * Safe way to include d3dkmthk.h. + */ + +/* + * Copyright (C) 2016-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef IPRT_INCLUDED_win_d3dkmthk_h +#define IPRT_INCLUDED_win_d3dkmthk_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# if _MSC_VER >= 1900 /*RT_MSC_VER_VC140*/ +# pragma warning(disable:4255) /* d3dkmthk.h(2061): warning C4255: 'PFND3DKMT_CHECKEXCLUSIVEOWNERSHIP': no function prototype given: converting '()' to '(void)' */ +# endif +#endif + +#include + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif /* !IPRT_INCLUDED_win_d3dkmthk_h */ + diff -Nru virtualbox-6.1.16-dfsg/include/iprt/win/dbghelp.h virtualbox-6.1.38-dfsg/include/iprt/win/dbghelp.h --- virtualbox-6.1.16-dfsg/include/iprt/win/dbghelp.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/win/dbghelp.h 2022-09-01 13:17:55.000000000 +0000 @@ -0,0 +1,51 @@ +/** @file + * Safe way to include Dbghelp.h. + */ + +/* + * Copyright (C) 2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef IPRT_INCLUDED_win_dbghelp_h +#define IPRT_INCLUDED_win_dbghelp_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef _MSC_VER +/* + * Unfortunately, the Windows.h file in SDK 7.1 is not clean wrt warning C4091 with VCC140+: + * Dbghelp.h(1540): warning C4091: 'typedef ': ignored on left of '' when no variable is declared + * Dbghelp.h(3056): warning C4091: 'typedef ': ignored on left of '' when no variable is declared + */ +# pragma warning(push) +# if _MSC_VER >= 1900 /*RT_MSC_VER_VC140*/ +# pragma warning(disable:4091) +# endif +#endif + +#include + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif /* !IPRT_INCLUDED_win_dbghelp_h */ + diff -Nru virtualbox-6.1.16-dfsg/include/iprt/win/endpointvolume.h virtualbox-6.1.38-dfsg/include/iprt/win/endpointvolume.h --- virtualbox-6.1.16-dfsg/include/iprt/win/endpointvolume.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/win/endpointvolume.h 2022-09-01 13:17:55.000000000 +0000 @@ -0,0 +1,48 @@ +/** @file + * Safe way to include endpointvolume.h. + */ + +/* + * Copyright (C) 2016-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef IPRT_INCLUDED_win_endpointvolume_h +#define IPRT_INCLUDED_win_endpointvolume_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4201) +# pragma warning(disable:4668) /* ks.h(1978): warning C4668: '_WIN64' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' [SDK 7.1] */ +# if _MSC_VER >= 1900 /*RT_MSC_VER_VC140*/ +# pragma warning(disable: 4091) /* v7.1\include\ksmedia.h(4356): warning C4091: 'typedef ': ignored on left of '' when no variable is declared */ +# endif +#endif + +#include + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif /* !IPRT_INCLUDED_win_endpointvolume_h */ + diff -Nru virtualbox-6.1.16-dfsg/include/iprt/win/imagehlp.h virtualbox-6.1.38-dfsg/include/iprt/win/imagehlp.h --- virtualbox-6.1.16-dfsg/include/iprt/win/imagehlp.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/win/imagehlp.h 2022-09-01 13:17:55.000000000 +0000 @@ -0,0 +1,51 @@ +/** @file + * Safe way to include ImageHlp.h. + */ + +/* + * Copyright (C) 2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef IPRT_INCLUDED_win_imagehlp_h +#define IPRT_INCLUDED_win_imagehlp_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef _MSC_VER +/* + * Unfortunately, the ImageHlp.h file in SDK 7.1 is not clean wrt warning C4091 with VCC141: + * ImageHlp.h(1869): warning C4091: 'typedef ': ignored on left of '' when no variable is declared + * ImageHlp.h(3385): warning C4091: 'typedef ': ignored on left of '' when no variable is declared + */ +# pragma warning(push) +# if _MSC_VER >= 1900 /*RT_MSC_VER_VC140*/ +# pragma warning(disable:4091) +# endif +#endif + +#include + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif /* !IPRT_INCLUDED_win_imagehlp_h */ + diff -Nru virtualbox-6.1.16-dfsg/include/iprt/win/mmreg.h virtualbox-6.1.38-dfsg/include/iprt/win/mmreg.h --- virtualbox-6.1.16-dfsg/include/iprt/win/mmreg.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/win/mmreg.h 2022-09-01 13:17:55.000000000 +0000 @@ -0,0 +1,43 @@ +/** @file + * Safe way to include mmreg.h. + */ + +/* + * Copyright (C) 2016-2021 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef IPRT_INCLUDED_win_mmreg_h +#define IPRT_INCLUDED_win_mmreg_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef _MSC_VER +# pragma warning(push) /* Looks like this header messes with warning config, at least in the 7.1 SDK. */ +#endif + +#include + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif /* !IPRT_INCLUDED_win_mmreg_h */ + diff -Nru virtualbox-6.1.16-dfsg/include/iprt/win/shlobj.h virtualbox-6.1.38-dfsg/include/iprt/win/shlobj.h --- virtualbox-6.1.16-dfsg/include/iprt/win/shlobj.h 2020-10-16 16:27:59.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/win/shlobj.h 2022-09-01 13:17:55.000000000 +0000 @@ -32,10 +32,16 @@ #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4668) /* warning C4668: 'USE_SP_ALTPLATFORM_INFO_V1' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ -#endif -#if _MSC_VER >= 1800 /*RT_MSC_VER_VC120*/ -# pragma warning(disable:4005) /* sdk/v7.1/include/sal_supp.h(57) : warning C4005: '__useHeader' : macro redefinition */ -# pragma warning(disable:4255) /* windef.h(227) : warning C4255: 'NEARPROC' : no function prototype given: converting '()' to '(void)' */ +# if _MSC_VER >= 1800 /*RT_MSC_VER_VC120*/ +# pragma warning(disable:4005) /* sdk/v7.1/include/sal_supp.h(57) : warning C4005: '__useHeader' : macro redefinition */ +# pragma warning(disable:4255) /* windef.h(227) : warning C4255: 'NEARPROC' : no function prototype given: converting '()' to '(void)' */ +# endif +# if _MSC_VER >= 1900 /*RT_MSC_VER_VC140*/ +# pragma warning(disable:4091) /* sdk/v7.1/include/shlobj.h(1151): warning C4091: 'typedef ': ignored on left of 'tagGPFIDL_FLAGS' when no variable is declared */ +# endif +# if _MSC_VER >= 1910 /*RT_MSC_VER_VC141*/ +# pragma warning(disable:4768) /* sdk/v7.1/include/shlobj.h(1065): warning C4768: __declspec attributes before linkage specification are ignored */ +# endif #endif #include diff -Nru virtualbox-6.1.16-dfsg/include/iprt/x86.h virtualbox-6.1.38-dfsg/include/iprt/x86.h --- virtualbox-6.1.16-dfsg/include/iprt/x86.h 2020-10-16 16:27:59.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/iprt/x86.h 2022-09-01 13:17:55.000000000 +0000 @@ -38,10 +38,12 @@ # pragma D depends_on library vbox-types.d #endif -/* Workaround for Solaris sys/regset.h defining CS, DS */ +/** Workaround for Solaris sys/regset.h defining CS, DS and sys/controlregs.h + * defining MSR_IA32_FLUSH_CMD */ #ifdef RT_OS_SOLARIS # undef CS # undef DS +# undef MSR_IA32_FLUSH_CMD #endif /** @defgroup grp_rt_x86 x86 Types and Definitions @@ -632,6 +634,8 @@ #define X86_CPUID_STEXT_FEATURE_EDX_FLUSH_CMD RT_BIT_32(28) /** EDX Bit 29 - ARCHCAP - Supports the IA32_ARCH_CAPABILITIES MSR. */ #define X86_CPUID_STEXT_FEATURE_EDX_ARCHCAP RT_BIT_32(29) +/** EDX Bit 31 - SSBD - Supports the SSBD flag in IA32_SPEC_CTRL. */ +#define X86_CPUID_STEXT_FEATURE_EDX_SSBD RT_BIT_32(31) /** @} */ @@ -782,18 +786,34 @@ * @{ */ /** Bit 0 - CLZERO - Clear zero instruction. */ -#define X86_CPUID_AMD_EFEID_EBX_CLZERO RT_BIT_32(0) +#define X86_CPUID_AMD_EFEID_EBX_CLZERO RT_BIT_32(0) /** Bit 1 - IRPerf - Instructions retired count support. */ -#define X86_CPUID_AMD_EFEID_EBX_IRPERF RT_BIT_32(1) +#define X86_CPUID_AMD_EFEID_EBX_IRPERF RT_BIT_32(1) /** Bit 2 - XSaveErPtr - Always XSAVE* and XRSTR* error pointers. */ -#define X86_CPUID_AMD_EFEID_EBX_XSAVE_ER_PTR RT_BIT_32(2) +#define X86_CPUID_AMD_EFEID_EBX_XSAVE_ER_PTR RT_BIT_32(2) /** Bit 4 - RDPRU - Supports the RDPRU instruction. */ -#define X86_CPUID_AMD_EFEID_EBX_RDPRU RT_BIT_32(4) +#define X86_CPUID_AMD_EFEID_EBX_RDPRU RT_BIT_32(4) /** Bit 8 - MCOMMIT - Supports the MCOMMIT instruction. */ -#define X86_CPUID_AMD_EFEID_EBX_MCOMMIT RT_BIT_32(8) +#define X86_CPUID_AMD_EFEID_EBX_MCOMMIT RT_BIT_32(8) /* AMD pipeline length: 9 feature bits ;-) */ /** Bit 12 - IBPB - Supports the IBPB command in IA32_PRED_CMD. */ -#define X86_CPUID_AMD_EFEID_EBX_IBPB RT_BIT_32(12) +#define X86_CPUID_AMD_EFEID_EBX_IBPB RT_BIT_32(12) +/** Bit 14 - IBRS - Supports the IBRS bit in IA32_SPEC_CTRL. */ +#define X86_CPUID_AMD_EFEID_EBX_IBRS RT_BIT_32(14) +/** Bit 15 - STIBP - Supports the STIBP bit in IA32_SPEC_CTRL. */ +#define X86_CPUID_AMD_EFEID_EBX_STIBP RT_BIT_32(15) +/** Bit 16 - IBRS always on mode - IBRS should be set once during boot only. */ +#define X86_CPUID_AMD_EFEID_EBX_IBRS_ALWAYS_ON RT_BIT_32(16) +/** Bit 17 - STIBP always on mode - STIBP should be set once during boot only. */ +#define X86_CPUID_AMD_EFEID_EBX_STIBP_ALWAYS_ON RT_BIT_32(17) +/** Bit 18 - IBRS preferred - IBRS is preferred over software mitigations. */ +#define X86_CPUID_AMD_EFEID_EBX_IBRS_PREFERRED RT_BIT_32(18) +/** Bit 24 - Speculative Store Bypass Disable supported in SPEC_CTL. */ +#define X86_CPUID_AMD_EFEID_EBX_SPEC_CTRL_SSBD RT_BIT_32(24) +/** Bit 25 - Speculative Store Bypass Disable supported in VIRT_SPEC_CTL. */ +#define X86_CPUID_AMD_EFEID_EBX_VIRT_SPEC_CTRL_SSBD RT_BIT_32(25) +/** Bit 26 - Speculative Store Bypass Disable not required. */ +#define X86_CPUID_AMD_EFEID_EBX_NO_SSBD_REQUIRED RT_BIT_32(26) /** @} */ @@ -1212,6 +1232,8 @@ #define MSR_IA32_SPEC_CTRL_F_IBRS RT_BIT_32(0) /** STIBP - Single thread indirect branch predictors. */ #define MSR_IA32_SPEC_CTRL_F_STIBP RT_BIT_32(1) +/** SSBD - Speculative Store Bypass Disable. */ +#define MSR_IA32_SPEC_CTRL_F_SSBD RT_BIT_32(2) /** Prediction command register. * Write only, logical processor scope, no state since write only. */ @@ -1843,6 +1865,16 @@ * host state during world switch. */ #define MSR_K8_VM_HSAVE_PA UINT32_C(0xc0010117) +/** Virtualized speculation control for AMD processors. + * + * Unified interface among different CPU generations. + * The VMM will set any architectural MSRs based on the CPU. + * See "White Paper: AMD64 Technology Speculative Store Bypass Disable 5.21.18" + * (12441_AMD64_SpeculativeStoreBypassDisable_Whitepaper_final.pdf) */ +#define MSR_AMD_VIRT_SPEC_CTL UINT32_C(0xc001011f) +/** Speculative Store Bypass Disable. */ +# define MSR_AMD_VIRT_SPEC_CTL_F_SSBD RT_BIT(2) + /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/Makefile.kmk virtualbox-6.1.38-dfsg/include/Makefile.kmk --- virtualbox-6.1.16-dfsg/include/Makefile.kmk 2020-10-16 16:27:49.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/Makefile.kmk 2022-09-01 13:17:44.000000000 +0000 @@ -25,6 +25,8 @@ VBOX_HDRS_CPP_FEATURES := \ VBox/vmm/hm.h \ VBox/vmm/hm_vmx.h \ + VBox/vmm/pdmaudioinline.h \ + VBox/vmm/pdmaudiohostenuminline.h \ VBox/HostServices/GuestControlSvc.h \ VBox/HostServices/DragAndDropSvc.h \ VBox/HostServices/Service.h \ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/err.h virtualbox-6.1.38-dfsg/include/VBox/err.h --- virtualbox-6.1.16-dfsg/include/VBox/err.h 2020-10-16 16:27:51.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/err.h 2022-09-01 13:17:46.000000000 +0000 @@ -263,6 +263,8 @@ #define VINF_EM_PENDING_R3_IOPORT_WRITE 1160 /** Trick for resuming EMHistoryExec after a VMCPU_FF_IOM is handled. */ #define VINF_EM_RESUME_R3_HISTORY_EXEC 1161 +/** Emulate split-lock access on SMP. */ +#define VINF_EM_EMULATE_SPLIT_LOCK 1162 /** @} */ @@ -606,6 +608,8 @@ #define VERR_PGM_MODE_IPE (-1686) /** Shadow mode 'none' internal error. */ #define VERR_PGM_SHW_NONE_IPE (-1687) +/** Attemted illegal operation in simplified memory management mode. */ +#define VERR_PGM_NOT_SUPPORTED_FOR_NEM_MODE (-1689) /** @} */ @@ -1231,6 +1235,8 @@ #define VERR_VMM_SMAP_BUT_AC_CLEAR (-2717) /** NEM returned in the wrong state. */ #define VERR_VMM_WRONG_NEM_VMCPU_STATE (-2718) +/** Got back from vmmR0CallRing3SetJmp with the context hook still enabled. */ +#define VERR_VMM_CONTEXT_HOOK_STILL_ENABLED (-2719) /** @} */ @@ -1552,7 +1558,10 @@ #define VINF_HGCM_SAVE_STATE (2906) /** Requested service already exists. */ #define VERR_HGCM_SERVICE_EXISTS (-2907) - +/** Too many clients for the service. */ +#define VERR_HGCM_TOO_MANY_CLIENTS (-2908) +/** Too many calls to the service from a client. */ +#define VERR_HGCM_TOO_MANY_CLIENT_CALLS (-2909) /** @} */ @@ -2847,6 +2856,13 @@ * required hardware, or that the requested stream configuration * is not supported by the host backend. */ #define VERR_AUDIO_STREAM_COULD_NOT_CREATE (-6606) +/** Generic audio device enumeration error. */ +#define VERR_AUDIO_ENUMERATION_FAILED (-6607) +/** Asynchronous stream initialization still on-going. */ +#define VERR_AUDIO_STREAM_INIT_IN_PROGRESS (-6608) +/** Special PDMIHOSTAUDIO::pfnStreamCreate return value for triggering + * calling of PDMIHOSTAUDIO::pfnStreamInitAsync on a worker thread. */ +#define VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED (6609) /** @} */ @@ -2898,6 +2914,8 @@ #define VERR_NEM_MISSING_KERNEL_API_4 (-6814) /** NEM init failed because of missing kernel API (\#5). */ #define VERR_NEM_MISSING_KERNEL_API_5 (-6815) +/** NEM failed to query dirty page bitmap. */ +#define VERR_NEM_QUERY_DIRTY_BITMAP_FAILED (-6816) /** NEM internal processing error \#0. */ #define VERR_NEM_IPE_0 (-6890) diff -Nru virtualbox-6.1.16-dfsg/include/VBox/ExtPack/ExtPack.h virtualbox-6.1.38-dfsg/include/VBox/ExtPack/ExtPack.h --- virtualbox-6.1.16-dfsg/include/VBox/ExtPack/ExtPack.h 2020-10-16 16:27:49.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/ExtPack/ExtPack.h 2022-09-01 13:17:44.000000000 +0000 @@ -319,11 +319,14 @@ DECLR3CALLBACKMEMBER(int, pfnReserved5,(PCVBOXEXTPACKHLP pHlp)); /**< Reserved for minor structure revisions. */ DECLR3CALLBACKMEMBER(int, pfnReserved6,(PCVBOXEXTPACKHLP pHlp)); /**< Reserved for minor structure revisions. */ + /** Reserved for minor structure revisions. */ + uint32_t uReserved7; + /** End of structure marker (VBOXEXTPACKHLP_VERSION). */ uint32_t u32EndMarker; } VBOXEXTPACKHLP; /** Current version of the VBOXEXTPACKHLP structure. */ -#define VBOXEXTPACKHLP_VERSION RT_MAKE_U32(3, 0) +#define VBOXEXTPACKHLP_VERSION RT_MAKE_U32(0, 3) /** Pointer to the extension pack callback table. */ @@ -424,7 +427,7 @@ uint32_t u32EndMarker; } VBOXEXTPACKREG; /** Current version of the VBOXEXTPACKREG structure. */ -#define VBOXEXTPACKREG_VERSION RT_MAKE_U32(2, 0) +#define VBOXEXTPACKREG_VERSION RT_MAKE_U32(0, 2) /** @@ -537,7 +540,7 @@ uint32_t u32EndMarker; } VBOXEXTPACKVMREG; /** Current version of the VBOXEXTPACKVMREG structure. */ -#define VBOXEXTPACKVMREG_VERSION RT_MAKE_U32(2, 0) +#define VBOXEXTPACKVMREG_VERSION RT_MAKE_U32(0, 2) /** diff -Nru virtualbox-6.1.16-dfsg/include/VBox/GuestHost/DragAndDrop.h virtualbox-6.1.38-dfsg/include/VBox/GuestHost/DragAndDrop.h --- virtualbox-6.1.16-dfsg/include/VBox/GuestHost/DragAndDrop.h 2020-10-16 16:27:49.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/GuestHost/DragAndDrop.h 2022-09-01 13:17:45.000000000 +0000 @@ -34,6 +34,8 @@ #include #include +#include + /** DnDURIDroppedFiles flags. */ typedef uint32_t DNDURIDROPPEDFILEFLAGS; @@ -89,6 +91,8 @@ int DnDDroppedFilesReset(PDNDDROPPEDFILES pDF, bool fDelete); int DnDDroppedFilesRollback(PDNDDROPPEDFILES pDF); +const char *DnDActionToStr(VBOXDNDACTION uAction); + bool DnDMIMEHasFileURLs(const char *pcszFormat, size_t cchFormatMax); bool DnDMIMENeedsDropDir(const char *pcszFormat, size_t cchFormatMax); diff -Nru virtualbox-6.1.16-dfsg/include/VBox/GuestHost/SharedClipboard-win.h virtualbox-6.1.38-dfsg/include/VBox/GuestHost/SharedClipboard-win.h --- virtualbox-6.1.16-dfsg/include/VBox/GuestHost/SharedClipboard-win.h 2020-10-16 16:27:49.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/GuestHost/SharedClipboard-win.h 2022-09-01 13:17:45.000000000 +0000 @@ -110,7 +110,11 @@ HWND hWnd; /** Window handle which is next to us in the clipboard chain. */ HWND hWndNextInChain; - /** Window handle of the clipboard owner *if* we are the owner. */ + /** Window handle of the clipboard owner *if* we are the owner. + * @todo r=bird: Ignore the misleading statement above. This is only set to + * NULL by the initialization code and then it's set to the clipboard owner + * after we announce data to the clipboard. So, essentially this will be our + * windows handle or NULL. End of story. */ HWND hWndClipboardOwnerUs; /** Structure for maintaining the new clipboard API. */ SHCLWINAPINEW newAPI; @@ -151,7 +155,7 @@ int SharedClipboardWinHandleWMRenderAllFormats(PSHCLWINCTX pWinCtx, HWND hWnd); int SharedClipboardWinHandleWMTimer(PSHCLWINCTX pWinCtx); -int SharedClipboardWinAnnounceFormats(PSHCLWINCTX pWinCtx, SHCLFORMATS fFormats); +int SharedClipboardWinClearAndAnnounceFormats(PSHCLWINCTX pWinCtx, SHCLFORMATS fFormats, HWND hWnd); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS int SharedClipboardWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); void SharedClipboardWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); diff -Nru virtualbox-6.1.16-dfsg/include/VBox/GuestHost/SharedClipboard-x11.h virtualbox-6.1.38-dfsg/include/VBox/GuestHost/SharedClipboard-x11.h --- virtualbox-6.1.16-dfsg/include/VBox/GuestHost/SharedClipboard-x11.h 2020-10-16 16:27:49.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/GuestHost/SharedClipboard-x11.h 2022-09-01 13:17:45.000000000 +0000 @@ -36,6 +36,18 @@ #include /** + * The maximum number of simultaneous connections to shared clipboard service. + * This constant limits amount of GUEST -> HOST connections to shared clipboard host service + * for X11 host only. Once amount of connections reaches this number, all the + * further attempts to CONNECT will be dropped on an early stage. Possibility to connect + * is available again after one of existing connections is closed by DISCONNECT call. + */ +#define VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX (20) + +/** Enables the Xt busy / update handling. */ +#define VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY 1 + +/** * Enumeration for all clipboard formats which we support on X11. */ typedef enum _SHCLX11FMT @@ -79,11 +91,14 @@ PSHCLCONTEXT pFrontend; /** Is an X server actually available? */ bool fHaveX11; - /** The X Toolkit application context structure */ - XtAppContext appContext; + /** The X Toolkit application context structure. */ + XtAppContext pAppContext; /** We have a separate thread to wait for window and clipboard events. */ RTTHREAD Thread; + /** Flag indicating that the thread is in a started state. */ + bool fThreadStarted; + /** The X Toolkit widget which we use as our clipboard client. It is never made visible. */ Widget pWidget; @@ -116,6 +131,7 @@ void (*fixesSelectInput)(Display *, Window, Atom, unsigned long); /** The first XFixes event number. */ int fixesEventBase; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY /** XtGetSelectionValue on some versions of libXt isn't re-entrant * so block overlapping requests on this flag. */ bool fXtBusy; @@ -123,6 +139,7 @@ * an update later - the first callback should check and clear this flag * before processing the callback event. */ bool fXtNeedsUpdate; +#endif } SHCLX11CTX, *PSHCLX11CTX; /** @name Shared Clipboard APIs for X11. diff -Nru virtualbox-6.1.16-dfsg/include/VBox/hgcmsvc.h virtualbox-6.1.38-dfsg/include/VBox/hgcmsvc.h --- virtualbox-6.1.16-dfsg/include/VBox/hgcmsvc.h 2020-10-16 16:27:51.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/hgcmsvc.h 2022-09-01 13:17:46.000000000 +0000 @@ -80,8 +80,11 @@ * parameter (VBox 6.0). * 6.5->7.1 Because pfnNotify was added (VBox 6.0). * 7.1->8.1 Because pfnCancelled & pfnIsCallCancelled were added (VBox 6.0). + * 8.1->9.1 Because pfnDisconnectClient was (temporarily) removed, and + * acMaxClients and acMaxCallsPerClient added (VBox 6.1.26). + * 9.1->10.1 Because pfnDisconnectClient was added back (VBox 6.1.28). */ -#define VBOX_HGCM_SVC_VERSION_MAJOR (0x0008) +#define VBOX_HGCM_SVC_VERSION_MAJOR (0x000a) #define VBOX_HGCM_SVC_VERSION_MINOR (0x0001) #define VBOX_HGCM_SVC_VERSION ((VBOX_HGCM_SVC_VERSION_MAJOR << 16) + VBOX_HGCM_SVC_VERSION_MINOR) @@ -98,8 +101,27 @@ void *pvInstance; - /** The service disconnects the client. */ - DECLR3CALLBACKMEMBER(void, pfnDisconnectClient, (void *pvInstance, uint32_t u32ClientID)); + /** + * The service disconnects the client. + * + * This can only be used during VBOXHGCMSVCFNTABLE::pfnConnect or + * VBOXHGCMSVCFNTABLE::pfnDisconnect and will fail if called out side that + * context. Using this on the new client during VBOXHGCMSVCFNTABLE::pfnConnect + * is not advisable, it would be better to just return a failure status for that + * and it will be done automatically. (It is not possible to call this method + * on a client passed to VBOXHGCMSVCFNTABLE::pfnDisconnect.) + * + * There will be no VBOXHGCMSVCFNTABLE::pfnDisconnect callback for a client + * diconnected in this manner. + * + * @returns VBox status code. + * @retval VERR_NOT_FOUND if the client ID was not found. + * @retval VERR_INVALID_CONTEXT if not called during connect or disconnect. + * + * @remarks Used by external parties, so don't remove just because we don't use + * it ourselves. + */ + DECLR3CALLBACKMEMBER(int, pfnDisconnectClient, (void *pvInstance, uint32_t idClient)); /** * Check if the @a callHandle is for a call restored and re-submitted from saved state. @@ -593,6 +615,14 @@ HGCMNOTIFYEVENT_32BIT_HACK = 0x7fffffff } HGCMNOTIFYEVENT; +/** @name HGCM_CLIENT_CATEGORY_XXX - Client categories + * @{ */ +#define HGCM_CLIENT_CATEGORY_KERNEL 0 /**< Guest kernel mode and legacy client. */ +#define HGCM_CLIENT_CATEGORY_ROOT 1 /**< Guest root or admin client. */ +#define HGCM_CLIENT_CATEGORY_USER 2 /**< Regular guest user client. */ +#define HGCM_CLIENT_CATEGORY_MAX 3 /**< Max number of categories. */ +/** @} */ + /** The Service DLL entry points. * @@ -609,23 +639,28 @@ * @{ */ /** Size of the structure. */ - uint32_t cbSize; + uint32_t cbSize; - /** Version of the structure, including the helpers. */ - uint32_t u32Version; + /** Version of the structure, including the helpers. (VBOX_HGCM_SVC_VERSION) */ + uint32_t u32Version; - PVBOXHGCMSVCHELPERS pHelpers; + PVBOXHGCMSVCHELPERS pHelpers; /** @} */ /** @name Filled in by the service. * @{ */ /** Size of client information the service want to have. */ - uint32_t cbClient; -#if ARCH_BITS == 64 - /** Ensure that the following pointers are properly aligned on 64-bit system. */ - uint32_t u32Alignment0; -#endif + uint32_t cbClient; + + /** The maximum number of clients per category. Leave entries as zero for defaults. */ + uint32_t acMaxClients[HGCM_CLIENT_CATEGORY_MAX]; + /** The maximum number of concurrent calls per client for each category. + * Leave entries as as zero for default. */ + uint32_t acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_MAX]; + /** The HGCM_CLIENT_CATEGORY_XXX value for legacy clients. + * Defaults to HGCM_CLIENT_CATEGORY_KERNEL. */ + uint32_t idxLegacyClientCategory; /** Uninitialize service */ DECLR3CALLBACKMEMBER(int, pfnUnload, (void *pvService)); diff -Nru virtualbox-6.1.16-dfsg/include/VBox/ostypes.h virtualbox-6.1.38-dfsg/include/VBox/ostypes.h --- virtualbox-6.1.16-dfsg/include/VBox/ostypes.h 2020-10-16 16:27:51.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/ostypes.h 2022-09-01 13:17:47.000000000 +0000 @@ -47,6 +47,8 @@ { VBOXOSTYPE_Unknown = 0, VBOXOSTYPE_Unknown_x64 = 0x00100, + /** @name DOS and it's descendants + * @{ */ VBOXOSTYPE_DOS = 0x10000, VBOXOSTYPE_Win31 = 0x15000, VBOXOSTYPE_Win9x = 0x20000, @@ -77,12 +79,17 @@ VBOXOSTYPE_Win10_x64 = 0x3B100, VBOXOSTYPE_Win2k16_x64 = 0x3C100, VBOXOSTYPE_Win2k19_x64 = 0x3D100, + VBOXOSTYPE_Win11_x64 = 0x3E100, VBOXOSTYPE_OS2 = 0x40000, VBOXOSTYPE_OS2Warp3 = 0x41000, VBOXOSTYPE_OS2Warp4 = 0x42000, VBOXOSTYPE_OS2Warp45 = 0x43000, VBOXOSTYPE_ECS = 0x44000, + VBOXOSTYPE_ArcaOS = 0x45000, VBOXOSTYPE_OS21x = 0x48000, + /** @} */ + /** @name Unixy related OSes + * @{ */ VBOXOSTYPE_Linux = 0x50000, VBOXOSTYPE_Linux_x64 = 0x50100, VBOXOSTYPE_Linux22 = 0x51000, @@ -119,8 +126,10 @@ VBOXOSTYPE_NetBSD = 0x62000, VBOXOSTYPE_NetBSD_x64 = 0x62100, VBOXOSTYPE_Netware = 0x70000, - VBOXOSTYPE_Solaris = 0x80000, - VBOXOSTYPE_Solaris_x64 = 0x80100, + VBOXOSTYPE_Solaris = 0x80000, // Solaris 10U7 (5/09) and earlier + VBOXOSTYPE_Solaris_x64 = 0x80100, // Solaris 10U7 (5/09) and earlier + VBOXOSTYPE_Solaris10U8_or_later = 0x80001, + VBOXOSTYPE_Solaris10U8_or_later_x64 = 0x80101, VBOXOSTYPE_OpenSolaris = 0x81000, VBOXOSTYPE_OpenSolaris_x64 = 0x81100, VBOXOSTYPE_Solaris11_x64 = 0x82100, @@ -137,14 +146,20 @@ VBOXOSTYPE_MacOS1011_x64 = 0xB7100, VBOXOSTYPE_MacOS1012_x64 = 0xB8100, VBOXOSTYPE_MacOS1013_x64 = 0xB9100, + /** @} */ + /** @name Other OSes and stuff + * @{ */ VBOXOSTYPE_JRockitVE = 0xC0000, VBOXOSTYPE_Haiku = 0xD0000, VBOXOSTYPE_Haiku_x64 = 0xD0100, VBOXOSTYPE_VBoxBS_x64 = 0xE0100, + /** @} */ + /** The bit number which indicates 64-bit or 32-bit. */ #define VBOXOSTYPE_x64_BIT 8 /** The mask which indicates 64-bit. */ - VBOXOSTYPE_x64 = 1 << VBOXOSTYPE_x64_BIT, + VBOXOSTYPE_x64 = 1 << VBOXOSTYPE_x64_BIT, + /** The usual 32-bit hack. */ VBOXOSTYPE_32BIT_HACK = 0x7fffffff } VBOXOSTYPE; diff -Nru virtualbox-6.1.16-dfsg/include/VBox/settings.h virtualbox-6.1.38-dfsg/include/VBox/settings.h --- virtualbox-6.1.16-dfsg/include/VBox/settings.h 2020-10-16 16:27:51.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/settings.h 2022-09-01 13:17:47.000000000 +0000 @@ -544,7 +544,7 @@ struct RecordingScreenSettings { - RecordingScreenSettings(); + RecordingScreenSettings(uint32_t idScreen = UINT32_MAX); virtual ~RecordingScreenSettings(); @@ -554,17 +554,23 @@ bool isFeatureEnabled(RecordingFeature_T enmFeature) const; + static const char *getDefaultOptions(void); + bool operator==(const RecordingScreenSettings &d) const; + /** Screen ID. + * UINT32_MAX if not set. */ + uint32_t idScreen; /** Whether to record this screen or not. */ bool fEnabled; // requires settings version 1.14 (VirtualBox 4.3) /** Destination to record to. */ - RecordingDestination_T enmDest; /** @todo Implement with next settings version bump. */ + RecordingDestination_T enmDest; /** Which features are enable or not. */ RecordingFeatureMap featureMap; /** @todo Implement with next settings version bump. */ /** Maximum time (in s) to record. If set to 0, no time limit is set. */ uint32_t ulMaxTimeS; // requires settings version 1.14 (VirtualBox 4.3) - /** Options string for hidden / advanced / experimental features. */ + /** Options string for hidden / advanced / experimental features. + * Use RecordingScreenSettings::getDefaultOptions(). */ com::Utf8Str strOptions; // new since VirtualBox 5.2. /** @@ -621,16 +627,34 @@ : ulMaxSizeMB(0) { } /** Maximum size (in MB) the file is allowed to have. - * When reaching the limit, recording will stop. */ + * When reaching the limit, recording will stop. 0 means no limit. */ uint32_t ulMaxSizeMB; // requires settings version 1.14 (VirtualBox 4.3) - /** Absolute file name path to use for recording. */ + /** Absolute file name path to use for recording. + * When empty, this is considered as being the default setting. */ com::Utf8Str strName; // requires settings version 1.14 (VirtualBox 4.3) } File; }; /** Map for keeping settings per virtual screen. * The key specifies the screen ID. */ -typedef std::map RecordingScreenMap; +typedef std::map RecordingScreenSettingsMap; + +/** + * Common recording settings, shared among all per-screen recording settings. + */ +struct RecordingCommonSettings +{ + RecordingCommonSettings(); + + void applyDefaults(void); + + bool areDefaultSettings(void) const; + + bool operator==(const RecordingCommonSettings &d) const; + + /** Whether recording as a whole is enabled or disabled. */ + bool fEnabled; // requires settings version 1.14 (VirtualBox 4.3) +}; /** * NOTE: If you add any fields in here, you must update a) the constructor and b) @@ -645,13 +669,13 @@ bool areDefaultSettings(void) const; - bool operator==(const RecordingSettings &d) const; + bool operator==(const RecordingSettings &that) const; - /** Whether recording as a whole is enabled or disabled. */ - bool fEnabled; // requires settings version 1.14 (VirtualBox 4.3) + /** Common settings for all per-screen recording settings. */ + RecordingCommonSettings common; /** Map of handled recording screen settings. * The key specifies the screen ID. */ - RecordingScreenMap mapScreens; + RecordingScreenSettingsMap mapScreens; }; /** @@ -1145,7 +1169,6 @@ VRDESettings vrdeSettings; BIOSSettings biosSettings; - RecordingSettings recordingSettings; GraphicsAdapter graphicsAdapter; USB usbSettings; NetworkAdaptersList llNetworkAdapters; @@ -1220,19 +1243,20 @@ bool operator==(const Snapshot &s) const; - com::Guid uuid; - com::Utf8Str strName, - strDescription; // optional - RTTIMESPEC timestamp; + com::Guid uuid; + com::Utf8Str strName, + strDescription; // optional + RTTIMESPEC timestamp; - com::Utf8Str strStateFile; // for online snapshots only + com::Utf8Str strStateFile; // for online snapshots only - Hardware hardware; + Hardware hardware; - Debugging debugging; - Autostart autostart; + Debugging debugging; + Autostart autostart; + RecordingSettings recordingSettings; - SnapshotsList llChildSnapshots; + SnapshotsList llChildSnapshots; static const struct Snapshot Empty; }; @@ -1291,6 +1315,7 @@ MediaRegistry mediaRegistry; Debugging debugging; Autostart autostart; + RecordingSettings recordingSettings; StringsMap mapExtraDataItems; @@ -1338,6 +1363,7 @@ void readTeleporter(const xml::ElementNode *pElmTeleporter, MachineUserData *pUserData); void readDebugging(const xml::ElementNode *pElmDbg, Debugging *pDbg); void readAutostart(const xml::ElementNode *pElmAutostart, Autostart *pAutostart); + void readRecordingSettings(const xml::ElementNode &elmRecording, uint32_t cMonitors, RecordingSettings &recording); void readGroups(const xml::ElementNode *elmGroups, StringsList *pllGroups); bool readSnapshot(const com::Guid &curSnapshotUuid, uint32_t depth, const xml::ElementNode &elmSnapshot, Snapshot &snap); void convertOldOSType_pre1_5(com::Utf8Str &str); @@ -1351,6 +1377,7 @@ std::list *pllElementsWithUuidAttributes); void buildDebuggingXML(xml::ElementNode *pElmParent, const Debugging *pDbg); void buildAutostartXML(xml::ElementNode *pElmParent, const Autostart *pAutostart); + void buildRecordingXML(xml::ElementNode &elmParent, const RecordingSettings &recording); void buildGroupsXML(xml::ElementNode *pElmParent, const StringsList *pllGroups); void buildSnapshotXML(uint32_t depth, xml::ElementNode &elmParent, const Snapshot &snap); diff -Nru virtualbox-6.1.16-dfsg/include/VBox/sup.h virtualbox-6.1.38-dfsg/include/VBox/sup.h --- virtualbox-6.1.16-dfsg/include/VBox/sup.h 2020-10-16 16:27:51.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/sup.h 2022-09-01 13:17:47.000000000 +0000 @@ -2093,6 +2093,18 @@ int32_t *piNativeRc); /** + * Writes to the debugger and/or kernel log, va_list version. + * + * The length of the formatted message is somewhat limited, so keep things short + * and to the point. + * + * @returns Number of bytes written, mabye. + * @param pszFormat IPRT format string. + * @param va Arguments referenced by the format string. + */ +SUPR0DECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0); + +/** * Writes to the debugger and/or kernel log. * * The length of the formatted message is somewhat limited, so keep things short @@ -2102,7 +2114,26 @@ * @param pszFormat IPRT format string. * @param ... Arguments referenced by the format string. */ -SUPR0DECL(int) SUPR0Printf(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); +#if defined(__GNUC__) && defined(__inline__) +/* Define it as static for GCC as it cannot inline functions using va_start() anyway, + and linux redefines __inline__ to always inlining forcing gcc to issue an error. */ +static int __attribute__((__unused__)) +#else +DECLINLINE(int) +#endif +RT_IPRT_FORMAT_ATTR(1, 2) SUPR0Printf(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + SUPR0PrintfV(pszFormat, va); + va_end(va); + return 0; +} + +/* HACK ALERT! See above. */ +#ifdef SUPR0PRINTF_UNDO_INLINE_HACK +# define __inline__ inline +#endif /** * Returns configuration flags of the host kernel. @@ -2111,6 +2142,26 @@ */ SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void); +/** + * Notification from R0 VMM prior to loading the guest-FPU register state. + * + * @returns Whether the host-FPU register state has been saved by the host kernel. + * @param fCtxHook Whether thread-context hooks are enabled. + * + * @remarks Called with preemption disabled. + */ +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook); + +/** + * Notification from R0 VMM after saving the guest-FPU register state (and + * potentially restoring the host-FPU register state) in ring-0. + * + * @param fCtxHook Whether thread-context hooks are enabled. + * + * @remarks Called with preemption disabled. + */ +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook); + /** @copydoc RTLogGetDefaultInstanceEx * @remarks To allow overriding RTLogGetDefaultInstanceEx locally. */ SUPR0DECL(struct RTLOGGER *) SUPR0GetDefaultLogInstanceEx(uint32_t fFlagsAndGroup); diff -Nru virtualbox-6.1.16-dfsg/include/VBox/SUPR0StackWrapper.mac virtualbox-6.1.38-dfsg/include/VBox/SUPR0StackWrapper.mac --- virtualbox-6.1.16-dfsg/include/VBox/SUPR0StackWrapper.mac 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/SUPR0StackWrapper.mac 2022-09-01 13:17:45.000000000 +0000 @@ -0,0 +1,170 @@ +; $Id: SUPR0StackWrapper.mac $ +;; @file +; SUP - Support Library, ring-0 stack switching wrappers. +; + +; +; Copyright (C) 2006-2021 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + +%ifndef ___VBox_SUPR0StackWrapper_mac +%define ___VBox_SUPR0StackWrapper_mac + +%include "VBox/asmdefs.mac" + +;; VBox's own Stack +%define SUPR0STACKINFO_MAGIC0 0786f4256h ; VBox +%define SUPR0STACKINFO_MAGIC1 06f207327h ; 's o +%define SUPR0STACKINFO_MAGIC2 053206e77h ; wn S +%define SUPR0STACKINFO_MAGIC3 06b636174h ; tack + +;; +; Stack info located before the start of the stack, at the top of the page. +; +struc SUPR0STACKINFO + .magic0 resd 1 + .magic1 resd 1 + .magic2 resd 1 + .magic3 resd 1 + .pResumeKernelStack resq 1 + .pSelf resq 1 +endstruc + +;; +; Number of parameters in GPRs and the spill area size. +%ifdef RT_ARCH_AMD64 + %ifdef ASM_CALL64_MSC + %define SUPR0_GRP_PARAMS 4 + %define SUPR0_SPILL_AREA 4 + %else + %define SUPR0_GRP_PARAMS 6 + %define SUPR0_SPILL_AREA 0 + %endif +%else + %define SUPR0_GRP_PARAMS 0 + %define SUPR0_SPILL_AREA 0 +%endif + +;; +; Generic stack switching wrapper. +; +; @param %1 The name +; @param %2 Number of arguments. +; +%macro SUPR0StackWrapperGeneric 2 +BEGINCODE +extern NAME(StkBack_ %+ %1) + +BEGINPROC %1 +%ifdef RT_ARCH_AMD64 ; Only for amd64 for now. + ; + ; Check for the stack info. + ; + mov rax, rsp + or rax, 0fffh + sub rax, SUPR0STACKINFO_size - 1 + + ; Check for the magic. + cmp dword [rax + SUPR0STACKINFO.magic0], SUPR0STACKINFO_MAGIC0 + jne .regular + cmp dword [rax + SUPR0STACKINFO.magic1], SUPR0STACKINFO_MAGIC1 + jne .regular + cmp dword [rax + SUPR0STACKINFO.magic2], SUPR0STACKINFO_MAGIC2 + jne .regular + cmp dword [rax + SUPR0STACKINFO.magic3], SUPR0STACKINFO_MAGIC3 + jne .regular + + ; Verify the self pointer. + cmp [rax + SUPR0STACKINFO.pSelf], rax + jne .regular + + ; + ; Perform a stack switch. We set up a RBP frame on the old stack so we + ; can use leave to restore the incoming stack upon return. + ; + push rbp + mov rbp, rsp + + ; The actual switch. + mov r10, 0ffffffffffffffe0h ; shuts up warning on 'and rsp, 0ffffffffffffffe0h' + and r10, [rax + SUPR0STACKINFO.pResumeKernelStack] + mov rsp, r10 + + ; + ; Copy over stack arguments. + ; + ; Note! We always copy 2-3 extra arguments (%2 + 2) just in case someone got + ; the argument count wrong. + ; +%if (%2 + 2) > SUPR0_GRP_PARAMS + 18 + %error too many parameters + %fatal too many parameters +%endif +%if (%2 + 2) > SUPR0_GRP_PARAMS + 16 + push qword [rbp + 98h] + push qword [rbp + 90h] +%endif +%if (%2 + 2) > SUPR0_GRP_PARAMS + 14 + push qword [rbp + 88h] + push qword [rbp + 80h] +%endif +%if (%2 + 2) > SUPR0_GRP_PARAMS + 12 + push qword [rbp + 78h] + push qword [rbp + 70h] +%endif +%if (%2 + 2) > SUPR0_GRP_PARAMS + 10 + push qword [rbp + 68h] + push qword [rbp + 60h] +%endif +%if (%2 + 2) > SUPR0_GRP_PARAMS + 8 + push qword [rbp + 58h] + push qword [rbp + 50h] +%endif +%if (%2 + 2) > SUPR0_GRP_PARAMS + 6 + push qword [rbp + 48h] + push qword [rbp + 40h] +%endif +%if (%2 + 2) > SUPR0_GRP_PARAMS + 4 + push qword [rbp + 38h] + push qword [rbp + 30h] +%endif +%if ((%2 + 2) > SUPR0_GRP_PARAMS + 2) || (SUPR0_SPILL_AREA > 2) + push qword [rbp + 28h] + push qword [rbp + 20h] +%endif +%if ((%2 + 2) > SUPR0_GRP_PARAMS) || (SUPR0_SPILL_AREA > 0) + push qword [rbp + 18h] + push qword [rbp + 10h] +%endif + + call NAME(StkBack_ %+ %1) + + leave + ret + +.regular: +%endif ; RT_ARCH_AMD64 + jmp NAME(StkBack_ %+ %1) +ENDPROC %1 +%endmacro + + +%endif + diff -Nru virtualbox-6.1.16-dfsg/include/VBox/VBoxGuest.h virtualbox-6.1.38-dfsg/include/VBox/VBoxGuest.h --- virtualbox-6.1.16-dfsg/include/VBox/VBoxGuest.h 2020-10-16 16:27:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/VBoxGuest.h 2022-09-01 13:17:45.000000000 +0000 @@ -125,7 +125,7 @@ #elif defined(RT_OS_SOLARIS) /* No automatic buffering, size limited to 255 bytes => use VBGLBIGREQ for everything. */ # include -# define VBGL_IOCTL_CODE_SIZE(Function, Size) _IOWRN('V', (Function), sizeof(VBGLREQHDR)) +# define VBGL_IOCTL_CODE_SIZE(Function, Size) ((uintptr_t)(_IOWRN('V', (Function), sizeof(VBGLREQHDR)))) # define VBGL_IOCTL_CODE_BIG(Function) _IOWRN('V', (Function), sizeof(VBGLREQHDR)) # define VBGL_IOCTL_CODE_FAST(Function) _IO( 'F', (Function)) # define VBGL_IOCTL_CODE_STRIPPED(a_uIOCtl) (a_uIOCtl) diff -Nru virtualbox-6.1.16-dfsg/include/VBox/VBoxGuestLib.h virtualbox-6.1.38-dfsg/include/VBox/VBoxGuestLib.h --- virtualbox-6.1.16-dfsg/include/VBox/VBoxGuestLib.h 2020-10-16 16:27:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/VBoxGuestLib.h 2022-09-01 13:17:45.000000000 +0000 @@ -684,7 +684,7 @@ VBGLR3DECL(int) VbglR3ClipboardReadData(HGCMCLIENTID idClient, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcb); VBGLR3DECL(int) VbglR3ClipboardReadDataEx(PVBGLR3SHCLCMDCTX pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbRead); VBGLR3DECL(int) VbglR3ClipboardWriteData(HGCMCLIENTID idClient, uint32_t fFormat, void *pv, uint32_t cb); -VBGLR3DECL(int) VbglR3ClipboardWriteDataEx(PVBGLR3SHCLCMDCTX pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); +VBGLR3DECL(int) VbglR3ClipboardWriteDataEx(PVBGLR3SHCLCMDCTX pCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData); VBGLR3DECL(int) VbglR3ClipboardReportFormats(HGCMCLIENTID idClient, uint32_t fFormats); VBGLR3DECL(int) VbglR3ClipboardConnectEx(PVBGLR3SHCLCMDCTX pCtx, uint64_t fGuestFeatures); diff -Nru virtualbox-6.1.16-dfsg/include/VBox/VBoxGuestLibSharedFoldersInline.h virtualbox-6.1.38-dfsg/include/VBox/VBoxGuestLibSharedFoldersInline.h --- virtualbox-6.1.16-dfsg/include/VBox/VBoxGuestLibSharedFoldersInline.h 2020-10-16 16:27:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/VBoxGuestLibSharedFoldersInline.h 2022-09-01 13:17:45.000000000 +0000 @@ -969,6 +969,7 @@ VBGLIOCIDCHGCMFASTCALL Hdr; VMMDevHGCMCall Call; VBoxSFParmRead Parms; + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abData[RT_FLEXIBLE_ARRAY]; } VBOXSFREADEMBEDDEDREQ; @@ -1116,6 +1117,7 @@ VBGLIOCIDCHGCMFASTCALL Hdr; VMMDevHGCMCall Call; VBoxSFParmWrite Parms; + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abData[RT_FLEXIBLE_ARRAY]; } VBOXSFWRITEEMBEDDEDREQ; diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/cpum.h virtualbox-6.1.38-dfsg/include/VBox/vmm/cpum.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/cpum.h 2020-10-16 16:27:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/cpum.h 2022-09-01 13:17:47.000000000 +0000 @@ -151,9 +151,16 @@ kCpumMicroarch_Intel_Core7_CoffeeLake, kCpumMicroarch_Intel_Core7_WhiskeyLake, kCpumMicroarch_Intel_Core7_CascadeLake, - kCpumMicroarch_Intel_Core7_CannonLake, - kCpumMicroarch_Intel_Core7_IceLake, - kCpumMicroarch_Intel_Core7_TigerLake, + kCpumMicroarch_Intel_Core7_CannonLake, /**< Limited 10nm. */ + kCpumMicroarch_Intel_Core7_CometLake, /**< 10th gen, 14nm desktop + high power mobile. */ + kCpumMicroarch_Intel_Core7_IceLake, /**< 10th gen, 10nm mobile and some Xeons. Actually 'Sunny Cove' march. */ + kCpumMicroarch_Intel_Core7_SunnyCove = kCpumMicroarch_Intel_Core7_IceLake, + kCpumMicroarch_Intel_Core7_RocketLake, /**< 11th gen, 14nm desktop + high power mobile. Aka 'Cypress Cove', backport of 'Willow Cove' to 14nm. */ + kCpumMicroarch_Intel_Core7_CypressCove = kCpumMicroarch_Intel_Core7_RocketLake, + kCpumMicroarch_Intel_Core7_TigerLake, /**< 11th gen, 10nm mobile. Actually 'Willow Cove' march. */ + kCpumMicroarch_Intel_Core7_WillowCove = kCpumMicroarch_Intel_Core7_TigerLake, + kCpumMicroarch_Intel_Core7_AlderLake, /**< 12th gen, 10nm all platforms(?). */ + kCpumMicroarch_Intel_Core7_SapphireRapids, /**< 12th? gen, 10nm server? */ kCpumMicroarch_Intel_Core7_End, kCpumMicroarch_Intel_Atom_First, @@ -1414,6 +1421,7 @@ VMMDECL(void) CPUMGetGuestCpuId(PVMCPUCC pVCpu, uint32_t iLeaf, uint32_t iSubLeaf, uint32_t *pEax, uint32_t *pEbx, uint32_t *pEcx, uint32_t *pEdx); VMMDECL(uint64_t) CPUMGetGuestEFER(PCVMCPU pVCpu); +VMM_INT_DECL(uint64_t) CPUMGetGuestIa32FeatCtrl(PCVMCPUCC pVCpu); VMM_INT_DECL(uint64_t) CPUMGetGuestIa32MtrrCap(PCVMCPU pVCpu); VMM_INT_DECL(uint64_t) CPUMGetGuestIa32SmmMonitorCtl(PCVMCPU pVCpu); VMMDECL(VBOXSTRICTRC) CPUMQueryGuestMsr(PVMCPUCC pVCpu, uint32_t idMsr, uint64_t *puValue); @@ -2427,6 +2435,8 @@ AssertReleaseFailedReturn(false); #else Assert(CPUMIsGuestInVmxNonRootMode(pCtx)); + if (CPUMIsGuestVmxPinCtlsSet(pCtx, VMX_PIN_CTLS_EXT_INT_EXIT)) + return true; return RT_BOOL(pCtx->eflags.u & X86_EFL_IF); #endif } diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/dbgf.h virtualbox-6.1.38-dfsg/include/VBox/vmm/dbgf.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/dbgf.h 2020-10-16 16:27:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/dbgf.h 2022-09-01 13:17:48.000000000 +0000 @@ -402,6 +402,12 @@ /** @} */ + /** @name Misc VT-x and AMD-V execution events. + * @{ */ + DBGFEVENT_VMX_SPLIT_LOCK, /**< VT-x: Split-lock \#AC triggered by host having detection enabled. */ + /** @} */ + + /** Access to an unassigned I/O port. * @todo not yet implemented. */ DBGFEVENT_IOPORT_UNASSIGNED, diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/gmm.h virtualbox-6.1.38-dfsg/include/VBox/vmm/gmm.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/gmm.h 2020-10-16 16:27:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/gmm.h 2022-09-01 13:17:48.000000000 +0000 @@ -88,10 +88,8 @@ #define GMM_CHUNKID_SHIFT (GMM_CHUNK_SHIFT - PAGE_SHIFT) /** The last valid Chunk ID value. */ #define GMM_CHUNKID_LAST (GMM_PAGEID_LAST >> GMM_CHUNKID_SHIFT) -/** The last valid Page ID value. - * The current limit is 2^28 - 1, or almost 1TB if you like. - * The constraints are currently dictated by PGMPAGE. */ -#define GMM_PAGEID_LAST (RT_BIT_32(28) - 1) +/** The last valid Page ID value. */ +#define GMM_PAGEID_LAST UINT32_C(0xfffffff0) /** Mask out the page index from the Page ID. */ #define GMM_PAGEID_IDX_MASK ((1U << GMM_CHUNKID_SHIFT) - 1) /** The NIL Chunk ID value. */ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/gvm.h virtualbox-6.1.38-dfsg/include/VBox/vmm/gvm.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/gvm.h 2020-10-16 16:27:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/gvm.h 2022-09-01 13:17:48.000000000 +0000 @@ -94,11 +94,19 @@ } nemr0; #endif + union + { +#if defined(VMM_INCLUDED_SRC_include_VMMInternal_h) && defined(IN_RING0) + struct VMMR0PERVCPU s; +#endif + uint8_t padding[64]; + } vmmr0; + /** Padding the structure size to page boundrary. */ #ifdef VBOX_WITH_NEM_R0 - uint8_t abPadding2[4096 - 64 - 64 - 64]; + uint8_t abPadding2[4096 - 64 - 64 - 64 - 64]; #else - uint8_t abPadding2[4096 - 64 - 64]; + uint8_t abPadding2[4096 - 64 - 64 - 64]; #endif } GVMCPU; #if RT_GNUC_PREREQ(4, 6) && defined(__cplusplus) diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/hm_vmx.h virtualbox-6.1.38-dfsg/include/VBox/vmm/hm_vmx.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/hm_vmx.h 2020-10-16 16:27:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/hm_vmx.h 2022-09-01 13:17:48.000000000 +0000 @@ -1156,8 +1156,6 @@ */ typedef struct VMXMSRS { - /** VMX/SMX Feature control. */ - uint64_t u64FeatCtrl; /** Basic information. */ uint64_t u64Basic; /** Pin-based VM-execution controls. */ @@ -1195,7 +1193,7 @@ /** EPT, VPID capabilities. */ uint64_t u64EptVpidCaps; /** Reserved for future. */ - uint64_t a_u64Reserved[9]; + uint64_t a_u64Reserved[10]; } VMXMSRS; AssertCompileSizeAlignment(VMXMSRS, 8); AssertCompileSize(VMXMSRS, 224); @@ -1225,152 +1223,143 @@ /** @name VMX Basic Exit Reasons. + * In accordance with the VT-x spec. + * Update g_apfnVMExitHandlers if new VM-exit reasons are added. * @{ */ -/** -1 Invalid exit code */ -#define VMX_EXIT_INVALID (-1) -/** 0 Exception or non-maskable interrupt (NMI). */ +/** Invalid exit code */ +#define VMX_EXIT_INVALID (-1) +/** Exception or non-maskable interrupt (NMI). */ #define VMX_EXIT_XCPT_OR_NMI 0 -/** 1 External interrupt. */ +/** External interrupt. */ #define VMX_EXIT_EXT_INT 1 -/** 2 Triple fault. */ +/** Triple fault. */ #define VMX_EXIT_TRIPLE_FAULT 2 -/** 3 INIT signal. */ +/** INIT signal. */ #define VMX_EXIT_INIT_SIGNAL 3 -/** 4 Start-up IPI (SIPI). */ +/** Start-up IPI (SIPI). */ #define VMX_EXIT_SIPI 4 -/** 5 I/O system-management interrupt (SMI). */ +/** I/O system-management interrupt (SMI). */ #define VMX_EXIT_IO_SMI 5 -/** 6 Other SMI. */ +/** Other SMI. */ #define VMX_EXIT_SMI 6 -/** 7 Interrupt window exiting. */ +/** Interrupt window exiting. */ #define VMX_EXIT_INT_WINDOW 7 -/** 8 NMI window exiting. */ +/** NMI window exiting. */ #define VMX_EXIT_NMI_WINDOW 8 -/** 9 Task switch. */ +/** Task switch. */ #define VMX_EXIT_TASK_SWITCH 9 -/** 10 Guest software attempted to execute CPUID. */ +/** CPUID. */ #define VMX_EXIT_CPUID 10 -/** 11 Guest software attempted to execute GETSEC. */ +/** GETSEC. */ #define VMX_EXIT_GETSEC 11 -/** 12 Guest software attempted to execute HLT. */ +/** HLT. */ #define VMX_EXIT_HLT 12 -/** 13 Guest software attempted to execute INVD. */ +/** INVD. */ #define VMX_EXIT_INVD 13 -/** 14 Guest software attempted to execute INVLPG. */ +/** INVLPG. */ #define VMX_EXIT_INVLPG 14 -/** 15 Guest software attempted to execute RDPMC. */ +/** RDPMC. */ #define VMX_EXIT_RDPMC 15 -/** 16 Guest software attempted to execute RDTSC. */ +/** RDTSC. */ #define VMX_EXIT_RDTSC 16 -/** 17 Guest software attempted to execute RSM in SMM. */ +/** RSM in SMM. */ #define VMX_EXIT_RSM 17 -/** 18 Guest software executed VMCALL. */ +/** VMCALL. */ #define VMX_EXIT_VMCALL 18 -/** 19 Guest software executed VMCLEAR. */ +/** VMCLEAR. */ #define VMX_EXIT_VMCLEAR 19 -/** 20 Guest software executed VMLAUNCH. */ +/** VMLAUNCH. */ #define VMX_EXIT_VMLAUNCH 20 -/** 21 Guest software executed VMPTRLD. */ +/** VMPTRLD. */ #define VMX_EXIT_VMPTRLD 21 -/** 22 Guest software executed VMPTRST. */ +/** VMPTRST. */ #define VMX_EXIT_VMPTRST 22 -/** 23 Guest software executed VMREAD. */ +/** VMREAD. */ #define VMX_EXIT_VMREAD 23 -/** 24 Guest software executed VMRESUME. */ +/** VMRESUME. */ #define VMX_EXIT_VMRESUME 24 -/** 25 Guest software executed VMWRITE. */ +/** VMWRITE. */ #define VMX_EXIT_VMWRITE 25 -/** 26 Guest software executed VMXOFF. */ +/** VMXOFF. */ #define VMX_EXIT_VMXOFF 26 -/** 27 Guest software executed VMXON. */ +/** VMXON. */ #define VMX_EXIT_VMXON 27 -/** 28 Control-register accesses. */ +/** Control-register accesses. */ #define VMX_EXIT_MOV_CRX 28 -/** 29 Debug-register accesses. */ +/** Debug-register accesses. */ #define VMX_EXIT_MOV_DRX 29 -/** 30 I/O instruction. */ +/** I/O instruction. */ #define VMX_EXIT_IO_INSTR 30 -/** 31 RDMSR. Guest software attempted to execute RDMSR. */ +/** RDMSR. */ #define VMX_EXIT_RDMSR 31 -/** 32 WRMSR. Guest software attempted to execute WRMSR. */ +/** WRMSR. */ #define VMX_EXIT_WRMSR 32 -/** 33 VM-entry failure due to invalid guest state. */ +/** VM-entry failure due to invalid guest state. */ #define VMX_EXIT_ERR_INVALID_GUEST_STATE 33 -/** 34 VM-entry failure due to MSR loading. */ +/** VM-entry failure due to MSR loading. */ #define VMX_EXIT_ERR_MSR_LOAD 34 -/** 36 Guest software executed MWAIT. */ +/** MWAIT. */ #define VMX_EXIT_MWAIT 36 -/** 37 VM-exit due to monitor trap flag. */ +/** VM-exit due to monitor trap flag. */ #define VMX_EXIT_MTF 37 -/** 39 Guest software attempted to execute MONITOR. */ +/** MONITOR. */ #define VMX_EXIT_MONITOR 39 -/** 40 Guest software attempted to execute PAUSE. */ +/** PAUSE. */ #define VMX_EXIT_PAUSE 40 -/** 41 VM-entry failure due to machine-check. */ +/** VM-entry failure due to machine-check. */ #define VMX_EXIT_ERR_MACHINE_CHECK 41 -/** 43 TPR below threshold. Guest software executed MOV to CR8. */ +/** TPR below threshold. Guest software executed MOV to CR8. */ #define VMX_EXIT_TPR_BELOW_THRESHOLD 43 -/** 44 APIC access. Guest software attempted to access memory at a physical - * address on the APIC-access page. */ +/** VM-exit due to guest accessing physical address in the APIC-access page. */ #define VMX_EXIT_APIC_ACCESS 44 -/** 45 Virtualized EOI. EOI virtualization was performed for a virtual - * interrupt whose vector indexed a bit set in the EOI-exit bitmap. */ +/** VM-exit due to EOI virtualization. */ #define VMX_EXIT_VIRTUALIZED_EOI 45 -/** 46 Access to GDTR or IDTR. Guest software attempted to execute LGDT, LIDT, - * SGDT, or SIDT. */ +/** Access to GDTR/IDTR using LGDT, LIDT, SGDT or SIDT. */ #define VMX_EXIT_GDTR_IDTR_ACCESS 46 -/** 47 Access to LDTR or TR. Guest software attempted to execute LLDT, LTR, - * SLDT, or STR. */ +/** Access to LDTR/TR due to LLDT, LTR, SLDT, or STR. */ #define VMX_EXIT_LDTR_TR_ACCESS 47 -/** 48 EPT violation. An attempt to access memory with a guest-physical address - * was disallowed by the configuration of the EPT paging structures. */ +/** EPT violation. */ #define VMX_EXIT_EPT_VIOLATION 48 -/** 49 EPT misconfiguration. An attempt to access memory with a guest-physical - * address encountered a misconfigured EPT paging-structure entry. */ +/** EPT misconfiguration. */ #define VMX_EXIT_EPT_MISCONFIG 49 -/** 50 INVEPT. Guest software attempted to execute INVEPT. */ +/** INVEPT. */ #define VMX_EXIT_INVEPT 50 -/** 51 RDTSCP. Guest software attempted to execute RDTSCP. */ +/** RDTSCP. */ #define VMX_EXIT_RDTSCP 51 -/** 52 VMX-preemption timer expired. The preemption timer counted down to zero. */ +/** VMX-preemption timer expired. */ #define VMX_EXIT_PREEMPT_TIMER 52 -/** 53 INVVPID. Guest software attempted to execute INVVPID. */ +/** INVVPID. */ #define VMX_EXIT_INVVPID 53 -/** 54 WBINVD. Guest software attempted to execute WBINVD. */ +/** WBINVD. */ #define VMX_EXIT_WBINVD 54 -/** 55 XSETBV. Guest software attempted to execute XSETBV. */ +/** XSETBV. */ #define VMX_EXIT_XSETBV 55 -/** 56 APIC write. Guest completed write to virtual-APIC. */ +/** Guest completed write to virtual-APIC. */ #define VMX_EXIT_APIC_WRITE 56 -/** 57 RDRAND. Guest software attempted to execute RDRAND. */ +/** RDRAND. */ #define VMX_EXIT_RDRAND 57 -/** 58 INVPCID. Guest software attempted to execute INVPCID. */ +/** INVPCID. */ #define VMX_EXIT_INVPCID 58 -/** 59 VMFUNC. Guest software attempted to execute VMFUNC. */ +/** VMFUNC. */ #define VMX_EXIT_VMFUNC 59 -/** 60 ENCLS. Guest software attempted to execute ENCLS. */ +/** ENCLS. */ #define VMX_EXIT_ENCLS 60 -/** 61 - RDSEED - Guest software attempted to executed RDSEED and exiting was - * enabled. */ +/** RDSEED. */ #define VMX_EXIT_RDSEED 61 -/** 62 - Page-modification log full. */ +/** Page-modification log full. */ #define VMX_EXIT_PML_FULL 62 -/** 63 - XSAVES. Guest software attempted to execute XSAVES and exiting was - * enabled (XSAVES/XRSTORS was enabled too, of course). */ +/** XSAVES. */ #define VMX_EXIT_XSAVES 63 -/** 64 - XRSTORS. Guest software attempted to execute XRSTORS and exiting - * was enabled (XSAVES/XRSTORS was enabled too, of course). */ +/** XRSTORS. */ #define VMX_EXIT_XRSTORS 64 -/** 66 - SPP-related event. Attempt to determine an access' sub-page write - * permission encountered an SPP miss or misconfiguration. */ +/** SPP-related event (SPP miss or misconfiguration). */ #define VMX_EXIT_SPP_EVENT 66 -/* 67 - UMWAIT. Guest software attempted to execute UMWAIT and exiting was enabled. */ +/* UMWAIT. */ #define VMX_EXIT_UMWAIT 67 -/** 68 - TPAUSE. Guest software attempted to execute TPAUSE and exiting was - * enabled. */ +/** TPAUSE. */ #define VMX_EXIT_TPAUSE 68 -/** The maximum exit value (inclusive). */ +/** The maximum VM-exit value (inclusive). */ #define VMX_EXIT_MAX (VMX_EXIT_TPAUSE) /** @} */ @@ -1813,8 +1802,8 @@ #define VMX_VMCS64_CTRL_VMREAD_BITMAP_HIGH 0x2027 #define VMX_VMCS64_CTRL_VMWRITE_BITMAP_FULL 0x2028 #define VMX_VMCS64_CTRL_VMWRITE_BITMAP_HIGH 0x2029 -#define VMX_VMCS64_CTRL_VIRTXCPT_INFO_ADDR_FULL 0x202a -#define VMX_VMCS64_CTRL_VIRTXCPT_INFO_ADDR_HIGH 0x202b +#define VMX_VMCS64_CTRL_VE_XCPT_INFO_ADDR_FULL 0x202a +#define VMX_VMCS64_CTRL_VE_XCPT_INFO_ADDR_HIGH 0x202b #define VMX_VMCS64_CTRL_XSS_EXITING_BITMAP_FULL 0x202c #define VMX_VMCS64_CTRL_XSS_EXITING_BITMAP_HIGH 0x202d #define VMX_VMCS64_CTRL_ENCLS_EXITING_BITMAP_FULL 0x202e @@ -2127,22 +2116,22 @@ * controls field in the VMCS. */ #define VMX_BF_PIN_CTLS_EXT_INT_EXIT_SHIFT 0 #define VMX_BF_PIN_CTLS_EXT_INT_EXIT_MASK UINT32_C(0x00000001) -#define VMX_BF_PIN_CTLS_UNDEF_1_2_SHIFT 1 -#define VMX_BF_PIN_CTLS_UNDEF_1_2_MASK UINT32_C(0x00000006) +#define VMX_BF_PIN_CTLS_RSVD_1_2_SHIFT 1 +#define VMX_BF_PIN_CTLS_RSVD_1_2_MASK UINT32_C(0x00000006) #define VMX_BF_PIN_CTLS_NMI_EXIT_SHIFT 3 #define VMX_BF_PIN_CTLS_NMI_EXIT_MASK UINT32_C(0x00000008) -#define VMX_BF_PIN_CTLS_UNDEF_4_SHIFT 4 -#define VMX_BF_PIN_CTLS_UNDEF_4_MASK UINT32_C(0x00000010) +#define VMX_BF_PIN_CTLS_RSVD_4_SHIFT 4 +#define VMX_BF_PIN_CTLS_RSVD_4_MASK UINT32_C(0x00000010) #define VMX_BF_PIN_CTLS_VIRT_NMI_SHIFT 5 #define VMX_BF_PIN_CTLS_VIRT_NMI_MASK UINT32_C(0x00000020) #define VMX_BF_PIN_CTLS_PREEMPT_TIMER_SHIFT 6 #define VMX_BF_PIN_CTLS_PREEMPT_TIMER_MASK UINT32_C(0x00000040) #define VMX_BF_PIN_CTLS_POSTED_INT_SHIFT 7 #define VMX_BF_PIN_CTLS_POSTED_INT_MASK UINT32_C(0x00000080) -#define VMX_BF_PIN_CTLS_UNDEF_8_31_SHIFT 8 -#define VMX_BF_PIN_CTLS_UNDEF_8_31_MASK UINT32_C(0xffffff00) +#define VMX_BF_PIN_CTLS_RSVD_8_31_SHIFT 8 +#define VMX_BF_PIN_CTLS_RSVD_8_31_MASK UINT32_C(0xffffff00) RT_BF_ASSERT_COMPILE_CHECKS(VMX_BF_PIN_CTLS_, UINT32_C(0), UINT32_MAX, - (EXT_INT_EXIT, UNDEF_1_2, NMI_EXIT, UNDEF_4, VIRT_NMI, PREEMPT_TIMER, POSTED_INT, UNDEF_8_31)); + (EXT_INT_EXIT, RSVD_1_2, NMI_EXIT, RSVD_4, VIRT_NMI, PREEMPT_TIMER, POSTED_INT, RSVD_8_31)); /** @} */ @@ -2198,18 +2187,18 @@ /** Bit fields for MSR_IA32_VMX_PROCBASED_CTLS and Processor-based VM-execution * controls field in the VMCS. */ -#define VMX_BF_PROC_CTLS_UNDEF_0_1_SHIFT 0 -#define VMX_BF_PROC_CTLS_UNDEF_0_1_MASK UINT32_C(0x00000003) +#define VMX_BF_PROC_CTLS_RSVD_0_1_SHIFT 0 +#define VMX_BF_PROC_CTLS_RSVD_0_1_MASK UINT32_C(0x00000003) #define VMX_BF_PROC_CTLS_INT_WINDOW_EXIT_SHIFT 2 #define VMX_BF_PROC_CTLS_INT_WINDOW_EXIT_MASK UINT32_C(0x00000004) #define VMX_BF_PROC_CTLS_USE_TSC_OFFSETTING_SHIFT 3 #define VMX_BF_PROC_CTLS_USE_TSC_OFFSETTING_MASK UINT32_C(0x00000008) -#define VMX_BF_PROC_CTLS_UNDEF_4_6_SHIFT 4 -#define VMX_BF_PROC_CTLS_UNDEF_4_6_MASK UINT32_C(0x00000070) +#define VMX_BF_PROC_CTLS_RSVD_4_6_SHIFT 4 +#define VMX_BF_PROC_CTLS_RSVD_4_6_MASK UINT32_C(0x00000070) #define VMX_BF_PROC_CTLS_HLT_EXIT_SHIFT 7 #define VMX_BF_PROC_CTLS_HLT_EXIT_MASK UINT32_C(0x00000080) -#define VMX_BF_PROC_CTLS_UNDEF_8_SHIFT 8 -#define VMX_BF_PROC_CTLS_UNDEF_8_MASK UINT32_C(0x00000100) +#define VMX_BF_PROC_CTLS_RSVD_8_SHIFT 8 +#define VMX_BF_PROC_CTLS_RSVD_8_MASK UINT32_C(0x00000100) #define VMX_BF_PROC_CTLS_INVLPG_EXIT_SHIFT 9 #define VMX_BF_PROC_CTLS_INVLPG_EXIT_MASK UINT32_C(0x00000200) #define VMX_BF_PROC_CTLS_MWAIT_EXIT_SHIFT 10 @@ -2218,14 +2207,14 @@ #define VMX_BF_PROC_CTLS_RDPMC_EXIT_MASK UINT32_C(0x00000800) #define VMX_BF_PROC_CTLS_RDTSC_EXIT_SHIFT 12 #define VMX_BF_PROC_CTLS_RDTSC_EXIT_MASK UINT32_C(0x00001000) -#define VMX_BF_PROC_CTLS_UNDEF_13_14_SHIFT 13 -#define VMX_BF_PROC_CTLS_UNDEF_13_14_MASK UINT32_C(0x00006000) +#define VMX_BF_PROC_CTLS_RSVD_13_14_SHIFT 13 +#define VMX_BF_PROC_CTLS_RSVD_13_14_MASK UINT32_C(0x00006000) #define VMX_BF_PROC_CTLS_CR3_LOAD_EXIT_SHIFT 15 #define VMX_BF_PROC_CTLS_CR3_LOAD_EXIT_MASK UINT32_C(0x00008000) #define VMX_BF_PROC_CTLS_CR3_STORE_EXIT_SHIFT 16 #define VMX_BF_PROC_CTLS_CR3_STORE_EXIT_MASK UINT32_C(0x00010000) -#define VMX_BF_PROC_CTLS_UNDEF_17_18_SHIFT 17 -#define VMX_BF_PROC_CTLS_UNDEF_17_18_MASK UINT32_C(0x00060000) +#define VMX_BF_PROC_CTLS_RSVD_17_18_SHIFT 17 +#define VMX_BF_PROC_CTLS_RSVD_17_18_MASK UINT32_C(0x00060000) #define VMX_BF_PROC_CTLS_CR8_LOAD_EXIT_SHIFT 19 #define VMX_BF_PROC_CTLS_CR8_LOAD_EXIT_MASK UINT32_C(0x00080000) #define VMX_BF_PROC_CTLS_CR8_STORE_EXIT_SHIFT 20 @@ -2240,8 +2229,8 @@ #define VMX_BF_PROC_CTLS_UNCOND_IO_EXIT_MASK UINT32_C(0x01000000) #define VMX_BF_PROC_CTLS_USE_IO_BITMAPS_SHIFT 25 #define VMX_BF_PROC_CTLS_USE_IO_BITMAPS_MASK UINT32_C(0x02000000) -#define VMX_BF_PROC_CTLS_UNDEF_26_SHIFT 26 -#define VMX_BF_PROC_CTLS_UNDEF_26_MASK UINT32_C(0x4000000) +#define VMX_BF_PROC_CTLS_RSVD_26_SHIFT 26 +#define VMX_BF_PROC_CTLS_RSVD_26_MASK UINT32_C(0x4000000) #define VMX_BF_PROC_CTLS_MONITOR_TRAP_FLAG_SHIFT 27 #define VMX_BF_PROC_CTLS_MONITOR_TRAP_FLAG_MASK UINT32_C(0x08000000) #define VMX_BF_PROC_CTLS_USE_MSR_BITMAPS_SHIFT 28 @@ -2253,10 +2242,10 @@ #define VMX_BF_PROC_CTLS_USE_SECONDARY_CTLS_SHIFT 31 #define VMX_BF_PROC_CTLS_USE_SECONDARY_CTLS_MASK UINT32_C(0x80000000) RT_BF_ASSERT_COMPILE_CHECKS(VMX_BF_PROC_CTLS_, UINT32_C(0), UINT32_MAX, - (UNDEF_0_1, INT_WINDOW_EXIT, USE_TSC_OFFSETTING, UNDEF_4_6, HLT_EXIT, UNDEF_8, INVLPG_EXIT, - MWAIT_EXIT, RDPMC_EXIT, RDTSC_EXIT, UNDEF_13_14, CR3_LOAD_EXIT, CR3_STORE_EXIT, UNDEF_17_18, + (RSVD_0_1, INT_WINDOW_EXIT, USE_TSC_OFFSETTING, RSVD_4_6, HLT_EXIT, RSVD_8, INVLPG_EXIT, + MWAIT_EXIT, RDPMC_EXIT, RDTSC_EXIT, RSVD_13_14, CR3_LOAD_EXIT, CR3_STORE_EXIT, RSVD_17_18, CR8_LOAD_EXIT, CR8_STORE_EXIT, USE_TPR_SHADOW, NMI_WINDOW_EXIT, MOV_DR_EXIT, UNCOND_IO_EXIT, - USE_IO_BITMAPS, UNDEF_26, MONITOR_TRAP_FLAG, USE_MSR_BITMAPS, MONITOR_EXIT, PAUSE_EXIT, + USE_IO_BITMAPS, RSVD_26, MONITOR_TRAP_FLAG, USE_MSR_BITMAPS, MONITOR_EXIT, PAUSE_EXIT, USE_SECONDARY_CTLS)); /** @} */ @@ -2365,8 +2354,8 @@ #define VMX_BF_PROC_CTLS2_CONCEAL_VMX_FROM_PT_MASK UINT32_C(0x00080000) #define VMX_BF_PROC_CTLS2_XSAVES_XRSTORS_SHIFT 20 #define VMX_BF_PROC_CTLS2_XSAVES_XRSTORS_MASK UINT32_C(0x00100000) -#define VMX_BF_PROC_CTLS2_UNDEF_21_SHIFT 21 -#define VMX_BF_PROC_CTLS2_UNDEF_21_MASK UINT32_C(0x00200000) +#define VMX_BF_PROC_CTLS2_RSVD_21_SHIFT 21 +#define VMX_BF_PROC_CTLS2_RSVD_21_MASK UINT32_C(0x00200000) #define VMX_BF_PROC_CTLS2_MODE_BASED_EPT_PERM_SHIFT 22 #define VMX_BF_PROC_CTLS2_MODE_BASED_EPT_PERM_MASK UINT32_C(0x00400000) #define VMX_BF_PROC_CTLS2_SPPTP_EPT_SHIFT 23 @@ -2377,19 +2366,36 @@ #define VMX_BF_PROC_CTLS2_TSC_SCALING_MASK UINT32_C(0x02000000) #define VMX_BF_PROC_CTLS2_USER_WAIT_PAUSE_SHIFT 26 #define VMX_BF_PROC_CTLS2_USER_WAIT_PAUSE_MASK UINT32_C(0x04000000) -#define VMX_BF_PROC_CTLS2_UNDEF_27_SHIFT 27 -#define VMX_BF_PROC_CTLS2_UNDEF_27_MASK UINT32_C(0x08000000) +#define VMX_BF_PROC_CTLS2_RSVD_27_SHIFT 27 +#define VMX_BF_PROC_CTLS2_RSVD_27_MASK UINT32_C(0x08000000) #define VMX_BF_PROC_CTLS2_ENCLV_EXIT_SHIFT 28 #define VMX_BF_PROC_CTLS2_ENCLV_EXIT_MASK UINT32_C(0x10000000) -#define VMX_BF_PROC_CTLS2_UNDEF_29_31_SHIFT 29 -#define VMX_BF_PROC_CTLS2_UNDEF_29_31_MASK UINT32_C(0xe0000000) +#define VMX_BF_PROC_CTLS2_RSVD_29_31_SHIFT 29 +#define VMX_BF_PROC_CTLS2_RSVD_29_31_MASK UINT32_C(0xe0000000) RT_BF_ASSERT_COMPILE_CHECKS(VMX_BF_PROC_CTLS2_, UINT32_C(0), UINT32_MAX, (VIRT_APIC_ACCESS, EPT, DESC_TABLE_EXIT, RDTSCP, VIRT_X2APIC_MODE, VPID, WBINVD_EXIT, UNRESTRICTED_GUEST, APIC_REG_VIRT, VIRT_INT_DELIVERY, PAUSE_LOOP_EXIT, RDRAND_EXIT, INVPCID, VMFUNC, - VMCS_SHADOWING, ENCLS_EXIT, RDSEED_EXIT, PML, EPT_VE, CONCEAL_VMX_FROM_PT, XSAVES_XRSTORS, UNDEF_21, - MODE_BASED_EPT_PERM, SPPTP_EPT, PT_EPT, TSC_SCALING, USER_WAIT_PAUSE, UNDEF_27, ENCLV_EXIT, - UNDEF_29_31)); + VMCS_SHADOWING, ENCLS_EXIT, RDSEED_EXIT, PML, EPT_VE, CONCEAL_VMX_FROM_PT, XSAVES_XRSTORS, RSVD_21, + MODE_BASED_EPT_PERM, SPPTP_EPT, PT_EPT, TSC_SCALING, USER_WAIT_PAUSE, RSVD_27, ENCLV_EXIT, + RSVD_29_31)); +/** @} */ + + +/** @name Tertiary Processor-based VM-execution controls. + * @{ + */ +/** VM-exit when executing LOADIWKEY. */ +#define VMX_PROC_CTLS3_LOADIWKEY_EXIT RT_BIT_64(0) + +/** Bit fields for Tertiary processor-based VM-execution controls field in the VMCS. */ +#define VMX_BF_PROC_CTLS3_LOADIWKEY_EXIT_SHIFT 0 +#define VMX_BF_PROC_CTLS3_LOADIWKEY_EXIT_MASK UINT64_C(0x0000000000000001) +#define VMX_BF_PROC_CTLS3_RSVD_1_63_SHIFT 1 +#define VMX_BF_PROC_CTLS3_RSVD_1_63_MASK UINT64_C(0xfffffffffffffffe) + +RT_BF_ASSERT_COMPILE_CHECKS(VMX_BF_PROC_CTLS3_, UINT64_C(0), UINT64_MAX, + (LOADIWKEY_EXIT, RSVD_1_63)); /** @} */ @@ -2424,20 +2430,20 @@ /** Bit fields for MSR_IA32_VMX_ENTRY_CTLS and VM-entry controls field in the * VMCS. */ -#define VMX_BF_ENTRY_CTLS_UNDEF_0_1_SHIFT 0 -#define VMX_BF_ENTRY_CTLS_UNDEF_0_1_MASK UINT32_C(0x00000003) +#define VMX_BF_ENTRY_CTLS_RSVD_0_1_SHIFT 0 +#define VMX_BF_ENTRY_CTLS_RSVD_0_1_MASK UINT32_C(0x00000003) #define VMX_BF_ENTRY_CTLS_LOAD_DEBUG_SHIFT 2 #define VMX_BF_ENTRY_CTLS_LOAD_DEBUG_MASK UINT32_C(0x00000004) -#define VMX_BF_ENTRY_CTLS_UNDEF_3_8_SHIFT 3 -#define VMX_BF_ENTRY_CTLS_UNDEF_3_8_MASK UINT32_C(0x000001f8) +#define VMX_BF_ENTRY_CTLS_RSVD_3_8_SHIFT 3 +#define VMX_BF_ENTRY_CTLS_RSVD_3_8_MASK UINT32_C(0x000001f8) #define VMX_BF_ENTRY_CTLS_IA32E_MODE_GUEST_SHIFT 9 #define VMX_BF_ENTRY_CTLS_IA32E_MODE_GUEST_MASK UINT32_C(0x00000200) #define VMX_BF_ENTRY_CTLS_ENTRY_SMM_SHIFT 10 #define VMX_BF_ENTRY_CTLS_ENTRY_SMM_MASK UINT32_C(0x00000400) #define VMX_BF_ENTRY_CTLS_DEACTIVATE_DUAL_MON_SHIFT 11 #define VMX_BF_ENTRY_CTLS_DEACTIVATE_DUAL_MON_MASK UINT32_C(0x00000800) -#define VMX_BF_ENTRY_CTLS_UNDEF_12_SHIFT 12 -#define VMX_BF_ENTRY_CTLS_UNDEF_12_MASK UINT32_C(0x00001000) +#define VMX_BF_ENTRY_CTLS_RSVD_12_SHIFT 12 +#define VMX_BF_ENTRY_CTLS_RSVD_12_MASK UINT32_C(0x00001000) #define VMX_BF_ENTRY_CTLS_LOAD_PERF_MSR_SHIFT 13 #define VMX_BF_ENTRY_CTLS_LOAD_PERF_MSR_MASK UINT32_C(0x00002000) #define VMX_BF_ENTRY_CTLS_LOAD_PAT_MSR_SHIFT 14 @@ -2450,12 +2456,12 @@ #define VMX_BF_ENTRY_CTLS_CONCEAL_VMX_FROM_PT_MASK UINT32_C(0x00020000) #define VMX_BF_ENTRY_CTLS_LOAD_RTIT_CTL_MSR_SHIFT 18 #define VMX_BF_ENTRY_CTLS_LOAD_RTIT_CTL_MSR_MASK UINT32_C(0x00040000) -#define VMX_BF_ENTRY_CTLS_UNDEF_19_31_SHIFT 19 -#define VMX_BF_ENTRY_CTLS_UNDEF_19_31_MASK UINT32_C(0xfff80000) +#define VMX_BF_ENTRY_CTLS_RSVD_19_31_SHIFT 19 +#define VMX_BF_ENTRY_CTLS_RSVD_19_31_MASK UINT32_C(0xfff80000) RT_BF_ASSERT_COMPILE_CHECKS(VMX_BF_ENTRY_CTLS_, UINT32_C(0), UINT32_MAX, - (UNDEF_0_1, LOAD_DEBUG, UNDEF_3_8, IA32E_MODE_GUEST, ENTRY_SMM, DEACTIVATE_DUAL_MON, UNDEF_12, + (RSVD_0_1, LOAD_DEBUG, RSVD_3_8, IA32E_MODE_GUEST, ENTRY_SMM, DEACTIVATE_DUAL_MON, RSVD_12, LOAD_PERF_MSR, LOAD_PAT_MSR, LOAD_EFER_MSR, LOAD_BNDCFGS_MSR, CONCEAL_VMX_FROM_PT, - LOAD_RTIT_CTL_MSR, UNDEF_19_31)); + LOAD_RTIT_CTL_MSR, RSVD_19_31)); /** @} */ @@ -2494,24 +2500,24 @@ /** Bit fields for MSR_IA32_VMX_EXIT_CTLS and VM-exit controls field in the * VMCS. */ -#define VMX_BF_EXIT_CTLS_UNDEF_0_1_SHIFT 0 -#define VMX_BF_EXIT_CTLS_UNDEF_0_1_MASK UINT32_C(0x00000003) +#define VMX_BF_EXIT_CTLS_RSVD_0_1_SHIFT 0 +#define VMX_BF_EXIT_CTLS_RSVD_0_1_MASK UINT32_C(0x00000003) #define VMX_BF_EXIT_CTLS_SAVE_DEBUG_SHIFT 2 #define VMX_BF_EXIT_CTLS_SAVE_DEBUG_MASK UINT32_C(0x00000004) -#define VMX_BF_EXIT_CTLS_UNDEF_3_8_SHIFT 3 -#define VMX_BF_EXIT_CTLS_UNDEF_3_8_MASK UINT32_C(0x000001f8) +#define VMX_BF_EXIT_CTLS_RSVD_3_8_SHIFT 3 +#define VMX_BF_EXIT_CTLS_RSVD_3_8_MASK UINT32_C(0x000001f8) #define VMX_BF_EXIT_CTLS_HOST_ADDR_SPACE_SIZE_SHIFT 9 #define VMX_BF_EXIT_CTLS_HOST_ADDR_SPACE_SIZE_MASK UINT32_C(0x00000200) -#define VMX_BF_EXIT_CTLS_UNDEF_10_11_SHIFT 10 -#define VMX_BF_EXIT_CTLS_UNDEF_10_11_MASK UINT32_C(0x00000c00) +#define VMX_BF_EXIT_CTLS_RSVD_10_11_SHIFT 10 +#define VMX_BF_EXIT_CTLS_RSVD_10_11_MASK UINT32_C(0x00000c00) #define VMX_BF_EXIT_CTLS_LOAD_PERF_MSR_SHIFT 12 #define VMX_BF_EXIT_CTLS_LOAD_PERF_MSR_MASK UINT32_C(0x00001000) -#define VMX_BF_EXIT_CTLS_UNDEF_13_14_SHIFT 13 -#define VMX_BF_EXIT_CTLS_UNDEF_13_14_MASK UINT32_C(0x00006000) +#define VMX_BF_EXIT_CTLS_RSVD_13_14_SHIFT 13 +#define VMX_BF_EXIT_CTLS_RSVD_13_14_MASK UINT32_C(0x00006000) #define VMX_BF_EXIT_CTLS_ACK_EXT_INT_SHIFT 15 #define VMX_BF_EXIT_CTLS_ACK_EXT_INT_MASK UINT32_C(0x00008000) -#define VMX_BF_EXIT_CTLS_UNDEF_16_17_SHIFT 16 -#define VMX_BF_EXIT_CTLS_UNDEF_16_17_MASK UINT32_C(0x00030000) +#define VMX_BF_EXIT_CTLS_RSVD_16_17_SHIFT 16 +#define VMX_BF_EXIT_CTLS_RSVD_16_17_MASK UINT32_C(0x00030000) #define VMX_BF_EXIT_CTLS_SAVE_PAT_MSR_SHIFT 18 #define VMX_BF_EXIT_CTLS_SAVE_PAT_MSR_MASK UINT32_C(0x00040000) #define VMX_BF_EXIT_CTLS_LOAD_PAT_MSR_SHIFT 19 @@ -2528,12 +2534,12 @@ #define VMX_BF_EXIT_CTLS_CONCEAL_VMX_FROM_PT_MASK UINT32_C(0x01000000) #define VMX_BF_EXIT_CTLS_CLEAR_RTIT_CTL_MSR_SHIFT 25 #define VMX_BF_EXIT_CTLS_CLEAR_RTIT_CTL_MSR_MASK UINT32_C(0x02000000) -#define VMX_BF_EXIT_CTLS_UNDEF_26_31_SHIFT 26 -#define VMX_BF_EXIT_CTLS_UNDEF_26_31_MASK UINT32_C(0xfc000000) +#define VMX_BF_EXIT_CTLS_RSVD_26_31_SHIFT 26 +#define VMX_BF_EXIT_CTLS_RSVD_26_31_MASK UINT32_C(0xfc000000) RT_BF_ASSERT_COMPILE_CHECKS(VMX_BF_EXIT_CTLS_, UINT32_C(0), UINT32_MAX, - (UNDEF_0_1, SAVE_DEBUG, UNDEF_3_8, HOST_ADDR_SPACE_SIZE, UNDEF_10_11, LOAD_PERF_MSR, UNDEF_13_14, - ACK_EXT_INT, UNDEF_16_17, SAVE_PAT_MSR, LOAD_PAT_MSR, SAVE_EFER_MSR, LOAD_EFER_MSR, - SAVE_PREEMPT_TIMER, CLEAR_BNDCFGS_MSR, CONCEAL_VMX_FROM_PT, CLEAR_RTIT_CTL_MSR, UNDEF_26_31)); + (RSVD_0_1, SAVE_DEBUG, RSVD_3_8, HOST_ADDR_SPACE_SIZE, RSVD_10_11, LOAD_PERF_MSR, RSVD_13_14, + ACK_EXT_INT, RSVD_16_17, SAVE_PAT_MSR, LOAD_PAT_MSR, SAVE_EFER_MSR, LOAD_EFER_MSR, + SAVE_PREEMPT_TIMER, CLEAR_BNDCFGS_MSR, CONCEAL_VMX_FROM_PT, CLEAR_RTIT_CTL_MSR, RSVD_26_31)); /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/iem.h virtualbox-6.1.38-dfsg/include/VBox/vmm/iem.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/iem.h 2020-10-16 16:27:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/iem.h 2022-09-01 13:17:48.000000000 +0000 @@ -287,6 +287,7 @@ VMMDECL(VBOXSTRICTRC) IEMExecOneBypassWithPrefetchedByPCWritten(PVMCPUCC pVCpu, PCPUMCTXCORE pCtxCore, uint64_t OpcodeBytesPC, const void *pvOpcodeBytes, size_t cbOpcodeBytes, uint32_t *pcbWritten); +VMMDECL(VBOXSTRICTRC) IEMExecOneIgnoreLock(PVMCPUCC pVCpu); VMMDECL(VBOXSTRICTRC) IEMExecLots(PVMCPUCC pVCpu, uint32_t cMaxInstructions, uint32_t cPollRate, uint32_t *pcInstructions); /** Statistics returned by IEMExecForExits. */ typedef struct IEMEXECFOREXITSTATS diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/nem.h virtualbox-6.1.38-dfsg/include/VBox/vmm/nem.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/nem.h 2020-10-16 16:27:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/nem.h 2022-09-01 13:17:48.000000000 +0000 @@ -61,19 +61,93 @@ VMMR3_INT_DECL(bool) NEMR3SetSingleInstruction(PVM pVM, PVMCPU pVCpu, bool fEnable); VMMR3_INT_DECL(void) NEMR3NotifyFF(PVM pVM, PVMCPU pVCpu, uint32_t fFlags); -VMMR3_INT_DECL(int) NEMR3NotifyPhysRamRegister(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb); -VMMR3_INT_DECL(int) NEMR3NotifyPhysMmioExMap(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, uint32_t fFlags, void *pvMmio2); -VMMR3_INT_DECL(int) NEMR3NotifyPhysMmioExUnmap(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, uint32_t fFlags); +/** + * Checks if dirty page tracking for MMIO2 ranges is supported. + * + * If it is, PGM will not install a physical write access handler for the MMIO2 + * region and instead just forward dirty bit queries NEMR3QueryMmio2DirtyBits. + * The enable/disable control of the tracking will be ignored, and PGM will + * always set NEM_NOTIFY_PHYS_MMIO_EX_F_TRACK_DIRTY_PAGES for such ranges. + * + * @retval true if supported. + * @retval false if not. + * @param pVM The cross context VM structure. + */ +VMMR3_INT_DECL(bool) NEMR3IsMmio2DirtyPageTrackingSupported(PVM pVM); + +/** + * Worker for PGMR3PhysMmio2QueryAndResetDirtyBitmap. + * + * @returns VBox status code. + * @param pVM The cross context VM structure. + * @param GCPhys The address of the MMIO2 range. + * @param cb The size of the MMIO2 range. + * @param uNemRange The NEM internal range number. + * @param pvBitmap The output bitmap. Must be 8-byte aligned. Ignored + * when @a cbBitmap is zero. + * @param cbBitmap The size of the bitmap. Must be the size of the whole + * MMIO2 range, rounded up to the nearest 8 bytes. + * When zero only a reset is done. + */ +VMMR3_INT_DECL(int) NEMR3PhysMmio2QueryAndResetDirtyBitmap(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, uint32_t uNemRange, + void *pvBitmap, size_t cbBitmap); + +VMMR3_INT_DECL(int) NEMR3NotifyPhysRamRegister(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, void *pvR3, + uint8_t *pu2State, uint32_t *puNemRange); +VMMR3_INT_DECL(int) NEMR3NotifyPhysMmioExMapEarly(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, uint32_t fFlags, + void *pvRam, void *pvMmio2, uint8_t *pu2State, uint32_t *puNemRange); +VMMR3_INT_DECL(int) NEMR3NotifyPhysMmioExMapLate(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, uint32_t fFlags, + void *pvRam, void *pvMmio2, uint32_t *puNemRange); +VMMR3_INT_DECL(int) NEMR3NotifyPhysMmioExUnmap(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, uint32_t fFlags, + void *pvRam, void *pvMmio2, uint8_t *pu2State); /** @name Flags for NEMR3NotifyPhysMmioExMap and NEMR3NotifyPhysMmioExUnmap. * @{ */ -/** Set if it's MMIO2 being mapped or unmapped. */ -#define NEM_NOTIFY_PHYS_MMIO_EX_F_MMIO2 RT_BIT(0) /** Set if the range is replacing RAM rather that unused space. */ -#define NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE RT_BIT(1) +#define NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE RT_BIT(0) +/** Set if it's MMIO2 being mapped or unmapped. */ +#define NEM_NOTIFY_PHYS_MMIO_EX_F_MMIO2 RT_BIT(1) +/** Set if MMIO2 and dirty page tracking is configured. */ +#define NEM_NOTIFY_PHYS_MMIO_EX_F_TRACK_DIRTY_PAGES RT_BIT(2) /** @} */ -VMMR3_INT_DECL(int) NEMR3NotifyPhysRomRegisterEarly(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, uint32_t fFlags); -VMMR3_INT_DECL(int) NEMR3NotifyPhysRomRegisterLate(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, uint32_t fFlags); +/** + * Called very early during ROM registration, basically so an existing RAM range + * can be adjusted if desired. + * + * It will be succeeded by a number of NEMHCNotifyPhysPageProtChanged() + * calls and finally a call to NEMR3NotifyPhysRomRegisterLate(). + * + * @returns VBox status code + * @param pVM The cross context VM structure. + * @param GCPhys The ROM address (page aligned). + * @param cb The size (page aligned). + * @param pvPages Pointer to the ROM (RAM) pages in simplified mode + * when NEM_NOTIFY_PHYS_ROM_F_REPLACE is set, otherwise + * NULL. + * @param fFlags NEM_NOTIFY_PHYS_ROM_F_XXX. + * @param pu2State New page state or UINT8_MAX to leave as-is. + */ +VMMR3_INT_DECL(int) NEMR3NotifyPhysRomRegisterEarly(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, void *pvPages, + uint32_t fFlags, uint8_t *pu2State); + +/** + * Called after the ROM range has been fully completed. + * + * This will be preceeded by a NEMR3NotifyPhysRomRegisterEarly() call as well a + * number of NEMHCNotifyPhysPageProtChanged calls. + * + * @returns VBox status code + * @param pVM The cross context VM structure. + * @param GCPhys The ROM address (page aligned). + * @param cb The size (page aligned). + * @param pvPages Pointer to the ROM pages. + * @param fFlags NEM_NOTIFY_PHYS_ROM_F_XXX. + * @param pu2State Where to return the new NEM page state, UINT8_MAX + * for unchanged. + */ +VMMR3_INT_DECL(int) NEMR3NotifyPhysRomRegisterLate(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, void *pvPages, + uint32_t fFlags, uint8_t *pu2State); + /** @name Flags for NEMR3NotifyPhysRomRegisterEarly and NEMR3NotifyPhysRomRegisterLate. * @{ */ /** Set if the range is replacing RAM rather that unused space. */ @@ -88,6 +162,8 @@ /** @defgroup grp_nem_r0 The NEM ring-0 Context API * @{ */ +VMMR0_INT_DECL(int) NEMR0Init(void); +VMMR0_INT_DECL(void) NEMR0Term(void); VMMR0_INT_DECL(int) NEMR0InitVM(PGVM pGVM); VMMR0_INT_DECL(int) NEMR0InitVMPart2(PGVM pGVM); VMMR0_INT_DECL(void) NEMR0CleanupVM(PGVM pGVM); @@ -100,6 +176,9 @@ VMMR0_INT_DECL(VBOXSTRICTRC) NEMR0RunGuestCode(PGVM pGVM, VMCPUID idCpu); VMMR0_INT_DECL(int) NEMR0UpdateStatistics(PGVM pGVM, VMCPUID idCpu); VMMR0_INT_DECL(int) NEMR0DoExperiment(PGVM pGVM, VMCPUID idCpu, uint64_t u64Arg); +#ifdef RT_OS_WINDOWS +VMMR0_INT_DECL(int) NEMR0WinGetPartitionId(PGVM pGVM, uintptr_t uHandle); +#endif /** @} */ @@ -111,16 +190,16 @@ VMM_INT_DECL(void) NEMHCNotifyHandlerPhysicalRegister(PVMCC pVM, PGMPHYSHANDLERKIND enmKind, RTGCPHYS GCPhys, RTGCPHYS cb); VMM_INT_DECL(void) NEMHCNotifyHandlerPhysicalDeregister(PVMCC pVM, PGMPHYSHANDLERKIND enmKind, RTGCPHYS GCPhys, RTGCPHYS cb, - int fRestoreAsRAM, bool fRestoreAsRAM2); + RTR3PTR pvMemR3, uint8_t *pu2State); VMM_INT_DECL(void) NEMHCNotifyHandlerPhysicalModify(PVMCC pVM, PGMPHYSHANDLERKIND enmKind, RTGCPHYS GCPhysOld, RTGCPHYS GCPhysNew, RTGCPHYS cb, bool fRestoreAsRAM); VMM_INT_DECL(int) NEMHCNotifyPhysPageAllocated(PVMCC pVM, RTGCPHYS GCPhys, RTHCPHYS HCPhys, uint32_t fPageProt, PGMPAGETYPE enmType, uint8_t *pu2State); -VMM_INT_DECL(void) NEMHCNotifyPhysPageProtChanged(PVMCC pVM, RTGCPHYS GCPhys, RTHCPHYS HCPhys, uint32_t fPageProt, +VMM_INT_DECL(void) NEMHCNotifyPhysPageProtChanged(PVMCC pVM, RTGCPHYS GCPhys, RTHCPHYS HCPhys, RTR3PTR pvR3, uint32_t fPageProt, PGMPAGETYPE enmType, uint8_t *pu2State); VMM_INT_DECL(void) NEMHCNotifyPhysPageChanged(PVMCC pVM, RTGCPHYS GCPhys, RTHCPHYS HCPhysPrev, RTHCPHYS HCPhysNew, - uint32_t fPageProt, PGMPAGETYPE enmType, uint8_t *pu2State); + RTR3PTR pvNewR3, uint32_t fPageProt, PGMPAGETYPE enmType, uint8_t *pu2State); /** @name NEM_PAGE_PROT_XXX - Page protection * @{ */ #define NEM_PAGE_PROT_NONE UINT32_C(0) /**< All access causes VM exits. */ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmaudiohostenuminline.h virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmaudiohostenuminline.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmaudiohostenuminline.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmaudiohostenuminline.h 2022-09-01 13:17:48.000000000 +0000 @@ -0,0 +1,453 @@ +/* $Id: pdmaudiohostenuminline.h $ */ +/** @file + * PDM - Audio Helpers for host audio device enumeration, Inlined Code. (DEV,++) + * + * This is all inlined because it's too tedious to create a couple libraries to + * contain it all (same bad excuse as for intnetinline.h & pdmnetinline.h). + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_vmm_pdmaudiohostenuminline_h +#define VBOX_INCLUDED_vmm_pdmaudiohostenuminline_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include + +#include +#include +#include + + +/** @defgroup grp_pdm_audio_host_enum_inline The PDM Host Audio Enumeration Helper APIs + * @ingroup grp_pdm + * @{ + */ + + +/** + * Allocates a host audio device for an enumeration result. + * + * @returns Newly allocated audio device, or NULL on failure. + * @param cb The total device structure size. This must be at least the + * size of PDMAUDIOHOSTDEV. The idea is that the caller extends + * the PDMAUDIOHOSTDEV structure and appends additional data + * after it in its private structure. + * @param cbName The number of bytes to allocate for the name field + * (including the terminator). Pass zero if RTStrAlloc and + * friends will be used. + * @param cbId The number of bytes to allocate for the ID field. Pass + * zero if RTStrAlloc and friends will be used. + */ +DECLINLINE(PPDMAUDIOHOSTDEV) PDMAudioHostDevAlloc(size_t cb, size_t cbName, size_t cbId) +{ + AssertReturn(cb >= sizeof(PDMAUDIOHOSTDEV), NULL); + AssertReturn(cb < _4M, NULL); + AssertReturn(cbName < _4K, NULL); + AssertReturn(cbId < _16K, NULL); + + PPDMAUDIOHOSTDEV pDev = (PPDMAUDIOHOSTDEV)RTMemAllocZ(RT_ALIGN_Z(cb + cbName + cbId, 64)); + if (pDev) + { + pDev->uMagic = PDMAUDIOHOSTDEV_MAGIC; + pDev->cbSelf = (uint32_t)cb; + RTListInit(&pDev->ListEntry); + if (cbName) + pDev->pszName = (char *)pDev + cb; + if (cbId) + pDev->pszId = (char *)pDev + cb + cbName; + } + return pDev; +} + +/** + * Frees a host audio device allocated by PDMAudioHostDevAlloc. + * + * @param pDev The device to free. NULL is ignored. + */ +DECLINLINE(void) PDMAudioHostDevFree(PPDMAUDIOHOSTDEV pDev) +{ + if (pDev) + { + Assert(pDev->uMagic == PDMAUDIOHOSTDEV_MAGIC); + pDev->uMagic = ~PDMAUDIOHOSTDEV_MAGIC; + pDev->cbSelf = 0; + + if (pDev->fFlags & PDMAUDIOHOSTDEV_F_NAME_ALLOC) + { + RTStrFree(pDev->pszName); + pDev->pszName = NULL; + } + + if (pDev->fFlags & PDMAUDIOHOSTDEV_F_ID_ALLOC) + { + RTStrFree(pDev->pszId); + pDev->pszId = NULL; + } + + RTMemFree(pDev); + } +} + +/** + * Duplicates a host audio device enumeration entry. + * + * @returns Duplicated audio device entry on success, or NULL on failure. + * @param pDev The audio device enum entry to duplicate. + * @param fOnlyCoreData + */ +DECLINLINE(PPDMAUDIOHOSTDEV) PDMAudioHostDevDup(PCPDMAUDIOHOSTDEV pDev, bool fOnlyCoreData) +{ + AssertPtrReturn(pDev, NULL); + Assert(pDev->uMagic == PDMAUDIOHOSTDEV_MAGIC); + Assert(fOnlyCoreData || !(pDev->fFlags & PDMAUDIOHOSTDEV_F_NO_DUP)); + + uint32_t cbToDup = fOnlyCoreData ? sizeof(PDMAUDIOHOSTDEV) : pDev->cbSelf; + AssertReturn(cbToDup >= sizeof(*pDev), NULL); + + PPDMAUDIOHOSTDEV pDevDup = PDMAudioHostDevAlloc(cbToDup, 0, 0); + if (pDevDup) + { + memcpy(pDevDup, pDev, cbToDup); + RTListInit(&pDevDup->ListEntry); + pDevDup->cbSelf = cbToDup; + + if (pDev->pszName) + { + uintptr_t off; + if ( (pDevDup->fFlags & PDMAUDIOHOSTDEV_F_NAME_ALLOC) + || (off = (uintptr_t)pDev->pszName - (uintptr_t)pDev) >= pDevDup->cbSelf) + { + pDevDup->fFlags |= PDMAUDIOHOSTDEV_F_NAME_ALLOC; + pDevDup->pszName = RTStrDup(pDev->pszName); + AssertReturnStmt(pDevDup->pszName, PDMAudioHostDevFree(pDevDup), NULL); + } + else + pDevDup->pszName = (char *)pDevDup + off; + } + + if (pDev->pszId) + { + uintptr_t off; + if ( (pDevDup->fFlags & PDMAUDIOHOSTDEV_F_ID_ALLOC) + || (off = (uintptr_t)pDev->pszId - (uintptr_t)pDev) >= pDevDup->cbSelf) + { + pDevDup->fFlags |= PDMAUDIOHOSTDEV_F_ID_ALLOC; + pDevDup->pszId = RTStrDup(pDev->pszId); + AssertReturnStmt(pDevDup->pszId, PDMAudioHostDevFree(pDevDup), NULL); + } + else + pDevDup->pszId = (char *)pDevDup + off; + } + } + + return pDevDup; +} + +/** + * Initializes a host audio device enumeration. + * + * @param pDevEnm The enumeration to initialize. + */ +DECLINLINE(void) PDMAudioHostEnumInit(PPDMAUDIOHOSTENUM pDevEnm) +{ + AssertPtr(pDevEnm); + + pDevEnm->uMagic = PDMAUDIOHOSTENUM_MAGIC; + pDevEnm->cDevices = 0; + RTListInit(&pDevEnm->LstDevices); +} + +/** + * Deletes the host audio device enumeration and frees all device entries + * associated with it. + * + * The user must call PDMAudioHostEnumInit again to use it again. + * + * @param pDevEnm The host audio device enumeration to delete. + */ +DECLINLINE(void) PDMAudioHostEnumDelete(PPDMAUDIOHOSTENUM pDevEnm) +{ + if (pDevEnm) + { + AssertPtr(pDevEnm); + AssertReturnVoid(pDevEnm->uMagic == PDMAUDIOHOSTENUM_MAGIC); + + PPDMAUDIOHOSTDEV pDev, pDevNext; + RTListForEachSafe(&pDevEnm->LstDevices, pDev, pDevNext, PDMAUDIOHOSTDEV, ListEntry) + { + RTListNodeRemove(&pDev->ListEntry); + + PDMAudioHostDevFree(pDev); + + pDevEnm->cDevices--; + } + + /* Sanity. */ + Assert(RTListIsEmpty(&pDevEnm->LstDevices)); + Assert(pDevEnm->cDevices == 0); + + pDevEnm->uMagic = ~PDMAUDIOHOSTENUM_MAGIC; + } +} + +/** + * Adds an audio device to a device enumeration. + * + * @param pDevEnm Device enumeration to add device to. + * @param pDev Device to add. The pointer will be owned by the device enumeration then. + */ +DECLINLINE(void) PDMAudioHostEnumAppend(PPDMAUDIOHOSTENUM pDevEnm, PPDMAUDIOHOSTDEV pDev) +{ + AssertPtr(pDevEnm); + AssertPtr(pDev); + Assert(pDevEnm->uMagic == PDMAUDIOHOSTENUM_MAGIC); + + RTListAppend(&pDevEnm->LstDevices, &pDev->ListEntry); + pDevEnm->cDevices++; +} + +/** + * Appends copies of matching host device entries from one to another enumeration. + * + * @returns VBox status code. + * @param pDstDevEnm The target to append copies of matching device to. + * @param pSrcDevEnm The source to copy matching devices from. + * @param enmUsage The usage to match for copying. + * Use PDMAUDIODIR_INVALID to match all entries. + * @param fOnlyCoreData Set this to only copy the PDMAUDIOHOSTDEV part. + * Careful with passing @c false here as not all + * backends have data that can be copied. + */ +DECLINLINE(int) PDMAudioHostEnumCopy(PPDMAUDIOHOSTENUM pDstDevEnm, PCPDMAUDIOHOSTENUM pSrcDevEnm, + PDMAUDIODIR enmUsage, bool fOnlyCoreData) +{ + AssertPtrReturn(pDstDevEnm, VERR_INVALID_POINTER); + AssertReturn(pDstDevEnm->uMagic == PDMAUDIOHOSTENUM_MAGIC, VERR_WRONG_ORDER); + + AssertPtrReturn(pSrcDevEnm, VERR_INVALID_POINTER); + AssertReturn(pSrcDevEnm->uMagic == PDMAUDIOHOSTENUM_MAGIC, VERR_WRONG_ORDER); + + PPDMAUDIOHOSTDEV pSrcDev; + RTListForEach(&pSrcDevEnm->LstDevices, pSrcDev, PDMAUDIOHOSTDEV, ListEntry) + { + if ( enmUsage == pSrcDev->enmUsage + || enmUsage == PDMAUDIODIR_INVALID /*all*/) + { + PPDMAUDIOHOSTDEV pDstDev = PDMAudioHostDevDup(pSrcDev, fOnlyCoreData); + AssertReturn(pDstDev, VERR_NO_MEMORY); + + PDMAudioHostEnumAppend(pDstDevEnm, pDstDev); + } + } + + return VINF_SUCCESS; +} + +/** + * Moves all the device entries from one enumeration to another, destroying the + * former. + * + * @returns VBox status code. + * @param pDstDevEnm The target to put move @a pSrcDevEnm to. This + * does not need to be initialized, but if it is it + * must not have any device entries. + * @param pSrcDevEnm The source to move from. This will be empty + * upon successful return. + */ +DECLINLINE(int) PDMAudioHostEnumMove(PPDMAUDIOHOSTENUM pDstDevEnm, PPDMAUDIOHOSTENUM pSrcDevEnm) +{ + AssertPtrReturn(pDstDevEnm, VERR_INVALID_POINTER); + AssertReturn(pDstDevEnm->uMagic != PDMAUDIOHOSTENUM_MAGIC || pDstDevEnm->cDevices == 0, VERR_WRONG_ORDER); + + AssertPtrReturn(pSrcDevEnm, VERR_INVALID_POINTER); + AssertReturn(pSrcDevEnm->uMagic == PDMAUDIOHOSTENUM_MAGIC, VERR_WRONG_ORDER); + + pDstDevEnm->uMagic = PDMAUDIOHOSTENUM_MAGIC; + RTListInit(&pDstDevEnm->LstDevices); + pDstDevEnm->cDevices = pSrcDevEnm->cDevices; + if (pSrcDevEnm->cDevices) + { + PPDMAUDIOHOSTDEV pCur; + while ((pCur = RTListRemoveFirst(&pSrcDevEnm->LstDevices, PDMAUDIOHOSTDEV, ListEntry)) != NULL) + RTListAppend(&pDstDevEnm->LstDevices, &pCur->ListEntry); + } + return VINF_SUCCESS; +} + +/** + * Get the default device with the given usage. + * + * This assumes that only one default device per usage is set, if there should + * be more than one, the first one is returned. + * + * @returns Default device if found, or NULL if not. + * @param pDevEnm Device enumeration to get default device for. + * @param enmUsage Usage to get default device for. + * Pass PDMAUDIODIR_INVALID to get the first device with + * either PDMAUDIOHOSTDEV_F_DEFAULT_OUT or + * PDMAUDIOHOSTDEV_F_DEFAULT_IN set. + */ +DECLINLINE(PPDMAUDIOHOSTDEV) PDMAudioHostEnumGetDefault(PCPDMAUDIOHOSTENUM pDevEnm, PDMAUDIODIR enmUsage) +{ + AssertPtrReturn(pDevEnm, NULL); + AssertReturn(pDevEnm->uMagic == PDMAUDIOHOSTENUM_MAGIC, NULL); + + Assert(enmUsage == PDMAUDIODIR_IN || enmUsage == PDMAUDIODIR_OUT || enmUsage == PDMAUDIODIR_INVALID); + uint32_t const fFlags = enmUsage == PDMAUDIODIR_IN ? PDMAUDIOHOSTDEV_F_DEFAULT_IN + : enmUsage == PDMAUDIODIR_OUT ? PDMAUDIOHOSTDEV_F_DEFAULT_OUT + : enmUsage == PDMAUDIODIR_INVALID ? PDMAUDIOHOSTDEV_F_DEFAULT_IN | PDMAUDIOHOSTDEV_F_DEFAULT_OUT + : 0; + + PPDMAUDIOHOSTDEV pDev; + RTListForEach(&pDevEnm->LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry) + { + if (pDev->fFlags & fFlags) + { + Assert(pDev->enmUsage == enmUsage || pDev->enmUsage == PDMAUDIODIR_DUPLEX || enmUsage == PDMAUDIODIR_INVALID); + return pDev; + } + } + + return NULL; +} + +/** + * Get the number of device with the given usage. + * + * @returns Number of matching devices. + * @param pDevEnm Device enumeration to get default device for. + * @param enmUsage Usage to count devices for. + * Pass PDMAUDIODIR_INVALID to get the total number of devices. + */ +DECLINLINE(uint32_t) PDMAudioHostEnumCountMatching(PCPDMAUDIOHOSTENUM pDevEnm, PDMAUDIODIR enmUsage) +{ + AssertPtrReturn(pDevEnm, 0); + AssertReturn(pDevEnm->uMagic == PDMAUDIOHOSTENUM_MAGIC, 0); + + if (enmUsage == PDMAUDIODIR_INVALID) + return pDevEnm->cDevices; + + uint32_t cDevs = 0; + PPDMAUDIOHOSTDEV pDev; + RTListForEach(&pDevEnm->LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry) + { + if (enmUsage == pDev->enmUsage) + cDevs++; + } + + return cDevs; +} + +/** The max string length for all PDMAUDIOHOSTDEV_F_XXX. + * @sa PDMAudioHostDevFlagsToString */ +#define PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN sizeof("DEFAULT_OUT DEFAULT_IN HOTPLUG BUGGY IGNORE LOCKED DEAD NAME_ALLOC ID_ALLOC NO_DUP ") + +/** + * Converts an audio device flags to a string. + * + * @returns + * @param pszDst Destination buffer with a size of at least + * PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN bytes (including + * the string terminator). + * @param fFlags Audio flags (PDMAUDIOHOSTDEV_F_XXX) to convert. + */ +DECLINLINE(const char *) PDMAudioHostDevFlagsToString(char pszDst[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN], uint32_t fFlags) +{ + static const struct { const char *pszMnemonic; uint32_t cchMnemonic; uint32_t fFlag; } s_aFlags[] = + { + { RT_STR_TUPLE("DEFAULT_OUT "), PDMAUDIOHOSTDEV_F_DEFAULT_OUT }, + { RT_STR_TUPLE("DEFAULT_IN "), PDMAUDIOHOSTDEV_F_DEFAULT_IN }, + { RT_STR_TUPLE("HOTPLUG "), PDMAUDIOHOSTDEV_F_HOTPLUG }, + { RT_STR_TUPLE("BUGGY "), PDMAUDIOHOSTDEV_F_BUGGY }, + { RT_STR_TUPLE("IGNORE "), PDMAUDIOHOSTDEV_F_IGNORE }, + { RT_STR_TUPLE("LOCKED "), PDMAUDIOHOSTDEV_F_LOCKED }, + { RT_STR_TUPLE("DEAD "), PDMAUDIOHOSTDEV_F_DEAD }, + { RT_STR_TUPLE("NAME_ALLOC "), PDMAUDIOHOSTDEV_F_NAME_ALLOC }, + { RT_STR_TUPLE("ID_ALLOC "), PDMAUDIOHOSTDEV_F_ID_ALLOC }, + { RT_STR_TUPLE("NO_DUP "), PDMAUDIOHOSTDEV_F_NO_DUP }, + }; + size_t offDst = 0; + for (uint32_t i = 0; i < RT_ELEMENTS(s_aFlags); i++) + if (fFlags & s_aFlags[i].fFlag) + { + fFlags &= ~s_aFlags[i].fFlag; + memcpy(&pszDst[offDst], s_aFlags[i].pszMnemonic, s_aFlags[i].cchMnemonic); + offDst += s_aFlags[i].cchMnemonic; + } + Assert(fFlags == 0); + Assert(offDst < PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN); + + if (offDst) + pszDst[offDst - 1] = '\0'; + else + memcpy(pszDst, "NONE", sizeof("NONE")); + return pszDst; +} + +/** + * Logs an audio device enumeration. + * + * @param pDevEnm Device enumeration to log. + * @param pszDesc Logging description (prefix). + */ +DECLINLINE(void) PDMAudioHostEnumLog(PCPDMAUDIOHOSTENUM pDevEnm, const char *pszDesc) +{ +#ifdef LOG_ENABLED + AssertPtrReturnVoid(pDevEnm); + AssertPtrReturnVoid(pszDesc); + AssertReturnVoid(pDevEnm->uMagic == PDMAUDIOHOSTENUM_MAGIC); + + if (LogIsEnabled()) + { + LogFunc(("%s: %RU32 devices\n", pszDesc, pDevEnm->cDevices)); + + PPDMAUDIOHOSTDEV pDev; + RTListForEach(&pDevEnm->LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry) + { + char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN]; + LogFunc(("Device '%s':\n", pDev->pszName)); + LogFunc((" ID = %s\n", pDev->pszId ? pDev->pszId : "")); + LogFunc((" Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage))); + LogFunc((" Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags))); + LogFunc((" Input channels = %RU8\n", pDev->cMaxInputChannels)); + LogFunc((" Output channels = %RU8\n", pDev->cMaxOutputChannels)); + LogFunc((" cbExtra = %RU32 bytes\n", pDev->cbSelf - sizeof(PDMAUDIOHOSTDEV))); + } + } +#else + RT_NOREF(pDevEnm, pszDesc); +#endif +} + +/** @} */ + +#endif /* !VBOX_INCLUDED_vmm_pdmaudiohostenuminline_h */ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmaudioifs.h virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmaudioifs.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmaudioifs.h 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmaudioifs.h 2022-09-01 13:17:48.000000000 +0000 @@ -25,202 +25,215 @@ /** @page pg_pdm_audio PDM Audio * - * @section sec_pdm_audio_overview Audio architecture overview + * PDM provides audio device emulations and their driver chains with the + * interfaces they need to communicate with each other. * - * The audio architecture mainly consists of two PDM interfaces, - * PDMIAUDIOCONNECTOR and PDMIHOSTAUDIO. * - * The PDMIAUDIOCONNECTOR interface is responsible of connecting a device - * emulation, such as SB16, AC'97 and HDA to one or multiple audio backend(s). - * Its API abstracts audio stream handling and I/O functions, device enumeration - * and so on. + * @section sec_pdm_audio_overview Overview * - * The PDMIHOSTAUDIO interface must be implemented by all audio backends to - * provide an abstract and common way of accessing needed functions, such as - * transferring output audio data for playing audio or recording input from the - * host. - * - * A device emulation can have one or more LUNs attached to it, whereas these - * LUNs in turn then all have their own PDMIAUDIOCONNECTOR, making it possible - * to connect multiple backends to a certain device emulation stream - * (multiplexing). - * - * An audio backend's job is to record and/or play audio data (depending on its - * capabilities). It highly depends on the host it's running on and needs very - * specific (host-OS-dependent) code. The backend itself only has very limited - * ways of accessing and/or communicating with the PDMIAUDIOCONNECTOR interface - * via callbacks, but never directly with the device emulation or other parts of - * the audio sub system. - * - * - * @section sec_pdm_audio_mixing Mixing - * - * The AUDIOMIXER API is optionally available to create and manage virtual audio - * mixers. Such an audio mixer in turn then can be used by the device emulation - * code to manage all the multiplexing to/from the connected LUN audio streams. - * - * Currently only input and output stream are supported. Duplex stream are not - * supported yet. - * - * This also is handy if certain LUN audio streams should be added or removed - * during runtime. - * - * To create a group of either input or output streams the AUDMIXSINK API can be - * used. - * - * For example: The device emulation has one hardware output stream (HW0), and - * that output stream shall be available to all connected LUN backends. For that - * to happen, an AUDMIXSINK sink has to be created and attached to the device's - * AUDIOMIXER object. - * - * As every LUN has its own AUDMIXSTREAM object, adding all those - * objects to the just created audio mixer sink will do the job. - * - * @note The AUDIOMIXER API is purely optional and is not used by all currently - * implemented device emulations (e.g. SB16). - * - * - * @section sec_pdm_audio_data_processing Data processing - * - * Audio input / output data gets handed off to/from the device emulation in an - * unmodified (raw) way. The actual audio frame / sample conversion is done via - * the PDMAUDIOMIXBUF API. - * - * This concentrates the audio data processing in one place and makes it easier - * to test / benchmark such code. - * - * A PDMAUDIOFRAME is the internal representation of a single audio frame, which - * consists of a single left and right audio sample in time. Only mono (1) and - * stereo (2) channel(s) currently are supported. +@startuml +skinparam componentStyle rectangle + +node VM { + [Music Player App] --> [Guest Audio Driver] + [Recording App] <-- [Guest Audio Driver] +} + +component "DevAudio (DevHda / DevIchAc97 / DevSB16)" as DevAudio { + [Output DMA Engine] + [Input DMA Engine] + () LUN0 + () LUN1 + + component "AudioMixer" { + component "Output Sink" { + () "Output Stream #0" as DrvStreamOut0 + () "Output Stream #1" as DrvStreamOut1 + [Output Mixer Buffer] --> DrvStreamOut0 + [Output Mixer Buffer] --> DrvStreamOut1 + [Output DMA Engine] --> [Output Mixer Buffer] + DrvStreamOut0 --> LUN0 + DrvStreamOut1 --> LUN1 + } + component "Input Sink" { + () "Input Stream #2" as DrvStreamIn0 + () "Input Stream #3" as DrvStreamIn1 + [Input Mixer Buffer] <-- DrvStreamIn0 + [Input Mixer Buffer] <-- DrvStreamIn1 + [Input DMA Engine] --> [Input Mixer Buffer] + DrvStreamIn0 <-- LUN0 + DrvStreamIn1 <-- LUN1 + } + } +} +[Guest Audio Driver] <..> DevAudio : " MMIO or Port I/O, DMA" + +node "Driver Chain #0" { + component "DrvAudio#0" { + () PDMIHOSTAUDIOPORT0 + () PDMIAUDIOCONNECTOR0 + } + component "DrvHostAudioWasApi" { + () PDMIHOSTAUDIO0 + } +} +PDMIHOSTAUDIOPORT0 <--> PDMIHOSTAUDIO0 + +node "Driver Chain #1" { + component "DrvAudio#1" { + () PDMIAUDIOCONNECTOR1 + () PDMIHOSTAUDIOPORT1 + } + component "DrvAudioVRDE" { + () PDMIHOSTAUDIO1 + } +} +note bottom of DrvAudioVRDE + The backend driver is sometimes not configured if the component it represents + is not configured for the VM. However, Main will still set up the LUN but + with just DrvAudio attached to simplify runtime activation of the component. + In the meanwhile, the DrvAudio instance works as if DrvHostAudioNull were attached. +end note + +LUN1 <--> PDMIAUDIOCONNECTOR1 +LUN0 <--> PDMIAUDIOCONNECTOR0 + +PDMIHOSTAUDIOPORT1 <--> PDMIHOSTAUDIO1 + +@enduml + * + * Actors: + * - An audio device implementation: "DevAudio" + * - Mixer instance (AudioMixer.cpp) with one or more mixer + * sinks: "Output Sink", "Input Sink" + * - One DMA engine teamed up with each mixer sink: "Output DMA + * Engine", "Input DMA Engine" + * - The audio driver "DrvAudio" instances attached to LUN0 and LUN1 + * respectively: "DrvAudio#0", "DrvAudio#1" + * - The Windows host audio driver attached to "DrvAudio0": "DrvHostAudioWas" + * - The VRDE/VRDP host audio driver attached to "DrvAudio1": "DrvAudioVRDE" + * + * Both "Output Sink" and "Input Sink" talks to all the attached driver chains + * ("DrvAudio #0" and "DrvAudio #1"), but using different PDMAUDIOSTREAM + * instances. There can be an arbritrary number of driver chains attached to an + * audio device, the mixer sinks will multiplex output to each of them and blend + * input from all of them, taking care of format and rate conversions. The + * mixer and mixer sinks does not fit into the PDM device/driver model, because + * a driver can only have exactly one or zero other drivers attached, so it is + * implemented as a separate component that all the audio devices share (see + * AudioMixer.h, AudioMixer.cpp, AudioMixBuffer.h and AudioMixBuffer.cpp). + * + * The driver chains attached to LUN0, LUN1, ... LUNn typically have two + * drivers attached, first DrvAudio and then a backend driver like + * DrvHostAudioWasApi, DrvHostAudioPulseAudio, or DrvAudioVRDE. DrvAudio + * exposes PDMIAUDIOCONNECTOR upwards towards the device and mixer component, + * and PDMIHOSTAUDIOPORT downwards towards DrvHostAudioWasApi and the other + * backends. + * + * The backend exposes the PDMIHOSTAUDIO upwards towards DrvAudio. It is + * possible, though, to only have the DrvAudio instance and not backend, in + * which case DrvAudio works as if the NULL backend was attached. Main does + * such setups when the main component we're interfacing with isn't currently + * active, as this simplifies runtime activation. + * + * The purpose of DrvAudio is to make the work of the backend as simple as + * possible and try avoid needing to write the same code over and over again for + * each backend. It takes care of: + * - Stream creation, operation, re-initialization and destruction. + * - Pre-buffering. + * - Thread pool. + * + * The purpose of a host audio driver (aka backend) is to interface with the + * host audio system (or other audio systems like VRDP and video recording). + * The backend will optionally provide a list of host audio devices, switch + * between them, and monitor changes to them. By default our host backends use + * the default host device and will trigger stream re-initialization if this + * changes while we're using it. + * + * + * @section sec_pdm_audio_device Virtual Audio Device + * + * The virtual device translates the settings of the emulated device into mixing + * sinks with sample format, sample rate, volume control, and whatnot. + * + * It also implements a DMA engine for transfering samples to (input) or from + * (output) the guest memory. The starting and stopping of the DMA engines are + * communicated to the associated mixing sinks and by then onto the + * PDMAUDIOSTREAM instance for each driver chain. A RTCIRCBUF is used as an + * intermediary between the DMA engine and the asynchronous worker thread of the + * mixing sink. + * + * + * @section sec_pdm_audio_mixing Audio Mixing + * + * The audio mixer is a mandatory component in an audio device. It consists of + * a mixer and one or more sinks with mixer buffers. The sinks are typically + * one per virtual output/input connector, so for instance you could have a + * device with a "PCM Output" sink and a "PCM Input" sink. + * + * The audio mixer takes care of: + * - Much of the driver chain (LUN) management work. + * - Multiplexing output to each active driver chain. + * - Blending input from each active driver chain into a single audio + * stream. + * - Do format conversion (it uses signed 32-bit PCM internally) between + * the audio device and all of the LUNs (no common format needed). + * - Do sample rate conversions between the device rate and that of the + * individual driver chains. + * - Apply the volume settings of the device to the audio stream. + * - Provide the asynchronous thread that pushes data from the device's + * internal DMA buffer and all the way to the backend for output sinks, + * and vice versa for input. + * + * The term active LUNs above means that not all LUNs will actually produce + * (input) or consume (output) audio. The mixer checks the return of + * PDMIHOSTAUDIO::pfnStreamGetState each time it's processing samples to see + * which streams are currently active and which aren't. Inactive streams are + * ignored. + * + * For more info: @ref pg_audio_mixer, @ref pg_audio_mixing_buffers + * + * The AudioMixer API reference can be found here: + * - @ref grp_pdm_ifs_audio_mixing + * - @ref grp_pdm_ifs_audio_mixing_buffers * * * @section sec_pdm_audio_timing Timing * * Handling audio data in a virtual environment is hard, as the human perception - * is very sensitive to the slightest cracks and stutters in the audible data. - * This can happen if the VM's timing is lagging behind or not within the - * expected time frame. - * - * The two main components which unfortunately contradict each other is a) the - * audio device emulation and b) the audio backend(s) on the host. Those need to - * be served in a timely manner to function correctly. To make e.g. the device - * emulation rely on the pace the host backend(s) set - or vice versa - will not - * work, as the guest's audio system / drivers then will not be able to - * compensate this accordingly. - * - * So each component, the device emulation, the audio connector(s) and the - * backend(s) must do its thing *when* it needs to do it, independently of the - * others. For that we use various (small) ring buffers to (hopefully) serve all - * components with the amount of data *when* they need it. - * - * Additionally, the device emulation can run with a different audio frame size, - * while the backends(s) may require a different frame size (16 bit stereo - * -> 8 bit mono, for example). - * - * The device emulation can give the audio connector(s) a scheduling hint - * (optional), e.g. in which interval it expects any data processing. - * - * A data transfer for playing audio data from the guest on the host looks like - * this: (RB = Ring Buffer, MB = Mixing Buffer) - * - * (A) Device DMA -> (B) Device RB -> (C) Audio Connector %Guest MB -> (D) Audio - * Connector %Host MB -> (E) Backend RB (optional, up to the backend) -> (F) - * Backend audio framework. - * - * When capturing audio data the chain is similar to the above one, just in a - * different direction, of course. - * - * The audio connector hereby plays a key role when it comes to (pre-)buffering - * data to minimize any audio stutters and/or cracks. The following values, - * which also can be tweaked via CFGM / extra-data are available: - * - * - The pre-buffering time (in ms): Audio data which needs to be buffered - * before any playback (or capturing) can happen. - * - The actual buffer size (in ms): How big the mixing buffer (for C and D) - * will be. - * - The period size (in ms): How big a chunk of audio (often called period or - * fragment) for F must be to get handled correctly. - * - * The above values can be set on a per-driver level, whereas input and output - * streams for a driver also can be handled set independently. The verbose audio - * (release) log will tell about the (final) state of each audio stream. - * - * - * @section sec_pdm_audio_diagram Diagram - * - * @todo r=bird: Not quite able to make sense of this, esp. the - * AUDMIXSINK/AUDIOMIXER bits crossing the LUN connections. - * - * @verbatim - +----------------------------------+ - |Device (SB16 / AC'97 / HDA) | - |----------------------------------| - |AUDIOMIXER (Optional) | - |AUDMIXSINK0 (Optional) | - |AUDMIXSINK1 (Optional) | - |AUDMIXSINKn (Optional) | - | | - | L L L | - | U U U | - | N N N | - | 0 1 n | - +-----+----+----+------------------+ - | | | - | | | - +--------------+ | | | +-------------+ - |AUDMIXSINK | | | | |AUDIOMIXER | - |--------------| | | | |-------------| - |AUDMIXSTREAM0 |+-|----|----|-->|AUDMIXSINK0 | - |AUDMIXSTREAM1 |+-|----|----|-->|AUDMIXSINK1 | - |AUDMIXSTREAMn |+-|----|----|-->|AUDMIXSINKn | - +--------------+ | | | +-------------+ - | | | - | | | - +----+----+----+----+ - |LUN | - |-------------------| - |PDMIAUDIOCONNECTOR | - |AUDMIXSTREAM | - | +------+ - | | | - | | | - | | | - +-------------------+ | - | - +-------------------------+ | - +-------------------------+ +----+--------------------+ - |PDMAUDIOSTREAM | |PDMIAUDIOCONNECTOR | - |-------------------------| |-------------------------| - |PDMAUDIOMIXBUF |+------>|PDMAUDIOSTREAM Host | - |PDMAUDIOSTREAMCFG |+------>|PDMAUDIOSTREAM Guest | - | | |Device capabilities | - | | |Device configuration | - | | | | - | | +--+|PDMIHOSTAUDIO | - | | | |+-----------------------+| - +-------------------------+ | ||Backend storage space || - | |+-----------------------+| - | +-------------------------+ - | - +---------------------+ | - |PDMIHOSTAUDIO | | - |+--------------+ | | - ||DirectSound | | | - |+--------------+ | | - | | | - |+--------------+ | | - ||PulseAudio | | | - |+--------------+ |+-------+ - | | - |+--------------+ | - ||Core Audio | | - |+--------------+ | - | | - | | - | | - | | - +---------------------+ - @endverbatim + * is very sensitive to the slightest cracks and stutters in the audible data, + * and the task of playing back and recording audio is in the real-time domain. + * + * The virtual machine is not executed with any real-time guarentees, only best + * effort, mainly because it is subject to preemptive scheduling on the host + * side. The audio processing done on the guest side is typically also subject + * to preemptive scheduling on the guest side and available CPU processing power + * there. + * + * Thus, the guest may be lagging behind because the host prioritizes other + * processes/threads over the virtual machine. This will, if it's too servere, + * cause the virtual machine to speed up it's time sense while it's trying to + * catch up. So, we can easily have a bit of a seesaw execution going on here, + * where in the playback case, the guest produces data too slowly for while and + * then switches to producing it too quickly for a while to catch up. + * + * Our working principle is that the backends and the guest are producing and + * consuming samples at the same rate, but we have to deal with the uneven + * execution. + * + * To deal with this we employ (by default) 300ms of backend buffer and + * pre-buffer 150ms of that for both input and output audio streams. This means + * we have about 150ms worth of samples to feed to the host audio device should + * the virtual machine be starving and lagging behind. Likewise, we have about + * 150ms of buffer space will can fill when the VM is in a catch-up mode. Now, + * 300ms and 150 ms isn't much for the purpose of glossing over + * scheduling/timinig differences here, but we can't do too much more or the lag + * will grow rather annoying. The pre-buffering is implemented by DrvAudio. + * + * In addition to the backend buffer that defaults to 300ms, we have the + * internal DMA buffer of the device and the mixing buffer of the mixing sink. + * The latter two are typically rather small, sized to fit the anticipated DMA + * period currently in use by the guest. */ #ifndef VBOX_INCLUDED_vmm_pdmaudioifs_h @@ -230,59 +243,25 @@ #endif #include +#include #include #include #include #include -#ifdef VBOX_WITH_STATISTICS -# include -#endif +#include +#include + +RT_C_DECLS_BEGIN + /** @defgroup grp_pdm_ifs_audio PDM Audio Interfaces * @ingroup grp_pdm_interfaces * @{ */ -#ifndef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH -# if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) -# define VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "c:\\temp\\" -# else -# define VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "/tmp/" -# endif -#endif - -/** PDM audio driver instance flags. */ -typedef uint32_t PDMAUDIODRVFLAGS; - -/** No flags set. */ -#define PDMAUDIODRVFLAGS_NONE 0 -/** Marks a primary audio driver which is critical - * when running the VM. */ -#define PDMAUDIODRVFLAGS_PRIMARY RT_BIT(0) - -/** - * Audio format in signed or unsigned variants. - */ -typedef enum PDMAUDIOFMT -{ - /** Invalid format, do not use. */ - PDMAUDIOFMT_INVALID = 0, - /** 8-bit, unsigned. */ - PDMAUDIOFMT_U8, - /** 8-bit, signed. */ - PDMAUDIOFMT_S8, - /** 16-bit, unsigned. */ - PDMAUDIOFMT_U16, - /** 16-bit, signed. */ - PDMAUDIOFMT_S16, - /** 32-bit, unsigned. */ - PDMAUDIOFMT_U32, - /** 32-bit, signed. */ - PDMAUDIOFMT_S32, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIOFMT_32BIT_HACK = 0x7fffffff -} PDMAUDIOFMT; +/** The maximum number of channels PDM supports. */ +#define PDMAUDIO_MAX_CHANNELS 12 /** * Audio direction. @@ -298,33 +277,38 @@ /** Output. */ PDMAUDIODIR_OUT, /** Duplex handling. */ - PDMAUDIODIR_ANY, + PDMAUDIODIR_DUPLEX, + /** End of valid values. */ + PDMAUDIODIR_END, /** Hack to blow the type up to 32-bit. */ PDMAUDIODIR_32BIT_HACK = 0x7fffffff } PDMAUDIODIR; -/** Device latency spec in milliseconds (ms). */ -typedef uint32_t PDMAUDIODEVLATSPECMS; -/** Device latency spec in seconds (s). */ -typedef uint32_t PDMAUDIODEVLATSPECSEC; - -/** @name PDMAUDIODEV_FLAGS_XXX +/** @name PDMAUDIOHOSTDEV_F_XXX * @{ */ /** No flags set. */ -#define PDMAUDIODEV_FLAGS_NONE UINT32_C(0) -/** The device marks the default device within the host OS. */ -#define PDMAUDIODEV_FLAGS_DEFAULT RT_BIT_32(0) +#define PDMAUDIOHOSTDEV_F_NONE UINT32_C(0) +/** The default input (capture/recording) device (for the user). */ +#define PDMAUDIOHOSTDEV_F_DEFAULT_IN RT_BIT_32(0) +/** The default output (playback) device (for the user). */ +#define PDMAUDIOHOSTDEV_F_DEFAULT_OUT RT_BIT_32(1) /** The device can be removed at any time and we have to deal with it. */ -#define PDMAUDIODEV_FLAGS_HOTPLUG RT_BIT_32(1) +#define PDMAUDIOHOSTDEV_F_HOTPLUG RT_BIT_32(2) /** The device is known to be buggy and needs special treatment. */ -#define PDMAUDIODEV_FLAGS_BUGGY RT_BIT_32(2) +#define PDMAUDIOHOSTDEV_F_BUGGY RT_BIT_32(3) /** Ignore the device, no matter what. */ -#define PDMAUDIODEV_FLAGS_IGNORE RT_BIT_32(3) +#define PDMAUDIOHOSTDEV_F_IGNORE RT_BIT_32(4) /** The device is present but marked as locked by some other application. */ -#define PDMAUDIODEV_FLAGS_LOCKED RT_BIT_32(4) +#define PDMAUDIOHOSTDEV_F_LOCKED RT_BIT_32(5) /** The device is present but not in an alive state (dead). */ -#define PDMAUDIODEV_FLAGS_DEAD RT_BIT_32(5) +#define PDMAUDIOHOSTDEV_F_DEAD RT_BIT_32(6) +/** Set if the PDMAUDIOHOSTDEV::pszName is allocated. */ +#define PDMAUDIOHOSTDEV_F_NAME_ALLOC RT_BIT_32(29) +/** Set if the PDMAUDIOHOSTDEV::pszId is allocated. */ +#define PDMAUDIOHOSTDEV_F_ID_ALLOC RT_BIT_32(30) +/** Set if the extra backend specific data cannot be duplicated. */ +#define PDMAUDIOHOSTDEV_F_NO_DUP RT_BIT_32(31) /** @} */ /** @@ -343,68 +327,77 @@ PDMAUDIODEVICETYPE_BUILTIN, /** The device is an (external) USB device. */ PDMAUDIODEVICETYPE_USB, + /** End of valid values. */ + PDMAUDIODEVICETYPE_END, /** Hack to blow the type up to 32-bit. */ PDMAUDIODEVICETYPE_32BIT_HACK = 0x7fffffff } PDMAUDIODEVICETYPE; /** - * Audio device info (enumeration result). - * @sa PDMAUDIODEVICEENUM, PDMIHOSTAUDIO::pfnGetDevices + * Host audio device info, part of enumeration result. + * + * @sa PDMAUDIOHOSTENUM, PDMIHOSTAUDIO::pfnGetDevices */ -typedef struct PDMAUDIODEVICE +typedef struct PDMAUDIOHOSTDEV { - /** List node. */ - RTLISTNODE Node; - /** Additional data which might be relevant for the current context. - * @todo r=bird: I would do this C++ style, having the host specific bits - * appended after this structure and downcast. */ - void *pvData; - /** Size of the additional data. */ - size_t cbData; + /** List entry (like PDMAUDIOHOSTENUM::LstDevices). */ + RTLISTNODE ListEntry; + /** Magic value (PDMAUDIOHOSTDEV_MAGIC). */ + uint32_t uMagic; + /** Size of this structure and whatever backend specific data that follows it. */ + uint32_t cbSelf; /** The device type. */ PDMAUDIODEVICETYPE enmType; /** Usage of the device. */ PDMAUDIODIR enmUsage; - /** Device flags, PDMAUDIODEV_FLAGS_XXX. */ + /** Device flags, PDMAUDIOHOSTDEV_F_XXX. */ uint32_t fFlags; - /** Reference count indicating how many audio streams currently are relying on this device. */ - uint8_t cRefCount; /** Maximum number of input audio channels the device supports. */ uint8_t cMaxInputChannels; /** Maximum number of output audio channels the device supports. */ uint8_t cMaxOutputChannels; - /** Device type union, based on enmType. */ - union - { - /** USB type specifics. */ - struct - { - /** Vendor ID. - * @todo r=bird: Why signed?? VUSB uses uint16_t for idVendor and idProduct! */ - int16_t VID; - /** Product ID. */ - int16_t PID; - } USB; - } Type; - /** Friendly name of the device, if any. */ - char szName[64]; -} PDMAUDIODEVICE; -/** Pointer to audio device info (enum result). */ -typedef PDMAUDIODEVICE *PPDMAUDIODEVICE; + uint8_t abAlignment[ARCH_BITS == 32 ? 2 + 8 : 2 + 8]; + /** Backend specific device identifier, can be NULL, used to select device. + * This can either point into some non-public part of this structure or to a + * RTStrAlloc allocation. PDMAUDIOHOSTDEV_F_ID_ALLOC is set in the latter + * case. + * @sa PDMIHOSTAUDIO::pfnSetDevice */ + char *pszId; + /** The friendly device name. */ + char *pszName; +} PDMAUDIOHOSTDEV; +AssertCompileSizeAlignment(PDMAUDIOHOSTDEV, 16); +/** Pointer to audio device info (enumeration result). */ +typedef PDMAUDIOHOSTDEV *PPDMAUDIOHOSTDEV; +/** Pointer to a const audio device info (enumeration result). */ +typedef PDMAUDIOHOSTDEV const *PCPDMAUDIOHOSTDEV; + +/** Magic value for PDMAUDIOHOSTDEV. */ +#define PDMAUDIOHOSTDEV_MAGIC PDM_VERSION_MAKE(0xa0d0, 3, 0) + /** - * An audio device enumeration result. + * A host audio device enumeration result. + * * @sa PDMIHOSTAUDIO::pfnGetDevices */ -typedef struct PDMAUDIODEVICEENUM +typedef struct PDMAUDIOHOSTENUM { + /** Magic value (PDMAUDIOHOSTENUM_MAGIC). */ + uint32_t uMagic; /** Number of audio devices in the list. */ - uint16_t cDevices; - /** List of audio devices. */ - RTLISTANCHOR lstDevices; -} PDMAUDIODEVICEENUM; + uint32_t cDevices; + /** List of audio devices (PDMAUDIOHOSTDEV). */ + RTLISTANCHOR LstDevices; +} PDMAUDIOHOSTENUM; /** Pointer to an audio device enumeration result. */ -typedef PDMAUDIODEVICEENUM *PPDMAUDIODEVICEENUM; +typedef PDMAUDIOHOSTENUM *PPDMAUDIOHOSTENUM; +/** Pointer to a const audio device enumeration result. */ +typedef PDMAUDIOHOSTENUM const *PCPDMAUDIOHOSTENUM; + +/** Magic for the host audio device enumeration. */ +#define PDMAUDIOHOSTENUM_MAGIC PDM_VERSION_MAKE(0xa0d1, 1, 0) + /** * Audio configuration (static) of an audio host backend. @@ -413,10 +406,10 @@ { /** The backend's friendly name. */ char szName[32]; - /** Size (in bytes) of the host backend's audio output stream structure. */ - size_t cbStreamOut; - /** Size (in bytes) of the host backend's audio input stream structure. */ - size_t cbStreamIn; + /** The size of the backend specific stream data (in bytes). */ + uint32_t cbStream; + /** PDMAUDIOBACKEND_F_XXX. */ + uint32_t fFlags; /** Number of concurrent output (playback) streams supported on the host. * UINT32_MAX for unlimited concurrent streams, 0 if no concurrent input streams are supported. */ uint32_t cMaxStreamsOut; @@ -427,277 +420,234 @@ /** Pointer to a static host audio audio configuration. */ typedef PDMAUDIOBACKENDCFG *PPDMAUDIOBACKENDCFG; -/** - * A single audio frame. - * - * Currently only two (2) channels, left and right, are supported. - * - * @note When changing this structure, make sure to also handle - * VRDP's input / output processing in DrvAudioVRDE, as VRDP - * expects audio data in st_sample_t format (historical reasons) - * which happens to be the same as PDMAUDIOFRAME for now. - */ -typedef struct PDMAUDIOFRAME -{ - /** Left channel. */ - int64_t i64LSample; - /** Right channel. */ - int64_t i64RSample; -} PDMAUDIOFRAME; -/** Pointer to a single (stereo) audio frame. */ -typedef PDMAUDIOFRAME *PPDMAUDIOFRAME; -/** Pointer to a const single (stereo) audio frame. */ -typedef PDMAUDIOFRAME const *PCPDMAUDIOFRAME; - -typedef enum PDMAUDIOENDIANNESS -{ - /** The usual invalid value. */ - PDMAUDIOENDIANNESS_INVALID = 0, - /** Little endian. */ - PDMAUDIOENDIANNESS_LITTLE, - /** Bit endian. */ - PDMAUDIOENDIANNESS_BIG, - /** Endianness doesn't have a meaning in the context. */ - PDMAUDIOENDIANNESS_NA, - /** The end of the valid endian values (exclusive). */ - PDMAUDIOENDIANNESS_END, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIOENDIANNESS_32BIT_HACK = 0x7fffffff -} PDMAUDIOENDIANNESS; - -/** @def PDMAUDIOHOSTENDIANNESS - * The PDMAUDIOENDIANNESS value for the host. */ -#if defined(RT_LITTLE_ENDIAN) -# define PDMAUDIOHOSTENDIANNESS PDMAUDIOENDIANNESS_LITTLE -#elif defined(RT_BIG_ENDIAN) -# define PDMAUDIOHOSTENDIANNESS PDMAUDIOENDIANNESS_BIG -#else -# error "Port me!" -#endif +/** @name PDMAUDIOBACKEND_F_XXX - PDMAUDIOBACKENDCFG::fFlags + * @{ */ +/** PDMIHOSTAUDIO::pfnStreamConfigHint should preferably be called on a + * worker thread rather than EMT as it may take a good while. */ +#define PDMAUDIOBACKEND_F_ASYNC_HINT RT_BIT_32(0) +/** PDMIHOSTAUDIO::pfnStreamDestroy and any preceeding + * PDMIHOSTAUDIO::pfnStreamControl/DISABLE should be preferably be called on a + * worker thread rather than EMT as it may take a good while. */ +#define PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY RT_BIT_32(1) +/** @} */ -/** - * Audio playback destinations. - */ -typedef enum PDMAUDIOPLAYBACKDST -{ - /** Invalid zero value as per usual (guards against using unintialized values). */ - PDMAUDIOPLAYBACKDST_INVALID = 0, - /** Unknown destination. */ - PDMAUDIOPLAYBACKDST_UNKNOWN, - /** Front channel. */ - PDMAUDIOPLAYBACKDST_FRONT, - /** Center / LFE (Subwoofer) channel. */ - PDMAUDIOPLAYBACKDST_CENTER_LFE, - /** Rear channel. */ - PDMAUDIOPLAYBACKDST_REAR, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIOPLAYBACKDST_32BIT_HACK = 0x7fffffff -} PDMAUDIOPLAYBACKDST; /** - * Audio recording sources. + * Audio path: input sources and playback destinations. * - * @note Because this is almost exclusively used in PDMAUDIODSTSRCUNION where it - * overlaps with PDMAUDIOPLAYBACKDST, the values starts at 64 instead of 0. - */ -typedef enum PDMAUDIORECSRC -{ - /** Unknown recording source. */ - PDMAUDIORECSRC_UNKNOWN = 64, - /** Microphone-In. */ - PDMAUDIORECSRC_MIC, - /** CD. */ - PDMAUDIORECSRC_CD, - /** Video-In. */ - PDMAUDIORECSRC_VIDEO, - /** AUX. */ - PDMAUDIORECSRC_AUX, - /** Line-In. */ - PDMAUDIORECSRC_LINE, - /** Phone-In. */ - PDMAUDIORECSRC_PHONE, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIORECSRC_32BIT_HACK = 0x7fffffff -} PDMAUDIORECSRC; - -/** - * Union for keeping an audio stream destination or source. - */ -typedef union PDMAUDIODSTSRCUNION -{ - /** Desired playback destination (for an output stream). */ - PDMAUDIOPLAYBACKDST enmDst; - /** Desired recording source (for an input stream). */ - PDMAUDIORECSRC enmSrc; -} PDMAUDIODSTSRCUNION; -/** Pointer to an audio stream src/dst union. */ -typedef PDMAUDIODSTSRCUNION *PPDMAUDIODSTSRCUNION; - -/** - * Audio stream (data) layout. - */ -typedef enum PDMAUDIOSTREAMLAYOUT -{ - /** Invalid zero value as per usual (guards against using unintialized values). */ - PDMAUDIOSTREAMLAYOUT_INVALID = 0, - /** Unknown access type; do not use (hdaR3StreamMapReset uses it). */ - PDMAUDIOSTREAMLAYOUT_UNKNOWN, - /** Non-interleaved access, that is, consecutive - * access to the data. */ - PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED, - /** Interleaved access, where the data can be - * mixed together with data of other audio streams. */ - PDMAUDIOSTREAMLAYOUT_INTERLEAVED, - /** Complex layout, which does not fit into the - * interleaved / non-interleaved layouts. */ - PDMAUDIOSTREAMLAYOUT_COMPLEX, - /** Raw (pass through) data, with no data layout processing done. - * - * This means that this stream will operate on PDMAUDIOFRAME data - * directly. Don't use this if you don't have to. */ - PDMAUDIOSTREAMLAYOUT_RAW, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIOSTREAMLAYOUT_32BIT_HACK = 0x7fffffff -} PDMAUDIOSTREAMLAYOUT; + * Think of this as the name of the socket you plug the virtual audio stream + * jack into. + * + * @note Not quite sure what the purpose of this type is. It used to be two + * separate enums (PDMAUDIOPLAYBACKDST & PDMAUDIORECSRC) without overlapping + * values and most commonly used in a union (PDMAUDIODSTSRCUNION). The output + * values were designated "channel" (e.g. "Front channel"), whereas this was not + * done to the input ones. So, I'm (bird) a little confused what the actual + * meaning was. + */ +typedef enum PDMAUDIOPATH +{ + /** Customary invalid zero value. */ + PDMAUDIOPATH_INVALID = 0, + + /** Unknown path / Doesn't care. */ + PDMAUDIOPATH_UNKNOWN, + + /** First output value. */ + PDMAUDIOPATH_OUT_FIRST, + /** Output: Front. */ + PDMAUDIOPATH_OUT_FRONT = PDMAUDIOPATH_OUT_FIRST, + /** Output: Center / LFE (Subwoofer). */ + PDMAUDIOPATH_OUT_CENTER_LFE, + /** Output: Rear. */ + PDMAUDIOPATH_OUT_REAR, + /** Last output value (inclusive) */ + PDMAUDIOPATH_OUT_END = PDMAUDIOPATH_OUT_REAR, + + /** First input value. */ + PDMAUDIOPATH_IN_FIRST, + /** Input: Microphone. */ + PDMAUDIOPATH_IN_MIC = PDMAUDIOPATH_IN_FIRST, + /** Input: CD. */ + PDMAUDIOPATH_IN_CD, + /** Input: Video-In. */ + PDMAUDIOPATH_IN_VIDEO, + /** Input: AUX. */ + PDMAUDIOPATH_IN_AUX, + /** Input: Line-In. */ + PDMAUDIOPATH_IN_LINE, + /** Input: Phone-In. */ + PDMAUDIOPATH_IN_PHONE, + /** Last intput value (inclusive). */ + PDMAUDIOPATH_IN_LAST = PDMAUDIOPATH_IN_PHONE, + + /** End of valid values. */ + PDMAUDIOPATH_END, + /** Hack to blow the typ up to 32 bits. */ + PDMAUDIOPATH_32BIT_HACK = 0x7fffffff +} PDMAUDIOPATH; -/** - * Stream channel data block. - */ -typedef struct PDMAUDIOSTREAMCHANNELDATA -{ - /** Circular buffer for the channel data. */ - PRTCIRCBUF pCircBuf; - /** Amount of audio data (in bytes) acquired for reading. */ - size_t cbAcq; - /** Channel data flags, PDMAUDIOSTREAMCHANNELDATA_FLAGS_XXX. */ - uint32_t fFlags; -} PDMAUDIOSTREAMCHANNELDATA; -/** Pointer to audio stream channel data buffer. */ -typedef PDMAUDIOSTREAMCHANNELDATA *PPDMAUDIOSTREAMCHANNELDATA; - -/** @name PDMAUDIOSTREAMCHANNELDATA_FLAGS_XXX - * @{ */ -/** No stream channel data flags defined. */ -#define PDMAUDIOSTREAMCHANNELDATA_FLAGS_NONE UINT32_C(0) -/** @} */ /** * Standard speaker channel IDs. - * - * This can cover up to 11.0 surround sound. - * - * @note Any of those channels can be marked / used as the LFE channel (played - * through the subwoofer). */ -typedef enum PDMAUDIOSTREAMCHANNELID +typedef enum PDMAUDIOCHANNELID { /** Invalid zero value as per usual (guards against using unintialized values). */ - PDMAUDIOSTREAMCHANNELID_INVALID = 0, - /** Unknown / not set channel ID. */ - PDMAUDIOSTREAMCHANNELID_UNKNOWN, - /** Front left channel. */ - PDMAUDIOSTREAMCHANNELID_FRONT_LEFT, - /** Front right channel. */ - PDMAUDIOSTREAMCHANNELID_FRONT_RIGHT, - /** Front center channel. */ - PDMAUDIOSTREAMCHANNELID_FRONT_CENTER, + PDMAUDIOCHANNELID_INVALID = 0, + + /** Unused channel - fill with zero when encoding, ignore when decoding. */ + PDMAUDIOCHANNELID_UNUSED_ZERO, + /** Unused channel - fill with silence when encoding, ignore when decoding. */ + PDMAUDIOCHANNELID_UNUSED_SILENCE, + + /** Unknown channel ID (unable to map to PDM terms). */ + PDMAUDIOCHANNELID_UNKNOWN, + + /** The first ID in the standard WAV-file assignment block. */ + PDMAUDIOCHANNELID_FIRST_STANDARD, + /** Front left channel (FR). */ + PDMAUDIOCHANNELID_FRONT_LEFT = PDMAUDIOCHANNELID_FIRST_STANDARD, + /** Front right channel (FR). */ + PDMAUDIOCHANNELID_FRONT_RIGHT, + /** Front center channel (FC). */ + PDMAUDIOCHANNELID_FRONT_CENTER, + /** Mono channel (alias for front center). */ + PDMAUDIOCHANNELID_MONO = PDMAUDIOCHANNELID_FRONT_CENTER, /** Low frequency effects (subwoofer) channel. */ - PDMAUDIOSTREAMCHANNELID_LFE, - /** Rear left channel. */ - PDMAUDIOSTREAMCHANNELID_REAR_LEFT, - /** Rear right channel. */ - PDMAUDIOSTREAMCHANNELID_REAR_RIGHT, - /** Front left of center channel. */ - PDMAUDIOSTREAMCHANNELID_FRONT_LEFT_OF_CENTER, - /** Front right of center channel. */ - PDMAUDIOSTREAMCHANNELID_FRONT_RIGHT_OF_CENTER, - /** Rear center channel. */ - PDMAUDIOSTREAMCHANNELID_REAR_CENTER, - /** Side left channel. */ - PDMAUDIOSTREAMCHANNELID_SIDE_LEFT, - /** Side right channel. */ - PDMAUDIOSTREAMCHANNELID_SIDE_RIGHT, - /** Left height channel. */ - PDMAUDIOSTREAMCHANNELID_LEFT_HEIGHT, - /** Right height channel. */ - PDMAUDIOSTREAMCHANNELID_RIGHT_HEIGHT, + PDMAUDIOCHANNELID_LFE, + /** Rear left channel (BL). */ + PDMAUDIOCHANNELID_REAR_LEFT, + /** Rear right channel (BR). */ + PDMAUDIOCHANNELID_REAR_RIGHT, + /** Front left of center channel (FLC). */ + PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER, + /** Front right of center channel (FLR). */ + PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER, + /** Rear center channel (BC). */ + PDMAUDIOCHANNELID_REAR_CENTER, + /** Side left channel (SL). */ + PDMAUDIOCHANNELID_SIDE_LEFT, + /** Side right channel (SR). */ + PDMAUDIOCHANNELID_SIDE_RIGHT, + /** Top center (TC). */ + PDMAUDIOCHANNELID_TOP_CENTER, + /** Front left height channel (TFL). */ + PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT, + /** Front center height channel (TFC). */ + PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT, + /** Front right height channel (TFR). */ + PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT, + /** Rear left height channel (TBL). */ + PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT, + /** Rear center height channel (TBC). */ + PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT, + /** Rear right height channel (TBR). */ + PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT, + /** The end of the standard WAV-file assignment block. */ + PDMAUDIOCHANNELID_END_STANDARD, + + /** End of valid values. */ + PDMAUDIOCHANNELID_END = PDMAUDIOCHANNELID_END_STANDARD, /** Hack to blow the type up to 32-bit. */ - PDMAUDIOSTREAMCHANNELID_32BIT_HACK = 0x7fffffff -} PDMAUDIOSTREAMCHANNELID; + PDMAUDIOCHANNELID_32BIT_HACK = 0x7fffffff +} PDMAUDIOCHANNELID; +AssertCompile(PDMAUDIOCHANNELID_FRONT_LEFT - PDMAUDIOCHANNELID_FIRST_STANDARD == 0); +AssertCompile(PDMAUDIOCHANNELID_LFE - PDMAUDIOCHANNELID_FIRST_STANDARD == 3); +AssertCompile(PDMAUDIOCHANNELID_REAR_CENTER - PDMAUDIOCHANNELID_FIRST_STANDARD == 8); +AssertCompile(PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT - PDMAUDIOCHANNELID_FIRST_STANDARD == 17); -/** - * Mappings channels onto an audio stream. - * - * The mappings are either for a single (mono) or dual (stereo) channels onto an - * audio stream (aka stream profile). An audio stream consists of one or - * multiple channels (e.g. 1 for mono, 2 for stereo), depending on the - * configuration. - */ -typedef struct PDMAUDIOSTREAMMAP -{ - /** Array of channel IDs being handled. - * @note The first (zero-based) index specifies the leftmost channel. */ - PDMAUDIOSTREAMCHANNELID aenmIDs[2]; - /** Step size (in bytes) to the channel's next frame. */ - uint32_t cbStep; - /** Frame size (in bytes) of this channel. */ - uint32_t cbFrame; - /** Byte offset to the first frame in the data block. */ - uint32_t offFirst; - /** Byte offset to the next frame in the data block. */ - uint32_t offNext; - /** Associated data buffer. */ - PDMAUDIOSTREAMCHANNELDATA Data; -} PDMAUDIOSTREAMMAP; -/** Pointer to an audio stream channel mapping. */ -typedef PDMAUDIOSTREAMMAP *PPDMAUDIOSTREAMMAP; /** * Properties of audio streams for host/guest for in or out directions. */ typedef struct PDMAUDIOPCMPROPS { - /** Sample width (in bytes). */ - uint8_t cbSample; - /** Number of audio channels. */ - uint8_t cChannels; + /** The frame size. */ + uint8_t cbFrame; /** Shift count used with PDMAUDIOPCMPROPS_F2B and PDMAUDIOPCMPROPS_B2F. * Depends on number of stream channels and the stream format being used, calc * value using PDMAUDIOPCMPROPS_MAKE_SHIFT. - * @sa PDMAUDIOSTREAMCFG_B2F, PDMAUDIOSTREAMCFG_F2B - * @todo r=bird: The original brief description: "Shift count used - * for faster calculation of various values, such as the alignment, bytes - * to frames and so on." I cannot make heads or tails from that. - * @todo Use some RTAsmXXX functions instead? */ - uint8_t cShift; + * @sa PDMAUDIOSTREAMCFG_B2F, PDMAUDIOSTREAMCFG_F2B */ + uint8_t cShiftX; + /** Sample width (in bytes). */ + RT_GCC_EXTENSION + uint8_t cbSampleX : 4; + /** Number of audio channels. */ + RT_GCC_EXTENSION + uint8_t cChannelsX : 4; /** Signed or unsigned sample. */ bool fSigned : 1; /** Whether the endianness is swapped or not. */ bool fSwapEndian : 1; + /** Raw mixer frames, only applicable for signed 64-bit samples. + * The raw mixer samples are really just signed 32-bit samples stored as 64-bit + * integers without any change in the value. + * + * @todo Get rid of this, only VRDE needs it an it should use the common + * mixer code rather than cooking its own stuff. */ + bool fRaw : 1; /** Sample frequency in Hertz (Hz). */ uint32_t uHz; + /** PDMAUDIOCHANNELID mappings for each channel. + * This ASSUMES all channels uses the same sample size. */ + uint8_t aidChannels[PDMAUDIO_MAX_CHANNELS]; + /** Padding the structure up to 32 bytes. */ + uint32_t auPadding[3]; } PDMAUDIOPCMPROPS; -AssertCompileSize(PDMAUDIOPCMPROPS, 8); +AssertCompileSize(PDMAUDIOPCMPROPS, 32); AssertCompileSizeAlignment(PDMAUDIOPCMPROPS, 8); /** Pointer to audio stream properties. */ typedef PDMAUDIOPCMPROPS *PPDMAUDIOPCMPROPS; +/** Pointer to const audio stream properties. */ +typedef PDMAUDIOPCMPROPS const *PCPDMAUDIOPCMPROPS; /** @name Macros for use with PDMAUDIOPCMPROPS * @{ */ -/** Initializor for PDMAUDIOPCMPROPS. */ -#define PDMAUDIOPCMPROPS_INITIALIZOR(a_cBytes, a_fSigned, a_cCannels, a_uHz, a_cShift, a_fSwapEndian) \ - { a_cBytes, a_cCannels, a_cShift, a_fSigned, a_fSwapEndian, a_uHz } +/** Initializer for PDMAUDIOPCMPROPS. + * @note The default channel mapping here is very simple and doesn't always + * match that of PDMAudioPropsInit and PDMAudioPropsInitEx. */ +#define PDMAUDIOPCMPROPS_INITIALIZER(a_cbSample, a_fSigned, a_cChannels, a_uHz, a_fSwapEndian) \ + { \ + (uint8_t)((a_cbSample) * (a_cChannels)), PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(a_cbSample, a_cChannels), \ + (uint8_t)(a_cbSample), (uint8_t)(a_cChannels), a_fSigned, a_fSwapEndian, false /*fRaw*/, a_uHz, \ + /*aidChannels =*/ { \ + (a_cChannels) > 1 ? PDMAUDIOCHANNELID_FRONT_LEFT : PDMAUDIOCHANNELID_MONO, \ + (a_cChannels) >= 2 ? PDMAUDIOCHANNELID_FRONT_RIGHT : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 3 ? PDMAUDIOCHANNELID_FRONT_CENTER : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 4 ? PDMAUDIOCHANNELID_LFE : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 5 ? PDMAUDIOCHANNELID_REAR_LEFT : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 6 ? PDMAUDIOCHANNELID_REAR_RIGHT : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 7 ? PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 8 ? PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 9 ? PDMAUDIOCHANNELID_REAR_CENTER : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 10 ? PDMAUDIOCHANNELID_SIDE_LEFT : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 11 ? PDMAUDIOCHANNELID_SIDE_RIGHT : PDMAUDIOCHANNELID_INVALID, \ + (a_cChannels) >= 12 ? PDMAUDIOCHANNELID_UNKNOWN : PDMAUDIOCHANNELID_INVALID, \ + }, \ + /* auPadding = */ { 0, 0, 0 } \ + } + /** Calculates the cShift value of given sample bits and audio channels. - * @note Does only support mono/stereo channels for now. */ -#define PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(cBytes, cChannels) ((cChannels == 2) + (cBytes / 2)) + * @note Does only support mono/stereo channels for now, for non-stereo/mono we + * returns a special value which the two conversion functions detect + * and make them fall back on cbSample * cChannels. */ +#define PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(cbSample, cChannels) \ + ( RT_IS_POWER_OF_TWO((unsigned)((cChannels) * (cbSample))) \ + ? (uint8_t)(ASMBitFirstSetU32((unsigned)((cChannels) * (cbSample))) - 1) : (uint8_t)UINT8_MAX ) /** Calculates the cShift value of a PDMAUDIOPCMPROPS structure. */ -#define PDMAUDIOPCMPROPS_MAKE_SHIFT(pProps) PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS((pProps)->cbSample, (pProps)->cChannels) +#define PDMAUDIOPCMPROPS_MAKE_SHIFT(pProps) \ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS((pProps)->cbSampleX, (pProps)->cChannelsX) /** Converts (audio) frames to bytes. - * Needs the cShift value set correctly, using PDMAUDIOPCMPROPS_MAKE_SHIFT. */ -#define PDMAUDIOPCMPROPS_F2B(pProps, frames) ((frames) << (pProps)->cShift) + * @note Requires properly initialized properties, i.e. cbFrames correctly calculated + * and cShift set using PDMAUDIOPCMPROPS_MAKE_SHIFT. */ +#define PDMAUDIOPCMPROPS_F2B(pProps, cFrames) \ + ( (pProps)->cShiftX != UINT8_MAX ? (cFrames) << (pProps)->cShiftX : (cFrames) * (pProps)->cbFrame ) /** Converts bytes to (audio) frames. - * Needs the cShift value set correctly, using PDMAUDIOPCMPROPS_MAKE_SHIFT. */ -#define PDMAUDIOPCMPROPS_B2F(pProps, cb) ((cb) >> (pProps)->cShift) + * @note Requires properly initialized properties, i.e. cbFrames correctly calculated + * and cShift set using PDMAUDIOPCMPROPS_MAKE_SHIFT. */ +#define PDMAUDIOPCMPROPS_B2F(pProps, cb) \ + ( (pProps)->cShiftX != UINT8_MAX ? (cb) >> (pProps)->cShiftX : (cb) / (pProps)->cbFrame ) /** @} */ /** @@ -705,25 +655,12 @@ */ typedef struct PDMAUDIOSTREAMCFG { - /** Direction of the stream. */ - PDMAUDIODIR enmDir; - /** Destination / source indicator, depending on enmDir. */ - PDMAUDIODSTSRCUNION u; /** The stream's PCM properties. */ PDMAUDIOPCMPROPS Props; - /** The stream's audio data layout. - * This indicates how the audio data buffers to/from the backend is being layouted. - * - * Currently, the following layouts are supported by the audio connector: - * - * PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED: - * One stream at once. The consecutive audio data is exactly in the format and frame width - * like defined in the PCM properties. This is the default. - * - * PDMAUDIOSTREAMLAYOUT_RAW: - * Can be one or many streams at once, depending on the stream's mixing buffer setup. - * The audio data will get handled as PDMAUDIOFRAME frames without any modification done. */ - PDMAUDIOSTREAMLAYOUT enmLayout; + /** Direction of the stream. */ + PDMAUDIODIR enmDir; + /** Destination / source path. */ + PDMAUDIOPATH enmPath; /** Device emulation-specific data needed for the audio connector. */ struct { @@ -751,43 +688,19 @@ * 0 if not set / available by the backend. UINT32_MAX if not defined (yet). */ uint32_t cFramesPreBuffering; } Backend; - uint32_t u32Padding; /** Friendly name of the stream. */ char szName[64]; } PDMAUDIOSTREAMCFG; AssertCompileSizeAlignment(PDMAUDIOSTREAMCFG, 8); /** Pointer to audio stream configuration keeper. */ typedef PDMAUDIOSTREAMCFG *PPDMAUDIOSTREAMCFG; +/** Pointer to a const audio stream configuration keeper. */ +typedef PDMAUDIOSTREAMCFG const *PCPDMAUDIOSTREAMCFG; /** Converts (audio) frames to bytes. */ -#define PDMAUDIOSTREAMCFG_F2B(pCfg, frames) ((frames) << (pCfg->Props).cShift) +#define PDMAUDIOSTREAMCFG_F2B(pCfg, frames) PDMAUDIOPCMPROPS_F2B(&(pCfg)->Props, (frames)) /** Converts bytes to (audio) frames. */ -#define PDMAUDIOSTREAMCFG_B2F(pCfg, cb) (cb >> (pCfg->Props).cShift) - -/** - * Audio mixer controls. - */ -typedef enum PDMAUDIOMIXERCTL -{ - /** Invalid zero value as per usual (guards against using unintialized values). */ - PDMAUDIOMIXERCTL_INVALID = 0, - /** Unknown mixer control. */ - PDMAUDIOMIXERCTL_UNKNOWN, - /** Master volume. */ - PDMAUDIOMIXERCTL_VOLUME_MASTER, - /** Front. */ - PDMAUDIOMIXERCTL_FRONT, - /** Center / LFE (Subwoofer). */ - PDMAUDIOMIXERCTL_CENTER_LFE, - /** Rear. */ - PDMAUDIOMIXERCTL_REAR, - /** Line-In. */ - PDMAUDIOMIXERCTL_LINE_IN, - /** Microphone-In. */ - PDMAUDIOMIXERCTL_MIC_IN, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIOMIXERCTL_32BIT_HACK = 0x7fffffff -} PDMAUDIOMIXERCTL; +#define PDMAUDIOSTREAMCFG_B2F(pCfg, cb) PDMAUDIOPCMPROPS_B2F(&(pCfg)->Props, (cb)) /** * Audio stream commands. @@ -798,310 +711,44 @@ { /** Invalid zero value as per usual (guards against using unintialized values). */ PDMAUDIOSTREAMCMD_INVALID = 0, - /** Unknown command, do not use. */ - PDMAUDIOSTREAMCMD_UNKNOWN, /** Enables the stream. */ PDMAUDIOSTREAMCMD_ENABLE, - /** Disables the stream. - * For output streams this stops the stream after playing the remaining (buffered) audio data. - * For input streams this will deliver the remaining (captured) audio data and not accepting - * any new audio input data afterwards. */ - PDMAUDIOSTREAMCMD_DISABLE, - /** Pauses the stream. */ + /** Pauses the stream. + * This is currently only issued when the VM is suspended (paused). + * @remarks This is issued by DrvAudio, never by the mixer or devices. */ PDMAUDIOSTREAMCMD_PAUSE, - /** Resumes the stream. */ + /** Resumes the stream. + * This is currently only issued when the VM is resumed. + * @remarks This is issued by DrvAudio, never by the mixer or devices. */ PDMAUDIOSTREAMCMD_RESUME, - /** Tells the stream to drain itself. - * For output streams this plays all remaining (buffered) audio frames, - * for input streams this permits receiving any new audio frames. - * No supported by all backends. */ + /** Drain the stream, that is, play what's in the buffers and then stop. + * + * There will be no more samples written after this command is issued. + * PDMIAUDIOCONNECTOR::pfnStreamIterate will drive progress for DrvAudio and + * calls to PDMIHOSTAUDIO::pfnStreamPlay with a zero sized buffer will provide + * the backend with a way to drive it forwards. These calls will come at a + * frequency set by the device and be on an asynchronous I/O thread. + * + * A DISABLE command maybe submitted if the device/mixer wants to re-enable the + * stream while it's still draining or if it gets impatient and thinks the + * draining has been going on too long, in which case the stream should stop + * immediately. + * + * @note This should not wait for the stream to finish draining, just change + * the state. (The caller could be an EMT and it must not block for + * hundreds of milliseconds of buffer to finish draining.) + * + * @note Does not apply to input streams. Backends should refuse such requests. */ PDMAUDIOSTREAMCMD_DRAIN, - /** Tells the stream to drop all (buffered) audio data immediately. - * No supported by all backends. */ - PDMAUDIOSTREAMCMD_DROP, + /** Stops the stream immediately w/o any draining. */ + PDMAUDIOSTREAMCMD_DISABLE, + /** End of valid values. */ + PDMAUDIOSTREAMCMD_END, /** Hack to blow the type up to 32-bit. */ PDMAUDIOSTREAMCMD_32BIT_HACK = 0x7fffffff } PDMAUDIOSTREAMCMD; /** - * Audio volume parameters. - */ -typedef struct PDMAUDIOVOLUME -{ - /** Set to @c true if this stream is muted, @c false if not. */ - bool fMuted; - /** Left channel volume. - * Range is from [0 ... 255], whereas 0 specifies - * the most silent and 255 the loudest value. */ - uint8_t uLeft; - /** Right channel volume. - * Range is from [0 ... 255], whereas 0 specifies - * the most silent and 255 the loudest value. */ - uint8_t uRight; -} PDMAUDIOVOLUME; -/** Pointer to audio volume settings. */ -typedef PDMAUDIOVOLUME *PPDMAUDIOVOLUME; - -/** Defines the minimum volume allowed. */ -#define PDMAUDIO_VOLUME_MIN (0) -/** Defines the maximum volume allowed. */ -#define PDMAUDIO_VOLUME_MAX (255) - -/** - * Rate processing information of a source & destination audio stream. - * - * This is needed because both streams can differ regarding their rates and - * therefore need to be treated accordingly. - */ -typedef struct PDMAUDIOSTREAMRATE -{ - /** Current (absolute) offset in the output (destination) stream. - * @todo r=bird: Please reveal which unit these members are given in. */ - uint64_t offDst; - /** Increment for moving offDst for the destination stream. - * This is needed because the source <-> destination rate might be different. */ - uint64_t uDstInc; - /** Current (absolute) offset in the input stream. */ - uint32_t offSrc; - /** Explicit alignment padding. */ - uint32_t u32AlignmentPadding; - /** Last processed frame of the input stream. - * Needed for interpolation. */ - PDMAUDIOFRAME SrcFrameLast; -} PDMAUDIOSTREAMRATE; -/** Pointer to rate processing information of a stream. */ -typedef PDMAUDIOSTREAMRATE *PPDMAUDIOSTREAMRATE; - -/** - * Mixing buffer volume parameters. - * - * The volume values are in fixed point style and must be converted to/from - * before using with e.g. PDMAUDIOVOLUME. - */ -typedef struct PDMAUDMIXBUFVOL -{ - /** Set to @c true if this stream is muted, @c false if not. */ - bool fMuted; - /** Left volume to apply during conversion. - * Pass 0 to convert the original values. May not apply to all conversion functions. */ - uint32_t uLeft; - /** Right volume to apply during conversion. - * Pass 0 to convert the original values. May not apply to all conversion functions. */ - uint32_t uRight; -} PDMAUDMIXBUFVOL; -/** Pointer to mixing buffer volument parameters. */ -typedef PDMAUDMIXBUFVOL *PPDMAUDMIXBUFVOL; - -/* - * Frame conversion parameters for the audioMixBufConvFromXXX / audioMixBufConvToXXX functions. - */ -typedef struct PDMAUDMIXBUFCONVOPTS -{ - /** Number of audio frames to convert. */ - uint32_t cFrames; - union - { - struct - { - /** Volume to use for conversion. */ - PDMAUDMIXBUFVOL Volume; - } From; - } RT_UNION_NM(u); -} PDMAUDMIXBUFCONVOPTS; -/** Pointer to conversion parameters for the audio mixer. */ -typedef PDMAUDMIXBUFCONVOPTS *PPDMAUDMIXBUFCONVOPTS; -/** Pointer to const conversion parameters for the audio mixer. */ -typedef PDMAUDMIXBUFCONVOPTS const *PCPDMAUDMIXBUFCONVOPTS; - -/** - * @note All internal handling is done in audio frames, not in bytes! - * @todo r=bird: What does this node actually apply to? - */ -typedef uint32_t PDMAUDIOMIXBUFFMT; -typedef PDMAUDIOMIXBUFFMT *PPDMAUDIOMIXBUFFMT; - -/** - * Convertion-from function used by the PDM audio buffer mixer. - * - * @returns Number of audio frames returned. - * @param paDst Where to return the converted frames. - * @param pvSrc The source frame bytes. - * @param cbSrc Number of bytes to convert. - * @param pOpts Conversion options. - * @todo r=bird: The @a paDst size is presumable given in @a pOpts->cFrames? - */ -typedef DECLCALLBACK(uint32_t) FNPDMAUDIOMIXBUFCONVFROM(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, - PCPDMAUDMIXBUFCONVOPTS pOpts); -/** Pointer to a convertion-from function used by the PDM audio buffer mixer. */ -typedef FNPDMAUDIOMIXBUFCONVFROM *PFNPDMAUDIOMIXBUFCONVFROM; - -/** - * Convertion-to function used by the PDM audio buffer mixer. - * - * @param pvDst Output buffer. - * @param paSrc The input frames. - * @param pOpts Conversion options. - * @todo r=bird: The @a paSrc size is presumable given in @a pOpts->cFrames and - * this implicitly gives the pvDst size too, right? - */ -typedef DECLCALLBACK(void) FNPDMAUDIOMIXBUFCONVTO(void *pvDst, PCPDMAUDIOFRAME paSrc, PCPDMAUDMIXBUFCONVOPTS pOpts); -/** Pointer to a convertion-to function used by the PDM audio buffer mixer. */ -typedef FNPDMAUDIOMIXBUFCONVTO *PFNPDMAUDIOMIXBUFCONVTO; - -/** Pointer to audio mixing buffer. */ -typedef struct PDMAUDIOMIXBUF *PPDMAUDIOMIXBUF; - -/** - * Audio mixing buffer. - */ -typedef struct PDMAUDIOMIXBUF -{ - RTLISTNODE Node; - /** Name of the buffer. */ - char *pszName; - /** Frame buffer. */ - PPDMAUDIOFRAME pFrames; - /** Size of the frame buffer (in audio frames). */ - uint32_t cFrames; - /** The current read position (in frames). */ - uint32_t offRead; - /** The current write position (in frames). */ - uint32_t offWrite; - /** - * Total frames already mixed down to the parent buffer (if any). - * - * Always starting at the parent's offRead position. - * @note Count always is specified in parent frames, as the sample count can - * differ between parent and child. - */ - uint32_t cMixed; - /** How much audio frames are currently being used - * in this buffer. - * Note: This also is known as the distance in ring buffer terms. */ - uint32_t cUsed; - /** Pointer to parent buffer (if any). */ - PPDMAUDIOMIXBUF pParent; - /** List of children mix buffers to keep in sync with (if being a parent buffer). */ - RTLISTANCHOR lstChildren; - /** Number of children mix buffers kept in lstChildren. */ - uint32_t cChildren; - /** Intermediate structure for buffer conversion tasks. */ - PPDMAUDIOSTREAMRATE pRate; - /** Internal representation of current volume used for mixing. */ - PDMAUDMIXBUFVOL Volume; - /** This buffer's audio format. - * @todo r=bird: This seems to be a value created by AUDMIXBUF_AUDIO_FMT_MAKE(), - * which is not define here. Does this structure really belong here at - * all? */ - PDMAUDIOMIXBUFFMT uAudioFmt; - /** Standard conversion-to function for set uAudioFmt. */ - PFNPDMAUDIOMIXBUFCONVTO pfnConvTo; - /** Standard conversion-from function for set uAudioFmt. */ - PFNPDMAUDIOMIXBUFCONVFROM pfnConvFrom; - /** - * Ratio of the associated parent stream's frequency by this stream's - * frequency (1<<32), represented as a signed 64 bit integer. - * - * For example, if the parent stream has a frequency of 44 khZ, and this - * stream has a frequency of 11 kHz, the ration then would be - * (44/11 * (1 << 32)). - * - * Currently this does not get changed once assigned. - */ - int64_t iFreqRatio; - /** For quickly converting frames <-> bytes and vice versa. */ - uint8_t cShift; -} PDMAUDIOMIXBUF; - -/** @name PDMAUDIOFILE_FLAGS_XXX - * @{ */ -/** No flags defined. */ -#define PDMAUDIOFILE_FLAGS_NONE UINT32_C(0) -/** Keep the audio file even if it contains no audio data. */ -#define PDMAUDIOFILE_FLAGS_KEEP_IF_EMPTY RT_BIT_32(0) -/** Audio file flag validation mask. */ -#define PDMAUDIOFILE_FLAGS_VALID_MASK UINT32_C(0x1) -/** @} */ - -/** Audio file default open flags. - * @todo r=bird: What is the exact purpose of this? */ -#define PDMAUDIOFILE_DEFAULT_OPEN_FLAGS (RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE) - -/** - * Audio file types. - */ -typedef enum PDMAUDIOFILETYPE -{ - /** The customary invalid zero value. */ - PDMAUDIOFILETYPE_INVALID = 0, - /** Unknown type, do not use. */ - PDMAUDIOFILETYPE_UNKNOWN, - /** Raw (PCM) file. */ - PDMAUDIOFILETYPE_RAW, - /** Wave (.WAV) file. */ - PDMAUDIOFILETYPE_WAV, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIOFILETYPE_32BIT_HACK = 0x7fffffff -} PDMAUDIOFILETYPE; - -/** @name PDMAUDIOFILENAME_FLAGS_XXX - * @{ */ -/** No flags defined. */ -#define PDMAUDIOFILENAME_FLAGS_NONE UINT32_C(0) -/** Adds an ISO timestamp to the file name. */ -#define PDMAUDIOFILENAME_FLAGS_TS RT_BIT(0) -/** @} */ - -/** - * Audio file handle. - */ -typedef struct PDMAUDIOFILE -{ - /** Type of the audio file. */ - PDMAUDIOFILETYPE enmType; - /** Audio file flags, PDMAUDIOFILE_FLAGS_XXX. */ - uint32_t fFlags; - /** Actual file handle. */ - RTFILE hFile; - /** Data needed for the specific audio file type implemented. - * Optional, can be NULL. */ - void *pvData; - /** Data size (in bytes). */ - size_t cbData; - /** File name and path. */ - char szName[RTPATH_MAX]; -} PDMAUDIOFILE; -/** Pointer to an audio file handle. */ -typedef PDMAUDIOFILE *PPDMAUDIOFILE; - -/** @name PDMAUDIOSTREAMSTS_FLAGS_XXX - * @{ */ -/** No flags being set. */ -#define PDMAUDIOSTREAMSTS_FLAGS_NONE UINT32_C(0) -/** Whether this stream has been initialized by the - * backend or not. */ -#define PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED RT_BIT_32(0) -/** Whether this stream is enabled or disabled. */ -#define PDMAUDIOSTREAMSTS_FLAGS_ENABLED RT_BIT_32(1) -/** Whether this stream has been paused or not. This also implies - * that this is an enabled stream! */ -#define PDMAUDIOSTREAMSTS_FLAGS_PAUSED RT_BIT_32(2) -/** Whether this stream was marked as being disabled - * but there are still associated guest output streams - * which rely on its data. */ -#define PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE RT_BIT_32(3) -/** Whether this stream is in re-initialization phase. - * All other bits remain untouched to be able to restore - * the stream's state after the re-initialization bas been - * finished. */ -#define PDMAUDIOSTREAMSTS_FLAGS_PENDING_REINIT RT_BIT_32(4) -/** Validation mask. */ -#define PDMAUDIOSTREAMSTS_VALID_MASK UINT32_C(0x0000001F) -/** Stream status flag, PDMAUDIOSTREAMSTS_FLAGS_XXX. */ -typedef uint32_t PDMAUDIOSTREAMSTS; -/** @} */ - -/** * Backend status. */ typedef enum PDMAUDIOBACKENDSTS @@ -1125,251 +772,87 @@ } PDMAUDIOBACKENDSTS; /** - * The specifics for an audio input stream. + * PDM audio stream state. * - * Do not use directly, use PDMAUDIOSTREAM instead. - */ -typedef struct PDMAUDIOSTREAMIN -{ -#ifdef VBOX_WITH_STATISTICS - struct - { - STAMCOUNTER TotalFramesCaptured; - STAMCOUNTER AvgFramesCaptured; - STAMCOUNTER TotalTimesCaptured; - STAMCOUNTER TotalFramesRead; - STAMCOUNTER AvgFramesRead; - STAMCOUNTER TotalTimesRead; - } Stats; -#endif - struct - { - /** File for writing stream reads. */ - PPDMAUDIOFILE pFileStreamRead; - /** File for writing non-interleaved captures. */ - PPDMAUDIOFILE pFileCaptureNonInterleaved; - } Dbg; -} PDMAUDIOSTREAMIN; -/** Pointer to the specifics for an audio input stream. */ -typedef PDMAUDIOSTREAMIN *PPDMAUDIOSTREAMIN; - -/** - * The specifics for an audio output stream. + * This is all the mixer/device needs. The PDMAUDIOSTREAM_STS_XXX stuff will + * become DrvAudio internal state once the backend stuff is destilled out of it. * - * Do not use directly, use PDMAUDIOSTREAM instead. + * @note The value order is significant, don't change it willy-nilly. */ -typedef struct PDMAUDIOSTREAMOUT +typedef enum PDMAUDIOSTREAMSTATE { -#ifdef VBOX_WITH_STATISTICS - struct - { - STAMCOUNTER TotalFramesPlayed; - STAMCOUNTER AvgFramesPlayed; - STAMCOUNTER TotalTimesPlayed; - STAMCOUNTER TotalFramesWritten; - STAMCOUNTER AvgFramesWritten; - STAMCOUNTER TotalTimesWritten; - } Stats; -#endif - struct - { - /** File for writing stream writes. */ - PPDMAUDIOFILE pFileStreamWrite; - /** File for writing stream playback. */ - PPDMAUDIOFILE pFilePlayNonInterleaved; - } Dbg; -} PDMAUDIOSTREAMOUT; -/** Pointer to the specifics for an audio output stream. */ -typedef PDMAUDIOSTREAMOUT *PPDMAUDIOSTREAMOUT; + /** Invalid state value. */ + PDMAUDIOSTREAMSTATE_INVALID = 0, + /** The stream is not operative and cannot be enabled. */ + PDMAUDIOSTREAMSTATE_NOT_WORKING, + /** The stream needs to be re-initialized by the device/mixer + * (i.e. call PDMIAUDIOCONNECTOR::pfnStreamReInit). */ + PDMAUDIOSTREAMSTATE_NEED_REINIT, + /** The stream is inactive (not enabled). */ + PDMAUDIOSTREAMSTATE_INACTIVE, + /** The stream is enabled but nothing to read/write. + * @todo not sure if we need this variant... */ + PDMAUDIOSTREAMSTATE_ENABLED, + /** The stream is enabled and captured samples can be read. */ + PDMAUDIOSTREAMSTATE_ENABLED_READABLE, + /** The stream is enabled and samples can be written for playback. */ + PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE, + /** End of valid states. */ + PDMAUDIOSTREAMSTATE_END, + /** Make sure the type is 32-bit wide. */ + PDMAUDIOSTREAMSTATE_32BIT_HACK = 0x7fffffff +} PDMAUDIOSTREAMSTATE; -/** Pointer to an audio stream. */ -typedef struct PDMAUDIOSTREAM *PPDMAUDIOSTREAM; - -/** - * Audio stream context. - * Needed for separating data from the guest and host side (per stream). - */ -typedef struct PDMAUDIOSTREAMCTX -{ - /** The stream's audio configuration. */ - PDMAUDIOSTREAMCFG Cfg; - /** This stream's mixing buffer. */ - PDMAUDIOMIXBUF MixBuf; -} PDMAUDIOSTREAMCTX; +/** @name PDMAUDIOSTREAM_CREATE_F_XXX + * @{ */ +/** Does not need any mixing buffers, the device takes care of all conversion. + * @note this is now default and assumed always set. */ +#define PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF RT_BIT_32(0) +/** @} */ -/** Pointer to an audio stream context. */ -typedef struct PDMAUDIOSTREAM *PPDMAUDIOSTREAMCTX; +/** @name PDMAUDIOSTREAM_WARN_FLAGS_XXX + * @{ */ +/** No stream warning flags set. */ +#define PDMAUDIOSTREAM_WARN_FLAGS_NONE 0 +/** Warned about a disabled stream. */ +#define PDMAUDIOSTREAM_WARN_FLAGS_DISABLED RT_BIT(0) +/** @} */ /** * An input or output audio stream. */ typedef struct PDMAUDIOSTREAM { - /** List node. */ - RTLISTNODE Node; - /** Name of this stream. */ - char szName[64]; - /** Number of references to this stream. - * Only can be destroyed when the reference count reaches 0. */ - uint32_t cRefs; - /** Stream status flag. */ - PDMAUDIOSTREAMSTS fStatus; - /** Audio direction of this stream. */ - PDMAUDIODIR enmDir; - /** For output streams this indicates whether the stream has reached - * its playback threshold, e.g. is playing audio. - * For input streams this indicates whether the stream has enough input - * data to actually start reading audio. */ - bool fThresholdReached; - bool afPadding[3]; - /** The guest side of the stream. */ - PDMAUDIOSTREAMCTX Guest; - /** The host side of the stream. */ - PDMAUDIOSTREAMCTX Host; - /** Union for input/output specifics depending on enmDir. */ - union - { - PDMAUDIOSTREAMIN In; - PDMAUDIOSTREAMOUT Out; - } RT_UNION_NM(u); - /** Timestamp (in ns) since last iteration. */ - uint64_t tsLastIteratedNs; - /** Timestamp (in ns) since last playback / capture. */ - uint64_t tsLastPlayedCapturedNs; - /** Timestamp (in ns) since last read (input streams) or - * write (output streams). */ - uint64_t tsLastReadWrittenNs; - /** Data to backend-specific stream data. - * This data block will be casted by the backend to access its backend-dependent data. + /** Critical section protecting the stream. * - * That way the backends do not have access to the audio connector's data. */ - void *pvBackend; + * When not otherwise stated, DrvAudio will enter this before calling the + * backend. The backend and device/mixer can normally safely enter it prior to + * a DrvAudio call, however not to pfnStreamDestroy, pfnStreamRelease or + * anything that may access the stream list. + * + * @note Lock ordering: + * - After DRVAUDIO::CritSectGlobals. + * - Before DRVAUDIO::CritSectHotPlug. */ + RTCRITSECT CritSect; + /** Stream configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** Magic value (PDMAUDIOSTREAM_MAGIC). */ + uint32_t uMagic; /** Size (in bytes) of the backend-specific stream data. */ - size_t cbBackend; + uint32_t cbBackend; + /** Warnings shown already in the release log. + * See PDMAUDIOSTREAM_WARN_FLAGS_XXX. */ + uint32_t fWarningsShown; } PDMAUDIOSTREAM; +/** Pointer to an audio stream. */ +typedef struct PDMAUDIOSTREAM *PPDMAUDIOSTREAM; +/** Pointer to a const audio stream. */ +typedef struct PDMAUDIOSTREAM const *PCPDMAUDIOSTREAM; +/** Magic value for PDMAUDIOSTREAM. */ +#define PDMAUDIOSTREAM_MAGIC PDM_VERSION_MAKE(0xa0d3, 5, 0) -/** - * Audio callback source. - */ -typedef enum PDMAUDIOCBSOURCE -{ - /** Invalid, do not use. */ - PDMAUDIOCBSOURCE_INVALID = 0, - /** Device emulation. */ - PDMAUDIOCBSOURCE_DEVICE, - /** Audio connector interface. */ - PDMAUDIOCBSOURCE_CONNECTOR, - /** Backend (lower). */ - PDMAUDIOCBSOURCE_BACKEND, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIOCBSOURCE_32BIT_HACK = 0x7fffffff -} PDMAUDIOCBSOURCE; - -/** - * Audio device callback types. - * Those callbacks are being sent from the audio connector -> device emulation. - */ -typedef enum PDMAUDIODEVICECBTYPE -{ - /** Invalid, do not use. */ - PDMAUDIODEVICECBTYPE_INVALID = 0, - /** Data is availabe as input for passing to the device emulation. */ - PDMAUDIODEVICECBTYPE_DATA_INPUT, - /** Free data for the device emulation to write to the backend. */ - PDMAUDIODEVICECBTYPE_DATA_OUTPUT, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIODEVICECBTYPE_32BIT_HACK = 0x7fffffff -} PDMAUDIODEVICECBTYPE; - -#if 0 /** @todo r=bird: Who needs this exactly? Fix the style or remove */ -/** - * Device callback data for audio input. - */ -typedef struct PDMAUDIODEVICECBDATA_DATA_INPUT -{ - /** Input: How many bytes are availabe as input for passing - * to the device emulation. */ - uint32_t cbInAvail; - /** Output: How many bytes have been read. */ - uint32_t cbOutRead; -} PDMAUDIODEVICECBDATA_DATA_INPUT; -typedef PDMAUDIODEVICECBDATA_DATA_INPUT *PPDMAUDIODEVICECBDATA_DATA_INPUT; - -/** - * Device callback data for audio output. - */ -typedef struct PDMAUDIODEVICECBDATA_DATA_OUTPUT -{ - /** Input: How many bytes are free for the device emulation to write. */ - uint32_t cbInFree; - /** Output: How many bytes were written by the device emulation. */ - uint32_t cbOutWritten; -} PDMAUDIODEVICECBDATA_DATA_OUTPUT, *PPDMAUDIODEVICECBDATA_DATA_OUTPUT; -#endif - -/** - * Audio backend callback types. - * Those callbacks are being sent from the backend -> audio connector. - */ -typedef enum PDMAUDIOBACKENDCBTYPE -{ - /** Invalid, do not use. */ - PDMAUDIOBACKENDCBTYPE_INVALID = 0, - /** The backend's status has changed. */ - PDMAUDIOBACKENDCBTYPE_STATUS, - /** One or more host audio devices have changed. */ - PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED, - /** Hack to blow the type up to 32-bit. */ - PDMAUDIOBACKENDCBTYPE_32BIT_HACK = 0x7fffffff -} PDMAUDIOBACKENDCBTYPE; - -/** Pointer to a host audio interface. */ -typedef struct PDMIHOSTAUDIO *PPDMIHOSTAUDIO; - -/** - * Host audio callback function. - * This function will be called from a backend to communicate with the host audio interface. - * - * @returns IPRT status code. - * @param pDrvIns Pointer to driver instance which called us. - * @param enmType Callback type. - * @param pvUser User argument. - * @param cbUser Size (in bytes) of user argument. - */ -typedef DECLCALLBACK(int) FNPDMHOSTAUDIOCALLBACK(PPDMDRVINS pDrvIns, PDMAUDIOBACKENDCBTYPE enmType, void *pvUser, size_t cbUser); -/** Pointer to a FNPDMHOSTAUDIOCALLBACK(). */ -typedef FNPDMHOSTAUDIOCALLBACK *PFNPDMHOSTAUDIOCALLBACK; -/** - * Audio callback registration record. - */ -typedef struct PDMAUDIOCBRECORD -{ - /** List node. */ - RTLISTANCHOR Node; - /** Callback source. */ - PDMAUDIOCBSOURCE enmSource; - /** Callback type, based on the given source. */ - union - { - /** Device callback stuff. */ - struct - { - PDMAUDIODEVICECBTYPE enmType; - } Device; - } RT_UNION_NM(u); - /** Pointer to context data. Optional. */ - void *pvCtx; - /** Size (in bytes) of context data. - * Must be 0 if pvCtx is NULL. */ - size_t cbCtx; -} PDMAUDIOCBRECORD; -/** Pointer to an audio callback registration record. */ -typedef PDMAUDIOCBRECORD *PPDMAUDIOCBRECORD; - -/** @todo r=bird: What is this exactly? */ -#define PPDMAUDIOBACKENDSTREAM void * /** Pointer to a audio connector interface. */ typedef struct PDMIAUDIOCONNECTOR *PPDMIAUDIOCONNECTOR; @@ -1389,6 +872,9 @@ * @param pInterface Pointer to the interface structure containing the called function pointer. * @param enmDir Audio direction to enable or disable driver for. * @param fEnable Whether to enable or disable the specified audio direction. + * + * @note Be very careful when using this function, as this could + * violate / run against the (global) VM settings. See @bugref{9882}. */ DECLR3CALLBACKMEMBER(int, pfnEnable, (PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)); @@ -1415,30 +901,62 @@ * * @returns Status of the host audio backend. * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param enmDir Audio direction to check host audio backend for. Specify PDMAUDIODIR_ANY for the overall + * @param enmDir Audio direction to check host audio backend for. Specify PDMAUDIODIR_DUPLEX for the overall * backend status. */ DECLR3CALLBACKMEMBER(PDMAUDIOBACKENDSTS, pfnGetStatus, (PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)); /** + * Gives the audio drivers a hint about a typical configuration. + * + * This is a little hack for windows (and maybe other hosts) where stream + * creation can take a relatively long time, making it very unsuitable for EMT. + * The audio backend can use this hint to cache pre-configured stream setups, + * so that when the guest actually wants to play something EMT won't be blocked + * configuring host audio. + * + * @param pInterface Pointer to this interface. + * @param pCfg The typical configuration. Can be modified by the + * drivers in unspecified ways. + */ + DECLR3CALLBACKMEMBER(void, pfnStreamConfigHint, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAMCFG pCfg)); + + /** * Creates an audio stream. * * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pCfgHost Stream configuration for host side. - * @param pCfgGuest Stream configuration for guest side. - * @param ppStream Pointer where to return the created audio stream on success. + * @param pInterface Pointer to this interface. + * @param fFlags PDMAUDIOSTREAM_CREATE_F_XXX. + * @param pCfgReq The requested stream configuration. The actual stream + * configuration can be found in pStream->Cfg on success. + * @param ppStream Pointer where to return the created audio stream on + * success. */ - DECLR3CALLBACKMEMBER(int, pfnStreamCreate, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAMCFG pCfgHost, - PPDMAUDIOSTREAMCFG pCfgGuest, PPDMAUDIOSTREAM *ppStream)); + DECLR3CALLBACKMEMBER(int, pfnStreamCreate, (PPDMIAUDIOCONNECTOR pInterface, uint32_t fFlags, PCPDMAUDIOSTREAMCFG pCfgReq, + PPDMAUDIOSTREAM *ppStream)); + /** * Destroys an audio stream. * * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. + * @param fImmediate Whether to immdiately stop and destroy a draining + * stream (@c true), or to allow it to complete + * draining first (@c false) if that's feasable. + * The latter depends on the draining stage and what + * the backend is capable of. */ - DECLR3CALLBACKMEMBER(int, pfnStreamDestroy, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); + DECLR3CALLBACKMEMBER(int, pfnStreamDestroy, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, bool fImmediate)); + + /** + * Re-initializes the stream in response to PDMAUDIOSTREAM_STS_NEED_REINIT. + * + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pStream The audio stream needing re-initialization. + */ + DECLR3CALLBACKMEMBER(int, pfnStreamReInit, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); /** * Adds a reference to the specified audio stream. @@ -1459,32 +977,6 @@ DECLR3CALLBACKMEMBER(uint32_t, pfnStreamRelease, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); /** - * Reads PCM audio data from the host (input). - * - * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream to write to. - * @param pvBuf Where to store the read data. - * @param cbBuf Number of bytes to read. - * @param pcbRead Bytes of audio data read. Optional. - */ - DECLR3CALLBACKMEMBER(int, pfnStreamRead, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, - void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)); - - /** - * Writes PCM audio data to the host (output). - * - * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream to read from. - * @param pvBuf Audio data to be written. - * @param cbBuf Number of bytes to be written. - * @param pcbWritten Bytes of audio data written. Optional. - */ - DECLR3CALLBACKMEMBER(int, pfnStreamWrite, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, - const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)); - - /** * Controls a specific audio stream. * * @returns VBox status code. @@ -1504,81 +996,115 @@ DECLR3CALLBACKMEMBER(int, pfnStreamIterate, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); /** - * Returns the number of readable data (in bytes) of a specific audio input stream. + * Returns the state of a specific audio stream (destilled status). * - * @returns Number of readable data (in bytes). + * @returns PDMAUDIOSTREAMSTATE value. + * @retval PDMAUDIOSTREAMSTATE_INVALID if the input isn't valid (w/ assertion). * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. */ - DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetReadable, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); + DECLR3CALLBACKMEMBER(PDMAUDIOSTREAMSTATE, pfnStreamGetState, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); /** - * Returns the number of writable data (in bytes) of a specific audio output stream. + * Returns the number of bytes that can be written to an audio output stream. * - * @returns Number of writable data (in bytes). + * @returns Number of bytes writable data. * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. */ DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetWritable, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); /** - * Returns the status of a specific audio stream. + * Plays (writes to) an audio output stream. * - * @returns Audio stream status + * @returns VBox status code. * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. + * @param pStream Pointer to audio stream to read from. + * @param pvBuf Audio data to be written. + * @param cbBuf Number of bytes to be written. + * @param pcbWritten Bytes of audio data written. Optional. */ - DECLR3CALLBACKMEMBER(PDMAUDIOSTREAMSTS, pfnStreamGetStatus, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); + DECLR3CALLBACKMEMBER(int, pfnStreamPlay, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)); /** - * Sets the audio volume of a specific audio stream. + * Returns the number of bytes that can be read from an input stream. * - * @returns VBox status code. + * @returns Number of bytes of readable data. * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. - * @param pVol Pointer to audio volume structure to set the stream's audio volume to. */ - DECLR3CALLBACKMEMBER(int, pfnStreamSetVolume, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)); - - /** - * Plays (transfers) available audio frames to the host backend. Only works with output streams. - * - * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. - * @param pcFramesPlayed Number of frames played. Optional. - */ - DECLR3CALLBACKMEMBER(int, pfnStreamPlay, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed)); + DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetReadable, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)); /** - * Captures (transfers) available audio frames from the host backend. Only works with input streams. + * Captures (reads) samples from an audio input stream. * * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. - * @param pcFramesCaptured Number of frames captured. Optional. + * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param pStream Pointer to audio stream to write to. + * @param pvBuf Where to store the read data. + * @param cbBuf Number of bytes to read. + * @param pcbRead Bytes of audio data read. Optional. */ DECLR3CALLBACKMEMBER(int, pfnStreamCapture, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, - uint32_t *pcFramesCaptured)); - - /** - * Registers (device) callbacks. - * This is handy for letting the device emulation know of certain events, e.g. processing input / output data - * or configuration changes. - * - * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param paCallbacks Pointer to array of callbacks to register. - * @param cCallbacks Number of callbacks to register. - */ - DECLR3CALLBACKMEMBER(int, pfnRegisterCallbacks, (PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOCBRECORD paCallbacks, - size_t cCallbacks)); - + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)); } PDMIAUDIOCONNECTOR; /** PDMIAUDIOCONNECTOR interface ID. */ -#define PDMIAUDIOCONNECTOR_IID "00e704ef-0078-4bb6-0005-a5fd000ded9f" +#define PDMIAUDIOCONNECTOR_IID "2900fe2a-6aeb-4953-ac12-f8965612f446" + + +/** + * Host audio backend specific stream data. + * + * The backend will put this as the first member of it's own data structure. + */ +typedef struct PDMAUDIOBACKENDSTREAM +{ + /** Magic value (PDMAUDIOBACKENDSTREAM_MAGIC). */ + uint32_t uMagic; + /** Explicit zero padding - do not touch! */ + uint32_t uReserved; + /** Pointer to the stream this backend data is associated with. */ + PPDMAUDIOSTREAM pStream; + /** Reserved for future use (zeroed) - do not touch. */ + void *apvReserved[2]; +} PDMAUDIOBACKENDSTREAM; +/** Pointer to host audio specific stream data! */ +typedef PDMAUDIOBACKENDSTREAM *PPDMAUDIOBACKENDSTREAM; + +/** Magic value for PDMAUDIOBACKENDSTREAM. */ +#define PDMAUDIOBACKENDSTREAM_MAGIC PDM_VERSION_MAKE(0xa0d4, 1, 0) + +/** + * Host audio (backend) stream state returned by PDMIHOSTAUDIO::pfnStreamGetState. + */ +typedef enum PDMHOSTAUDIOSTREAMSTATE +{ + /** Invalid zero value, as per usual. */ + PDMHOSTAUDIOSTREAMSTATE_INVALID = 0, + /** The stream is being initialized. + * This should also be used when switching to a new device and the stream + * stops to work with the old device while the new one being configured. */ + PDMHOSTAUDIOSTREAMSTATE_INITIALIZING, + /** The stream does not work (async init failed, audio subsystem gone + * fishing, or similar). */ + PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING, + /** Backend is working okay. */ + PDMHOSTAUDIOSTREAMSTATE_OKAY, + /** Backend is working okay, but currently draining the stream. */ + PDMHOSTAUDIOSTREAMSTATE_DRAINING, + /** Backend is working but doesn't want any commands or data reads/writes. */ + PDMHOSTAUDIOSTREAMSTATE_INACTIVE, + /** End of valid values. */ + PDMHOSTAUDIOSTREAMSTATE_END, + /** Blow the type up to 32 bits. */ + PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK = 0x7fffffff +} PDMHOSTAUDIOSTREAMSTATE; + +/** Pointer to a host audio interface. */ +typedef struct PDMIHOSTAUDIO *PPDMIHOSTAUDIO; /** * PDM host audio interface. @@ -1586,56 +1112,87 @@ typedef struct PDMIHOSTAUDIO { /** - * Initializes the host backend (driver). + * Returns the host backend's configuration (backend). * * @returns VBox status code. * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param pBackendCfg Where to store the backend audio configuration to. */ - DECLR3CALLBACKMEMBER(int, pfnInit, (PPDMIHOSTAUDIO pInterface)); + DECLR3CALLBACKMEMBER(int, pfnGetConfig, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)); /** - * Shuts down the host backend (driver). + * Returns (enumerates) host audio device information (optional). * * @returns VBox status code. * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param pDeviceEnum Where to return the enumerated audio devices. */ - DECLR3CALLBACKMEMBER(void, pfnShutdown, (PPDMIHOSTAUDIO pInterface)); + DECLR3CALLBACKMEMBER(int, pfnGetDevices, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)); /** - * Returns the host backend's configuration (backend). + * Changes the output or input device. * * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pBackendCfg Where to store the backend audio configuration to. + * @param pInterface Pointer to this interface. + * @param enmDir The direction to set the device for: PDMAUDIODIR_IN, + * PDMAUDIODIR_OUT or PDMAUDIODIR_DUPLEX (both the + * previous). + * @param pszId The PDMAUDIOHOSTDEV::pszId value of the device to + * use, or NULL / empty string for the default device. */ - DECLR3CALLBACKMEMBER(int, pfnGetConfig, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)); + DECLR3CALLBACKMEMBER(int, pfnSetDevice, (PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)); /** - * Returns (enumerates) host audio device information. + * Returns the current status from the audio backend (optional). * - * @returns VBox status code. + * @returns PDMAUDIOBACKENDSTS enum. * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pDeviceEnum Where to return the enumerated audio devices. + * @param enmDir Audio direction to get status for. Pass PDMAUDIODIR_DUPLEX for overall status. */ - DECLR3CALLBACKMEMBER(int, pfnGetDevices, (PPDMIHOSTAUDIO pInterface, PPDMAUDIODEVICEENUM pDeviceEnum)); + DECLR3CALLBACKMEMBER(PDMAUDIOBACKENDSTS, pfnGetStatus, (PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)); /** - * Returns the current status from the audio backend. + * Callback for genric on-worker-thread requests initiated by the backend itself. * - * @returns PDMAUDIOBACKENDSTS enum. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param enmDir Audio direction to get status for. Pass PDMAUDIODIR_ANY for overall status. + * This is the counterpart to PDMIHOSTAUDIOPORT::pfnDoOnWorkerThread that will + * be invoked on a worker thread when the backend requests it - optional. + * + * This does not return a value, so the backend must keep track of + * failure/success on its own. + * + * This method is optional. A non-NULL will, together with pfnStreamInitAsync + * and PDMAUDIOBACKEND_F_ASYNC_HINT, force DrvAudio to create the thread pool. + * + * @param pInterface Pointer to this interface. + * @param pStream Optionally a backend stream if specified in the + * PDMIHOSTAUDIOPORT::pfnDoOnWorkerThread() call. + * @param uUser User specific value as specified in the + * PDMIHOSTAUDIOPORT::pfnDoOnWorkerThread() call. + * @param pvUser User specific pointer as specified in the + * PDMIHOSTAUDIOPORT::pfnDoOnWorkerThread() call. */ - DECLR3CALLBACKMEMBER(PDMAUDIOBACKENDSTS, pfnGetStatus, (PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)); + DECLR3CALLBACKMEMBER(void, pfnDoOnWorkerThread,(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + uintptr_t uUser, void *pvUser)); /** - * Sets a callback the audio backend can call. Optional. + * Gives the audio backend a hint about a typical configuration (optional). * - * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pfnCallback The callback function to use, or NULL when unregistering. + * This is a little hack for windows (and maybe other hosts) where stream + * creation can take a relatively long time, making it very unsuitable for EMT. + * The audio backend can use this hint to cache pre-configured stream setups, + * so that when the guest actually wants to play something EMT won't be blocked + * configuring host audio. + * + * The backend can return PDMAUDIOBACKEND_F_ASYNC_HINT in + * PDMIHOSTAUDIO::pfnGetConfig to avoid having EMT making this call and thereby + * speeding up VM construction. + * + * @param pInterface Pointer to this interface. + * @param pCfg The typical configuration. (Feel free to change it + * to the actual stream config that would be used, + * however caller will probably ignore this.) */ - DECLR3CALLBACKMEMBER(int, pfnSetCallback, (PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback)); + DECLR3CALLBACKMEMBER(void, pfnStreamConfigHint, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAMCFG pCfg)); /** * Creates an audio stream using the requested stream configuration. @@ -1644,127 +1201,191 @@ * best match in the acquired configuration structure on success. * * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. - * @param pCfgReq Pointer to requested stream configuration. - * @param pCfgAcq Pointer to acquired stream configuration. - * @todo r=bird: Implementation (at least Alsa) seems to make undocumented - * assumptions about the content of @a pCfgAcq. + * @retval VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED if + * PDMIHOSTAUDIO::pfnStreamInitAsync should be called. + * @param pInterface Pointer to this interface. + * @param pStream Pointer to the audio stream. + * @param pCfgReq The requested stream configuration. + * @param pCfgAcq The acquired stream configuration - output. This is + * the same as @a *pCfgReq when called, the + * implementation will adjust it to make the actual + * stream configuration as needed. */ DECLR3CALLBACKMEMBER(int, pfnStreamCreate, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)); + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)); /** - * Destroys an audio stream. + * Asynchronous stream initialization step, optional. + * + * This is called on a worker thread iff the PDMIHOSTAUDIO::pfnStreamCreate + * method returns VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED. * * @returns VBox status code. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. + * @param pInterface Pointer to this interface. + * @param pStream Pointer to audio stream to continue + * initialization of. + * @param fDestroyed Set to @c true if the stream has been destroyed + * before the worker thread got to making this + * call. The backend should just ready the stream + * for destruction in that case. */ - DECLR3CALLBACKMEMBER(int, pfnStreamDestroy, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(int, pfnStreamInitAsync, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fDestroyed)); /** - * Controls an audio stream. + * Destroys an audio stream. * * @returns VBox status code. - * @retval VERR_AUDIO_STREAM_NOT_READY if stream is not ready for required operation (yet). - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. - * @param enmStreamCmd The stream command to issue. + * @param pInterface Pointer to the interface containing the called function. + * @param pStream Pointer to audio stream. + * @param fImmediate Whether to immdiately stop and destroy a draining + * stream (@c true), or to allow it to complete + * draining first (@c false) if that's feasable. */ - DECLR3CALLBACKMEMBER(int, pfnStreamControl, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PDMAUDIOSTREAMCMD enmStreamCmd)); + DECLR3CALLBACKMEMBER(int, pfnStreamDestroy, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)); /** - * Returns the amount which is readable from the audio (input) stream. + * Called from PDMIHOSTAUDIOPORT::pfnNotifyDeviceChanged so the backend can start + * the device change for a stream. * - * @returns For non-raw layout streams: Number of readable bytes. - * for raw layout streams : Number of readable audio frames. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. + * This is mainly to avoid the need for a list of streams in the backend. + * + * @param pInterface Pointer to this interface. + * @param pStream Pointer to audio stream (locked). + * @param pvUser Backend specific parameter from the call to + * PDMIHOSTAUDIOPORT::pfnNotifyDeviceChanged. */ - DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetReadable, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(void, pfnStreamNotifyDeviceChanged,(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, void *pvUser)); /** - * Returns the amount which is writable to the audio (output) stream. + * Enables (starts) the stream. * - * @returns For non-raw layout streams: Number of writable bytes. - * for raw layout streams : Number of writable audio frames. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pStream Pointer to the audio stream to enable. + * @sa PDMAUDIOSTREAMCMD_ENABLE */ - DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetWritable, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(int, pfnStreamEnable, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); /** - * Returns the amount which is pending (in other words has not yet been processed) by/from the backend yet. - * Optional. + * Disables (stops) the stream immediately. * - * For input streams this is read audio data by the backend which has not been processed by the host yet. - * For output streams this is written audio data to the backend which has not been processed by the backend yet. + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pStream Pointer to the audio stream to disable. + * @sa PDMAUDIOSTREAMCMD_DISABLE + */ + DECLR3CALLBACKMEMBER(int, pfnStreamDisable, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + + /** + * Pauses the stream - called when the VM is suspended. * - * @returns For non-raw layout streams: Number of pending bytes. - * for raw layout streams : Number of pending audio frames. - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pStream Pointer to the audio stream to pause. + * @sa PDMAUDIOSTREAMCMD_PAUSE */ - DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetPending, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(int, pfnStreamPause, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); /** - * Returns the current status of the given backend stream. + * Resumes a paused stream - called when the VM is resumed. * - * @returns PDMAUDIOSTREAMSTS - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pStream Pointer to the audio stream to resume. + * @sa PDMAUDIOSTREAMCMD_RESUME */ - DECLR3CALLBACKMEMBER(PDMAUDIOSTREAMSTS, pfnStreamGetStatus, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(int, pfnStreamResume, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); /** - * Gives the host backend the chance to do some (necessary) iteration work. + * Drain the stream, that is, play what's in the buffers and then stop. + * + * There will be no more samples written after this command is issued. + * PDMIHOSTAUDIO::pfnStreamPlay with a zero sized buffer will provide the + * backend with a way to drive it forwards. These calls will come at a + * frequency set by the device and be on an asynchronous I/O thread. + * + * The PDMIHOSTAUDIO::pfnStreamDisable method maybe called if the device/mixer + * wants to re-enable the stream while it's still draining or if it gets + * impatient and thinks the draining has been going on too long, in which case + * the stream should stop immediately. + * + * @note This should not wait for the stream to finish draining, just change + * the state. (The caller could be an EMT and it must not block for + * hundreds of milliseconds of buffer to finish draining.) + * + * @note Does not apply to input streams. Backends should refuse such + * requests. * * @returns VBox status code. + * @retval VERR_WRONG_ORDER if not output stream. + * @param pInterface Pointer to this interface. + * @param pStream Pointer to the audio stream to drain. + * @sa PDMAUDIOSTREAMCMD_DRAIN + */ + DECLR3CALLBACKMEMBER(int, pfnStreamDrain, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + + /** + * Returns the current state of the given backend stream. + * + * @returns PDMHOSTAUDIOSTREAMSTATE value. + * @retval PDMHOSTAUDIOSTREAMSTATE_INVALID if invalid stream. * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. */ - DECLR3CALLBACKMEMBER(int, pfnStreamIterate, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(PDMHOSTAUDIOSTREAMSTATE, pfnStreamGetState, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); /** - * Signals the backend that the host wants to begin playing for this iteration. Optional. + * Returns the number of buffered bytes that hasn't been played yet (optional). + * + * Is not valid on an input stream, implementions shall assert and return zero. + * + * @returns Number of pending bytes. + * @param pInterface Pointer to this interface. + * @param pStream Pointer to the audio stream. * + * @todo This is no longer not used by DrvAudio and can probably be removed. + */ + DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetPending, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + + /** + * Returns the amount which is writable to the audio (output) stream. + * + * @returns Number of writable bytes. * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. */ - DECLR3CALLBACKMEMBER(void, pfnStreamPlayBegin, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetWritable, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); /** * Plays (writes to) an audio (output) stream. * + * This is always called with data in the buffer, except after + * PDMAUDIOSTREAMCMD_DRAIN is issued when it's called every so often to assist + * the backend with moving the draining operation forward (kind of like + * PDMIAUDIOCONNECTOR::pfnStreamIterate). + * * @returns VBox status code. * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. - * @param pvBuf Pointer to audio data buffer to play. - * @param uBufSize The audio data buffer size (see note below for unit). - * @param puWritten Number of unit written. - * @note The @a uBufSize and @a puWritten values are in bytes for non-raw - * layout streams and in frames for raw layout ones. + * @param pvBuf Pointer to audio data buffer to play. This will be NULL + * when called to assist draining the stream. + * @param cbBuf The number of bytes of audio data to play. This will be + * zero when called to assist draining the stream. + * @param pcbWritten Where to return the actual number of bytes played. */ DECLR3CALLBACKMEMBER(int, pfnStreamPlay, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten)); - - /** - * Signals the backend that the host finished playing for this iteration. Optional. - * - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. - */ - DECLR3CALLBACKMEMBER(void, pfnStreamPlayEnd, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)); /** - * Signals the backend that the host wants to begin capturing for this iteration. Optional. + * Returns the amount which is readable from the audio (input) stream. * + * @returns For non-raw layout streams: Number of readable bytes. + * for raw layout streams : Number of readable audio frames. * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. */ - DECLR3CALLBACKMEMBER(void, pfnStreamCaptureBegin, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(uint32_t, pfnStreamGetReadable, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); /** * Captures (reads from) an audio (input) stream. @@ -1773,28 +1394,164 @@ * @param pInterface Pointer to the interface structure containing the called function pointer. * @param pStream Pointer to audio stream. * @param pvBuf Buffer where to store read audio data. - * @param uBufSize Size of the audio data buffer (see note below for unit). - * @param puRead Returns number of units read. - * @note The @a uBufSize and @a puRead values are in bytes for non-raw - * layout streams and in frames for raw layout ones. + * @param cbBuf Size of the audio data buffer in bytes. + * @param pcbRead Where to return the number of bytes actually captured. */ DECLR3CALLBACKMEMBER(int, pfnStreamCapture, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead)); + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)); +} PDMIHOSTAUDIO; + +/** PDMIHOSTAUDIO interface ID. */ +#define PDMIHOSTAUDIO_IID "c0875b91-a4f9-48be-8595-31d27048432d" + +/** Pointer to a audio notify from host interface. */ +typedef struct PDMIHOSTAUDIOPORT *PPDMIHOSTAUDIOPORT; + +/** + * PDM host audio port interface, upwards sibling of PDMIHOSTAUDIO. + */ +typedef struct PDMIHOSTAUDIOPORT +{ /** - * Signals the backend that the host finished capturing for this iteration. Optional. + * Ask DrvAudio to call PDMIHOSTAUDIO::pfnDoOnWorkerThread on a worker thread. * - * @param pInterface Pointer to the interface structure containing the called function pointer. - * @param pStream Pointer to audio stream. + * Generic method for doing asynchronous work using the DrvAudio thread pool. + * + * This function will not wait for PDMIHOSTAUDIO::pfnDoOnWorkerThread to + * complete, but returns immediately after submitting the request to the thread + * pool. + * + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pStream Optional backend stream structure to pass along. The + * reference count will be increased till the call + * completes to make sure the stream stays valid. + * @param uUser User specific value. + * @param pvUser User specific pointer. */ - DECLR3CALLBACKMEMBER(void, pfnStreamCaptureEnd, (PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)); + DECLR3CALLBACKMEMBER(int, pfnDoOnWorkerThread,(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream, + uintptr_t uUser, void *pvUser)); -} PDMIHOSTAUDIO; + /** + * The device for the given direction changed. + * + * The driver above backend (DrvAudio) will call the backend back + * (PDMIHOSTAUDIO::pfnStreamNotifyDeviceChanged) for all open streams in the + * given direction. (This ASSUMES the backend uses one output device and one + * input devices for all streams.) + * + * @param pInterface Pointer to this interface. + * @param enmDir The audio direction. + * @param pvUser Backend specific parameter for + * PDMIHOSTAUDIO::pfnStreamNotifyDeviceChanged. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyDeviceChanged,(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser)); -/** PDMIHOSTAUDIO interface ID. */ -#define PDMIHOSTAUDIO_IID "007847a0-0075-4964-007d-343f0010f081" + /** + * Notification that the stream is about to change device in a bit. + * + * This will assume PDMAUDIOSTREAM_STS_PREPARING_SWITCH will be set when + * PDMIHOSTAUDIO::pfnStreamGetStatus is next called and change the stream state + * accordingly. + * + * @param pInterface Pointer to this interface. + * @param pStream The stream that changed device (backend variant). + */ + DECLR3CALLBACKMEMBER(void, pfnStreamNotifyPreparingDeviceSwitch,(PPDMIHOSTAUDIOPORT pInterface, + PPDMAUDIOBACKENDSTREAM pStream)); + + /** + * The stream has changed its device and left the + * PDMAUDIOSTREAM_STS_PREPARING_SWITCH state (if it entered it at all). + * + * @param pInterface Pointer to this interface. + * @param pStream The stream that changed device (backend variant). + * @param fReInit Set if a re-init is required, clear if not. + */ + DECLR3CALLBACKMEMBER(void, pfnStreamNotifyDeviceChanged,(PPDMIHOSTAUDIOPORT pInterface, + PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)); + + /** + * One or more audio devices have changed in some way. + * + * The upstream driver/device should re-evaluate the devices they're using. + * + * @todo r=bird: The upstream driver/device does not know which host audio + * devices they are using. This is mainly for triggering enumeration and + * logging of the audio devices. + * + * @param pInterface Pointer to this interface. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyDevicesChanged,(PPDMIHOSTAUDIOPORT pInterface)); +} PDMIHOSTAUDIOPORT; + +/** PDMIHOSTAUDIOPORT interface ID. */ +#define PDMIHOSTAUDIOPORT_IID "92ea5169-8271-402d-99a7-9de26a52acaf" + + +/** + * Audio mixer controls. + * + * @note This isn't part of any official PDM interface as such, it's more of a + * common thing that all the devices seem to need. + */ +typedef enum PDMAUDIOMIXERCTL +{ + /** Invalid zero value as per usual (guards against using unintialized values). */ + PDMAUDIOMIXERCTL_INVALID = 0, + /** Unknown mixer control. */ + PDMAUDIOMIXERCTL_UNKNOWN, + /** Master volume. */ + PDMAUDIOMIXERCTL_VOLUME_MASTER, + /** Front. */ + PDMAUDIOMIXERCTL_FRONT, + /** Center / LFE (Subwoofer). */ + PDMAUDIOMIXERCTL_CENTER_LFE, + /** Rear. */ + PDMAUDIOMIXERCTL_REAR, + /** Line-In. */ + PDMAUDIOMIXERCTL_LINE_IN, + /** Microphone-In. */ + PDMAUDIOMIXERCTL_MIC_IN, + /** End of valid values. */ + PDMAUDIOMIXERCTL_END, + /** Hack to blow the type up to 32-bit. */ + PDMAUDIOMIXERCTL_32BIT_HACK = 0x7fffffff +} PDMAUDIOMIXERCTL; + +/** + * Audio volume parameters. + * + * @note This isn't part of any official PDM interface any more (it used to be + * used to PDMIAUDIOCONNECTOR). It's currently only used by the mixer API. + */ +typedef struct PDMAUDIOVOLUME +{ + /** Set to @c true if this stream is muted, @c false if not. */ + bool fMuted; + /** The volume for each channel. + * The values zero is the most silent one (although not quite muted), and 255 + * the loudest. */ + uint8_t auChannels[PDMAUDIO_MAX_CHANNELS]; +} PDMAUDIOVOLUME; +/** Pointer to audio volume settings. */ +typedef PDMAUDIOVOLUME *PPDMAUDIOVOLUME; +/** Pointer to const audio volume settings. */ +typedef PDMAUDIOVOLUME const *PCPDMAUDIOVOLUME; + +/** Defines the minimum volume allowed. */ +#define PDMAUDIO_VOLUME_MIN (0) +/** Defines the maximum volume allowed. */ +#define PDMAUDIO_VOLUME_MAX (255) +/** Initializator for max volume on all channels. */ +#define PDMAUDIOVOLUME_INITIALIZER_MAX \ + { /* .fMuted = */ false, \ + /* .auChannels = */ { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } } /** @} */ +RT_C_DECLS_END + #endif /* !VBOX_INCLUDED_vmm_pdmaudioifs_h */ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmaudioinline.h virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmaudioinline.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmaudioinline.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmaudioinline.h 2022-09-01 13:17:48.000000000 +0000 @@ -0,0 +1,1483 @@ +/* $Id: pdmaudioinline.h $ */ +/** @file + * PDM - Audio Helpers, Inlined Code. (DEV,++) + * + * This is all inlined because it's too tedious to create a couple libraries to + * contain it all (same bad excuse as for intnetinline.h & pdmnetinline.h). + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_vmm_pdmaudioinline_h +#define VBOX_INCLUDED_vmm_pdmaudioinline_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#include +#include +#include +#include +#include + + +/** @defgroup grp_pdm_audio_inline The PDM Audio Helper APIs + * @ingroup grp_pdm + * @{ + */ + + +/** + * Gets the name of an audio direction enum value. + * + * @returns Pointer to read-only name string on success, "bad" if passed an + * invalid enum value. + * @param enmDir The audio direction value to name. + */ +DECLINLINE(const char *) PDMAudioDirGetName(PDMAUDIODIR enmDir) +{ + switch (enmDir) + { + case PDMAUDIODIR_INVALID: return "invalid"; + case PDMAUDIODIR_UNKNOWN: return "unknown"; + case PDMAUDIODIR_IN: return "input"; + case PDMAUDIODIR_OUT: return "output"; + case PDMAUDIODIR_DUPLEX: return "duplex"; + + /* no default */ + case PDMAUDIODIR_END: + case PDMAUDIODIR_32BIT_HACK: + break; + } + AssertMsgFailedReturn(("Invalid audio direction %d\n", enmDir), "bad"); +} + +/** + * Gets the name of an audio mixer control enum value. + * + * @returns Pointer to read-only name, "bad" if invalid input. + * @param enmMixerCtl The audio mixer control value. + */ +DECLINLINE(const char *) PDMAudioMixerCtlGetName(PDMAUDIOMIXERCTL enmMixerCtl) +{ + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_INVALID: return "Invalid"; + case PDMAUDIOMIXERCTL_UNKNOWN: return "Unknown"; + case PDMAUDIOMIXERCTL_VOLUME_MASTER: return "Master Volume"; + case PDMAUDIOMIXERCTL_FRONT: return "Front"; + case PDMAUDIOMIXERCTL_CENTER_LFE: return "Center / LFE"; + case PDMAUDIOMIXERCTL_REAR: return "Rear"; + case PDMAUDIOMIXERCTL_LINE_IN: return "Line-In"; + case PDMAUDIOMIXERCTL_MIC_IN: return "Microphone-In"; + + /* no default */ + case PDMAUDIOMIXERCTL_END: + case PDMAUDIOMIXERCTL_32BIT_HACK: + break; + } + AssertMsgFailedReturn(("Invalid mixer control %ld\n", enmMixerCtl), "bad"); +} + +/** + * Gets the name of a path enum value. + * + * @returns Pointer to read-only name, "bad" if invalid input. + * @param enmPath The path value to name. + */ +DECLINLINE(const char *) PDMAudioPathGetName(PDMAUDIOPATH enmPath) +{ + switch (enmPath) + { + case PDMAUDIOPATH_INVALID: return "invalid"; + case PDMAUDIOPATH_UNKNOWN: return "unknown"; + + case PDMAUDIOPATH_OUT_FRONT: return "front"; + case PDMAUDIOPATH_OUT_CENTER_LFE: return "center-lfe"; + case PDMAUDIOPATH_OUT_REAR: return "rear"; + + case PDMAUDIOPATH_IN_MIC: return "mic"; + case PDMAUDIOPATH_IN_CD: return "cd"; + case PDMAUDIOPATH_IN_VIDEO: return "video-in"; + case PDMAUDIOPATH_IN_AUX: return "aux-in"; + case PDMAUDIOPATH_IN_LINE: return "line-in"; + case PDMAUDIOPATH_IN_PHONE: return "phone"; + + /* no default */ + case PDMAUDIOPATH_END: + case PDMAUDIOPATH_32BIT_HACK: + break; + } + AssertMsgFailedReturn(("Unknown enmPath=%d\n", enmPath), "bad"); +} + +/** + * Gets the name of a channel. + * + * @returns Pointer to read-only name, "bad" if invalid input. + * @param enmChannelId The channel ID to name. + */ +DECLINLINE(const char *) PDMAudioChannelIdGetName(PDMAUDIOCHANNELID enmChannelId) +{ + switch (enmChannelId) + { + case PDMAUDIOCHANNELID_INVALID: return "invalid"; + case PDMAUDIOCHANNELID_UNUSED_ZERO: return "unused-zero"; + case PDMAUDIOCHANNELID_UNUSED_SILENCE: return "unused-silence"; + case PDMAUDIOCHANNELID_UNKNOWN: return "unknown"; + + case PDMAUDIOCHANNELID_FRONT_LEFT: return "FL"; + case PDMAUDIOCHANNELID_FRONT_RIGHT: return "FR"; + case PDMAUDIOCHANNELID_FRONT_CENTER: return "FC"; + case PDMAUDIOCHANNELID_LFE: return "LFE"; + case PDMAUDIOCHANNELID_REAR_LEFT: return "BL"; + case PDMAUDIOCHANNELID_REAR_RIGHT: return "BR"; + case PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER: return "FLC"; + case PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER: return "FRC"; + case PDMAUDIOCHANNELID_REAR_CENTER: return "BC"; + case PDMAUDIOCHANNELID_SIDE_LEFT: return "SL"; + case PDMAUDIOCHANNELID_SIDE_RIGHT: return "SR"; + case PDMAUDIOCHANNELID_TOP_CENTER: return "TC"; + case PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT: return "TFL"; + case PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT: return "TFC"; + case PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT: return "TFR"; + case PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT: return "TBL"; + case PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT: return "TBC"; + case PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT: return "TBR"; + + /* no default */ + case PDMAUDIOCHANNELID_END: + case PDMAUDIOCHANNELID_32BIT_HACK: + break; + } + AssertMsgFailedReturn(("Unknown enmChannelId=%d\n", enmChannelId), "bad"); +} + + +/********************************************************************************************************************************* +* Volume Helpers * +*********************************************************************************************************************************/ + +/** + * Initializes a PDMAUDIOVOLUME structure to max. + * + * @param pVol The structure to initialize. + */ +DECLINLINE(void) PDMAudioVolumeInitMax(PPDMAUDIOVOLUME pVol) +{ + pVol->fMuted = false; + for (uintptr_t i = 0; i < RT_ELEMENTS(pVol->auChannels); i++) + pVol->auChannels[i] = PDMAUDIO_VOLUME_MAX; +} + + +/** + * Initializes a PDMAUDIOVOLUME structure from a simple stereo setting. + * + * The additional channels will simply be assigned the higer of the two. + * + * @param pVol The structure to initialize. + * @param fMuted Muted. + * @param bLeft The left channel volume. + * @param bRight The right channel volume. + */ +DECLINLINE(void) PDMAudioVolumeInitFromStereo(PPDMAUDIOVOLUME pVol, bool fMuted, uint8_t bLeft, uint8_t bRight) +{ + pVol->fMuted = fMuted; + pVol->auChannels[0] = bLeft; + pVol->auChannels[1] = bRight; + + uint8_t const bOther = RT_MAX(bLeft, bRight); + for (uintptr_t i = 2; i < RT_ELEMENTS(pVol->auChannels); i++) + pVol->auChannels[i] = bOther; +} + + +/** + * Combines two volume settings (typically master and sink). + * + * @param pVol Where to return the combined volume + * @param pVol1 The first volume settings to combine. + * @param pVol2 The second volume settings. + */ +DECLINLINE(void) PDMAudioVolumeCombine(PPDMAUDIOVOLUME pVol, PCPDMAUDIOVOLUME pVol1, PCPDMAUDIOVOLUME pVol2) +{ + if (pVol1->fMuted || pVol2->fMuted) + { + pVol->fMuted = true; + for (uintptr_t i = 0; i < RT_ELEMENTS(pVol->auChannels); i++) + pVol->auChannels[i] = 0; + } + else + { + pVol->fMuted = false; + /** @todo Very crude implementation for now -- needs more work! (At least + * when used in audioMixerSinkUpdateVolume it was considered as such.) */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pVol->auChannels); i++) + { +#if 0 /* bird: I think the shift variant should produce the exact same result, w/o two conditionals per iteration. */ + /* 255 * 255 / 255 = 0xFF (255) */ + /* 17 * 127 / 255 = 8 */ + /* 39 * 39 / 255 = 5 */ + pVol->auChannels[i] = (uint8_t)( (RT_MAX(pVol1->auChannels[i], 1U) * RT_MAX(pVol2->auChannels[i], 1U)) + / PDMAUDIO_VOLUME_MAX); +#else + /* (((255 + 1) * (255 + 1)) >> 8) - 1 = 0xFF (255) */ + /* ((( 17 + 1) * (127 + 1)) >> 8) - 1 = 0x8 (8) */ + /* ((( 39 + 1) * ( 39 + 1)) >> 8) - 1 = 0x5 (5) */ + pVol->auChannels[i] = (uint8_t)((((1U + pVol1->auChannels[i]) * (1U + pVol2->auChannels[i])) >> 8) - 1U); +#endif + } + } +} + + +/********************************************************************************************************************************* +* PCM Property Helpers * +*********************************************************************************************************************************/ + +/** + * Assigns default channel IDs according to the channel count. + * + * The assignments are taken from the standard speaker channel layouts table + * in the wikipedia article on surround sound: + * https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels + */ +DECLINLINE(void) PDMAudioPropsSetDefaultChannelIds(PPDMAUDIOPCMPROPS pProps) +{ + unsigned cChannels = pProps->cChannelsX; + switch (cChannels) + { + case 1: + pProps->aidChannels[0] = PDMAUDIOCHANNELID_MONO; + break; + case 2: + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + break; + case 3: /* 2.1 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_LFE; + break; + case 4: /* 4.0 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_REAR_LEFT; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_REAR_RIGHT; + break; + case 5: /* 4.1 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_FRONT_CENTER; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_LFE; + pProps->aidChannels[4] = PDMAUDIOCHANNELID_REAR_CENTER; + break; + case 6: /* 5.1 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_FRONT_CENTER; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_LFE; + pProps->aidChannels[4] = PDMAUDIOCHANNELID_REAR_LEFT; + pProps->aidChannels[5] = PDMAUDIOCHANNELID_REAR_RIGHT; + break; + case 7: /* 6.1 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_FRONT_CENTER; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_LFE; + pProps->aidChannels[4] = PDMAUDIOCHANNELID_REAR_LEFT; + pProps->aidChannels[5] = PDMAUDIOCHANNELID_REAR_RIGHT; + pProps->aidChannels[6] = PDMAUDIOCHANNELID_REAR_CENTER; + break; + case 8: /* 7.1 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_FRONT_CENTER; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_LFE; + pProps->aidChannels[4] = PDMAUDIOCHANNELID_REAR_LEFT; + pProps->aidChannels[5] = PDMAUDIOCHANNELID_REAR_RIGHT; + pProps->aidChannels[6] = PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER; + pProps->aidChannels[7] = PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER; + break; + case 9: /* 9.0 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_FRONT_CENTER; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_REAR_LEFT; + pProps->aidChannels[4] = PDMAUDIOCHANNELID_REAR_RIGHT; + pProps->aidChannels[5] = PDMAUDIOCHANNELID_SIDE_LEFT; + pProps->aidChannels[6] = PDMAUDIOCHANNELID_SIDE_RIGHT; + pProps->aidChannels[7] = PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT; + pProps->aidChannels[8] = PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT; + break; + case 10: /* 9.1 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_FRONT_CENTER; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_LFE; + pProps->aidChannels[4] = PDMAUDIOCHANNELID_REAR_LEFT; + pProps->aidChannels[5] = PDMAUDIOCHANNELID_REAR_RIGHT; + pProps->aidChannels[6] = PDMAUDIOCHANNELID_SIDE_LEFT; + pProps->aidChannels[7] = PDMAUDIOCHANNELID_SIDE_RIGHT; + pProps->aidChannels[8] = PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT; + pProps->aidChannels[9] = PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT; + break; + case 11: /* 11.0 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_FRONT_CENTER; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_REAR_LEFT; + pProps->aidChannels[4] = PDMAUDIOCHANNELID_REAR_RIGHT; + pProps->aidChannels[5] = PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER; + pProps->aidChannels[6] = PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER; + pProps->aidChannels[7] = PDMAUDIOCHANNELID_SIDE_LEFT; + pProps->aidChannels[8] = PDMAUDIOCHANNELID_SIDE_RIGHT; + pProps->aidChannels[9] = PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT; + pProps->aidChannels[10]= PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT; + break; + default: + AssertFailed(); + cChannels = 12; + RT_FALL_THROUGH(); + case 12: /* 11.1 */ + pProps->aidChannels[0] = PDMAUDIOCHANNELID_FRONT_LEFT; + pProps->aidChannels[1] = PDMAUDIOCHANNELID_FRONT_RIGHT; + pProps->aidChannels[2] = PDMAUDIOCHANNELID_FRONT_CENTER; + pProps->aidChannels[3] = PDMAUDIOCHANNELID_LFE; + pProps->aidChannels[4] = PDMAUDIOCHANNELID_REAR_LEFT; + pProps->aidChannels[5] = PDMAUDIOCHANNELID_REAR_RIGHT; + pProps->aidChannels[6] = PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER; + pProps->aidChannels[7] = PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER; + pProps->aidChannels[8] = PDMAUDIOCHANNELID_SIDE_LEFT; + pProps->aidChannels[9] = PDMAUDIOCHANNELID_SIDE_RIGHT; + pProps->aidChannels[10]= PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT; + pProps->aidChannels[11]= PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT; + break; + case 0: + break; + } + AssertCompile(RT_ELEMENTS(pProps->aidChannels) >= 12); + + while (cChannels < RT_ELEMENTS(pProps->aidChannels)) + pProps->aidChannels[cChannels++] = PDMAUDIOCHANNELID_INVALID; +} + + +/** + * Initialize PCM audio properties. + */ +DECLINLINE(void) PDMAudioPropsInit(PPDMAUDIOPCMPROPS pProps, uint8_t cbSample, bool fSigned, uint8_t cChannels, uint32_t uHz) +{ + pProps->cbFrame = cbSample * cChannels; + pProps->cbSampleX = cbSample; + pProps->cChannelsX = cChannels; + pProps->cShiftX = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(cbSample, cChannels); + pProps->fSigned = fSigned; + pProps->fSwapEndian = false; + pProps->fRaw = false; + pProps->uHz = uHz; + + Assert(pProps->cbFrame == (uint32_t)cbSample * cChannels); + Assert(pProps->cbSampleX == cbSample); + Assert(pProps->cChannelsX == cChannels); + + PDMAudioPropsSetDefaultChannelIds(pProps); +} + +/** + * Initialize PCM audio properties, extended version. + */ +DECLINLINE(void) PDMAudioPropsInitEx(PPDMAUDIOPCMPROPS pProps, uint8_t cbSample, bool fSigned, uint8_t cChannels, uint32_t uHz, + bool fLittleEndian, bool fRaw) +{ + Assert(!fRaw || cbSample == sizeof(int64_t)); + pProps->cbFrame = cbSample * cChannels; + pProps->cbSampleX = cbSample; + pProps->cChannelsX = cChannels; + pProps->cShiftX = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(cbSample, cChannels); + pProps->fSigned = fSigned; +#ifdef RT_LITTLE_ENDIAN + pProps->fSwapEndian = !fLittleEndian; +#else + pProps->fSwapEndian = fLittleEndian; +#endif + pProps->fRaw = fRaw; + pProps->uHz = uHz; + + Assert(pProps->cbFrame == (uint32_t)cbSample * cChannels); + Assert(pProps->cbSampleX == cbSample); + Assert(pProps->cChannelsX == cChannels); + + PDMAudioPropsSetDefaultChannelIds(pProps); +} + +/** + * Modifies the channel count. + * + * @note This will reset the channel IDs to defaults. + * + * @param pProps The PCM properties to update. + * @param cChannels The new channel count. + */ +DECLINLINE(void) PDMAudioPropsSetChannels(PPDMAUDIOPCMPROPS pProps, uint8_t cChannels) +{ + Assert(cChannels > 0); Assert(cChannels < 16); + pProps->cChannelsX = cChannels; + pProps->cbFrame = pProps->cbSampleX * cChannels; + pProps->cShiftX = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cbSampleX, cChannels); + + PDMAudioPropsSetDefaultChannelIds(pProps); +} + +/** + * Modifies the sample size. + * + * @param pProps The PCM properties to update. + * @param cbSample The new sample size (in bytes). + */ +DECLINLINE(void) PDMAudioPropsSetSampleSize(PPDMAUDIOPCMPROPS pProps, uint8_t cbSample) +{ + Assert(cbSample == 1 || cbSample == 2 || cbSample == 4 || cbSample == 8); + pProps->cbSampleX = cbSample; + pProps->cbFrame = cbSample * pProps->cChannelsX; + pProps->cShiftX = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(cbSample, pProps->cChannelsX); +} + +/** + * Gets the bitrate. + * + * Divide the result by 8 to get the byte rate. + * + * @returns Bit rate. + * @param pProps PCM properties to calculate bitrate for. + */ +DECLINLINE(uint32_t) PDMAudioPropsGetBitrate(PCPDMAUDIOPCMPROPS pProps) +{ + Assert(pProps->cbFrame == pProps->cbSampleX * pProps->cChannelsX); + return pProps->cbFrame * pProps->uHz * 8; +} + +/** + * Gets the number of channels. + * @returns The channel count. + * @param pProps The PCM properties. + */ +DECL_FORCE_INLINE(uint8_t) PDMAudioPropsChannels(PCPDMAUDIOPCMPROPS pProps) +{ + return pProps->cChannelsX; +} + +/** + * Gets the sample size in bytes. + * @returns Number of bytes per sample. + * @param pProps The PCM properties. + */ +DECL_FORCE_INLINE(uint8_t) PDMAudioPropsSampleSize(PCPDMAUDIOPCMPROPS pProps) +{ + return pProps->cbSampleX; +} + +/** + * Gets the sample size in bits. + * @returns Number of bits per sample. + * @param pProps The PCM properties. + */ +DECLINLINE(uint8_t) PDMAudioPropsSampleBits(PCPDMAUDIOPCMPROPS pProps) +{ + return pProps->cbSampleX * 8; +} + +/** + * Gets the frame size in bytes. + * @returns Number of bytes per frame. + * @param pProps The PCM properties. + */ +DECL_FORCE_INLINE(uint8_t) PDMAudioPropsFrameSize(PCPDMAUDIOPCMPROPS pProps) +{ + return pProps->cbFrame; +} + +/** + * Gets the frequency. + * @returns Frequency. + * @param pProps The PCM properties. + */ +DECL_FORCE_INLINE(uint32_t) PDMAudioPropsHz(PCPDMAUDIOPCMPROPS pProps) +{ + return pProps->uHz; +} + +/** + * Checks if the format is signed or unsigned. + * @returns true if signed, false if unsigned. + * @param pProps The PCM properties. + */ +DECL_FORCE_INLINE(bool) PDMAudioPropsIsSigned(PCPDMAUDIOPCMPROPS pProps) +{ + return pProps->fSigned; +} + +/** + * Checks if the format is little-endian or not. + * @returns true if little-endian (or if 8-bit), false if big-endian. + * @param pProps The PCM properties. + */ +DECL_FORCE_INLINE(bool) PDMAudioPropsIsLittleEndian(PCPDMAUDIOPCMPROPS pProps) +{ +#ifdef RT_LITTLE_ENDIAN + return !pProps->fSwapEndian || pProps->cbSampleX < 2; +#else + return pProps->fSwapEndian || pProps->cbSampleX < 2; +#endif +} + +/** + * Checks if the format is big-endian or not. + * @returns true if big-endian (or if 8-bit), false if little-endian. + * @param pProps The PCM properties. + */ +DECL_FORCE_INLINE(bool) PDMAudioPropsIsBigEndian(PCPDMAUDIOPCMPROPS pProps) +{ +#ifdef RT_LITTLE_ENDIAN + return pProps->fSwapEndian || pProps->cbSampleX < 2; +#else + return !pProps->fSwapEndian || pProps->cbSampleX < 2; +#endif +} + +/** + * Rounds down the given byte amount to the nearest frame boundrary. + * + * @returns Rounded byte amount. + * @param pProps PCM properties to use. + * @param cb The size (in bytes) to round. + */ +DECLINLINE(uint32_t) PDMAudioPropsFloorBytesToFrame(PCPDMAUDIOPCMPROPS pProps, uint32_t cb) +{ + AssertPtrReturn(pProps, 0); + return PDMAUDIOPCMPROPS_F2B(pProps, PDMAUDIOPCMPROPS_B2F(pProps, cb)); +} + +/** + * Rounds up the given byte amount to the nearest frame boundrary. + * + * @returns Rounded byte amount. + * @param pProps PCM properties to use. + * @param cb The size (in bytes) to round. + */ +DECLINLINE(uint32_t) PDMAudioPropsRoundUpBytesToFrame(PCPDMAUDIOPCMPROPS pProps, uint32_t cb) +{ + AssertPtrReturn(pProps, 0); + uint32_t const cbFrame = PDMAudioPropsFrameSize(pProps); + AssertReturn(cbFrame, 0); + return PDMAUDIOPCMPROPS_F2B(pProps, PDMAUDIOPCMPROPS_B2F(pProps, cb + cbFrame - 1)); +} + +/** + * Checks if the given size is aligned on a frame boundrary. + * + * @returns @c true if properly aligned, @c false if not. + * @param pProps PCM properties to use. + * @param cb The size (in bytes) to check. + */ +DECLINLINE(bool) PDMAudioPropsIsSizeAligned(PCPDMAUDIOPCMPROPS pProps, uint32_t cb) +{ + AssertPtrReturn(pProps, false); + uint32_t const cbFrame = PDMAudioPropsFrameSize(pProps); + AssertReturn(cbFrame, false); + return cb % cbFrame == 0; +} + +/** + * Converts bytes to frames (rounding down of course). + * + * @returns Number of frames. + * @param pProps PCM properties to use. + * @param cb The number of bytes to convert. + */ +DECLINLINE(uint32_t) PDMAudioPropsBytesToFrames(PCPDMAUDIOPCMPROPS pProps, uint32_t cb) +{ + AssertPtrReturn(pProps, 0); + return PDMAUDIOPCMPROPS_B2F(pProps, cb); +} + +/** + * Converts bytes to milliseconds. + * + * @return Number milliseconds @a cb takes to play or record. + * @param pProps PCM properties to use. + * @param cb The number of bytes to convert. + * + * @note Rounds up the result. + */ +DECLINLINE(uint64_t) PDMAudioPropsBytesToMilli(PCPDMAUDIOPCMPROPS pProps, uint32_t cb) +{ + AssertPtrReturn(pProps, 0); + + /* Check parameters to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + { + const unsigned cbFrame = PDMAudioPropsFrameSize(pProps); + if (cbFrame) + { + /* Round cb up to closest frame size: */ + cb = (cb + cbFrame - 1) / cbFrame; + + /* Convert to milliseconds. */ + return (cb * (uint64_t)RT_MS_1SEC + uHz - 1) / uHz; + } + } + return 0; +} + +/** + * Converts bytes to microseconds. + * + * @return Number microseconds @a cb takes to play or record. + * @param pProps PCM properties to use. + * @param cb The number of bytes to convert. + * + * @note Rounds up the result. + */ +DECLINLINE(uint64_t) PDMAudioPropsBytesToMicro(PCPDMAUDIOPCMPROPS pProps, uint32_t cb) +{ + AssertPtrReturn(pProps, 0); + + /* Check parameters to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + { + const unsigned cbFrame = PDMAudioPropsFrameSize(pProps); + if (cbFrame) + { + /* Round cb up to closest frame size: */ + cb = (cb + cbFrame - 1) / cbFrame; + + /* Convert to microseconds. */ + return (cb * (uint64_t)RT_US_1SEC + uHz - 1) / uHz; + } + } + return 0; +} + +/** + * Converts bytes to nanoseconds. + * + * @return Number nanoseconds @a cb takes to play or record. + * @param pProps PCM properties to use. + * @param cb The number of bytes to convert. + * + * @note Rounds up the result. + */ +DECLINLINE(uint64_t) PDMAudioPropsBytesToNano(PCPDMAUDIOPCMPROPS pProps, uint32_t cb) +{ + AssertPtrReturn(pProps, 0); + + /* Check parameters to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + { + const unsigned cbFrame = PDMAudioPropsFrameSize(pProps); + if (cbFrame) + { + /* Round cb up to closest frame size: */ + cb = (cb + cbFrame - 1) / cbFrame; + + /* Convert to nanoseconds. */ + return (cb * (uint64_t)RT_NS_1SEC + uHz - 1) / uHz; + } + } + return 0; +} + +/** + * Converts bytes to nanoseconds, 64-bit version. + * + * @return Number nanoseconds @a cb takes to play or record. + * @param pProps PCM properties to use. + * @param cb The number of bytes to convert (64-bit). + * + * @note Rounds up the result. + */ +DECLINLINE(uint64_t) PDMAudioPropsBytesToNano64(PCPDMAUDIOPCMPROPS pProps, uint64_t cb) +{ + AssertPtrReturn(pProps, 0); + + /* Check parameters to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + { + const unsigned cbFrame = PDMAudioPropsFrameSize(pProps); + if (cbFrame) + { + /* Round cb up to closest frame size: */ + cb = (cb + cbFrame - 1) / cbFrame; + + /* Convert to nanoseconds. */ + return (cb * RT_NS_1SEC + uHz - 1) / uHz; + } + } + return 0; +} + +/** + * Converts frames to bytes. + * + * @returns Number of bytes. + * @param pProps The PCM properties to use. + * @param cFrames Number of audio frames to convert. + * @sa PDMAUDIOPCMPROPS_F2B + */ +DECLINLINE(uint32_t) PDMAudioPropsFramesToBytes(PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +{ + AssertPtrReturn(pProps, 0); + return PDMAUDIOPCMPROPS_F2B(pProps, cFrames); +} + +/** + * Converts frames to milliseconds. + * + * @returns milliseconds. + * @param pProps The PCM properties to use. + * @param cFrames Number of audio frames to convert. + * @note No rounding here, result is floored. + */ +DECLINLINE(uint64_t) PDMAudioPropsFramesToMilli(PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +{ + AssertPtrReturn(pProps, 0); + + /* Check input to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + return ASMMultU32ByU32DivByU32(cFrames, RT_MS_1SEC, uHz); + return 0; +} + +/** + * Converts frames to milliseconds, but not returning more than @a cMsMax + * + * This is a convenience for logging and such. + * + * @returns milliseconds (32-bit). + * @param pProps The PCM properties to use. + * @param cFrames Number of audio frames to convert. + * @param cMsMax Max return value (32-bit). + * @note No rounding here, result is floored. + */ +DECLINLINE(uint32_t) PDMAudioPropsFramesToMilliMax(PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames, uint32_t cMsMax) +{ + AssertPtrReturn(pProps, 0); + + /* Check input to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + { + uint32_t const cMsResult = ASMMultU32ByU32DivByU32(cFrames, RT_MS_1SEC, uHz); + return RT_MIN(cMsResult, cMsMax); + } + return 0; +} + +/** + * Converts frames to microseconds. + * + * @returns microseconds. + * @param pProps The PCM properties to use. + * @param cFrames Number of audio frames to convert. + * @note No rounding here, result is floored. + */ +DECLINLINE(uint64_t) PDMAudioPropsFramesToMicro(PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +{ + AssertPtrReturn(pProps, 0); + + /* Check input to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + return ASMMultU32ByU32DivByU32(cFrames, RT_US_1SEC, uHz); + return 0; +} + +/** + * Converts frames to nanoseconds. + * + * @returns Nanoseconds. + * @param pProps The PCM properties to use. + * @param cFrames Number of audio frames to convert. + * @note No rounding here, result is floored. + */ +DECLINLINE(uint64_t) PDMAudioPropsFramesToNano(PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +{ + AssertPtrReturn(pProps, 0); + + /* Check input to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + return ASMMultU32ByU32DivByU32(cFrames, RT_NS_1SEC, uHz); + return 0; +} + +/** + * Converts frames to NT ticks (100 ns units). + * + * @returns NT ticks. + * @param pProps The PCM properties to use. + * @param cFrames Number of audio frames to convert. + * @note No rounding here, result is floored. + */ +DECLINLINE(uint64_t) PDMAudioPropsFramesToNtTicks(PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +{ + AssertPtrReturn(pProps, 0); + + /* Check input to prevent division by chainsaw: */ + uint32_t const uHz = pProps->uHz; + if (uHz) + return ASMMultU32ByU32DivByU32(cFrames, RT_NS_1SEC / 100, uHz); + return 0; +} + +/** + * Converts milliseconds to frames. + * + * @returns Number of frames + * @param pProps The PCM properties to use. + * @param cMs The number of milliseconds to convert. + * + * @note The result is rounded rather than floored (hysterical raisins). + */ +DECLINLINE(uint32_t) PDMAudioPropsMilliToFrames(PCPDMAUDIOPCMPROPS pProps, uint64_t cMs) +{ + AssertPtrReturn(pProps, 0); + + uint32_t const uHz = pProps->uHz; + uint32_t cFrames; + if (cMs < RT_MS_1SEC) + cFrames = 0; + else + { + cFrames = cMs / RT_MS_1SEC * uHz; + cMs %= RT_MS_1SEC; + } + cFrames += (ASMMult2xU32RetU64(uHz, (uint32_t)cMs) + RT_MS_1SEC - 1) / RT_MS_1SEC; + return cFrames; +} + +/** + * Converts milliseconds to bytes. + * + * @returns Number of bytes (frame aligned). + * @param pProps The PCM properties to use. + * @param cMs The number of milliseconds to convert. + * + * @note The result is rounded rather than floored (hysterical raisins). + */ +DECLINLINE(uint32_t) PDMAudioPropsMilliToBytes(PCPDMAUDIOPCMPROPS pProps, uint64_t cMs) +{ + return PDMAUDIOPCMPROPS_F2B(pProps, PDMAudioPropsMilliToFrames(pProps, cMs)); +} + +/** + * Converts nanoseconds to frames. + * + * @returns Number of frames. + * @param pProps The PCM properties to use. + * @param cNs The number of nanoseconds to convert. + * + * @note The result is rounded rather than floored (hysterical raisins). + */ +DECLINLINE(uint32_t) PDMAudioPropsNanoToFrames(PCPDMAUDIOPCMPROPS pProps, uint64_t cNs) +{ + AssertPtrReturn(pProps, 0); + + uint32_t const uHz = pProps->uHz; + uint32_t cFrames; + if (cNs < RT_NS_1SEC) + cFrames = 0; + else + { + cFrames = cNs / RT_NS_1SEC * uHz; + cNs %= RT_NS_1SEC; + } + cFrames += (ASMMult2xU32RetU64(uHz, (uint32_t)cNs) + RT_NS_1SEC - 1) / RT_NS_1SEC; + return cFrames; +} + +/** + * Converts nanoseconds to frames, 64-bit return. + * + * @returns Number of frames (64-bit). + * @param pProps The PCM properties to use. + * @param cNs The number of nanoseconds to convert. + * + * @note The result is floored! + */ +DECLINLINE(uint64_t) PDMAudioPropsNanoToFrames64(PCPDMAUDIOPCMPROPS pProps, uint64_t cNs) +{ + AssertPtrReturn(pProps, 0); + + uint32_t const uHz = pProps->uHz; + uint64_t cFrames; + if (cNs < RT_NS_1SEC) + cFrames = 0; + else + { + cFrames = cNs / RT_NS_1SEC * uHz; + cNs %= RT_NS_1SEC; + } + cFrames += ASMMult2xU32RetU64(uHz, (uint32_t)cNs) / RT_NS_1SEC; + return cFrames; +} + +/** + * Converts nanoseconds to bytes. + * + * @returns Number of bytes (frame aligned). + * @param pProps The PCM properties to use. + * @param cNs The number of nanoseconds to convert. + * + * @note The result is rounded rather than floored (hysterical raisins). + */ +DECLINLINE(uint32_t) PDMAudioPropsNanoToBytes(PCPDMAUDIOPCMPROPS pProps, uint64_t cNs) +{ + return PDMAUDIOPCMPROPS_F2B(pProps, PDMAudioPropsNanoToFrames(pProps, cNs)); +} + +/** + * Converts nanoseconds to bytes, 64-bit version. + * + * @returns Number of bytes (frame aligned), 64-bit. + * @param pProps The PCM properties to use. + * @param cNs The number of nanoseconds to convert. + * + * @note The result is floored. + */ +DECLINLINE(uint64_t) PDMAudioPropsNanoToBytes64(PCPDMAUDIOPCMPROPS pProps, uint64_t cNs) +{ + return PDMAUDIOPCMPROPS_F2B(pProps, PDMAudioPropsNanoToFrames(pProps, cNs)); +} + +/** + * Clears a sample buffer by the given amount of audio frames with silence (according to the format + * given by the PCM properties). + * + * @param pProps The PCM properties to apply. + * @param pvBuf The buffer to clear. + * @param cbBuf The buffer size in bytes. + * @param cFrames The number of audio frames to clear. Capped at @a cbBuf + * if exceeding the buffer. If the size is an unaligned + * number of frames, the extra bytes may be left + * uninitialized in some configurations. + */ +DECLINLINE(void) PDMAudioPropsClearBuffer(PCPDMAUDIOPCMPROPS pProps, void *pvBuf, size_t cbBuf, uint32_t cFrames) +{ + /* + * Validate input + */ + AssertPtrReturnVoid(pProps); + Assert(pProps->cbSampleX); + if (!cbBuf || !cFrames) + return; + AssertPtrReturnVoid(pvBuf); + + /* + * Decide how much needs clearing. + */ + size_t cbToClear = PDMAudioPropsFramesToBytes(pProps, cFrames); + AssertStmt(cbToClear <= cbBuf, cbToClear = cbBuf); + + Log2Func(("pProps=%p, pvBuf=%p, cFrames=%RU32, fSigned=%RTbool, cbSample=%RU8\n", + pProps, pvBuf, cFrames, pProps->fSigned, pProps->cbSampleX)); + + /* + * Do the job. + */ + if (pProps->fSigned) + RT_BZERO(pvBuf, cbToClear); + else /* Unsigned formats. */ + { + switch (pProps->cbSampleX) + { + case 1: /* 8 bit */ + memset(pvBuf, 0x80, cbToClear); + break; + + case 2: /* 16 bit */ + { + uint16_t *pu16Dst = (uint16_t *)pvBuf; + uint16_t const u16Offset = !pProps->fSwapEndian ? UINT16_C(0x8000) : UINT16_C(0x80); + cbBuf /= sizeof(*pu16Dst); + while (cbBuf-- > 0) + *pu16Dst++ = u16Offset; + break; + } + + case 4: /* 32 bit */ + ASMMemFill32(pvBuf, cbToClear & ~(size_t)(sizeof(uint32_t) - 1), + !pProps->fSwapEndian ? UINT32_C(0x80000000) : UINT32_C(0x80)); + break; + + default: + AssertMsgFailed(("Invalid bytes per sample: %RU8\n", pProps->cbSampleX)); + } + } +} + +/** + * Checks if the given buffer is silence. + * + * @param pProps The PCM properties to use checking the buffer. + * @param pvBuf The buffer to check. + * @param cbBuf The number of bytes to check (must be frame aligned). + */ +DECLINLINE(bool) PDMAudioPropsIsBufferSilence(PCPDMAUDIOPCMPROPS pProps, void const *pvBuf, size_t cbBuf) +{ + /* + * Validate input + */ + AssertPtrReturn(pProps, false); + if (!cbBuf) + return false; + AssertPtrReturn(pvBuf, false); + + /* + * Do the job. + */ + if (pProps->fSigned) + return ASMMemIsZero(pvBuf, cbBuf); + + switch (pProps->cbSampleX) + { + case 1: /* 8 bit */ + return ASMMemIsAllU8(pvBuf, cbBuf, 0x80); + + case 2: /* 16 bit */ + { + uint16_t const *pu16 = (uint16_t const *)pvBuf; + uint16_t const u16Offset = !pProps->fSwapEndian ? UINT16_C(0x8000) : UINT16_C(0x80); + cbBuf /= sizeof(*pu16); + while (cbBuf-- > 0) + if (*pu16 != u16Offset) + return false; + return true; + } + + case 4: /* 32 bit */ + { + uint32_t const *pu32 = (uint32_t const *)pvBuf; + uint32_t const u32Offset = !pProps->fSwapEndian ? UINT32_C(0x80000000) : UINT32_C(0x80); + cbBuf /= sizeof(*pu32); + while (cbBuf-- > 0) + if (*pu32 != u32Offset) + return false; + return true; + } + + default: + AssertMsgFailed(("Invalid bytes per sample: %RU8\n", pProps->cbSampleX)); + return false; + } +} + +/** + * Compares two sets of PCM properties. + * + * @returns @c true if the same, @c false if not. + * @param pProps1 The first set of properties to compare. + * @param pProps2 The second set of properties to compare. + */ +DECLINLINE(bool) PDMAudioPropsAreEqual(PCPDMAUDIOPCMPROPS pProps1, PCPDMAUDIOPCMPROPS pProps2) +{ + uintptr_t idxCh; + AssertPtrReturn(pProps1, false); + AssertPtrReturn(pProps2, false); + + if (pProps1 == pProps2) /* If the pointers match, take a shortcut. */ + return true; + + if (pProps1->uHz != pProps2->uHz) + return false; + if (pProps1->cChannelsX != pProps2->cChannelsX) + return false; + if (pProps1->cbSampleX != pProps2->cbSampleX) + return false; + if (pProps1->fSigned != pProps2->fSigned) + return false; + if (pProps1->fSwapEndian != pProps2->fSwapEndian) + return false; + if (pProps1->fRaw != pProps2->fRaw) + return false; + + idxCh = pProps1->cChannelsX; + while (idxCh-- > 0) + if (pProps1->aidChannels[idxCh] != pProps2->aidChannels[idxCh]) + return false; + + return true; +} + +/** + * Checks whether the given PCM properties are valid or not. + * + * @returns true/false accordingly. + * @param pProps The PCM properties to check. + * + * @remarks This just performs a generic check of value ranges. Further, it + * will assert if the input is invalid. + * + * @sa PDMAudioStrmCfgIsValid + */ +DECLINLINE(bool) PDMAudioPropsAreValid(PCPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, false); + + AssertReturn(pProps->cChannelsX != 0, false); + AssertReturn(pProps->cChannelsX <= PDMAUDIO_MAX_CHANNELS, false); + AssertMsgReturn( pProps->cbSampleX == 1 || pProps->cbSampleX == 2 || pProps->cbSampleX == 4 || (pProps->cbSampleX == 8 && pProps->fRaw), + ("%u\n", pProps->cbSampleX), false); + AssertMsgReturn(pProps->cbFrame == pProps->cbSampleX * pProps->cChannelsX, + ("cbFrame=%u cbSample=%u cChannels=%u\n", pProps->cbFrame, pProps->cbSampleX, pProps->cChannelsX), + false); + AssertMsgReturn(pProps->uHz >= 1000 && pProps->uHz < 1000000, ("%u\n", pProps->uHz), false); + AssertMsgReturn(pProps->cShiftX == PDMAUDIOPCMPROPS_MAKE_SHIFT(pProps), + ("cShift=%u cbSample=%u cChannels=%u\n", pProps->cShiftX, pProps->cbSampleX, pProps->cChannelsX), + false); + AssertReturn(!pProps->fRaw || (pProps->fSigned && pProps->cbSampleX == sizeof(int64_t)), false); + return true; +} + +/** + * Get number of bytes per frame. + * + * @returns Number of bytes per audio frame. + * @param pProps PCM properties to use. + * @sa PDMAUDIOPCMPROPS_F2B + */ +DECLINLINE(uint32_t) PDMAudioPropsBytesPerFrame(PCPDMAUDIOPCMPROPS pProps) +{ + return PDMAUDIOPCMPROPS_F2B(pProps, 1 /*cFrames*/); +} + +/** + * Prints PCM properties to the debug log. + * + * @param pProps PCM properties to use. + */ +DECLINLINE(void) PDMAudioPropsLog(PCPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturnVoid(pProps); + + Log(("uHz=%RU32, cChannels=%RU8, cBits=%RU8%s", + pProps->uHz, pProps->cChannelsX, pProps->cbSampleX * 8, pProps->fSigned ? "S" : "U")); +} + +/** Max necessary buffer space for PDMAudioPropsToString */ +#define PDMAUDIOPROPSTOSTRING_MAX sizeof("16ch S64 4294967296Hz swap raw") + +/** + * Formats the PCM audio properties into a string buffer. + * + * @returns pszDst + * @param pProps PCM properties to use. + * @param pszDst The destination buffer. + * @param cchDst The size of the destination buffer. Recommended to be at + * least PDMAUDIOPROPSTOSTRING_MAX bytes. + */ +DECLINLINE(char *) PDMAudioPropsToString(PCPDMAUDIOPCMPROPS pProps, char *pszDst, size_t cchDst) +{ + /* 2ch S64 44100Hz swap raw */ + RTStrPrintf(pszDst, cchDst, "%uch %c%u %RU32Hz%s%s", + PDMAudioPropsChannels(pProps), PDMAudioPropsIsSigned(pProps) ? 'S' : 'U', PDMAudioPropsSampleBits(pProps), + PDMAudioPropsHz(pProps), pProps->fSwapEndian ? " swap" : "", pProps->fRaw ? " raw" : ""); + return pszDst; +} + + +/********************************************************************************************************************************* +* Stream Configuration Helpers * +*********************************************************************************************************************************/ + +/** + * Initializes a stream configuration from PCM properties. + * + * @returns VBox status code. + * @param pCfg The stream configuration to initialize. + * @param pProps The PCM properties to use. + */ +DECLINLINE(int) PDMAudioStrmCfgInitWithProps(PPDMAUDIOSTREAMCFG pCfg, PCPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + RT_ZERO(*pCfg); + pCfg->Backend.cFramesPreBuffering = UINT32_MAX; /* Explicitly set to "undefined". */ + + memcpy(&pCfg->Props, pProps, sizeof(PDMAUDIOPCMPROPS)); + + return VINF_SUCCESS; +} + +/** + * Checks whether stream configuration matches the given PCM properties. + * + * @returns @c true if equal, @c false if not. + * @param pCfg The stream configuration. + * @param pProps The PCM properties to match with. + */ +DECLINLINE(bool) PDMAudioStrmCfgMatchesProps(PCPDMAUDIOSTREAMCFG pCfg, PCPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pCfg, false); + return PDMAudioPropsAreEqual(pProps, &pCfg->Props); +} + +/** + * Checks whether two stream configuration matches. + * + * @returns @c true if equal, @c false if not. + * @param pCfg1 The first stream configuration. + * @param pCfg2 The second stream configuration. + */ +DECLINLINE(bool) PDMAudioStrmCfgEquals(PCPDMAUDIOSTREAMCFG pCfg1, PCPDMAUDIOSTREAMCFG pCfg2) +{ + if (!pCfg1 || !pCfg2) + return false; + if (pCfg1 == pCfg2) + return pCfg1 != NULL; + if (PDMAudioPropsAreEqual(&pCfg1->Props, &pCfg2->Props)) + return pCfg1->enmDir == pCfg2->enmDir + && pCfg1->enmPath == pCfg2->enmPath + && pCfg1->Device.cMsSchedulingHint == pCfg2->Device.cMsSchedulingHint + && pCfg1->Backend.cFramesPeriod == pCfg2->Backend.cFramesPeriod + && pCfg1->Backend.cFramesBufferSize == pCfg2->Backend.cFramesBufferSize + && pCfg1->Backend.cFramesPreBuffering == pCfg2->Backend.cFramesPreBuffering + && strcmp(pCfg1->szName, pCfg2->szName) == 0; + return false; +} + +/** + * Frees an audio stream allocated by PDMAudioStrmCfgDup(). + * + * @param pCfg The stream configuration to free. + */ +DECLINLINE(void) PDMAudioStrmCfgFree(PPDMAUDIOSTREAMCFG pCfg) +{ + if (pCfg) + RTMemFree(pCfg); +} + +/** + * Checks whether the given stream configuration is valid or not. + * + * @returns true/false accordingly. + * @param pCfg Stream configuration to check. + * + * @remarks This just performs a generic check of value ranges. Further, it + * will assert if the input is invalid. + * + * @sa PDMAudioPropsAreValid + */ +DECLINLINE(bool) PDMAudioStrmCfgIsValid(PCPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pCfg, false); + AssertMsgReturn(pCfg->enmDir >= PDMAUDIODIR_UNKNOWN && pCfg->enmDir < PDMAUDIODIR_END, ("%d\n", pCfg->enmDir), false); + return PDMAudioPropsAreValid(&pCfg->Props); +} + +/** + * Copies one stream configuration to another. + * + * @returns VBox status code. + * @param pDstCfg The destination stream configuration. + * @param pSrcCfg The source stream configuration. + */ +DECLINLINE(int) PDMAudioStrmCfgCopy(PPDMAUDIOSTREAMCFG pDstCfg, PCPDMAUDIOSTREAMCFG pSrcCfg) +{ + AssertPtrReturn(pDstCfg, VERR_INVALID_POINTER); + AssertPtrReturn(pSrcCfg, VERR_INVALID_POINTER); + + /* This used to be VBOX_STRICT only and return VERR_INVALID_PARAMETER, but + that's making release builds work differently from debug & strict builds, + which is a terrible idea: */ + Assert(PDMAudioStrmCfgIsValid(pSrcCfg)); + + memcpy(pDstCfg, pSrcCfg, sizeof(PDMAUDIOSTREAMCFG)); + + return VINF_SUCCESS; +} + +/** + * Duplicates an audio stream configuration. + * + * @returns Pointer to duplicate on success, NULL on failure. Must be freed + * using PDMAudioStrmCfgFree(). + * + * @param pCfg The audio stream configuration to duplicate. + */ +DECLINLINE(PPDMAUDIOSTREAMCFG) PDMAudioStrmCfgDup(PCPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pCfg, NULL); + + PPDMAUDIOSTREAMCFG pDst = (PPDMAUDIOSTREAMCFG)RTMemAllocZ(sizeof(PDMAUDIOSTREAMCFG)); + if (pDst) + { + int rc = PDMAudioStrmCfgCopy(pDst, pCfg); + if (RT_SUCCESS(rc)) + return pDst; + + PDMAudioStrmCfgFree(pDst); + } + return NULL; +} + +/** + * Logs an audio stream configuration. + * + * @param pCfg The stream configuration to log. + */ +DECLINLINE(void) PDMAudioStrmCfgLog(PCPDMAUDIOSTREAMCFG pCfg) +{ + if (pCfg) + LogFunc(("szName=%s enmDir=%RU32 uHz=%RU32 cBits=%RU8%s cChannels=%RU8\n", pCfg->szName, pCfg->enmDir, + pCfg->Props.uHz, pCfg->Props.cbSampleX * 8, pCfg->Props.fSigned ? "S" : "U", pCfg->Props.cChannelsX)); +} + +/** + * Converts a stream command enum value to a string. + * + * @returns Pointer to read-only stream command name on success, + * "bad" if invalid command value. + * @param enmCmd The stream command to name. + */ +DECLINLINE(const char *) PDMAudioStrmCmdGetName(PDMAUDIOSTREAMCMD enmCmd) +{ + switch (enmCmd) + { + case PDMAUDIOSTREAMCMD_INVALID: return "Invalid"; + case PDMAUDIOSTREAMCMD_ENABLE: return "Enable"; + case PDMAUDIOSTREAMCMD_DISABLE: return "Disable"; + case PDMAUDIOSTREAMCMD_PAUSE: return "Pause"; + case PDMAUDIOSTREAMCMD_RESUME: return "Resume"; + case PDMAUDIOSTREAMCMD_DRAIN: return "Drain"; + case PDMAUDIOSTREAMCMD_END: + case PDMAUDIOSTREAMCMD_32BIT_HACK: + break; + /* no default! */ + } + AssertMsgFailedReturn(("Invalid stream command %d\n", enmCmd), "bad"); +} + +/** Max necessary buffer space for PDMAudioStrmCfgToString */ +#define PDMAUDIOSTRMCFGTOSTRING_MAX \ + sizeof("'01234567890123456789012345678901234567890123456789012345678901234' unknown 16ch S64 4294967295Hz swap raw, 9999999ms buffer, 9999999ms period, 9999999ms pre-buffer, 4294967295ms sched, center-lfe") + +/** + * Formats an audio stream configuration. + * + * @param pCfg The stream configuration to stringify. + * @param pszDst The destination buffer. + * @param cbDst The size of the destination buffer. Recommend this be + * at least PDMAUDIOSTRMCFGTOSTRING_MAX bytes. + */ +DECLINLINE(const char *) PDMAudioStrmCfgToString(PCPDMAUDIOSTREAMCFG pCfg, char *pszDst, size_t cbDst) +{ + /* 'front' output 2ch 44100Hz raw, 300ms buffer, 75ms period, 150ms pre-buffer, 10ms sched */ + RTStrPrintf(pszDst, cbDst, + "'%s' %s %uch %c%u %RU32Hz%s%s, %RU32ms buffer, %RU32ms period, %RU32ms pre-buffer, %RU32ms sched%s%s", + pCfg->szName, PDMAudioDirGetName(pCfg->enmDir), PDMAudioPropsChannels(&pCfg->Props), + PDMAudioPropsIsSigned(&pCfg->Props) ? 'S' : 'U', PDMAudioPropsSampleBits(&pCfg->Props), + PDMAudioPropsHz(&pCfg->Props), pCfg->Props.fSwapEndian ? " swap" : "", pCfg->Props.fRaw ? " raw" : "", + PDMAudioPropsFramesToMilliMax(&pCfg->Props, pCfg->Backend.cFramesBufferSize, 9999999), + PDMAudioPropsFramesToMilliMax(&pCfg->Props, pCfg->Backend.cFramesPeriod, 9999999), + PDMAudioPropsFramesToMilliMax(&pCfg->Props, pCfg->Backend.cFramesPreBuffering, 9999999), + pCfg->Device.cMsSchedulingHint, + pCfg->enmPath == PDMAUDIOPATH_UNKNOWN ? "" : ", ", + pCfg->enmPath == PDMAUDIOPATH_UNKNOWN ? "" : PDMAudioPathGetName(pCfg->enmPath) ); + return pszDst; +} + + +/********************************************************************************************************************************* +* Stream Status Helpers * +*********************************************************************************************************************************/ + +/** + * Converts a audio stream state enum value to a string. + * + * @returns Pointer to read-only audio stream state string on success, + * "illegal" if invalid command value. + * @param enmStreamState The state to convert. + */ +DECLINLINE(const char *) PDMAudioStreamStateGetName(PDMAUDIOSTREAMSTATE enmStreamState) +{ + switch (enmStreamState) + { + case PDMAUDIOSTREAMSTATE_INVALID: return "invalid"; + case PDMAUDIOSTREAMSTATE_NOT_WORKING: return "not-working"; + case PDMAUDIOSTREAMSTATE_NEED_REINIT: return "need-reinit"; + case PDMAUDIOSTREAMSTATE_INACTIVE: return "inactive"; + case PDMAUDIOSTREAMSTATE_ENABLED: return "enabled"; + case PDMAUDIOSTREAMSTATE_ENABLED_READABLE: return "enabled-readable"; + case PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE: return "enabled-writable"; + /* no default: */ + case PDMAUDIOSTREAMSTATE_END: + case PDMAUDIOSTREAMSTATE_32BIT_HACK: + break; + } + AssertMsgFailedReturn(("Invalid audio stream state: %d\n", enmStreamState), "illegal"); +} + +/** + * Converts a host audio (backend) stream state enum value to a string. + * + * @returns Pointer to read-only host audio stream state string on success, + * "illegal" if invalid command value. + * @param enmHostAudioStreamState The state to convert. + */ +DECLINLINE(const char *) PDMHostAudioStreamStateGetName(PDMHOSTAUDIOSTREAMSTATE enmHostAudioStreamState) +{ + switch (enmHostAudioStreamState) + { + case PDMHOSTAUDIOSTREAMSTATE_INVALID: return "invalid"; + case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING: return "initializing"; + case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING: return "not-working"; + case PDMHOSTAUDIOSTREAMSTATE_OKAY: return "okay"; + case PDMHOSTAUDIOSTREAMSTATE_DRAINING: return "draining"; + case PDMHOSTAUDIOSTREAMSTATE_INACTIVE: return "inactive"; + /* no default: */ + case PDMHOSTAUDIOSTREAMSTATE_END: + case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK: + break; + } + AssertMsgFailedReturn(("Invalid host audio stream state: %d\n", enmHostAudioStreamState), "illegal"); +} + +/** @} */ + +#endif /* !VBOX_INCLUDED_vmm_pdmaudioinline_h */ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmdev.h virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmdev.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmdev.h 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmdev.h 2022-09-01 13:17:48.000000000 +0000 @@ -1741,13 +1741,14 @@ /** * DMA Transfer Handler. * - * @returns Number of bytes transferred. - * @param pDevIns Device instance of the DMA. + * @returns Number of bytes transferred. + * @param pDevIns The device instance that registered the handler. * @param pvUser User pointer. * @param uChannel Channel number. * @param off DMA position. * @param cb Block size. - * @remarks The device lock is not taken, however, the DMA device lock is held. + * @remarks The device lock is take before the callback (in fact, the locks of + * DMA devices and the DMA controller itself are taken). */ typedef DECLCALLBACK(uint32_t) FNDMATRANSFERHANDLER(PPDMDEVINS pDevIns, void *pvUser, unsigned uChannel, uint32_t off, uint32_t cb); /** Pointer to a FNDMATRANSFERHANDLER(). */ @@ -1775,11 +1776,14 @@ * * @param pDevIns Device instance of the DMAC. * @param uChannel Channel number. + * @param pDevInsHandler The device instance of the device making the + * regstration (will be passed to the callback). * @param pfnTransferHandler Device specific transfer function. * @param pvUser User pointer to be passed to the callback. * @remarks No locks held, called on an EMT. */ - DECLR3CALLBACKMEMBER(void, pfnRegister,(PPDMDEVINS pDevIns, unsigned uChannel, PFNDMATRANSFERHANDLER pfnTransferHandler, void *pvUser)); + DECLR3CALLBACKMEMBER(void, pfnRegister,(PPDMDEVINS pDevIns, unsigned uChannel, PPDMDEVINS pDevInsHandler, + PFNDMATRANSFERHANDLER pfnTransferHandler, void *pvUser)); /** * Read memory @@ -1832,7 +1836,7 @@ typedef PDMDMACREG *PPDMDMACREG; /** Current PDMDMACREG version number. */ -#define PDM_DMACREG_VERSION PDM_VERSION_MAKE(0xffeb, 1, 0) +#define PDM_DMACREG_VERSION PDM_VERSION_MAKE(0xffeb, 2, 0) /** @@ -1963,7 +1967,7 @@ /** @} */ /** Current PDMDEVHLPR3 version number. */ -#define PDM_DEVHLPR3_VERSION PDM_VERSION_MAKE_PP(0xffe7, 41, 0) +#define PDM_DEVHLPR3_VERSION PDM_VERSION_MAKE_PP(0xffe7, 42, 1) /** * PDM Device API. @@ -2161,7 +2165,7 @@ * it's not associated with the PCI device, then * any number up to UINT8_MAX is fine. * @param cbRegion The size (in bytes) of the region. - * @param fFlags Reserved for future use, must be zero. + * @param fFlags PGMPHYS_MMIO2_FLAGS_XXX (see pgm.h). * @param pszDesc Pointer to description string. This must not be * freed. * @param ppvMapping Where to store the address of the ring-3 mapping @@ -2241,6 +2245,40 @@ DECLR3CALLBACKMEMBER(RTGCPHYS, pfnMmio2GetMappingAddress,(PPDMDEVINS pDevIns, PGMMMIO2HANDLE hRegion)); /** + * Queries and resets the dirty bitmap for an MMIO2 region. + * + * The MMIO2 region must have been created with the + * PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES flag for this to work. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param hRegion The MMIO2 region handle. + * @param pvBitmap Where to return the bitmap. Must be 8-byte aligned. + * Can be NULL if only resetting the tracking is desired. + * @param cbBitmap The bitmap size. One bit per page in the region, + * rounded up to 8-bytes. If pvBitmap is NULL this must + * also be zero. + */ + DECLR3CALLBACKMEMBER(int, pfnMmio2QueryAndResetDirtyBitmap, (PPDMDEVINS pDevIns, PGMMMIO2HANDLE hRegion, + void *pvBitmap, size_t cbBitmap)); + + /** + * Controls the dirty page tracking for an MMIO2 region. + * + * The MMIO2 region must have been created with the + * PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES flag for this to work. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param hRegion The MMIO2 region handle. + * @param fEnabled When set to @c true the dirty page tracking will be + * enabled if currently disabled (bitmap is reset). When + * set to @c false the dirty page tracking will be + * disabled. + */ + DECLR3CALLBACKMEMBER(int, pfnMmio2ControlDirtyPageTracking, (PPDMDEVINS pDevIns, PGMMMIO2HANDLE hRegion, bool fEnabled)); + + /** * Changes the number of an MMIO2 or pre-registered MMIO region. * * This should only be used to deal with saved state problems, so there is no @@ -2272,7 +2310,7 @@ * @param pvBinary Pointer to the binary data backing the ROM image. * @param cbBinary The size of the binary pointer. This must * be equal or smaller than @a cbRange. - * @param fFlags Shadow ROM flags, PGMPHYS_ROM_FLAGS_* in pgm.h. + * @param fFlags PGMPHYS_ROM_FLAGS_XXX (see pgm.h). * @param pszDesc Pointer to description string. This must not be freed. * * @remark There is no way to remove the rom, automatically on device cleanup or @@ -3773,7 +3811,16 @@ /** Space reserved for future members. * @{ */ - DECLR3CALLBACKMEMBER(void, pfnReserved1,(void)); + /** + * Deregister zero or more samples given their name prefix. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pszPrefix The name prefix of the samples to remove. If this does + * not start with a '/', the default prefix will be + * prepended, otherwise it will be used as-is. + */ + DECLR3CALLBACKMEMBER(int, pfnSTAMDeregisterByPrefix,(PPDMDEVINS pDevIns, const char *pszPrefix)); DECLR3CALLBACKMEMBER(void, pfnReserved2,(void)); DECLR3CALLBACKMEMBER(void, pfnReserved3,(void)); DECLR3CALLBACKMEMBER(void, pfnReserved4,(void)); @@ -5685,6 +5732,38 @@ return pDevIns->pHlpR3->pfnMmio2GetMappingAddress(pDevIns, hRegion); } +/** + * @copydoc PDMDEVHLPR3::pfnMmio2QueryAndResetDirtyBitmap + */ +DECLINLINE(int) PDMDevHlpMmio2QueryAndResetDirtyBitmap(PPDMDEVINS pDevIns, PGMMMIO2HANDLE hRegion, + void *pvBitmap, size_t cbBitmap) +{ + return pDevIns->pHlpR3->pfnMmio2QueryAndResetDirtyBitmap(pDevIns, hRegion, pvBitmap, cbBitmap); +} + +/** + * Reset the dirty bitmap tracking for an MMIO2 region. + * + * The MMIO2 region must have been created with the + * PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES flag for this to work. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param hRegion The MMIO2 region handle. + */ +DECLINLINE(int) PDMDevHlpMmio2ResetDirtyBitmap(PPDMDEVINS pDevIns, PGMMMIO2HANDLE hRegion) +{ + return pDevIns->pHlpR3->pfnMmio2QueryAndResetDirtyBitmap(pDevIns, hRegion, NULL, 0); +} + +/** + * @copydoc PDMDEVHLPR3::pfnMmio2ControlDirtyPageTracking + */ +DECLINLINE(int) PDMDevHlpMmio2ControlDirtyPageTracking(PPDMDEVINS pDevIns, PGMMMIO2HANDLE hRegion, bool fEnabled) +{ + return pDevIns->pHlpR3->pfnMmio2ControlDirtyPageTracking(pDevIns, hRegion, fEnabled); +} + #endif /* IN_RING3 */ #if !defined(IN_RING3) || defined(DOXYGEN_RUNNING) @@ -6261,6 +6340,14 @@ } /** + * @copydoc PDMDEVHLPR3::pfnSTAMDeregisterByPrefix + */ +DECLINLINE(int) PDMDevHlpSTAMDeregisterByPrefix(PPDMDEVINS pDevIns, const char *pszPrefix) +{ + return pDevIns->pHlpR3->pfnSTAMDeregisterByPrefix(pDevIns, pszPrefix); +} + +/** * Registers the device with the default PCI bus. * * @returns VBox status code. @@ -6520,7 +6607,7 @@ * @param enmType PCI_ADDRESS_SPACE_MEM or * PCI_ADDRESS_SPACE_MEM_PREFETCH, optionally or-ing in * PCI_ADDRESS_SPACE_BAR64 or PCI_ADDRESS_SPACE_BAR32. - * @param fMmio2Flags To be defined, must be zero. + * @param fMmio2Flags PGMPHYS_MMIO2_FLAGS_XXX (see pgm.h). * @param pfnMapUnmap Callback for doing the mapping, optional. The * callback will be invoked holding only the PDM lock. * The device lock will _not_ be taken (due to lock diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmdrv.h virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmdrv.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmdrv.h 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmdrv.h 2022-09-01 13:17:48.000000000 +0000 @@ -949,10 +949,10 @@ * @param fFlags Timer creation flags, see grp_tm_timer_flags. * @param pszDesc Pointer to description string which must stay around * until the timer is fully destroyed (i.e. a bit after TMTimerDestroy()). - * @param ppTimer Where to store the timer on success. + * @param phTimer Where to store the timer handle on success. * @thread EMT */ - DECLR3CALLBACKMEMBER(int, pfnTMTimerCreate,(PPDMDRVINS pDrvIns, TMCLOCK enmClock, PFNTMTIMERDRV pfnCallback, void *pvUser, uint32_t fFlags, const char *pszDesc, PPTMTIMERR3 ppTimer)); + DECLR3CALLBACKMEMBER(int, pfnTMTimerCreate,(PPDMDRVINS pDrvIns, TMCLOCK enmClock, PFNTMTIMERDRV pfnCallback, void *pvUser, uint32_t fFlags, const char *pszDesc, PTMTIMERHANDLE phTimer)); /** * Register a save state data unit. @@ -1033,7 +1033,9 @@ * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param pszName Sample name. The name is on this form "//". - * Further nesting is possible. + * Further nesting is possible. If this does not start + * with a '/', the default prefix will be prepended, + * otherwise it will be used as-is. * @param enmUnit Sample unit. * @param pszDesc Sample description. */ @@ -1050,7 +1052,9 @@ * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param pszDesc Sample description. - * @param pszName The sample name format string. + * @param pszName The sample name format string. If this does not start + * with a '/', the default prefix will be prepended, + * otherwise it will be used as-is. * @param ... Arguments to the format string. */ DECLR3CALLBACKMEMBER(void, pfnSTAMRegisterF,(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, @@ -1067,7 +1071,9 @@ * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param pszDesc Sample description. - * @param pszName The sample name format string. + * @param pszName The sample name format string. If this does not + * start with a '/', the default prefix will be prepended, + * otherwise it will be used as-is. * @param args Arguments to the format string. */ DECLR3CALLBACKMEMBER(void, pfnSTAMRegisterV,(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, @@ -1311,8 +1317,28 @@ /** @name Space reserved for minor interface changes. * @{ */ - DECLR3CALLBACKMEMBER(void, pfnReserved0,(PPDMDRVINS pDrvIns)); - DECLR3CALLBACKMEMBER(void, pfnReserved1,(PPDMDRVINS pDrvIns)); + DECLR3CALLBACKMEMBER(int, pfnTimerSetMillies,(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, uint64_t cMilliesToNext)); + + /** + * Deregister zero or more samples given their name prefix. + * + * @returns VBox status code. + * @param pDrvIns The driver instance. + * @param pszPrefix The name prefix of the samples to remove. If this does + * not start with a '/', the default prefix will be + * prepended, otherwise it will be used as-is. + */ + DECLR3CALLBACKMEMBER(int, pfnSTAMDeregisterByPrefix,(PPDMDRVINS pDrvIns, const char *pszPrefix)); + + /** + * Destroys a timer. + * + * @returns VBox status. + * @param pDrvIns Driver instance. + * @param hTimer The timer handle to destroy. + */ + DECLR3CALLBACKMEMBER(int, pfnTimerDestroy,(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer)); + DECLR3CALLBACKMEMBER(void, pfnReserved2,(PPDMDRVINS pDrvIns)); DECLR3CALLBACKMEMBER(void, pfnReserved3,(PPDMDRVINS pDrvIns)); DECLR3CALLBACKMEMBER(void, pfnReserved4,(PPDMDRVINS pDrvIns)); @@ -1320,14 +1346,13 @@ DECLR3CALLBACKMEMBER(void, pfnReserved6,(PPDMDRVINS pDrvIns)); DECLR3CALLBACKMEMBER(void, pfnReserved7,(PPDMDRVINS pDrvIns)); DECLR3CALLBACKMEMBER(void, pfnReserved8,(PPDMDRVINS pDrvIns)); - DECLR3CALLBACKMEMBER(void, pfnReserved9,(PPDMDRVINS pDrvIns)); /** @} */ /** Just a safety precaution. */ uint32_t u32TheEnd; } PDMDRVHLPR3; /** Current DRVHLP version number. */ -#define PDM_DRVHLPR3_VERSION PDM_VERSION_MAKE(0xf0fb, 5, 0) +#define PDM_DRVHLPR3_VERSION PDM_VERSION_MAKE(0xf0fb, 5, 2) #endif /* IN_RING3 */ @@ -1508,9 +1533,27 @@ /** * @copydoc PDMDRVHLPR3::pfnTMTimerCreate */ -DECLINLINE(int) PDMDrvHlpTMTimerCreate(PPDMDRVINS pDrvIns, TMCLOCK enmClock, PFNTMTIMERDRV pfnCallback, void *pvUser, uint32_t fFlags, const char *pszDesc, PPTMTIMERR3 ppTimer) +DECLINLINE(int) PDMDrvHlpTMTimerCreate(PPDMDRVINS pDrvIns, TMCLOCK enmClock, PFNTMTIMERDRV pfnCallback, void *pvUser, uint32_t fFlags, const char *pszDesc, PTMTIMERHANDLE phTimer) +{ + return pDrvIns->pHlpR3->pfnTMTimerCreate(pDrvIns, enmClock, pfnCallback, pvUser, fFlags, pszDesc, phTimer); +} + +/** + * @copydoc PDMDRVHLPR3::pfnTimerDestroy + */ +DECLINLINE(int) PDMDrvHlpTimerDestroy(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer) + { - return pDrvIns->pHlpR3->pfnTMTimerCreate(pDrvIns, enmClock, pfnCallback, pvUser, fFlags, pszDesc, ppTimer); + return pDrvIns->pHlpR3->pfnTimerDestroy(pDrvIns, hTimer); +} + +/** + * @copydoc PDMDRVHLPR3::pfnTimerSetMillies + */ +DECLINLINE(int) PDMDrvHlpTimerSetMillies(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, uint64_t cMilliesToNext) + +{ + return pDrvIns->pHlpR3->pfnTimerSetMillies(pDrvIns, hTimer, cMilliesToNext); } /** @@ -1710,6 +1753,14 @@ } /** + * @copydoc PDMDRVHLPR3::pfnSTAMDeregisterByPrefix + */ +DECLINLINE(int) PDMDrvHlpSTAMDeregisterByPrefix(PPDMDRVINS pDrvIns, const char *pszPrefix) +{ + return pDrvIns->pHlpR3->pfnSTAMDeregisterByPrefix(pDrvIns, pszPrefix); +} + +/** * @copydoc PDMDRVHLPR3::pfnSUPCallVMMR0Ex */ DECLINLINE(int) PDMDrvHlpSUPCallVMMR0Ex(PPDMDRVINS pDrvIns, unsigned uOperation, void *pvArg, unsigned cbArg) diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmnetinline.h virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmnetinline.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmnetinline.h 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmnetinline.h 2022-09-01 13:17:48.000000000 +0000 @@ -145,6 +145,14 @@ if (RT_LIKELY( cbFrame - pGso->cbHdrsTotal >= pGso->cbMaxSeg )) { /* likely */ } else return false; + /* Make sure the segment size is enough to fit a UDP header. */ + if (RT_LIKELY(enmType != PDMNETWORKGSOTYPE_IPV4_UDP || pGso->cbMaxSeg >= RTNETUDP_MIN_LEN)) + { /* likely */ } else return false; + + /* Make sure the segment size is not zero. */ + if (RT_LIKELY(pGso->cbMaxSeg > 0)) + { /* likely */ } else return false; + return true; } @@ -413,11 +421,17 @@ /* * Figure out where the payload is and where the header starts before we * do the protocol specific carving. + * + * UDP GSO uses IPv4 fragmentation, meaning that UDP header is present in + * the first fragment only. When computing the total frame size of the + * first fragment we need to use PDMNETWORKGSO::cbHdrsTotal instead of + * PDMNETWORKGSO::cbHdrsSeg. In case of TCP GSO both cbHdrsTotal and + * cbHdrsSeg have the same value, so it will work as well. */ uint8_t * const pbSegHdrs = pbFrame + pGso->cbMaxSeg * iSeg; uint8_t * const pbSegPayload = pbSegHdrs + pGso->cbHdrsSeg; uint32_t const cbSegPayload = pdmNetSegPayloadLen(pGso, iSeg, cSegs, (uint32_t)cbFrame); - uint32_t const cbSegFrame = cbSegPayload + pGso->cbHdrsSeg; + uint32_t const cbSegFrame = cbSegPayload + (iSeg ? pGso->cbHdrsSeg : pGso->cbHdrsTotal); /* * Check assumptions (doing it after declaring the variables because of C). @@ -443,8 +457,24 @@ break; case PDMNETWORKGSOTYPE_IPV4_UDP: if (iSeg == 0) + { + /* uh_ulen shall not exceed cbFrame - pGso->offHdr2 (offset of UDP header) */ + PRTNETUDP pUdpHdr = (PRTNETUDP)&pbFrame[pGso->offHdr2]; + Assert(pGso->offHdr2 + RT_UOFFSET_AFTER(RTNETUDP, uh_ulen) <= cbFrame); + if ((unsigned)(pGso->offHdr2 + RT_BE2H_U16(pUdpHdr->uh_ulen)) > cbFrame) + { + size_t cbUdp = cbFrame - pGso->offHdr2; + if (cbUdp >= UINT16_MAX) + pUdpHdr->uh_ulen = UINT16_MAX; + else + pUdpHdr->uh_ulen = RT_H2BE_U16((uint16_t)cbUdp); + } + /* uh_ulen shall be at least the size of UDP header */ + if (RT_BE2H_U16(pUdpHdr->uh_ulen) < sizeof(RTNETUDP)) + pUdpHdr->uh_ulen = RT_H2BE_U16(sizeof(RTNETUDP)); pdmNetGsoUpdateUdpHdrUfo(RTNetIPv4PseudoChecksum((PRTNETIPV4)&pbFrame[pGso->offHdr1]), pbSegHdrs, pbFrame, pGso->offHdr2); + } pdmNetGsoUpdateIPv4HdrUfo(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg * pGso->cbMaxSeg, pdmNetSegHdrLen(pGso, iSeg), iSeg + 1 == cSegs); break; @@ -542,16 +572,20 @@ case PDMNETWORKGSOTYPE_IPV4_UDP: if (iSeg == 0) { - Assert(pGso->offHdr2 + sizeof(uint16_t) <= cbFrame); - /* uh_ulen cannot exceed cbFrame - pGso->offHdr2 (offset of UDP header) */ - if ((unsigned)(pGso->offHdr2 + RT_BE2H_U16(((PCRTNETUDP)&pbFrame[pGso->offHdr2])->uh_ulen)) > cbFrame) + /* uh_ulen shall not exceed cbFrame - pGso->offHdr2 (offset of UDP header) */ + PRTNETUDP pUdpHdr = (PRTNETUDP)&pbFrame[pGso->offHdr2]; + Assert(pGso->offHdr2 + RT_UOFFSET_AFTER(RTNETUDP, uh_ulen) <= cbFrame); + if ((unsigned)(pGso->offHdr2 + RT_BE2H_U16(pUdpHdr->uh_ulen)) > cbFrame) { - if (cbFrame > UINT16_MAX) - ((PRTNETUDP)&pbFrame[pGso->offHdr2])->uh_ulen = 0xFFFF; + size_t cbUdp = cbFrame - pGso->offHdr2; + if (cbUdp >= UINT16_MAX) + pUdpHdr->uh_ulen = UINT16_MAX; else - ((PRTNETUDP)&pbFrame[pGso->offHdr2])->uh_ulen = RT_H2BE_U16((uint16_t)(cbFrame - pGso->offHdr2)); + pUdpHdr->uh_ulen = RT_H2BE_U16((uint16_t)cbUdp); } - Assert((unsigned)(pGso->offHdr2 + ((PCRTNETUDP)&pbFrame[pGso->offHdr2])->uh_ulen) <= cbFrame); + /* uh_ulen shall be at least the size of UDP header */ + if (RT_BE2H_U16(pUdpHdr->uh_ulen) < sizeof(RTNETUDP)) + pUdpHdr->uh_ulen = RT_H2BE_U16(sizeof(RTNETUDP)); pdmNetGsoUpdateUdpHdrUfo(RTNetIPv4PseudoChecksum((PRTNETIPV4)&pbFrame[pGso->offHdr1]), pbSegHdrs, pbFrame, pGso->offHdr2); } diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmpcidev.h virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmpcidev.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/pdmpcidev.h 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/pdmpcidev.h 2022-09-01 13:17:49.000000000 +0000 @@ -243,6 +243,7 @@ * followed by a MSI-X state area. */ uint8_t abConfig[4096]; /** The MSI-X state data. Optional. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abMsixState[RT_FLEXIBLE_ARRAY]; } PDMPCIDEV; #ifdef PDMPCIDEVINT_DECLARED diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/pgm.h virtualbox-6.1.38-dfsg/include/VBox/vmm/pgm.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/pgm.h 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/pgm.h 2022-09-01 13:17:49.000000000 +0000 @@ -709,6 +709,8 @@ /** @defgroup grp_pgm_r3 The PGM Host Context Ring-3 API * @{ */ +VMMR3_INT_DECL(void) PGMR3EnableNemMode(PVM pVM); +VMMR3_INT_DECL(bool) PGMR3IsNemModeEnabled(PVM pVM); VMMR3DECL(int) PGMR3Init(PVM pVM); VMMR3DECL(int) PGMR3InitDynMap(PVM pVM); VMMR3DECL(int) PGMR3InitFinalize(PVM pVM); @@ -733,6 +735,17 @@ VMMR3DECL(int) PGMR3PhysMMIORegister(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, PGMPHYSHANDLERTYPE hType, RTR3PTR pvUserR3, RTR0PTR pvUserR0, RTRCPTR pvUserRC, const char *pszDesc); VMMR3DECL(int) PGMR3PhysMMIODeregister(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb); + +/** @name PGMPHYS_MMIO2_FLAGS_XXX - MMIO2 registration flags. + * @see PGMR3PhysMmio2Register, PDMDevHlpMmio2Create + * @{ */ +/** Track dirty pages. + * @see PGMR3PhysMmio2QueryAndResetDirtyBitmap(), PGMR3PhysMmio2ControlDirtyPageTracking(). */ +#define PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES RT_BIT_32(0) +/** Valid flags. */ +#define PGMPHYS_MMIO2_FLAGS_VALID_MASK UINT32_C(0x00000001) +/** @} */ + VMMR3_INT_DECL(int) PGMR3PhysMmio2Register(PVM pVM, PPDMDEVINS pDevIns, uint32_t iSubDev, uint32_t iRegion, RTGCPHYS cb, uint32_t fFlags, const char *pszDesc, void **ppv, PGMMMIO2HANDLE *phRegion); VMMR3_INT_DECL(int) PGMR3PhysMmio2Deregister(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2); @@ -743,24 +756,27 @@ VMMR3_INT_DECL(RTGCPHYS) PGMR3PhysMmio2GetMappingAddress(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2); VMMR3_INT_DECL(int) PGMR3PhysMmio2ChangeRegionNo(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2, uint32_t iNewRegion); VMMR3_INT_DECL(int) PGMR3PhysMMIO2GetHCPhys(PVM pVM, PPDMDEVINS pDevIns, uint32_t iSubDev, uint32_t iRegion, RTGCPHYS off, PRTHCPHYS pHCPhys); +VMMR3_INT_DECL(int) PGMR3PhysMmio2QueryAndResetDirtyBitmap(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2, + void *pvBitmap, size_t cbBitmap); +VMMR3_INT_DECL(int) PGMR3PhysMmio2ControlDirtyPageTracking(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2, bool fEnabled); - -/** @name PGMR3PhysRegisterRom flags. +/** @name PGMPHYS_ROM_FLAGS_XXX - ROM registration flags. + * @see PGMR3PhysRegisterRom, PDMDevHlpROMRegister * @{ */ /** Inidicates that ROM shadowing should be enabled. */ -#define PGMPHYS_ROM_FLAGS_SHADOWED RT_BIT_32(0) +#define PGMPHYS_ROM_FLAGS_SHADOWED UINT8_C(0x01) /** Indicates that what pvBinary points to won't go away * and can be used for strictness checks. */ -#define PGMPHYS_ROM_FLAGS_PERMANENT_BINARY RT_BIT_32(1) +#define PGMPHYS_ROM_FLAGS_PERMANENT_BINARY UINT8_C(0x02) /** Indicates that the ROM is allowed to be missing from saved state. * @note This is a hack for EFI, see @bugref{6940} */ -#define PGMPHYS_ROM_FLAGS_MAYBE_MISSING_FROM_STATE RT_BIT_32(2) +#define PGMPHYS_ROM_FLAGS_MAYBE_MISSING_FROM_STATE UINT8_C(0x04) /** Valid flags. */ -#define PGMPHYS_ROM_FLAGS_VALID_MASK UINT32_C(0x00000007) +#define PGMPHYS_ROM_FLAGS_VALID_MASK UINT8_C(0x07) /** @} */ VMMR3DECL(int) PGMR3PhysRomRegister(PVM pVM, PPDMDEVINS pDevIns, RTGCPHYS GCPhys, RTGCPHYS cb, - const void *pvBinary, uint32_t cbBinary, uint32_t fFlags, const char *pszDesc); + const void *pvBinary, uint32_t cbBinary, uint8_t fFlags, const char *pszDesc); VMMR3DECL(int) PGMR3PhysRomProtect(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, PGMROMPROT enmProt); VMMR3DECL(int) PGMR3PhysRegister(PVM pVM, void *pvRam, RTGCPHYS GCPhys, size_t cb, unsigned fFlags, const SUPPAGE *paPages, const char *pszDesc); VMMDECL(void) PGMR3PhysSetA20(PVMCPU pVCpu, bool fEnable); @@ -783,12 +799,12 @@ VMMR3DECL(int) PGMR3MappingsFix(PVM pVM, RTGCPTR GCPtrBase, uint32_t cb); VMMR3DECL(int) PGMR3MappingsUnfix(PVM pVM); -VMMR3_INT_DECL(int) PGMR3HandlerPhysicalTypeRegisterEx(PVM pVM, PGMPHYSHANDLERKIND enmKind, +VMMR3_INT_DECL(int) PGMR3HandlerPhysicalTypeRegisterEx(PVM pVM, PGMPHYSHANDLERKIND enmKind, bool fKeepPgmLock, PFNPGMPHYSHANDLER pfnHandlerR3, R0PTRTYPE(PFNPGMPHYSHANDLER) pfnHandlerR0, R0PTRTYPE(PFNPGMRZPHYSPFHANDLER) pfnPfHandlerR0, const char *pszDesc, PPGMPHYSHANDLERTYPE phType); -VMMR3DECL(int) PGMR3HandlerPhysicalTypeRegister(PVM pVM, PGMPHYSHANDLERKIND enmKind, +VMMR3DECL(int) PGMR3HandlerPhysicalTypeRegister(PVM pVM, PGMPHYSHANDLERKIND enmKind, bool fKeepPgmLock, R3PTRTYPE(PFNPGMPHYSHANDLER) pfnHandlerR3, const char *pszModR0, const char *pszHandlerR0, const char *pszPfHandlerR0, const char *pszModRC, const char *pszHandlerRC, const char *pszPfHandlerRC, diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/stam.h virtualbox-6.1.38-dfsg/include/VBox/vmm/stam.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/stam.h 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/stam.h 2022-09-01 13:17:49.000000000 +0000 @@ -799,6 +799,61 @@ #endif +/** @def STAM_REL_PROFILE_START_NS + * Samples the start time of a profiling period, using RTTimeNanoTS(). + * + * @param pProfile Pointer to the STAMPROFILE structure to operate on. + * @param Prefix Identifier prefix used to internal variables. + * + * @remarks Declears a stack variable that will be used by related macros. + */ +#ifndef VBOX_WITHOUT_RELEASE_STATISTICS +# define STAM_REL_PROFILE_START_NS(pProfile, Prefix) \ + uint64_t const Prefix##_tsStart = RTTimeNanoTS() +#else +# define STAM_REL_PROFILE_START_NS(pProfile, Prefix) do { } while (0) +#endif +/** @def STAM_PROFILE_START_NS + * Samples the start time of a profiling period, using RTTimeNanoTS(). + * + * @param pProfile Pointer to the STAMPROFILE structure to operate on. + * @param Prefix Identifier prefix used to internal variables. + * + * @remarks Declears a stack variable that will be used by related macros. + */ +#ifdef VBOX_WITH_STATISTICS +# define STAM_PROFILE_START_NS(pProfile, Prefix) STAM_REL_PROFILE_START_NS(pProfile, Prefix) +#else +# define STAM_PROFILE_START_NS(pProfile, Prefix) do { } while (0) +#endif + +/** @def STAM_REL_PROFILE_STOP_NS + * Samples the stop time of a profiling period and updates the sample, using + * RTTimeNanoTS(). + * + * @param pProfile Pointer to the STAMPROFILE structure to operate on. + * @param Prefix Identifier prefix used to internal variables. + */ +#ifndef VBOX_WITHOUT_RELEASE_STATISTICS +# define STAM_REL_PROFILE_STOP_NS(pProfile, Prefix) \ + STAM_REL_PROFILE_ADD_PERIOD(pProfile, RTTimeNanoTS() - Prefix##_tsStart) +#else +# define STAM_REL_PROFILE_STOP_NS(pProfile, Prefix) do { } while (0) +#endif +/** @def STAM_PROFILE_STOP_NS + * Samples the stop time of a profiling period and updates the sample, using + * RTTimeNanoTS(). + * + * @param pProfile Pointer to the STAMPROFILE structure to operate on. + * @param Prefix Identifier prefix used to internal variables. + */ +#ifdef VBOX_WITH_STATISTICS +# define STAM_PROFILE_STOP_NS(pProfile, Prefix) STAM_REL_PROFILE_STOP_NS(pProfile, Prefix) +#else +# define STAM_PROFILE_STOP_NS(pProfile, Prefix) do { } while (0) +#endif + + /** * Advanced profiling sample - STAMTYPE_PROFILE_ADV. * diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/vm.h virtualbox-6.1.38-dfsg/include/VBox/vmm/vm.h --- virtualbox-6.1.16-dfsg/include/VBox/vmm/vm.h 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/vm.h 2022-09-01 13:17:49.000000000 +0000 @@ -222,7 +222,7 @@ #ifdef VMM_INCLUDED_SRC_include_VMMInternal_h struct VMMCPU s; #endif - uint8_t padding[896]; /* multiple of 64 */ + uint8_t padding[960]; /* multiple of 64 */ } vmm; /** PDM part. */ @@ -284,7 +284,7 @@ STAMPROFILEADV aStatAdHoc[8]; /* size: 40*8 = 320 */ /** Align the following members on page boundary. */ - uint8_t abAlignment2[1400]; + uint8_t abAlignment2[1336]; /** PGM part. */ union VMCPUUNIONPGM @@ -1341,7 +1341,7 @@ #ifdef VMM_INCLUDED_SRC_include_NEMInternal_h struct NEM s; #endif - uint8_t padding[128]; /* multiple of 64 */ + uint8_t padding[512]; /* multiple of 64 */ } nem; /** TM part. */ @@ -1449,7 +1449,7 @@ } R0Stats; /** Padding for aligning the structure size on a page boundrary. */ - uint8_t abAlignment2[600 - 64 + 256 - sizeof(PVMCPUR3) * VMM_MAX_CPU_COUNT]; + uint8_t abAlignment2[4504 - sizeof(PVMCPUR3) * VMM_MAX_CPU_COUNT]; /* ---- end small stuff ---- */ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vmm/vm.mac virtualbox-6.1.38-dfsg/include/VBox/vmm/vm.mac --- virtualbox-6.1.16-dfsg/include/VBox/vmm/vm.mac 2020-10-16 16:27:53.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vmm/vm.mac 2022-09-01 13:17:49.000000000 +0000 @@ -65,7 +65,7 @@ .nem resb 512 .trpm resb 128 .tm resb 5760 - .vmm resb 896 + .vmm resb 960 .pdm resb 256 .iom resb 512 .dbgf resb 256 @@ -128,7 +128,7 @@ .pdm resb 7808 .iom resb 1152 .em resb 256 - .nem resb 128 + .nem resb 512 .tm resb 7872 .dbgf resb 2432 .ssm resb 128 @@ -138,9 +138,7 @@ .cfgm resb 8 .R0Stats resb 64 - .abAlignment2 resb 600 - 64 + 256 - RTR0PTR_CB * VMM_MAX_CPU_COUNT - - alignb RTR0PTR_CB * VMM_MAX_CPU_COUNT ; ASSUMES VMM_MAX_CPU_COUNT is a power of two. + times ((($ + VMM_MAX_CPU_COUNT * RTR0PTR_CB + 4095) & ~4095) - ($ + VMM_MAX_CPU_COUNT * RTR0PTR_CB)) resb 1 .apCpusR3 RTR3PTR_RES VMM_MAX_CPU_COUNT alignb 4096 diff -Nru virtualbox-6.1.16-dfsg/include/VBox/VMMDevTesting.h virtualbox-6.1.38-dfsg/include/VBox/VMMDevTesting.h --- virtualbox-6.1.16-dfsg/include/VBox/VMMDevTesting.h 2020-10-16 16:27:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/VMMDevTesting.h 2022-09-01 13:17:46.000000000 +0000 @@ -39,9 +39,10 @@ */ /** The base address of the MMIO range used for testing. - * This is intentionally put at the 2nd page above 1M so that it can be - * accessed from both real (!A20) and protected mode. */ -#define VMMDEV_TESTING_MMIO_BASE UINT32_C(0x00101000) + * @remarks This used to be at 0x101000 but moved to 0xdf000 so that it would + * work better with prototype NEM code. This also means enabling A20 + * is not a requirement. */ +#define VMMDEV_TESTING_MMIO_BASE UINT32_C(0x000df000) /** The size of the MMIO range used for testing. */ #define VMMDEV_TESTING_MMIO_SIZE UINT32_C(0x00001000) @@ -65,13 +66,12 @@ /** Default address of VMMDEV_TESTING_MMIO_OFF_READBACK_R3. */ #define VMMDEV_TESTING_MMIO_READBACK_R3 (VMMDEV_TESTING_MMIO_BASE + VMMDEV_TESTING_MMIO_OFF_READBACK_R3) -/** The real mode selector to use. - * @remarks Requires that the A20 gate is enabled. */ -#define VMMDEV_TESTING_MMIO_RM_SEL 0xffff +/** The real mode selector to use. */ +#define VMMDEV_TESTING_MMIO_RM_SEL 0xdf00 /** Calculate the real mode offset of a MMIO register. */ -#define VMMDEV_TESTING_MMIO_RM_OFF(val) ((val) - 0xffff0) +#define VMMDEV_TESTING_MMIO_RM_OFF(val) ((val) - VMMDEV_TESTING_MMIO_BASE) /** Calculate the real mode offset of a MMIO register offset. */ -#define VMMDEV_TESTING_MMIO_RM_OFF2(off) ((off) + 16 + 0x1000) +#define VMMDEV_TESTING_MMIO_RM_OFF2(off) (off) /** The base port of the I/O range used for testing. */ #define VMMDEV_TESTING_IOPORT_BASE 0x0510 @@ -122,6 +122,7 @@ /** @} */ /** @name Value units + * @note Same as RTTESTUNIT, see rules here for adding new units. * @{ */ #define VMMDEV_TESTING_UNIT_PCT UINT8_C(0x01) /**< Percentage. */ #define VMMDEV_TESTING_UNIT_BYTES UINT8_C(0x02) /**< Bytes. */ @@ -150,6 +151,17 @@ #define VMMDEV_TESTING_UNIT_INSTRS UINT8_C(0x19) /**< Instructions. */ #define VMMDEV_TESTING_UNIT_INSTRS_PER_SEC UINT8_C(0x1a) /**< Instructions per second. */ #define VMMDEV_TESTING_UNIT_NONE UINT8_C(0x1b) /**< No unit. */ +#define VMMDEV_TESTING_UNIT_PP1K UINT8_C(0x1c) /**< Parts per thousand (10^-3). */ +#define VMMDEV_TESTING_UNIT_PP10K UINT8_C(0x1d) /**< Parts per ten thousand (10^-4). */ +#define VMMDEV_TESTING_UNIT_PPM UINT8_C(0x1e) /**< Parts per million (10^-6). */ +#define VMMDEV_TESTING_UNIT_PPB UINT8_C(0x1f) /**< Parts per billion (10^-9). */ +#define VMMDEV_TESTING_UNIT_TICKS UINT8_C(0x20) /**< CPU ticks. */ +#define VMMDEV_TESTING_UNIT_TICKS_PER_CALL UINT8_C(0x21) /**< CPU ticks per call. */ +#define VMMDEV_TESTING_UNIT_TICKS_PER_OCCURENCE UINT8_C(0x22) /**< CPU ticks per occurence. */ +#define VMMDEV_TESTING_UNIT_PAGES UINT8_C(0x23) /**< Page count. */ +#define VMMDEV_TESTING_UNIT_PAGES_PER_SEC UINT8_C(0x24) /**< Pages per second. */ +#define VMMDEV_TESTING_UNIT_TICKS_PER_PAGE UINT8_C(0x25) /**< CPU ticks per page. */ +#define VMMDEV_TESTING_UNIT_NS_PER_PAGE UINT8_C(0x26) /**< Nanoseconds per page. */ /** @} */ diff -Nru virtualbox-6.1.16-dfsg/include/VBox/VMMDevTesting.mac virtualbox-6.1.38-dfsg/include/VBox/VMMDevTesting.mac --- virtualbox-6.1.16-dfsg/include/VBox/VMMDevTesting.mac 2020-10-16 16:27:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/VMMDevTesting.mac 2022-09-01 13:17:46.000000000 +0000 @@ -28,7 +28,7 @@ %define VBOX_INCLUDED_VMMDevTesting_h %ifndef RT_WITHOUT_PRAGMA_ONCE %endif -%define VMMDEV_TESTING_MMIO_BASE 0x00101000 +%define VMMDEV_TESTING_MMIO_BASE 0x000df000 %define VMMDEV_TESTING_MMIO_SIZE 0x00001000 %define VMMDEV_TESTING_MMIO_OFF_NOP (0x000) %define VMMDEV_TESTING_MMIO_OFF_NOP_R3 (0x008) @@ -39,9 +39,9 @@ %define VMMDEV_TESTING_MMIO_NOP_R3 (VMMDEV_TESTING_MMIO_BASE + VMMDEV_TESTING_MMIO_OFF_NOP_R3) %define VMMDEV_TESTING_MMIO_READBACK (VMMDEV_TESTING_MMIO_BASE + VMMDEV_TESTING_MMIO_OFF_READBACK) %define VMMDEV_TESTING_MMIO_READBACK_R3 (VMMDEV_TESTING_MMIO_BASE + VMMDEV_TESTING_MMIO_OFF_READBACK_R3) -%define VMMDEV_TESTING_MMIO_RM_SEL 0xffff -%define VMMDEV_TESTING_MMIO_RM_OFF(val) ((val) - 0xffff0) -%define VMMDEV_TESTING_MMIO_RM_OFF2(off) ((off) + 16 + 0x1000) +%define VMMDEV_TESTING_MMIO_RM_SEL 0xdf00 +%define VMMDEV_TESTING_MMIO_RM_OFF(val) ((val) - VMMDEV_TESTING_MMIO_BASE) +%define VMMDEV_TESTING_MMIO_RM_OFF2(off) (off) %define VMMDEV_TESTING_IOPORT_BASE 0x0510 %define VMMDEV_TESTING_IOPORT_COUNT 0x0010 %define VMMDEV_TESTING_IOPORT_NOP (VMMDEV_TESTING_IOPORT_BASE + 0) diff -Nru virtualbox-6.1.16-dfsg/include/VBox/vusb.h virtualbox-6.1.38-dfsg/include/VBox/vusb.h --- virtualbox-6.1.16-dfsg/include/VBox/vusb.h 2020-10-16 16:27:54.000000000 +0000 +++ virtualbox-6.1.38-dfsg/include/VBox/vusb.h 2022-09-01 13:17:49.000000000 +0000 @@ -563,6 +563,52 @@ typedef struct VUSBURB *PVUSBURB; +/** + * VUSB device reset completion callback function. + * This is called by the reset thread when the reset has been completed. + * + * @param pDevice Pointer to the virtual USB device core. + * @param uPort The port of the device which completed the reset. + * @param rc The VBox status code of the reset operation. + * @param pvUser User specific argument. + * + * @thread The reset thread or EMT. + */ +typedef DECLCALLBACKTYPE(void, FNVUSBRESETDONE,(PVUSBIDEVICE pDevice, uint32_t uPort, int rc, void *pvUser)); +/** Pointer to a device reset completion callback function (FNUSBRESETDONE). */ +typedef FNVUSBRESETDONE *PFNVUSBRESETDONE; + + +/** + * The state of a VUSB Device. + * + * @remark The order of these states is vital. + */ +typedef enum VUSBDEVICESTATE +{ + VUSB_DEVICE_STATE_INVALID = 0, + VUSB_DEVICE_STATE_DETACHED, + VUSB_DEVICE_STATE_ATTACHED, + VUSB_DEVICE_STATE_POWERED, + VUSB_DEVICE_STATE_DEFAULT, + VUSB_DEVICE_STATE_ADDRESS, + VUSB_DEVICE_STATE_CONFIGURED, + VUSB_DEVICE_STATE_SUSPENDED, + /** The device is being reset. Don't mess with it. + * Next states: VUSB_DEVICE_STATE_DEFAULT, VUSB_DEVICE_STATE_DESTROYED + */ + VUSB_DEVICE_STATE_RESET, + /** The device has been destroyed. */ + VUSB_DEVICE_STATE_DESTROYED, + /** The usual 32-bit hack. */ + VUSB_DEVICE_STATE_32BIT_HACK = 0x7fffffff +} VUSBDEVICESTATE; + + +/** Maximum number of USB devices supported. */ +#define VUSB_DEVICES_MAX 128 +/** An invalid device port. */ +#define VUSB_DEVICE_PORT_INVALID UINT32_MAX /** * VBox USB port bitmap. @@ -572,10 +618,11 @@ typedef struct VUSBPORTBITMAP { /** 128 bits */ - char ach[16]; + char ach[VUSB_DEVICES_MAX / 8]; } VUSBPORTBITMAP; /** Pointer to a VBox USB port bitmap. */ typedef VUSBPORTBITMAP *PVUSBPORTBITMAP; +AssertCompile(sizeof(VUSBPORTBITMAP) * 8 >= VUSB_DEVICES_MAX); #ifndef RDESKTOP @@ -606,26 +653,25 @@ * A device is being attached to a port in the roothub. * * @param pInterface Pointer to this structure. - * @param pDev Pointer to the device being attached. * @param uPort The port number assigned to the device. + * @param enmSpeed The speed of the device being attached. */ - DECLR3CALLBACKMEMBER(int, pfnAttach,(PVUSBIROOTHUBPORT pInterface, PVUSBIDEVICE pDev, unsigned uPort)); + DECLR3CALLBACKMEMBER(int, pfnAttach,(PVUSBIROOTHUBPORT pInterface, uint32_t uPort, VUSBSPEED enmSpeed)); /** * A device is being detached from a port in the roothub. * * @param pInterface Pointer to this structure. - * @param pDev Pointer to the device being detached. * @param uPort The port number assigned to the device. */ - DECLR3CALLBACKMEMBER(void, pfnDetach,(PVUSBIROOTHUBPORT pInterface, PVUSBIDEVICE pDev, unsigned uPort)); + DECLR3CALLBACKMEMBER(void, pfnDetach,(PVUSBIROOTHUBPORT pInterface, uint32_t uPort)); /** * Reset the root hub. * * @returns VBox status code. * @param pInterface Pointer to this structure. - * @param pResetOnLinux Whether or not to do real reset on linux. + * @param fResetOnLinux Whether or not to do real reset on linux. */ DECLR3CALLBACKMEMBER(int, pfnReset,(PVUSBIROOTHUBPORT pInterface, bool fResetOnLinux)); @@ -638,7 +684,7 @@ * @param pInterface Pointer to this structure. * @param pUrb Pointer to the URB in question. */ - DECLR3CALLBACKMEMBER(void, pfnXferCompletion,(PVUSBIROOTHUBPORT pInterface, PVUSBURB urb)); + DECLR3CALLBACKMEMBER(void, pfnXferCompletion,(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)); /** * Handle transfer errors. @@ -676,7 +722,7 @@ } VUSBIROOTHUBPORT; /** VUSBIROOTHUBPORT interface ID. */ -# define VUSBIROOTHUBPORT_IID "6571aece-6c33-4714-a8ac-9508a3b8b429" +# define VUSBIROOTHUBPORT_IID "2ece01c2-4dbf-4bd5-96ca-09fc14164cd4" /** Pointer to a VUSB RootHub connector interface. */ typedef struct VUSBIROOTHUBCONNECTOR *PVUSBIROOTHUBCONNECTOR; @@ -703,6 +749,31 @@ DECLR3CALLBACKMEMBER(int, pfnSetUrbParams, (PVUSBIROOTHUBCONNECTOR pInterface, size_t cbHci, size_t cbHciTd)); /** + * Resets the roothub. + * + * @returns VBox status code. + * @param pInterface Pointer to this struct. + * @param fResetOnLinux Whether or not to do real reset on linux. + */ + DECLR3CALLBACKMEMBER(int, pfnReset, (PVUSBIROOTHUBCONNECTOR pInterface, bool fResetOnLinux)); + + /** + * Powers on the roothub. + * + * @returns VBox status code. + * @param pInterface Pointer to this struct. + */ + DECLR3CALLBACKMEMBER(int, pfnPowerOn, (PVUSBIROOTHUBCONNECTOR pInterface)); + + /** + * Power off the roothub. + * + * @returns VBox status code. + * @param pInterface Pointer to this struct. + */ + DECLR3CALLBACKMEMBER(int, pfnPowerOff, (PVUSBIROOTHUBCONNECTOR pInterface)); + + /** * Allocates a new URB for a transfer. * * Either submit using pfnSubmitUrb or free using VUSBUrbFree(). @@ -713,7 +784,7 @@ * at submit time, since that makes the usage of this api simpler. * @param pInterface Pointer to this struct. * @param DstAddress The destination address of the URB. - * @param pDev Optional device pointer the URB is for. + * @param uPort Optional port of the device the URB is for, use VUSB_DEVICE_PORT_INVALID to indicate to use the destination address. * @param enmType Type of the URB. * @param enmDir Data transfer direction. * @param cbData The amount of data space required. @@ -724,7 +795,7 @@ * @note pDev should be NULL in most cases. The only useful case is for USB3 where * it is required for the SET_ADDRESS request because USB3 uses unicast traffic. */ - DECLR3CALLBACKMEMBER(PVUSBURB, pfnNewUrb,(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, PVUSBIDEVICE pDev, + DECLR3CALLBACKMEMBER(PVUSBURB, pfnNewUrb,(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, uint32_t uPort, VUSBXFERTYPE enmType, VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag)); /** @@ -760,10 +831,10 @@ * @returns Other VBox status code. * * @param pInterface Pointer to this struct. - * @param pDevice Pointer to a USB device. + * @param uPort Port of the device to reap URBs on. * @param cMillies Number of milliseconds to poll for completion. */ - DECLR3CALLBACKMEMBER(void, pfnReapAsyncUrbs,(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, RTMSINTERVAL cMillies)); + DECLR3CALLBACKMEMBER(void, pfnReapAsyncUrbs,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, RTMSINTERVAL cMillies)); /** * Cancels and completes - with CRC failure - all URBs queued on an endpoint. @@ -789,11 +860,11 @@ * * @returns VBox status code. * @param pInterface Pointer to this struct. - * @param pDevice Pointer to a USB device. + * @param uPort Port of the device. * @param EndPt Endpoint number. * @param enmDir Endpoint direction. */ - DECLR3CALLBACKMEMBER(int, pfnAbortEp,(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, int EndPt, VUSBDIRECTION enmDir)); + DECLR3CALLBACKMEMBER(int, pfnAbortEp,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, int EndPt, VUSBDIRECTION enmDir)); /** * Attach the device to the root hub. @@ -801,9 +872,9 @@ * * @returns VBox status code. * @param pInterface Pointer to this struct. - * @param pDevice Pointer to the device (interface) to attach. + * @param uPort Port of the device to attach. */ - DECLR3CALLBACKMEMBER(int, pfnAttachDevice,(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice)); + DECLR3CALLBACKMEMBER(int, pfnAttachDevice,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)); /** * Detach the device from the root hub. @@ -811,9 +882,9 @@ * * @returns VBox status code. * @param pInterface Pointer to this struct. - * @param pDevice Pointer to the device (interface) to detach. + * @param uPort Port of the device to detach. */ - DECLR3CALLBACKMEMBER(int, pfnDetachDevice,(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice)); + DECLR3CALLBACKMEMBER(int, pfnDetachDevice,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)); /** * Sets periodic frame processing. @@ -842,17 +913,90 @@ * @returns Delta between currently and previously scheduled frame. * @retval 0 if no previous frame was set. * @param pInterface Pointer to this struct. - * @param pDevice Pointer to a USB device. + * @param uPort Port of the device. * @param EndPt Endpoint number. * @param enmDir Endpoint direction. * @param uNewFrameID The frame ID of a new transfer. * @param uBits The number of significant bits in frame ID. */ - DECLR3CALLBACKMEMBER(uint32_t, pfnUpdateIsocFrameDelta, (PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, + DECLR3CALLBACKMEMBER(uint32_t, pfnUpdateIsocFrameDelta, (PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, int EndPt, VUSBDIRECTION enmDir, uint16_t uNewFrameID, uint8_t uBits)); - /** Alignment dummy. */ - RTR3PTR Alignment; + /** + * Resets the device. + * + * Since a device reset shall take at least 10ms from the guest point of view, + * it must be performed asynchronously. We create a thread which performs this + * operation and ensures it will take at least 10ms. + * + * At times - like init - a synchronous reset is required, this can be done + * by passing NULL for pfnDone. + * + * -- internal stuff, move it -- + * While the device is being reset it is in the VUSB_DEVICE_STATE_RESET state. + * On completion it will be in the VUSB_DEVICE_STATE_DEFAULT state if successful, + * or in the VUSB_DEVICE_STATE_DETACHED state if the rest failed. + * -- internal stuff, move it -- + * + * @returns VBox status code. + * @param pInterface Pointer to this struct. + * @param uPort Port of the device to reset. + * @param fResetOnLinux Set if we can permit a real reset and a potential logical + * device reconnect on linux hosts. + * @param pfnDone Pointer to the completion routine. If NULL a synchronous + * reset is performed not respecting the 10ms. + * @param pvUser User argument to the completion routine. + * @param pVM The cross context VM structure. Required if pfnDone + * is not NULL. + */ + DECLR3CALLBACKMEMBER(int, pfnDevReset,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, bool fResetOnLinux, + PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM)); + + /** + * Powers on the device. + * + * @returns VBox status code. + * @param pInterface Pointer to this struct. + * @param uPort Port of the device to power on. + */ + DECLR3CALLBACKMEMBER(int, pfnDevPowerOn,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)); + + /** + * Powers off the device. + * + * @returns VBox status code. + * @param pInterface Pointer to this struct. + * @param uPort Port of the device to power off. + */ + DECLR3CALLBACKMEMBER(int, pfnDevPowerOff,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)); + + /** + * Get the state of the device. + * + * @returns Device state. + * @param pInterface Pointer to this struct. + * @param uPort Port of the device to get the state for. + */ + DECLR3CALLBACKMEMBER(VUSBDEVICESTATE, pfnDevGetState,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)); + + /** + * Returns whether the device implements the saved state handlers + * and doesn't need to get detached. + * + * @returns true if the device supports saving the state, false otherwise. + * @param pInterface Pointer to this struct. + * @param uPort Port of the device to query saved state support for. + */ + DECLR3CALLBACKMEMBER(bool, pfnDevIsSavedStateSupported,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)); + + /** + * Get the speed the device is operating at. + * + * @returns Device state. + * @param pInterface Pointer to this struct. + * @param uPort Port of the device to query the speed for. + */ + DECLR3CALLBACKMEMBER(VUSBSPEED, pfnDevGetSpeed,(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)); } VUSBIROOTHUBCONNECTOR; AssertCompileSizeAlignment(VUSBIROOTHUBCONNECTOR, 8); @@ -868,10 +1012,10 @@ } /** @copydoc VUSBIROOTHUBCONNECTOR::pfnNewUrb */ -DECLINLINE(PVUSBURB) VUSBIRhNewUrb(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, PVUSBIDEVICE pDev, +DECLINLINE(PVUSBURB) VUSBIRhNewUrb(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, uint32_t uPort, VUSBXFERTYPE enmType, VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag) { - return pInterface->pfnNewUrb(pInterface, DstAddress, pDev, enmType, enmDir, cbData, cTds, pszTag); + return pInterface->pfnNewUrb(pInterface, DstAddress, uPort, enmType, enmDir, cbData, cTds, pszTag); } /** @copydoc VUSBIROOTHUBCONNECTOR::pfnFreeUrb */ @@ -887,9 +1031,9 @@ } /** @copydoc VUSBIROOTHUBCONNECTOR::pfnReapAsyncUrbs */ -DECLINLINE(void) VUSBIRhReapAsyncUrbs(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, RTMSINTERVAL cMillies) +DECLINLINE(void) VUSBIRhReapAsyncUrbs(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, RTMSINTERVAL cMillies) { - pInterface->pfnReapAsyncUrbs(pInterface, pDevice, cMillies); + pInterface->pfnReapAsyncUrbs(pInterface, uPort, cMillies); } /** @copydoc VUSBIROOTHUBCONNECTOR::pfnCancelAllUrbs */ @@ -899,15 +1043,15 @@ } /** @copydoc VUSBIROOTHUBCONNECTOR::pfnAttachDevice */ -DECLINLINE(int) VUSBIRhAttachDevice(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice) +DECLINLINE(int) VUSBIRhAttachDevice(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) { - return pInterface->pfnAttachDevice(pInterface, pDevice); + return pInterface->pfnAttachDevice(pInterface, uPort); } /** @copydoc VUSBIROOTHUBCONNECTOR::pfnDetachDevice */ -DECLINLINE(int) VUSBIRhDetachDevice(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice) +DECLINLINE(int) VUSBIRhDetachDevice(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) { - return pInterface->pfnDetachDevice(pInterface, pDevice); + return pInterface->pfnDetachDevice(pInterface, uPort); } /** @copydoc VUSBIROOTHUBCONNECTOR::pfnSetPeriodicFrameProcessing */ @@ -921,49 +1065,47 @@ { return pInterface->pfnGetPeriodicFrameRate(pInterface); } -# endif /* IN_RING3 */ -#endif /* ! RDESKTOP */ +/** @copydoc VUSBIROOTHUBCONNECTOR::pfnDevReset */ +DECLINLINE(int) VUSBIRhDevReset(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, bool fResetOnLinux, + PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM) +{ + return pInterface->pfnDevReset(pInterface, uPort, fResetOnLinux, pfnDone, pvUser, pVM); +} +/** @copydoc VUSBIROOTHUBCONNECTOR::pfnDevPowerOn */ +DECLINLINE(int) VUSBIRhDevPowerOn(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + return pInterface->pfnDevPowerOn(pInterface, uPort); +} -/** - * VUSB device reset completion callback function. - * This is called by the reset thread when the reset has been completed. - * - * @param pDev Pointer to the virtual USB device core. - * @param rc The VBox status code of the reset operation. - * @param pvUser User specific argument. - * - * @thread The reset thread or EMT. - */ -typedef DECLCALLBACK(void) FNVUSBRESETDONE(PVUSBIDEVICE pDevice, int rc, void *pvUser); -/** Pointer to a device reset completion callback function (FNUSBRESETDONE). */ -typedef FNVUSBRESETDONE *PFNVUSBRESETDONE; +/** @copydoc VUSBIROOTHUBCONNECTOR::pfnDevPowerOff */ +DECLINLINE(int) VUSBIRhDevPowerOff(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + return pInterface->pfnDevPowerOff(pInterface, uPort); +} -/** - * The state of a VUSB Device. - * - * @remark The order of these states is vital. - */ -typedef enum VUSBDEVICESTATE +/** @copydoc VUSBIROOTHUBCONNECTOR::pfnDevGetState */ +DECLINLINE(VUSBDEVICESTATE) VUSBIRhDevGetState(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) { - VUSB_DEVICE_STATE_INVALID = 0, - VUSB_DEVICE_STATE_DETACHED, - VUSB_DEVICE_STATE_ATTACHED, - VUSB_DEVICE_STATE_POWERED, - VUSB_DEVICE_STATE_DEFAULT, - VUSB_DEVICE_STATE_ADDRESS, - VUSB_DEVICE_STATE_CONFIGURED, - VUSB_DEVICE_STATE_SUSPENDED, - /** The device is being reset. Don't mess with it. - * Next states: VUSB_DEVICE_STATE_DEFAULT, VUSB_DEVICE_STATE_DESTROYED - */ - VUSB_DEVICE_STATE_RESET, - /** The device has been destroyed. */ - VUSB_DEVICE_STATE_DESTROYED, - /** The usual 32-bit hack. */ - VUSB_DEVICE_STATE_32BIT_HACK = 0x7fffffff -} VUSBDEVICESTATE; + return pInterface->pfnDevGetState(pInterface, uPort); +} + +/** @copydoc VUSBIROOTHUBCONNECTOR::pfnDevGetState */ +DECLINLINE(bool) VUSBIRhDevIsSavedStateSupported(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + return pInterface->pfnDevIsSavedStateSupported(pInterface, uPort); +} + +/** @copydoc VUSBIROOTHUBCONNECTOR::pfnDevGetSpeed */ +DECLINLINE(VUSBSPEED) VUSBIRhDevGetSpeed(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + return pInterface->pfnDevGetSpeed(pInterface, uPort); +} +# endif /* IN_RING3 */ + +#endif /* ! RDESKTOP */ + #ifndef RDESKTOP diff -Nru virtualbox-6.1.16-dfsg/Makefile.kmk virtualbox-6.1.38-dfsg/Makefile.kmk --- virtualbox-6.1.16-dfsg/Makefile.kmk 2020-10-16 16:27:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/Makefile.kmk 2022-09-01 13:17:42.000000000 +0000 @@ -881,16 +881,20 @@ $(QUIET)$(APPEND) $@.tmp "PREDEFINED += $(DEFS) $(DEFS.$(KBUILD_TARGET)) $(DEFS.$(KBUILD_TARGET_ARCH)) $(ARCH_BITS_DEFS)" $(QUIET)$(APPEND) $@.tmp "PREDEFINED += ARCH_BITS=HC_ARCH_BITS R3_ARCH_BITS=HC_ARCH_BITS R0_ARCH_BITS=HC_ARCH_BITS " $(QUIET)$(APPEND) $@.tmp + $(QUIET)$(APPEND) $@.tmp "PLANTUML_JAR_PATH = $(firstword $(rsort $(wildcard $(KBUILD_DEVTOOLS)/common/plantuml/v*/plantuml*.jar)))" + $(QUIET)$(APPEND) $@.tmp $(QUIET)$(MV) -f $@.tmp $@ @$(APPEND) $@.dep "DOXYGEN_CORE_OUTPUT_PREV = $(VBOX_CORE_DOXYFILE_OUTPUT)" @$(APPEND) $@.dep "DOXYGEN_CORE_INPUT_PREV = $(VBOX_CORE_DOXYFILE_INPUT)" # Do the actual job. +# Note! We must add the VBOX_JAVA dir to the path so doxygen can run plantuml.jar. $(VBOX_CORE_DOXYFILE_OUTPUT)/docs.Core: $(VBOX_CORE_DOXYFILE_OUTPUT)/Doxyfile.Core $$(VBOX_CORE_DOXYFILE_INPUT) \ | $(VBOX_CORE_DOXYFILE_OUTPUT)/ $(QUIET)$(RM) -f $@ $(QUIET)$(RM) -Rf $(VBOX_CORE_DOXYFILE_OUTPUT)/html/ - doxygen $(VBOX_CORE_DOXYFILE_OUTPUT)/Doxyfile.Core + $(if-expr $(VBOX_JAVA_VERSION)+0 >= 70000, $(REDIRECT) -E "PATH=$(VBOX_JAVA_BIN_PATH)$(HOST_PATH_SEP)$(PATH)" --,) \ + doxygen $(VBOX_CORE_DOXYFILE_OUTPUT)/Doxyfile.Core $(SED) -n \ -e ':nextwarning' \ -e '/^ *$(DOLLAR)/d' \ diff -Nru virtualbox-6.1.16-dfsg/src/libs/Makefile.kmk virtualbox-6.1.38-dfsg/src/libs/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/libs/Makefile.kmk 2020-10-16 16:38:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/Makefile.kmk 2022-09-01 13:29:42.000000000 +0000 @@ -55,9 +55,9 @@ # OpenSSL. if !defined(VBOX_ONLY_SDK) \ && (!defined(VBOX_ONLY_ADDITIONS) || !defined(VBOX_ONLY_ADDITIONS_WITHOUT_RTISOMAKER)) \ - && (!defined(VBOX_ONLY_EXTPACKS) || defined(VBOX_NEED_EXTPACK_OPENSSL) || !defined(VBOX_ONLY_EXTPACKS_USE_IMPLIBS)) \ + && (!defined(VBOX_ONLY_EXTPACKS) || defined(VBOX_NEED_EXTPACK_OPENSSL) || defined(VBOX_WITH_BLD_RTSIGNTOOL_SIGNING) || !defined(VBOX_ONLY_EXTPACKS_USE_IMPLIBS)) \ && ("$(SDK_VBOX_OPENSSL_INCS)" == "$(SDK_VBOX_OPENSSL_VBOX_DEFAULT_INCS)" || defined(VBOX_NEED_EXTPACK_OPENSSL)) - include $(PATH_SUB_CURRENT)/openssl-1.1.1g/Makefile.kmk + include $(PATH_SUB_CURRENT)/openssl-1.1.1q/Makefile.kmk endif # libjpeg for VRDP video redirection and ExtPack's DrvHostWebcam diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/Makefile.kmk virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/Makefile.kmk 2020-10-16 16:40:04.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/Makefile.kmk 2022-09-01 13:30:55.000000000 +0000 @@ -476,17 +476,17 @@ libIDL_config_libs := $(shell $(VBOX_LIBIDL_CONFIG) --libs) xpidl_CFLAGS = \ $(libIDL_config_cflags) - ifeq ($(BUILD_PLATFORM),linux) - xpidl_LDFLAGS = \ + if1of ($(KBUILD_HOST), linux solaris) + xpidl_LDFLAGS = \ $(filter-out -l%,$(libIDL_config_libs)) + xpidl_LIBS.$(KBUILD_HOST) += \ + $(subst -l,,$(filter -l%,$(libIDL_config_libs))) else - xpidl_LDFLAGS = \ + xpidl_LDFLAGS = \ $(libIDL_config_libs) endif xpidl_LDFLAGS.linux = \ $(VBOX_LD_as_needed) - xpidl_LIBS.linux += \ - $(subst -l,,$(filter -l%,$(libIDL_config_libs))) endif # diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/components.py virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/components.py --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/components.py 2020-10-16 16:40:24.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/components.py 2022-09-01 13:31:15.000000000 +0000 @@ -232,6 +232,10 @@ manager = registrar = classes = interfaces = interfaceInfoManager = _shutdownObserver = serviceManager = _constants_by_iid_map = None xpcom.client._shutdown() xpcom.server._shutdown() + def _query_interface_(self, iid): # VBox: Needed so that the interface check in the DefaultPolicy initialization will pass; @bugref{10079}. + if iid == interfaces.nsIObserver: + return 1 + return None svc = _xpcom.GetServiceManager().getServiceByContractID("@mozilla.org/observer-service;1", interfaces.nsIObserverService) # Observers will be QI'd for a weak-reference, so we must keep the diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/gen_python_deps.py virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/gen_python_deps.py --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/gen_python_deps.py 2020-10-16 16:40:24.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/gen_python_deps.py 2022-09-01 13:31:15.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/python """ -Copyright (C) 2009-2016 Oracle Corporation +Copyright (C) 2009-2021 Oracle Corporation This file is part of VirtualBox Open Source Edition (OSE), as available from http://www.virtualbox.org. This file is free software; @@ -16,7 +16,7 @@ import os,sys from distutils.version import StrictVersion -versions = ["2.6", "2.7", "3.1", "3.2", "3.2m", "3.3", "3.3m", "3.4", "3.4m", "3.5", "3.5m", "3.6", "3.6m", "3.7", "3.7m", "3.8", "3.8m" ] +versions = ["2.6", "2.7", "3.1", "3.2", "3.2m", "3.3", "3.3m", "3.4", "3.4m", "3.5", "3.5m", "3.6", "3.6m", "3.7", "3.7m", "3.8", "3.8m", "3.9", "3.9m", "3.10", "3.10m" ] prefixes = ["/usr", "/usr/local", "/opt", "/opt/local"] known = {} diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/Makefile.kmk virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/Makefile.kmk 2020-10-16 16:40:23.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/Makefile.kmk 2022-09-01 13:31:15.000000000 +0000 @@ -4,7 +4,7 @@ # # -# Copyright (C) 2009-2017 Oracle Corporation +# Copyright (C) 2009-2021 Oracle Corporation # # This file is part of VirtualBox Open Source Edition (OSE), as # available from http://www.virtualbox.org. This file is free software; @@ -26,7 +26,7 @@ # # List of supported Python versions, defining a number of -# VBOX_PYTHON[26|27|31|32|32M|33|33M|34|34M|35|35M|36|36M|37|37M|38|38M|DEF]_[INC|LIB] variables +# VBOX_PYTHON[26|27|31|32|32M|33|33M|34|34M|35|35M|36|36M|37|37M|38|38M|39|39M|310|310M|DEF]_[INC|LIB] variables # which get picked up below. # ifeq ($(KBUILD_TARGET),darwin) # Relatively predictable, don't script. @@ -533,6 +533,98 @@ endif endif +ifdef VBOX_PYTHON39_INC +# +# Python 3.9 version +# +DLLS += VBoxPython3_9 +VBoxPython3_9_EXTENDS = VBoxPythonBase +VBoxPython3_9_EXTENDS_BY = appending +VBoxPython3_9_TEMPLATE = XPCOM +VBoxPython3_9_INCS = $(VBOX_PYTHON39_INC) +VBoxPython3_9_LIBS = $(VBOX_PYTHON39_LIB) + + ifdef VBOX_WITH_32_ON_64_MAIN_API + ifdef VBOX_PYTHON39_LIB_X86 +DLLS += VBoxPython3_9_x86 +VBoxPython3_9_x86_EXTENDS = VBoxPythonBase_x86 +VBoxPython3_9_x86_EXTENDS_BY = appending +VBoxPython3_9_x86_TEMPLATE = XPCOM +VBoxPython3_9_x86_INCS = $(VBOX_PYTHON39_INC) +VBoxPython3_9_x86_LIBS = $(VBOX_PYTHON39_LIB_X86) + endif + endif +endif + +ifdef VBOX_PYTHON39M_INC +# +# Python 3.9 version with pymalloc +# +DLLS += VBoxPython3_9m +VBoxPython3_9m_EXTENDS = VBoxPythonBase_m +VBoxPython3_9m_EXTENDS_BY = appending +VBoxPython3_9m_TEMPLATE = XPCOM +VBoxPython3_9m_INCS = $(VBOX_PYTHON39M_INC) +VBoxPython3_9m_LIBS = $(VBOX_PYTHON39M_LIB) + + ifdef VBOX_WITH_32_ON_64_MAIN_API + ifdef VBOX_PYTHON39M_LIB_X86 +DLLS += VBoxPython3_9m_x86 +VBoxPython3_9m_x86_EXTENDS = VBoxPythonBase_x86_m +VBoxPython3_9m_x86_EXTENDS_BY = appending +VBoxPython3_9m_x86_TEMPLATE_ = XPCOM +VBoxPython3_9m_x86_INCS = $(VBOX_PYTHON39M_INC) +VBoxPython3_9m_x86_LIBS = $(VBOX_PYTHON39M_LIB_X86) + endif + endif +endif + +ifdef VBOX_PYTHON310_INC +# +# Python 3.10 version +# +DLLS += VBoxPython3_10 +VBoxPython3_10_EXTENDS = VBoxPythonBase +VBoxPython3_10_EXTENDS_BY = appending +VBoxPython3_10_TEMPLATE = XPCOM +VBoxPython3_10_INCS = $(VBOX_PYTHON310_INC) +VBoxPython3_10_LIBS = $(VBOX_PYTHON310_LIB) + + ifdef VBOX_WITH_32_ON_64_MAIN_API + ifdef VBOX_PYTHON310_LIB_X86 +DLLS += VBoxPython3_10_x86 +VBoxPython3_10_x86_EXTENDS = VBoxPythonBase_x86 +VBoxPython3_10_x86_EXTENDS_BY = appending +VBoxPython3_10_x86_TEMPLATE = XPCOM +VBoxPython3_10_x86_INCS = $(VBOX_PYTHON310_INC) +VBoxPython3_10_x86_LIBS = $(VBOX_PYTHON310_LIB_X86) + endif + endif +endif + +ifdef VBOX_PYTHON310M_INC +# +# Python 3.10 version with pymalloc +# +DLLS += VBoxPython3_10m +VBoxPython3_10m_EXTENDS = VBoxPythonBase_m +VBoxPython3_10m_EXTENDS_BY = appending +VBoxPython3_10m_TEMPLATE = XPCOM +VBoxPython3_10m_INCS = $(VBOX_PYTHON310M_INC) +VBoxPython3_10m_LIBS = $(VBOX_PYTHON310M_LIB) + + ifdef VBOX_WITH_32_ON_64_MAIN_API + ifdef VBOX_PYTHON310M_LIB_X86 +DLLS += VBoxPython3_10m_x86 +VBoxPython3_10m_x86_EXTENDS = VBoxPythonBase_x86_m +VBoxPython3_10m_x86_EXTENDS_BY = appending +VBoxPython3_10m_x86_TEMPLATE_ = XPCOM +VBoxPython3_10m_x86_INCS = $(VBOX_PYTHON310M_INC) +VBoxPython3_10m_x86_LIBS = $(VBOX_PYTHON310M_LIB_X86) + endif + endif +endif + ifdef VBOX_PYTHONDEF_INC # # Python without versioning @@ -588,4 +680,3 @@ include $(FILE_KBUILD_SUB_FOOTER) - diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/module/_xpcom.cpp virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/module/_xpcom.cpp --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/module/_xpcom.cpp 2020-10-16 16:40:24.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/module/_xpcom.cpp 2022-09-01 13:31:16.000000000 +0000 @@ -84,7 +84,15 @@ # define MANGLE_MODULE_INIT(a_Name) RT_CONCAT(a_Name, MODULE_NAME_SUFFIX) # endif # ifdef VBOX_PYXPCOM_VERSIONED -# if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 +# if PY_VERSION_HEX >= 0x030a0000 && PY_VERSION_HEX < 0x030b0000 +# define MODULE_NAME MANGLE_MODULE_NAME("VBoxPython3_10") +# define initVBoxPython MANGLE_MODULE_INIT(PyInit_VBoxPython3_10) + +# elif PY_VERSION_HEX >= 0x03090000 && PY_VERSION_HEX < 0x030a0000 +# define MODULE_NAME MANGLE_MODULE_NAME("VBoxPython3_9") +# define initVBoxPython MANGLE_MODULE_INIT(PyInit_VBoxPython3_9) + +# elif PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 # define MODULE_NAME MANGLE_MODULE_NAME("VBoxPython3_8") # define initVBoxPython MANGLE_MODULE_INIT(PyInit_VBoxPython3_8) @@ -863,8 +871,8 @@ # include # include -/** Set if NS_ShutdownXPCOM has been called successfully already and we don't - * need to do it again during module termination. This avoids assertion in the +/** Set if NS_ShutdownXPCOM has been called successfully already and we don't + * need to do it again during module termination. This avoids assertion in the * VBoxCOM glue code. */ static bool g_fComShutdownAlready = true; diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/PyIID.cpp virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/PyIID.cpp --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/PyIID.cpp 2020-10-16 16:40:24.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/PyIID.cpp 2022-09-01 13:31:15.000000000 +0000 @@ -207,7 +207,12 @@ Py_nsIID::Py_nsIID(const nsIID &riid) { ob_type = &type; +#if 1 /* VBox: Must use for 3.9+, includes _Py_NewReferences. Works for all older versions too. @bugref{10079} */ + PyObject_Init(this, ob_type); +#else _Py_NewReference(this); +#endif + m_iid = riid; } diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/PyISupports.cpp virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/PyISupports.cpp --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/PyISupports.cpp 2020-10-16 16:40:24.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/PyISupports.cpp 2022-09-01 13:31:15.000000000 +0000 @@ -136,7 +136,11 @@ // refcnt of object managed by caller. PR_AtomicIncrement(&cInterfaces); PyXPCOM_DLLAddRef(); +#if 1 /* VBox: Must use for 3.9+, includes _Py_NewReferences. Works for all older versions too. @bugref{10079} */ + PyObject_Init(this, ob_type); +#else _Py_NewReference(this); +#endif #ifdef VBOX_DEBUG_LIFETIMES RTOnce(&g_Once, initOnceCallback, NULL); diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/PyXPCOM.h virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/PyXPCOM.h --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/PyXPCOM.h 2020-10-16 16:40:24.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/PyXPCOM.h 2022-09-01 13:31:15.000000000 +0000 @@ -137,11 +137,13 @@ # define PyInt_Check(o) PyLong_Check(o) # define PyInt_AsLong(o) PyLong_AsLong(o) # define PyNumber_Int(o) PyNumber_Long(o) -# ifndef PyUnicode_AsUTF8 -# define PyUnicode_AsUTF8(o) _PyUnicode_AsString(o) -# endif -# ifndef PyUnicode_AsUTF8AndSize -# define PyUnicode_AsUTF8AndSize(o,s) _PyUnicode_AsStringAndSize(o,s) +# if PY_VERSION_HEX <= 0x03030000 /* 3.3 added PyUnicode_AsUTF8AndSize */ +# ifndef PyUnicode_AsUTF8 +# define PyUnicode_AsUTF8(o) _PyUnicode_AsString(o) +# endif +# ifndef PyUnicode_AsUTF8AndSize +# define PyUnicode_AsUTF8AndSize(o,s) _PyUnicode_AsStringAndSize(o,s) +# endif # endif typedef struct PyMethodChain { diff -Nru virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/VariantUtils.cpp virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/VariantUtils.cpp --- virtualbox-6.1.16-dfsg/src/libs/xpcom18a4/python/src/VariantUtils.cpp 2020-10-16 16:40:24.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/libs/xpcom18a4/python/src/VariantUtils.cpp 2022-09-01 13:31:16.000000000 +0000 @@ -628,7 +628,7 @@ #if PY_MAJOR_VERSION <= 2 return PyString_FromStringAndSize( (char *)array_ptr, sequence_size ); #else - return PyUnicode_FromStringAndSize( (char *)array_ptr, sequence_size ); + return PyBytes_FromStringAndSize( (char *)array_ptr, sequence_size ); #endif PRUint32 array_element_size = GetArrayElementSize(array_type); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/Additions/common/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/Makefile.kmk 2020-10-16 16:30:08.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/Makefile.kmk 2022-09-01 13:20:44.000000000 +0000 @@ -19,13 +19,11 @@ include $(KBUILD_PATH)/subheader.kmk # Include sub-makefile. -ifndef VBOX_ONLY_VALIDATIONKIT - include $(PATH_SUB_CURRENT)/VBoxControl/Makefile.kmk - include $(PATH_SUB_CURRENT)/VBoxGuest/Makefile.kmk - include $(PATH_SUB_CURRENT)/VBoxService/Makefile.kmk - ifdef VBOX_WITH_PAM - include $(PATH_SUB_CURRENT)/pam/Makefile.kmk - endif -endif # !VBOX_ONLY_VALIDATIONKIT +include $(PATH_SUB_CURRENT)/VBoxControl/Makefile.kmk +include $(PATH_SUB_CURRENT)/VBoxGuest/Makefile.kmk +include $(PATH_SUB_CURRENT)/VBoxService/Makefile.kmk +ifdef VBOX_WITH_PAM + include $(PATH_SUB_CURRENT)/pam/Makefile.kmk +endif include $(FILE_KBUILD_SUB_FOOTER) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest 2022-09-01 13:20:45.000000000 +0000 @@ -131,10 +131,12 @@ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNLen.cpp=>common/string/RTStrNLen.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/stringalloc.cpp=>common/string/stringalloc.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformat.cpp=>common/string/strformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrFormat.cpp=>common/string/RTStrFormat.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatnum.cpp=>common/string/strformatnum.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatrt.cpp=>common/string/strformatrt.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformattype.cpp=>common/string/strformattype.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf.cpp=>common/string/strprintf.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp=>common/string/strprintf-ellipsis.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strtonum.cpp=>common/string/strtonum.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/memchr.cpp=>common/string/memchr.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/utf-8.cpp=>common/string/utf-8.c \ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile 2022-09-01 13:20:45.000000000 +0000 @@ -95,10 +95,12 @@ RTStrNLen.c \ stringalloc.c \ strformat.c \ + RTStrFormat.c \ strformatnum.c \ strformatrt.c \ strformattype.c \ strprintf.c \ + strprintf-ellipsis.c \ strtonum.c \ memchr.c \ utf-8.c diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp 2022-09-01 13:20:45.000000000 +0000 @@ -295,58 +295,55 @@ pBlock->pPrev = NULL; } -static VBGLPHYSHEAPBLOCK *vbglPhysHeapChunkAlloc (uint32_t cbSize) +static VBGLPHYSHEAPBLOCK *vbglPhysHeapChunkAlloc(uint32_t cbMinBlock) { - RTCCPHYS physAddr; + RTCCPHYS PhysAddr = NIL_RTHCPHYS; VBGLPHYSHEAPCHUNK *pChunk; - VBGLPHYSHEAPBLOCK *pBlock; - VBGL_PH_dprintf(("Allocating new chunk of size %d\n", cbSize)); + uint32_t cbChunk; + VBGL_PH_dprintf(("Allocating new chunk for %#x byte allocation\n", cbMinBlock)); + AssertReturn(cbMinBlock < _128M, NULL); /* paranoia */ + + /* Compute the size of the new chunk, rounding up to next chunk size, + which must be power of 2. */ + Assert(RT_IS_POWER_OF_TWO(VBGL_PH_CHUNKSIZE)); + cbChunk = cbMinBlock + sizeof(VBGLPHYSHEAPCHUNK) + sizeof(VBGLPHYSHEAPBLOCK); + cbChunk = RT_ALIGN_32(cbChunk, VBGL_PH_CHUNKSIZE); + + /* This function allocates physical contiguous memory below 4 GB. This 4GB + limitation stems from using a 32-bit OUT instruction to pass a block + physical address to the host. */ + pChunk = (VBGLPHYSHEAPCHUNK *)RTMemContAlloc(&PhysAddr, cbChunk); + if (pChunk) + { + VBGLPHYSHEAPCHUNK *pOldHeadChunk; + VBGLPHYSHEAPBLOCK *pBlock; + AssertRelease(PhysAddr < _4G && PhysAddr + cbChunk <= _4G); + + /* Init the new chunk. */ + pChunk->u32Signature = VBGL_PH_CHUNKSIGNATURE; + pChunk->cbSize = cbChunk; + pChunk->physAddr = (uint32_t)PhysAddr; + pChunk->cAllocatedBlocks = 0; + pChunk->pNext = NULL; + pChunk->pPrev = NULL; + + /* Initialize the free block, which now occupies entire chunk. */ + pBlock = (VBGLPHYSHEAPBLOCK *)(pChunk + 1); + vbglPhysHeapInitBlock(pBlock, pChunk, cbChunk - sizeof(VBGLPHYSHEAPCHUNK) - sizeof(VBGLPHYSHEAPBLOCK)); + vbglPhysHeapInsertBlock(NULL, pBlock); + + /* Add the chunk to the list. */ + pOldHeadChunk = g_vbgldata.pChunkHead; + pChunk->pNext = pOldHeadChunk; + if (pOldHeadChunk) + pOldHeadChunk->pPrev = pChunk; + g_vbgldata.pChunkHead = pChunk; - /* Compute chunk size to allocate */ - if (cbSize < VBGL_PH_CHUNKSIZE) - { - /* Includes case of block size 0 during initialization */ - cbSize = VBGL_PH_CHUNKSIZE; - } - else - { - /* Round up to next chunk size, which must be power of 2 */ - cbSize = (cbSize + (VBGL_PH_CHUNKSIZE - 1)) & ~(VBGL_PH_CHUNKSIZE - 1); - } - - physAddr = 0; - /* This function allocates physical contiguous memory (below 4GB) according to the IPRT docs. - * Address < 4G is required for the port IO. - */ - pChunk = (VBGLPHYSHEAPCHUNK *)RTMemContAlloc (&physAddr, cbSize); - - if (!pChunk) - { - LogRel(("vbglPhysHeapChunkAlloc: failed to alloc %u contiguous bytes.\n", cbSize)); - return NULL; + VBGL_PH_dprintf(("Allocated chunk %p LB %#x, block %p LB %#x\n", pChunk, cbChunk, pBlock, pBlock->cbDataSize)); + return pBlock; } - - AssertRelease(physAddr < _4G && physAddr + cbSize <= _4G); - - pChunk->u32Signature = VBGL_PH_CHUNKSIGNATURE; - pChunk->cbSize = cbSize; - pChunk->physAddr = (uint32_t)physAddr; - pChunk->cAllocatedBlocks = 0; - pChunk->pNext = g_vbgldata.pChunkHead; - pChunk->pPrev = NULL; - - /* Initialize the free block, which now occupies entire chunk. */ - pBlock = (VBGLPHYSHEAPBLOCK *)((char *)pChunk + sizeof (VBGLPHYSHEAPCHUNK)); - - vbglPhysHeapInitBlock (pBlock, pChunk, cbSize - sizeof (VBGLPHYSHEAPCHUNK) - sizeof (VBGLPHYSHEAPBLOCK)); - - vbglPhysHeapInsertBlock (NULL, pBlock); - - g_vbgldata.pChunkHead = pChunk; - - VBGL_PH_dprintf(("Allocated chunk %p, block = %p size=%x\n", pChunk, pBlock, cbSize)); - - return pBlock; + LogRel(("vbglPhysHeapChunkAlloc: failed to alloc %u (%#x) contiguous bytes.\n", cbChunk, cbChunk)); + return NULL; } @@ -404,8 +401,14 @@ DECLR0VBGL(void *) VbglR0PhysHeapAlloc (uint32_t cbSize) { VBGLPHYSHEAPBLOCK *pBlock, *pIter; - int rc = vbglPhysHeapEnter (); + int rc; + + /* + * Align the size to a pointer size to avoid getting misaligned header pointers and whatnot. + */ + cbSize = RT_ALIGN_32(cbSize, sizeof(void *)); + rc = vbglPhysHeapEnter (); if (RT_FAILURE(rc)) return NULL; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp 2022-09-01 13:20:46.000000000 +0000 @@ -631,7 +631,10 @@ { rc = Msg.ReqParms.fRoots.GetUInt32(&pRootListHdr->fRoots); AssertRC(rc); if (RT_SUCCESS(rc)) - rc = Msg.cRoots.GetUInt32(&pRootListHdr->cRoots); AssertRC(rc); + { + rc = Msg.cRoots.GetUInt32(&pRootListHdr->cRoots); + AssertRC(rc); + } } LogFlowFuncLeaveRC(rc); @@ -777,13 +780,25 @@ { rc = Msg.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc); if (RT_SUCCESS(rc)) - rc = Msg.enmDir.GetUInt32((uint32_t *)pEnmDir); AssertRC(rc); + { + rc = Msg.enmDir.GetUInt32((uint32_t *)pEnmDir); + AssertRC(rc); + } if (RT_SUCCESS(rc)) - rc = Msg.enmStatus.GetUInt32(&pReport->uStatus); AssertRC(rc); + { + rc = Msg.enmStatus.GetUInt32(&pReport->uStatus); + AssertRC(rc); + } if (RT_SUCCESS(rc)) - rc = Msg.rc.GetUInt32((uint32_t *)&pReport->rc); AssertRC(rc); + { + rc = Msg.rc.GetUInt32((uint32_t *)&pReport->rc); + AssertRC(rc); + } if (RT_SUCCESS(rc)) - rc = Msg.fFlags.GetUInt32(&pReport->fFlags); AssertRC(rc); + { + rc = Msg.fFlags.GetUInt32(&pReport->fFlags); + AssertRC(rc); + } } LogFlowFuncLeaveRC(rc); @@ -855,7 +870,10 @@ { rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc); if (RT_SUCCESS(rc)) - rc = Msg.ReqParms.fRoots.GetUInt32(pfRoots); AssertRC(rc); + { + rc = Msg.ReqParms.fRoots.GetUInt32(pfRoots); + AssertRC(rc); + } } LogFlowFuncLeaveRC(rc); @@ -920,9 +938,15 @@ { rc = Msg.Parms.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc); if (RT_SUCCESS(rc)) - rc = Msg.Parms.fInfo.GetUInt32(pfInfo); AssertRC(rc); + { + rc = Msg.Parms.fInfo.GetUInt32(pfInfo); + AssertRC(rc); + } if (RT_SUCCESS(rc)) - rc = Msg.Parms.uIndex.GetUInt32(puIndex); AssertRC(rc); + { + rc = Msg.Parms.uIndex.GetUInt32(puIndex); + AssertRC(rc); + } } LogFlowFuncLeaveRC(rc); @@ -1101,7 +1125,10 @@ { rc = Msg.uContext.GetUInt64(&pCtx->idContext); if (RT_SUCCESS(rc)) - rc = Msg.uHandle.GetUInt64(phList); AssertRC(rc); + { + rc = Msg.uHandle.GetUInt64(phList); + AssertRC(rc); + } } LogFlowFuncLeaveRC(rc); @@ -1350,9 +1377,15 @@ { rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext); if (RT_SUCCESS(rc)) - rc = Msg.ReqParms.uHandle.GetUInt64(phList); AssertRC(rc); + { + rc = Msg.ReqParms.uHandle.GetUInt64(phList); + AssertRC(rc); + } if (RT_SUCCESS(rc)) - rc = Msg.ReqParms.fInfo.GetUInt32(pfInfo); AssertRC(rc); + { + rc = Msg.ReqParms.fInfo.GetUInt32(pfInfo); + AssertRC(rc); + } } LogFlowFuncLeaveRC(rc); @@ -2519,10 +2552,11 @@ * from the host. * * @returns VBox status code. - * @param idClient The client id returned by VbglR3ClipboardConnect(). - * @param fFormat The format of the data. - * @param pv The data. - * @param cb The size of the data. + * @param idClient The client id returned by VbglR3ClipboardConnect(). + * @param fFormat The format of the data. + * @param pvData Pointer to the data to send. Can be NULL if @a cbData + * is zero. + * @param cbData Number of bytes of data to send. Zero is valid. */ VBGLR3DECL(int) VbglR3ClipboardWriteData(HGCMCLIENTID idClient, uint32_t fFormat, void *pv, uint32_t cb) { @@ -2551,24 +2585,22 @@ * from the host. * * @returns VBox status code. - * @param pCtx The command context returned by VbglR3ClipboardConnectEx(). - * @param uFormat Clipboard format to send. - * @param pvData Pointer to data to send. - * @param cbData Size (in bytes) of data to send. + * @param pCtx The command context returned by VbglR3ClipboardConnectEx(). + * @param fFormat Clipboard format to send. + * @param pvData Pointer to the data to send. Can be NULL if @a cbData + * is zero. + * @param cbData Number of bytes of data to send. Zero is valid. */ -VBGLR3DECL(int) VbglR3ClipboardWriteDataEx(PVBGLR3SHCLCMDCTX pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +VBGLR3DECL(int) VbglR3ClipboardWriteDataEx(PVBGLR3SHCLCMDCTX pCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) { - AssertPtrReturn(pCtx, VERR_INVALID_POINTER); - AssertPtrReturn(pvData, VERR_INVALID_POINTER); + LogFlowFunc(("ENTER: fFormat=%#x pvData=%p cbData=%#x\n", fFormat, pvData, cbData)); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + if (cbData > 0) + AssertPtrReturn(pvData, VERR_INVALID_POINTER); int rc; - - LogFlowFuncEnter(); - if (pCtx->fUseLegacyProtocol) - { - rc = VbglR3ClipboardWriteData(pCtx->idClient, uFormat, pvData, cbData); - } + rc = VbglR3ClipboardWriteData(pCtx->idClient, fFormat, pvData, cbData); else { struct @@ -2579,7 +2611,7 @@ VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, VBOX_SHCL_CPARMS_DATA_WRITE); Msg.Parms.id64Context.SetUInt64(pCtx->idContext); - Msg.Parms.f32Format.SetUInt32(uFormat); + Msg.Parms.f32Format.SetUInt32(fFormat); Msg.Parms.pData.SetPtr(pvData, cbData); LogFlowFunc(("CID=%RU32\n", pCtx->idContext)); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp 2022-09-01 13:20:46.000000000 +0000 @@ -44,13 +44,17 @@ */ VBGLR3DECL(int) VbglR3HGCMConnect(const char *pszServiceName, HGCMCLIENTID *pidClient) { + AssertPtrReturn(pszServiceName, VERR_INVALID_POINTER); + AssertPtrReturn(pidClient, VERR_INVALID_POINTER); + VBGLIOCHGCMCONNECT Info; RT_ZERO(Info); VBGLREQHDR_INIT(&Info.Hdr, HGCM_CONNECT); Info.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing; - strcpy(Info.u.In.Loc.u.host.achName, pszServiceName); - - int rc = vbglR3DoIOCtl(VBGL_IOCTL_HGCM_CONNECT, &Info.Hdr, sizeof(Info)); + int rc = RTStrCopy(Info.u.In.Loc.u.host.achName, sizeof(Info.u.In.Loc.u.host.achName), pszServiceName); + if (RT_FAILURE(rc)) + return rc; + rc = vbglR3DoIOCtl(VBGL_IOCTL_HGCM_CONNECT, &Info.Hdr, sizeof(Info)); if (RT_SUCCESS(rc)) *pidClient = Info.u.Out.idClient; return rc; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c 2022-09-01 13:20:47.000000000 +0000 @@ -107,12 +107,16 @@ #undef LOG_GROUP #include "common/string/strformat.c" #undef LOG_GROUP +#include "common/string/RTStrFormat.c" +#undef LOG_GROUP #include "common/string/strformatnum.c" #undef LOG_GROUP #include "common/string/strformattype.c" #undef LOG_GROUP #include "common/string/strprintf.c" #undef LOG_GROUP +#include "common/string/strprintf-ellipsis.c" +#undef LOG_GROUP #include "common/string/strtonum.c" #undef LOG_GROUP #include "common/string/utf-8.c" diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest 2022-09-01 13:20:47.000000000 +0000 @@ -122,6 +122,7 @@ ${PATH_ROOT}/src/VBox/Runtime/common/log/logformat.cpp=>common/log/logformat.c \ ${PATH_ROOT}/src/VBox/Runtime/common/log/logcom.cpp=>common/log/logcom.c \ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divdi3.c=>common/math/gcc/divdi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divmoddi4.c=>common/math/gcc/divmoddi4.c \ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/moddi3.c=>common/math/gcc/moddi3.c \ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/qdivrem.c=>common/math/gcc/qdivrem.c \ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/quad.h=>common/math/gcc/quad.h \ @@ -148,10 +149,12 @@ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNLen.cpp=>common/string/RTStrNLen.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/stringalloc.cpp=>common/string/stringalloc.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformat.cpp=>common/string/strformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrFormat.cpp=>common/string/RTStrFormat.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatnum.cpp=>common/string/strformatnum.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatrt.cpp=>common/string/strformatrt.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformattype.cpp=>common/string/strformattype.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf.cpp=>common/string/strprintf.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp=>common/string/strprintf-ellipsis.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/strtonum.cpp=>common/string/strtonum.c \ ${PATH_ROOT}/src/VBox/Runtime/common/string/utf-8.cpp=>common/string/utf-8.c \ ${PATH_ROOT}/src/VBox/Runtime/common/table/avlpv.cpp=>common/table/avlpv.c \ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/linux/Makefile virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/linux/Makefile --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/linux/Makefile 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/linux/Makefile 2022-09-01 13:20:47.000000000 +0000 @@ -101,10 +101,12 @@ common/string/RTStrNLen.o \ common/string/stringalloc.o \ common/string/strformat.o \ + common/string/RTStrFormat.o \ common/string/strformatnum.o \ common/string/strformatrt.o \ common/string/strformattype.o \ common/string/strprintf.o \ + common/string/strprintf-ellipsis.o \ common/string/strtonum.o \ common/string/utf-8.o \ common/table/avlpv.o \ @@ -122,13 +124,14 @@ generic/mppresent-generic.o \ VBox/log-vbox.o \ VBox/logbackdoor.o - ifeq ($(BUILD_TARGET_ARCH),amd64) + ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64) VBOXMOD_OBJS += common/alloc/heapsimple.o endif endif # VBOX_WITHOUT_COMBINED_SOURCES -ifeq ($(BUILD_TARGET_ARCH),x86) +ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86) VBOXMOD_OBJS += \ common/math/gcc/divdi3.o \ + common/math/gcc/divmoddi4.o \ common/math/gcc/moddi3.o \ common/math/gcc/udivdi3.o \ common/math/gcc/udivmoddi4.o \ @@ -147,7 +150,7 @@ RT_WITH_VBOX \ VBGL_VBOXGUEST \ VBOX_WITH_HGCM -ifeq ($(BUILD_TARGET_ARCH),amd64) +ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64) VBOXMOD_DEFS += VBOX_WITH_64_BITS_GUESTS endif ifeq ($(KERN_VERSION),24) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp 2022-09-01 13:20:45.000000000 +0000 @@ -2343,6 +2343,7 @@ case VMMDevReq_RegisterPatchMemory: case VMMDevReq_DeregisterPatchMemory: case VMMDevReq_GetMemBalloonChangeRequest: + case VMMDevReq_ChangeMemBalloon: enmRequired = kLevel_OnlyVBoxGuest; break; @@ -2362,7 +2363,6 @@ case VMMDevReq_ReportGuestStats: case VMMDevReq_ReportGuestUserState: case VMMDevReq_GetStatisticsChangeRequest: - case VMMDevReq_ChangeMemBalloon: enmRequired = kLevel_TrustedUsers; break; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c 2022-09-01 13:20:44.000000000 +0000 @@ -667,11 +667,11 @@ if (rc >= 0) { /* some useful information for the user but don't show this on the console */ - LogRel((DEVICE_NAME ": Successfully loaded version " VBOX_VERSION_STRING "\n")); + LogRel((DEVICE_NAME ": Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n")); LogRel((DEVICE_NAME ": misc device minor %d, IRQ %d, I/O port %RTiop, MMIO at %RHp (size 0x%x)\n", g_MiscDevice.minor, g_pPciDev->irq, g_IOPortBase, g_MMIOPhysAddr, g_cbMMIO)); printk(KERN_DEBUG DEVICE_NAME ": Successfully loaded version " - VBOX_VERSION_STRING " (interface " RT_XSTR(VMMDEV_VERSION) ")\n"); + VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " (interface " RT_XSTR(VMMDEV_VERSION) ")\n"); return rc; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c 2022-09-01 13:20:44.000000000 +0000 @@ -416,7 +416,18 @@ if (RT_FAILURE(rc)) goto fail; - sc->sc_wsmousedev = config_found_ia(sc->sc_dev, "wsmousedev", &am, wsmousedevprint); +#if __NetBSD_Prereq__(9,99,88) + sc->sc_wsmousedev = config_found(sc->sc_dev, &am, wsmousedevprint, + CFARGS(.iattr = "wsmousedev")); +#elif __NetBSD_Prereq__(9,99,82) + sc->sc_wsmousedev = config_found(sc->sc_dev, &am, wsmousedevprint, + CFARG_IATTR, "wsmousedev", + CFARG_EOL); +#else + sc->sc_wsmousedev = config_found_ia(sc->sc_dev, "wsmousedev", + &am, wsmousedevprint); +#endif + if (sc->sc_wsmousedev == NULL) goto fail; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c 2022-09-01 13:20:44.000000000 +0000 @@ -292,8 +292,6 @@ return DDI_FAILURE; } - int instance = ddi_get_instance(pDip); - /* * Enable resources for PCI access. */ @@ -346,7 +344,8 @@ */ VGDrvCommonProcessOptionsFromHost(&g_DevExt); - rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, instance, DDI_PSEUDO, 0 /* fFlags */); + rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO, + 0 /* fFlags */); if (rc == DDI_SUCCESS) { g_pDip = pDip; @@ -467,12 +466,19 @@ switch (enmCmd) { case DDI_INFO_DEVT2DEVINFO: + { *ppvResult = (void *)g_pDip; + if (!*ppvResult) + rc = DDI_FAILURE; break; + } case DDI_INFO_DEVT2INSTANCE: - *ppvResult = (void *)(uintptr_t)ddi_get_instance(g_pDip); + { + /* There can only be a single-instance of this driver and thus its instance number is 0. */ + *ppvResult = (void *)0; break; + } default: rc = DDI_FAILURE; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp 2020-10-16 16:30:09.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp 2022-09-01 13:20:45.000000000 +0000 @@ -173,7 +173,8 @@ VGDRVNTVER_WIN7, VGDRVNTVER_WIN8, VGDRVNTVER_WIN81, - VGDRVNTVER_WIN10 + VGDRVNTVER_WIN10, + VGDRVNTVER_WIN11 } VGDRVNTVER; @@ -339,8 +340,12 @@ NTSTATUS rcNt = STATUS_SUCCESS; switch (ulMajorVer) { - case 10: /* Windows 10 Preview builds starting with 9926. */ + case 10: + /* Windows 10 Preview builds starting with 9926. */ g_enmVGDrvNtVer = VGDRVNTVER_WIN10; + /* Windows 11 Preview builds starting with 22000. */ + if (ulBuildNo >= 22000) + g_enmVGDrvNtVer = VGDRVNTVER_WIN11; break; case 6: /* Windows Vista or Windows 7 (based on minor ver) */ switch (ulMinorVer) @@ -538,6 +543,7 @@ case VGDRVNTVER_WIN8: enmOsType = VBOXOSTYPE_Win8; break; case VGDRVNTVER_WIN81: enmOsType = VBOXOSTYPE_Win81; break; case VGDRVNTVER_WIN10: enmOsType = VBOXOSTYPE_Win10; break; + case VGDRVNTVER_WIN11: enmOsType = VBOXOSTYPE_Win11_x64; break; default: /* We don't know, therefore NT family. */ @@ -1479,7 +1485,7 @@ rc = vgdrvNtSetupDevice(pDevExt, pDeviceObject, NULL /*pIrp*/, pDrvObj, pRegPath); if (NT_SUCCESS(rc)) { - Log(("vgdrvNt4CreateDevice: Returning rc = 0x%x (succcess)\n", rc)); + Log(("vgdrvNt4CreateDevice: Returning rc = 0x%x (success)\n", rc)); return rc; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp 2022-09-01 13:20:47.000000000 +0000 @@ -64,12 +64,10 @@ # include # include # include -RT_C_DECLS_BEGIN /* Only needed for old code.*/ -# include "../../linux/sharedfolders/vbsfmount.h" -RT_C_DECLS_END # elif defined(RT_OS_LINUX) # include # include +# include RT_C_DECLS_BEGIN # include "../../linux/sharedfolders/vbsfmount.h" RT_C_DECLS_END @@ -312,19 +310,21 @@ * @returns VBox status code * @param pszMountPoint The mount point. * @param pszShareName Unused. - * @param pOpts For getting the group ID. + * @param gidGroup The group ID. */ -static int vbsvcAutoMountPrepareMountPointOld(const char *pszMountPoint, const char *pszShareName, vbsf_mount_opts *pOpts) +static int vbsvcAutoMountPrepareMountPointOld(const char *pszMountPoint, const char *pszShareName, RTGID gidGroup) { - AssertPtrReturn(pOpts, VERR_INVALID_PARAMETER); AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER); AssertPtrReturn(pszShareName, VERR_INVALID_PARAMETER); + /** @todo r=bird: There is no reason why gidGroup should have write access? + * Seriously, what kind of non-sense is this? */ + RTFMODE fMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG; /* Owner (=root) and the group (=vboxsf) have full access. */ int rc = RTDirCreateFullPath(pszMountPoint, fMode); if (RT_SUCCESS(rc)) { - rc = RTPathSetOwnerEx(pszMountPoint, NIL_RTUID /* Owner, unchanged */, pOpts->gid, RTPATH_F_ON_LINK); + rc = RTPathSetOwnerEx(pszMountPoint, NIL_RTUID /* Owner, unchanged */, gidGroup, RTPATH_F_ON_LINK); if (RT_SUCCESS(rc)) { rc = RTPathSetMode(pszMountPoint, fMode); @@ -373,43 +373,16 @@ return VINF_SUCCESS; } - struct vbsf_mount_opts Opts = - { - -1, /* ttl */ - -1, /* msDirCacheTTL */ - -1, /* msInodeTTL */ - 0, /* cMaxIoPages */ - 0, /* cbDirBuf */ - kVbsfCacheMode_Default, - 0, /* uid */ - (int)grp_vboxsf->gr_gid, /* gid */ - 0770, /* dmode, owner and group "vboxsf" have full access */ - 0770, /* fmode, owner and group "vboxsf" have full access */ - 0, /* dmask */ - 0, /* fmask */ - 0, /* ronly */ - 0, /* sloppy */ - 0, /* noexec */ - 0, /* nodev */ - 0, /* nosuid */ - 0, /* remount */ - "\0", /* nls_name */ - NULL, /* convertcp */ - }; - - int rc = vbsvcAutoMountPrepareMountPointOld(pszMountPoint, pszShareName, &Opts); + int rc = vbsvcAutoMountPrepareMountPointOld(pszMountPoint, pszShareName, grp_vboxsf->gr_gid); if (RT_SUCCESS(rc)) { # ifdef RT_OS_SOLARIS - int fFlags = 0; - if (Opts.ronly) - fFlags |= MS_RDONLY; + int const fFlags = MS_OPTIONSTR; char szOptBuf[MAX_MNTOPT_STR] = { '\0', }; - RTStrPrintf(szOptBuf, sizeof(szOptBuf), "uid=%d,gid=%d,dmode=%0o,fmode=%0o,dmask=%0o,fmask=%0o", - Opts.uid, Opts.gid, Opts.dmode, Opts.fmode, Opts.dmask, Opts.fmask); + RTStrPrintf(szOptBuf, sizeof(szOptBuf), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000", grp_vboxsf->gr_gid); int r = mount(pszShareName, pszMountPoint, - fFlags | MS_OPTIONSTR, + fFlags, "vboxfs", NULL, /* char *dataptr */ 0, /* int datalen */ @@ -422,61 +395,46 @@ pszShareName, pszMountPoint, strerror(errno)); # else /* RT_OS_LINUX */ - unsigned long fFlags = MS_NODEV; - - /*const char *szOptions = { "rw" }; - ??? */ - struct vbsf_mount_info_new mntinf; - RT_ZERO(mntinf); - - mntinf.nullchar = '\0'; - mntinf.signature[0] = VBSF_MOUNT_SIGNATURE_BYTE_0; - mntinf.signature[1] = VBSF_MOUNT_SIGNATURE_BYTE_1; - mntinf.signature[2] = VBSF_MOUNT_SIGNATURE_BYTE_2; - mntinf.length = sizeof(mntinf); - - mntinf.uid = Opts.uid; - mntinf.gid = Opts.gid; - mntinf.ttl = Opts.ttl; - mntinf.dmode = Opts.dmode; - mntinf.fmode = Opts.fmode; - mntinf.dmask = Opts.dmask; - mntinf.fmask = Opts.fmask; - mntinf.cMaxIoPages = Opts.cMaxIoPages; - mntinf.szTag[0] = '\0'; + struct utsname uts; + AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0")); - strcpy(mntinf.name, pszShareName); - strcpy(mntinf.nls_name, "\0"); + unsigned long const fFlags = MS_NODEV; + char szOpts[MAX_MNTOPT_STR] = { '\0' }; + ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000", + grp_vboxsf->gr_gid); + if (cchOpts > 0 && RTStrVersionCompare(uts.release, "2.6.0") < 0) + cchOpts = RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pszShareName); + if (cchOpts <= 0) + { + VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd (share %s)\n", cchOpts, pszShareName); + return VERR_BUFFER_OVERFLOW; + } int r = mount(pszShareName, pszMountPoint, "vboxsf", fFlags, - &mntinf); + szOpts); if (r == 0) { VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint); - r = vbsfmount_complete(pszShareName, pszMountPoint, fFlags, &Opts); + r = vbsfmount_complete(pszShareName, pszMountPoint, fFlags, szOpts); switch (r) { case 0: /* Success. */ errno = 0; /* Clear all errors/warnings. */ break; - case 1: - VGSvcError("vbsvcAutoMountWorker: Could not update mount table (failed to create memstream): %s\n", - strerror(errno)); + VGSvcError("vbsvcAutoMountWorker: Could not update mount table (malloc failure)\n"); break; - case 2: VGSvcError("vbsvcAutoMountWorker: Could not open mount table for update: %s\n", strerror(errno)); break; - case 3: /* VGSvcError("vbsvcAutoMountWorker: Could not add an entry to the mount table: %s\n", strerror(errno)); */ errno = 0; break; - default: VGSvcError("vbsvcAutoMountWorker: Unknown error while completing mount operation: %d\n", r); break; @@ -484,52 +442,22 @@ } else /* r == -1, we got some error in errno. */ { - if (errno == EPROTO) - { - VGSvcVerbose(3, "vbsvcAutoMountWorker: Messed up share name, re-trying ...\n"); - - /** @todo r=bird: What on earth is going on here????? Why can't you - * strcpy(mntinf.name, pszShareName) to fix it again? */ - - /* Sometimes the mount utility messes up the share name. Try to - * un-mangle it again. */ - char szCWD[RTPATH_MAX]; - size_t cchCWD; - if (!getcwd(szCWD, sizeof(szCWD))) - { - VGSvcError("vbsvcAutoMountWorker: Failed to get the current working directory\n"); - szCWD[0] = '\0'; - } - cchCWD = strlen(szCWD); - if (!strncmp(pszMountPoint, szCWD, cchCWD)) - { - while (pszMountPoint[cchCWD] == '/') - ++cchCWD; - /* We checked before that we have enough space */ - strcpy(mntinf.name, pszMountPoint + cchCWD); - } - r = mount(mntinf.name, pszMountPoint, "vboxsf", fFlags, &mntinf); - } - if (r == -1) /* Was there some error from one of the tries above? */ + switch (errno) { - switch (errno) - { - /* If we get EINVAL here, the system already has mounted the Shared Folder to another - * mount point. */ - case EINVAL: - VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' already is mounted!\n", pszShareName); - /* Ignore this error! */ - break; - case EBUSY: - /* Ignore these errors! */ - break; - - default: - VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s': %s (%d)\n", - pszShareName, pszMountPoint, strerror(errno), errno); - rc = RTErrConvertFromErrno(errno); - break; - } + /* If we get EINVAL here, the system already has mounted the Shared Folder to another + * mount point. */ + case EINVAL: + VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' is already mounted!\n", pszShareName); + /* Ignore this error! */ + break; + case EBUSY: + /* Ignore these errors! */ + break; + default: + VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s': %s (%d)\n", + pszShareName, pszMountPoint, strerror(errno), errno); + rc = RTErrConvertFromErrno(errno); + break; } } # endif @@ -1527,51 +1455,49 @@ /* * Linux a bit more work... */ - struct vbsf_mount_info_new MntInfo; - RT_ZERO(MntInfo); - struct vbsf_mount_opts MntOpts; - RT_ZERO(MntOpts); - MntInfo.nullchar = '\0'; - MntInfo.signature[0] = VBSF_MOUNT_SIGNATURE_BYTE_0; - MntInfo.signature[1] = VBSF_MOUNT_SIGNATURE_BYTE_1; - MntInfo.signature[2] = VBSF_MOUNT_SIGNATURE_BYTE_2; - MntInfo.length = sizeof(MntInfo); - MntInfo.ttl = MntOpts.ttl = -1 /*default*/; - MntInfo.msDirCacheTTL= MntOpts.msDirCacheTTL = -1 /*default*/; - MntInfo.msInodeTTL = MntOpts.msInodeTTL = -1 /*default*/; - MntInfo.cMaxIoPages = MntOpts.cMaxIoPages = 0 /*default*/; - MntInfo.cbDirBuf = MntOpts.cbDirBuf = 0 /*default*/; - MntInfo.enmCacheMode = MntOpts.enmCacheMode = kVbsfCacheMode_Default; - MntInfo.uid = MntOpts.uid = 0; - MntInfo.gid = MntOpts.gid = gidMount; - MntInfo.dmode = MntOpts.dmode = 0770; - MntInfo.fmode = MntOpts.fmode = 0770; - MntInfo.dmask = MntOpts.dmask = 0000; - MntInfo.fmask = MntOpts.fmask = 0000; - memcpy(MntInfo.szTag, g_szTag, sizeof(g_szTag)); AssertCompile(sizeof(MntInfo.szTag) >= sizeof(g_szTag)); - rc = RTStrCopy(MntInfo.name, sizeof(MntInfo.name), pEntry->pszName); - if (RT_FAILURE(rc)) + struct utsname uts; + AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0")); + + /* Built mount option string. Need st_name for pre 2.6.0 kernels. */ + unsigned long const fFlags = MS_NODEV; + char szOpts[MAX_MNTOPT_STR] = { '\0' }; + ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), + "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag); + if (RTStrVersionCompare(uts.release, "2.6.0") < 0 && cchOpts > 0) + cchOpts += RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pEntry->pszName); + if (cchOpts <= 0) { - VGSvcError("vbsvcAutomounterMountIt: Share name '%s' is too long for the MntInfo.name field!\n", pEntry->pszName); - return rc; + VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts); + return VERR_BUFFER_OVERFLOW; } + /* Do the mounting. The fallback w/o tag is for the Linux vboxsf fork + which lagged a lot behind when it first appeared in 5.6. */ errno = 0; - unsigned long fFlags = MS_NODEV; - rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, &MntInfo); + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts); + if (rc != 0 && errno == EINVAL && RTStrVersionCompare(uts.release, "5.6.0") >= 0) + { + VGSvcVerbose(2, "vbsvcAutomounterMountIt: mount returned EINVAL, retrying without the tag.\n"); + *strstr(szOpts, ",tag=") = '\0'; + errno = 0; + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts); + if (rc == 0) + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Running outdated vboxsf module without support for the 'tag' option?\n"); + } if (rc == 0) { VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", pEntry->pszName, pEntry->pszActualMountPoint); errno = 0; - rc = vbsfmount_complete(pEntry->pszName, pEntry->pszActualMountPoint, fFlags, &MntOpts); + rc = vbsfmount_complete(pEntry->pszName, pEntry->pszActualMountPoint, fFlags, szOpts); if (rc != 0) /* Ignorable. /etc/mtab is probably a link to /proc/mounts. */ VGSvcVerbose(1, "vbsvcAutomounterMountIt: vbsfmount_complete failed: %s (%d/%d)\n", - rc == 1 ? "open_memstream" : rc == 2 ? "setmntent" : rc == 3 ? "addmntent" : "unknown", rc, errno); + rc == 1 ? "malloc" : rc == 2 ? "setmntent" : rc == 3 ? "addmntent" : "unknown", rc, errno); return VINF_SUCCESS; } - else if (errno == EINVAL) + + if (errno == EINVAL) VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s' because it is probably mounted elsewhere arleady! (%d,%d)\n", pEntry->pszName, pEntry->pszActualMountPoint, rc, errno); else diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp 2022-09-01 13:20:47.000000000 +0000 @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -962,9 +963,11 @@ /** - * Expands a file name / path to its real content. This only works on Windows - * for now (e.g. translating "%TEMP%\foo.exe" to "C:\Windows\Temp" when starting - * with system / administrative rights). + * Expands a file name / path to its real content. + * + * ~~This only works on Windows for now (e.g. translating "%TEMP%\foo.exe" to + * "C:\Windows\Temp" when starting with system / administrative rights).~~ See + * todo in code. * * @return IPRT status code. * @param pszPath Path to resolve. @@ -973,7 +976,6 @@ */ static int vgsvcGstCtrlProcessMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded) { - int rc = VINF_SUCCESS; /** @todo r=bird: This feature shall be made optional, i.e. require a * flag to be passed down. Further, it shall work on the environment * block of the new process (i.e. include env changes passed down from @@ -982,12 +984,13 @@ * * Since this currently not available on non-windows guests, I suggest * we disable it until such a time as it is implemented correctly. */ -#ifdef RT_OS_WINDOWS +#if 0 /*def RT_OS_WINDOWS - see above. Don't know why this wasn't disabled before 7.0, didn't see the @todo yet? */ + int rc = VINF_SUCCESS; if (!ExpandEnvironmentStrings(pszPath, pszExpanded, (DWORD)cbExpanded)) rc = RTErrConvertFromWin32(GetLastError()); #else - /* No expansion for non-Windows yet. */ - rc = RTStrCopy(pszExpanded, cbExpanded, pszPath); + /* There is no expansion anywhere yet, see above @todo. */ + int rc = RTStrCopy(pszExpanded, cbExpanded, pszPath); #endif #ifdef DEBUG VGSvcVerbose(3, "vgsvcGstCtrlProcessMakeFullPath: %s -> %s\n", pszPath, pszExpanded); @@ -997,46 +1000,30 @@ /** - * Resolves the full path of a specified executable name. This function also - * resolves internal VBoxService tools to its appropriate executable path + name if - * VBOXSERVICE_NAME is specified as pszFileName. + * Resolves the full path of a specified executable name. + * + * This function also resolves internal VBoxService tools to its appropriate + * executable path + name if VBOXSERVICE_NAME is specified as pszFilename. * * @return IPRT status code. - * @param pszFileName File name to resolve. + * @param pszFilename File name to resolve. * @param pszResolved Pointer to a string where the resolved file name will be stored. * @param cbResolved Size (in bytes) of resolved file name string. */ -static int vgsvcGstCtrlProcessResolveExecutable(const char *pszFileName, char *pszResolved, size_t cbResolved) +static int vgsvcGstCtrlProcessResolveExecutable(const char *pszFilename, char *pszResolved, size_t cbResolved) { - AssertPtrReturn(pszFileName, VERR_INVALID_POINTER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); AssertPtrReturn(pszResolved, VERR_INVALID_POINTER); AssertReturn(cbResolved, VERR_INVALID_PARAMETER); - int rc = VINF_SUCCESS; - - char szPathToResolve[RTPATH_MAX]; - if ( (g_pszProgName && (RTStrICmp(pszFileName, g_pszProgName) == 0)) - || !RTStrICmp(pszFileName, VBOXSERVICE_NAME)) - { - /* Resolve executable name of this process. */ - if (!RTProcGetExecutablePath(szPathToResolve, sizeof(szPathToResolve))) - rc = VERR_FILE_NOT_FOUND; - } - else - { - /* Take the raw argument to resolve. */ - rc = RTStrCopy(szPathToResolve, sizeof(szPathToResolve), pszFileName); - } + const char * const pszOrgFilename = pszFilename; + if ( RTStrICmp(pszFilename, g_pszProgName) == 0 + || RTStrICmp(pszFilename, VBOXSERVICE_NAME) == 0) + pszFilename = RTProcExecutablePath(); + int rc = vgsvcGstCtrlProcessMakeFullPath(pszFilename, pszResolved, cbResolved); if (RT_SUCCESS(rc)) - { - rc = vgsvcGstCtrlProcessMakeFullPath(szPathToResolve, pszResolved, cbResolved); - if (RT_SUCCESS(rc)) - VGSvcVerbose(3, "Looked up executable: %s -> %s\n", pszFileName, pszResolved); - } - - if (RT_FAILURE(rc)) - VGSvcError("Failed to lookup executable '%s' with rc=%Rrc\n", pszFileName, rc); + VGSvcVerbose(3, "Looked up executable: %s -> %s\n", pszOrgFilename, pszResolved); return rc; } @@ -1049,18 +1036,25 @@ * @param pszArgv0 First argument (argv0), either original or modified version. * @param papszArgs Original argv command line from the host, starting at argv[1]. * @param fFlags The process creation flags pass to us from the host. + * @param fExecutingSelf Set if we're executing the VBoxService executable + * and should inject the --utf8-argv trick. * @param ppapszArgv Pointer to a pointer with the new argv command line. * Needs to be freed with RTGetOptArgvFree. */ static int vgsvcGstCtrlProcessAllocateArgv(const char *pszArgv0, const char * const *papszArgs, uint32_t fFlags, - char ***ppapszArgv) + bool fExecutingSelf, char ***ppapszArgv) { - VGSvcVerbose(3, "VGSvcGstCtrlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fFlags=%#x, ppapszArgv=%p\n", - pszArgv0, papszArgs, fFlags, ppapszArgv); + VGSvcVerbose(3, "VGSvcGstCtrlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fFlags=%#x, fExecutingSelf=%d, ppapszArgv=%p\n", + pszArgv0, papszArgs, fFlags, fExecutingSelf, ppapszArgv); AssertPtrReturn(pszArgv0, VERR_INVALID_POINTER); AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER); +#ifndef VBOXSERVICE_ARG1_UTF8_ARGV + fExecutingSelf = false; +#endif + + /* Count arguments: */ int rc = VINF_SUCCESS; uint32_t cArgs; for (cArgs = 0; papszArgs[cArgs]; cArgs++) @@ -1070,7 +1064,7 @@ } /* Allocate new argv vector (adding + 2 for argv0 + termination). */ - size_t cbSize = (cArgs + 2) * sizeof(char *); + size_t cbSize = (fExecutingSelf + cArgs + 2) * sizeof(char *); char **papszNewArgv = (char **)RTMemAlloc(cbSize); if (!papszNewArgv) return VERR_NO_MEMORY; @@ -1088,6 +1082,10 @@ argument separately from the executable image, so we have to fudge a little in the unquoted argument case to deal with executables containing spaces. */ + /** @todo r=bird: WTF!?? This makes absolutely no sense on non-windows. An + * on windows the first flag test must be inverted, as it's when RTProcCreateEx + * doesn't do any quoting that we have to do it here, isn't it? + * Aaaaaaaaaaaaaaaaaaarrrrrrrrrrrrrrrrrrrrrrrrrrgggggggggggggggggggggggg! */ if ( !(fFlags & EXECUTEPROCESSFLAG_UNQUOTED_ARGS) || !strpbrk(pszArgv0, " \t\n\r") || pszArgv0[0] == '"') @@ -1112,50 +1110,60 @@ if (RT_SUCCESS(rc)) { - size_t i; - for (i = 0; i < cArgs; i++) + size_t iDst = 1; + +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + /* Insert --utf8-argv as the first argument if executing the VBoxService binary. */ + if (fExecutingSelf) { - char *pszArg; -#if 0 /* Arguments expansion -- untested. */ - if (fFlags & EXECUTEPROCESSFLAG_EXPAND_ARGUMENTS) + rc = RTStrDupEx(&papszNewArgv[iDst], VBOXSERVICE_ARG1_UTF8_ARGV); + if (RT_SUCCESS(rc)) + iDst++; + } +#endif + /* Copy over the other arguments. */ + if (RT_SUCCESS(rc)) + for (size_t iSrc = 0; iSrc < cArgs; iSrc++) { +#if 0 /* Arguments expansion -- untested. */ + if (fFlags & EXECUTEPROCESSFLAG_EXPAND_ARGUMENTS) + { /** @todo r=bird: If you want this, we need a generic implementation, preferably in RTEnv or somewhere like that. The marking * up of the variables must be the same on all platforms. */ - /* According to MSDN the limit on older Windows version is 32K, whereas - * Vista+ there are no limits anymore. We still stick to 4K. */ - char szExpanded[_4K]; + /* According to MSDN the limit on older Windows version is 32K, whereas + * Vista+ there are no limits anymore. We still stick to 4K. */ + char szExpanded[_4K]; # ifdef RT_OS_WINDOWS - if (!ExpandEnvironmentStrings(papszArgs[i], szExpanded, sizeof(szExpanded))) - rc = RTErrConvertFromWin32(GetLastError()); + if (!ExpandEnvironmentStrings(papszArgs[i], szExpanded, sizeof(szExpanded))) + rc = RTErrConvertFromWin32(GetLastError()); # else - /* No expansion for non-Windows yet. */ - rc = RTStrCopy(papszArgs[i], sizeof(szExpanded), szExpanded); + /* No expansion for non-Windows yet. */ + rc = RTStrCopy(papszArgs[i], sizeof(szExpanded), szExpanded); # endif + if (RT_SUCCESS(rc)) + rc = RTStrDupEx(&pszArg, szExpanded); + } + else +#endif + rc = RTStrDupEx(&papszNewArgv[iDst], papszArgs[iSrc]); if (RT_SUCCESS(rc)) - rc = RTStrDupEx(&pszArg, szExpanded); + iDst++; + else + break; } - else -#endif - rc = RTStrDupEx(&pszArg, papszArgs[i]); - - if (RT_FAILURE(rc)) - break; - - papszNewArgv[i + 1] = pszArg; - } if (RT_SUCCESS(rc)) { /* Terminate array. */ - papszNewArgv[cArgs + 1] = NULL; + papszNewArgv[iDst] = NULL; *ppapszArgv = papszNewArgv; return VINF_SUCCESS; } /* Failed, bail out. */ - for (; i > 0; i--) - RTStrFree(papszNewArgv[i]); + while (iDst-- > 0) + RTStrFree(papszNewArgv[iDst]); } RTMemFree(papszNewArgv); return rc; @@ -1282,13 +1290,7 @@ * On Windows Vista (and up) sysprep is located in "system32\\Sysprep\\sysprep.exe", * so detect the OS and use a different path. */ - OSVERSIONINFOEX OSInfoEx; - RT_ZERO(OSInfoEx); - OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - BOOL fRet = GetVersionEx((LPOSVERSIONINFO) &OSInfoEx); - if ( fRet - && OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT - && OSInfoEx.dwMajorVersion >= 6 /* Vista or later */) + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6,0,0) /* Vista and later */) { rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL); #ifndef RT_ARCH_AMD64 @@ -1311,15 +1313,14 @@ if (RT_FAILURE(rc)) VGSvcError("Failed to detect sysrep location, rc=%Rrc\n", rc); } - else if (!fRet) - VGSvcError("Failed to retrieve OS information, last error=%ld\n", GetLastError()); VGSvcVerbose(3, "Sysprep executable is: %s\n", szSysprepCmd); if (RT_SUCCESS(rc)) { char **papszArgsExp; - rc = vgsvcGstCtrlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, fFlags, &papszArgsExp); + rc = vgsvcGstCtrlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, fFlags, + false /*fExecutingSelf*/, &papszArgsExp); if (RT_SUCCESS(rc)) { /* As we don't specify credentials for the sysprep process, it will @@ -1339,11 +1340,13 @@ } #endif /* RT_OS_WINDOWS */ + bool fExecutingSelf = false; #ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX - if (RTStrStr(pszExec, "vbox_") == pszExec) + if (RTStrStr(pszExec, "vbox_") == pszExec) /** @todo WTF search the whole string for "vbox_" when all you want is to know if whether string starts with "vbox_" or not. geee^2 */ { /* We want to use the internal toolbox (all internal * tools are starting with "vbox_" (e.g. "vbox_cat"). */ + fExecutingSelf = true; rc = vgsvcGstCtrlProcessResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp)); } else @@ -1380,7 +1383,7 @@ fHasArgv0, pcszArgv0, uArgvIdx, g_fControlHostFeatures0); char **papszArgsExp; - rc = vgsvcGstCtrlProcessAllocateArgv(pcszArgv0, &papszArgs[uArgvIdx], fFlags, &papszArgsExp); + rc = vgsvcGstCtrlProcessAllocateArgv(pcszArgv0, &papszArgs[uArgvIdx], fFlags, fExecutingSelf, &papszArgsExp); if (RT_FAILURE(rc)) { /* Don't print any arguments -- may contain passwords or other sensible data! */ @@ -1388,15 +1391,17 @@ } else { - uint32_t uProcFlags = 0; + uint32_t fProcCreateFlags = 0; + if (fExecutingSelf) + fProcCreateFlags |= VBOXSERVICE_PROC_F_UTF8_ARGV; if (fFlags) { if (fFlags & EXECUTEPROCESSFLAG_HIDDEN) - uProcFlags |= RTPROC_FLAGS_HIDDEN; + fProcCreateFlags |= RTPROC_FLAGS_HIDDEN; if (fFlags & EXECUTEPROCESSFLAG_PROFILE) - uProcFlags |= RTPROC_FLAGS_PROFILE; + fProcCreateFlags |= RTPROC_FLAGS_PROFILE; if (fFlags & EXECUTEPROCESSFLAG_UNQUOTED_ARGS) - uProcFlags |= RTPROC_FLAGS_UNQUOTED_ARGS; + fProcCreateFlags |= RTPROC_FLAGS_UNQUOTED_ARGS; } /* If no user name specified run with current credentials (e.g. @@ -1406,11 +1411,11 @@ * code (at least on Windows) for running processes as different users * started from our system service. */ if (pszAsUser && *pszAsUser) - uProcFlags |= RTPROC_FLAGS_SERVICE; + fProcCreateFlags |= RTPROC_FLAGS_SERVICE; #ifdef DEBUG VGSvcVerbose(3, "Command: %s\n", szExecExp); for (size_t i = 0; papszArgsExp[i]; i++) - VGSvcVerbose(3, "\targv[%ld]: %s\n", i, papszArgsExp[i]); + VGSvcVerbose(3, " argv[%zu]: %s\n", i, papszArgsExp[i]); #endif VGSvcVerbose(3, "Starting process '%s' ...\n", szExecExp); @@ -1420,7 +1425,7 @@ * the domain name built-in, e.g. "joedoe@example.com". */ char *pszUserUPN = NULL; if ( pszDomain - && strlen(pszDomain)) + && *pszDomain != '\0') { int cbUserUPN = RTStrAPrintf(&pszUserUPN, "%s@%s", pszAsUser, pszDomain); if (cbUserUPN > 0) @@ -1432,7 +1437,7 @@ #endif /* Do normal execution. */ - rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, uProcFlags, + rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, fProcCreateFlags, phStdIn, phStdOut, phStdErr, pszUser, pszPassword && *pszPassword ? pszPassword : NULL, @@ -2063,7 +2068,7 @@ static int vgsvcGstCtrlProcessRequestExV(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fAsync, - RTMSINTERVAL uTimeoutMS, PRTREQ pReq, PFNRT pfnFunction, unsigned cArgs, va_list Args) + RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, va_list Args) { RT_NOREF1(pHostCtx); AssertPtrReturn(pProcess, VERR_INVALID_POINTER); @@ -2086,7 +2091,9 @@ fFlags |= RTREQFLAGS_NO_WAIT; } - rc = RTReqQueueCallV(pProcess->hReqQueue, &pReq, uTimeoutMS, fFlags, pfnFunction, cArgs, Args); + PRTREQ hReq = NIL_RTREQ; + rc = RTReqQueueCallV(pProcess->hReqQueue, &hReq, uTimeoutMS, fFlags, pfnFunction, cArgs, Args); + RTReqRelease(hReq); if (RT_SUCCESS(rc)) { /* Wake up the process' notification pipe to get @@ -2130,7 +2137,7 @@ va_list va; va_start(va, cArgs); int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, true /* fAsync */, 0 /* uTimeoutMS */, - NULL /* pReq */, pfnFunction, cArgs, va); + pfnFunction, cArgs, va); va_end(va); return rc; @@ -2139,7 +2146,7 @@ #if 0 /* unused */ static int vgsvcGstCtrlProcessRequestWait(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, - RTMSINTERVAL uTimeoutMS, PRTREQ pReq, PFNRT pfnFunction, unsigned cArgs, ...) + RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, ...) { AssertPtrReturn(pProcess, VERR_INVALID_POINTER); /* pHostCtx is optional. */ @@ -2148,7 +2155,7 @@ va_list va; va_start(va, cArgs); int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, false /* fAsync */, uTimeoutMS, - pReq, pfnFunction, cArgs, va); + pfnFunction, cArgs, va); va_end(va); return rc; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp 2022-09-01 13:20:47.000000000 +0000 @@ -2250,6 +2250,9 @@ char const *apszArgs[24]; apszArgs[idxArg++] = pszExeName; +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + apszArgs[idxArg++] = VBOXSERVICE_ARG1_UTF8_ARGV; Assert(idxArg == 2); +#endif apszArgs[idxArg++] = "guestsession"; apszArgs[idxArg++] = szParmSessionID; apszArgs[idxArg++] = szParmSessionProto; @@ -2346,7 +2349,7 @@ | RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_HIDDEN #endif - ; + | VBOXSERVICE_PROC_F_UTF8_ARGV; /* * Configure standard handles. @@ -2630,6 +2633,11 @@ VbglR3GuestCtrlSessionStartupInfoFree(pThread->pStartupInfo); pThread->pStartupInfo = NULL; + RTPipeClose(pThread->hKeyPipe); + pThread->hKeyPipe = NIL_RTPIPE; + + RTCritSectDelete(&pThread->CritSect); + /* Remove session from list and destroy object. */ RTListNodeRemove(&pThread->Node); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxService.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxService.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxService.cpp 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxService.cpp 2022-09-01 13:20:47.000000000 +0000 @@ -928,10 +928,24 @@ /* * Init globals and such. + * + * Note! The --utf8-argv stuff is an internal hack to avoid locale configuration + * issues preventing us from passing non-ASCII string to child processes. */ - int rc = RTR3InitExe(argc, &argv, 0); + uint32_t fIprtFlags = 0; +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + if (argc > 1 && strcmp(argv[1], VBOXSERVICE_ARG1_UTF8_ARGV) == 0) + { + argv[1] = argv[0]; + argv++; + argc--; + fIprtFlags |= RTR3INIT_FLAGS_UTF8_ARGV; + } +#endif + int rc = RTR3InitExe(argc, &argv, fIprtFlags); if (RT_FAILURE(rc)) return RTMsgInitFailure(rc); + g_pszProgName = RTPathFilename(argv[0]); #ifdef RT_OS_WINDOWS VGSvcWinResolveApis(); @@ -1204,14 +1218,13 @@ RT_ZERO(OSInfoEx); OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + SetLastError(NO_ERROR); HANDLE hMutexAppRunning; - if ( GetVersionEx((LPOSVERSIONINFO)&OSInfoEx) - && OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT - && OSInfoEx.dwMajorVersion >= 5 /* NT 5.0 a.k.a W2K */) - hMutexAppRunning = CreateMutex(NULL, FALSE, "Global\\" VBOXSERVICE_NAME); + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(5,0,0)) /* Windows 2000 */ + hMutexAppRunning = CreateMutexW(NULL, FALSE, L"Global\\" RT_CONCAT(L,VBOXSERVICE_NAME)); else - hMutexAppRunning = CreateMutex(NULL, FALSE, VBOXSERVICE_NAME); + hMutexAppRunning = CreateMutexW(NULL, FALSE, RT_CONCAT(L,VBOXSERVICE_NAME)); if (hMutexAppRunning == NULL) { DWORD dwErr = GetLastError(); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h 2020-10-16 16:30:10.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h 2022-09-01 13:20:48.000000000 +0000 @@ -35,6 +35,28 @@ #include #include + +#if !defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING) +/** Special argv[1] value that indicates that argv is UTF-8. + * This causes RTR3Init to be called with RTR3INIT_FLAGS_UTF8_ARGV and helps + * work around potential issues caused by a user's locale config not being + * UTF-8. See @bugref{10153}. + * + * @note We don't need this on windows and it would be harmful to enable it + * as the argc/argv vs __argc/__argv comparison would fail and we would + * not use the unicode command line to create a UTF-8 argv. Since the + * original argv is ANSI, it may be missing codepoints not present in + * the ANSI code page of the process. */ +# define VBOXSERVICE_ARG1_UTF8_ARGV "--utf8-argv" +#endif +/** RTProcCreateEx flags corresponding to VBOXSERVICE_ARG1_UTF8_ARGV. */ +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV +# define VBOXSERVICE_PROC_F_UTF8_ARGV RTPROC_FLAGS_UTF8_ARGV +#else +# define VBOXSERVICE_PROC_F_UTF8_ARGV 0 +#endif + + /** * A service descriptor. */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp 2020-10-16 16:30:11.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp 2022-09-01 13:20:48.000000000 +0000 @@ -1712,7 +1712,14 @@ * when the first VBoxService argument is --use-toolbox. */ if (argc < 2 || strcmp(argv[1], "--use-toolbox")) + { + /** @todo must check for 'vbox_' and fail with a complaint that the tool does + * not exist, because vgsvcGstCtrlProcessResolveExecutable will send + * us anything with 'vbox_' as a prefix and no absolute path. So, + * handing non-existing vbox_xxx tools to the regular VBoxService main + * routine is inconsistent and bound to cause trouble. */ return false; + } /* No tool specified? Show toolbox help. */ if (argc < 3) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp 2020-10-16 16:30:11.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp 2022-09-01 13:20:48.000000000 +0000 @@ -1326,7 +1326,7 @@ struct lifreq IfReq; RT_ZERO(IfReq); AssertCompile(sizeof(IfReq.lifr_name) >= sizeof(pCur->ifr_name)); - strncpy(IfReq.lifr_name, pCur->ifr_name, sizeof(pCur->ifr_name)); + strncpy(IfReq.lifr_name, pCur->ifr_name, sizeof(IfReq.lifr_name)); if (ioctl(sd, SIOCGLIFADDR, &IfReq) >= 0) { struct arpreq ArpReq; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp 2020-10-16 16:30:11.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp 2022-09-01 13:20:48.000000000 +0000 @@ -133,9 +133,6 @@ /** @} */ -/** Windows version. */ -static OSVERSIONINFOEXA g_WinVersion; - /** * An RTOnce callback function. @@ -146,7 +143,7 @@ /* SECUR32 */ RTLDRMOD hLdrMod; - int rc = RTLdrLoadSystem("secur32.dll", true, &hLdrMod); + int rc = RTLdrLoadSystem("secur32.dll", true /*fNoUnload*/, &hLdrMod); if (RT_SUCCESS(rc)) { rc = RTLdrGetSymbol(hLdrMod, "LsaGetLogonSessionData", (void **)&g_pfnLsaGetLogonSessionData); @@ -163,11 +160,11 @@ g_pfnLsaGetLogonSessionData = NULL; g_pfnLsaEnumerateLogonSessions = NULL; g_pfnLsaFreeReturnBuffer = NULL; - Assert(g_WinVersion.dwMajorVersion < 5); + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); } /* WTSAPI32 */ - rc = RTLdrLoadSystem("wtsapi32.dll", true, &hLdrMod); + rc = RTLdrLoadSystem("wtsapi32.dll", true /*fNoUnload*/, &hLdrMod); if (RT_SUCCESS(rc)) { rc = RTLdrGetSymbol(hLdrMod, "WTSFreeMemory", (void **)&g_pfnWTSFreeMemory); @@ -181,11 +178,11 @@ VGSvcVerbose(1, "WtsApi32.dll APIs are not available (%Rrc)\n", rc); g_pfnWTSFreeMemory = NULL; g_pfnWTSQuerySessionInformationA = NULL; - Assert(g_WinVersion.dwMajorVersion < 5); + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); } /* PSAPI */ - rc = RTLdrLoadSystem("psapi.dll", true, &hLdrMod); + rc = RTLdrLoadSystem("psapi.dll", true /*fNoUnload*/, &hLdrMod); if (RT_SUCCESS(rc)) { rc = RTLdrGetSymbol(hLdrMod, "EnumProcesses", (void **)&g_pfnEnumProcesses); @@ -199,43 +196,27 @@ VGSvcVerbose(1, "psapi.dll APIs are not available (%Rrc)\n", rc); g_pfnEnumProcesses = NULL; g_pfnGetModuleFileNameExW = NULL; - Assert(g_WinVersion.dwMajorVersion < 5); + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); } /* Kernel32: */ - rc = RTLdrLoadSystem("kernel32.dll", true, &hLdrMod); + rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod); AssertRCReturn(rc, rc); rc = RTLdrGetSymbol(hLdrMod, "QueryFullProcessImageNameW", (void **)&g_pfnQueryFullProcessImageNameW); if (RT_FAILURE(rc)) { - Assert(g_WinVersion.dwMajorVersion < 6); + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)); g_pfnQueryFullProcessImageNameW = NULL; } RTLdrClose(hLdrMod); - /* - * Get the extended windows version once and for all. - */ - g_WinVersion.dwOSVersionInfoSize = sizeof(g_WinVersion); - if (!GetVersionExA((OSVERSIONINFO *)&g_WinVersion)) - { - RT_ZERO(g_WinVersion); - g_WinVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - if (!GetVersionExA((OSVERSIONINFO *)&g_WinVersion)) - { - AssertFailed(); - RT_ZERO(g_WinVersion); - } - } - return VINF_SUCCESS; } static bool vgsvcVMInfoSession0Separation(void) { - return g_WinVersion.dwPlatformId == VER_PLATFORM_WIN32_NT - && g_WinVersion.dwMajorVersion >= 6; /* Vista = 6.0 */ + return RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0); /* Vista */ } @@ -244,61 +225,57 @@ * * @return IPRT status code. */ -static int vgsvcVMInfoWinProcessesGetModuleNameA(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName) +static int vgsvcVMInfoWinProcessesGetModuleNameW(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName) { - AssertPtrReturn(pProc, VERR_INVALID_POINTER); + *ppszName = NULL; AssertPtrReturn(ppszName, VERR_INVALID_POINTER); + AssertPtrReturn(pProc, VERR_INVALID_POINTER); + AssertReturn(g_pfnGetModuleFileNameExW || g_pfnQueryFullProcessImageNameW, VERR_NOT_SUPPORTED); - /** @todo Only do this once. Later. */ - /* Platform other than NT (e.g. Win9x) not supported. */ - if (g_WinVersion.dwPlatformId != VER_PLATFORM_WIN32_NT) - return VERR_NOT_SUPPORTED; - - int rc = VINF_SUCCESS; - + /* + * Open the process. + */ DWORD dwFlags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; - if (g_WinVersion.dwMajorVersion >= 6 /* Vista or later */) + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)) /* Vista and later */ dwFlags = PROCESS_QUERY_LIMITED_INFORMATION; /* possible to do on more processes */ - HANDLE h = OpenProcess(dwFlags, FALSE, pProc->id); - if (h == NULL) + HANDLE hProcess = OpenProcess(dwFlags, FALSE, pProc->id); + if (hProcess == NULL) { DWORD dwErr = GetLastError(); if (g_cVerbosity) VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr); - rc = RTErrConvertFromWin32(dwErr); + return RTErrConvertFromWin32(dwErr); } + + /* + * Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps + * cannot query 64-bit apps and vice verse) we have to use a different code + * path for Vista and up. + * + * So use QueryFullProcessImageNameW when available (Vista+), fall back on + * GetModuleFileNameExW on older windows version ( + */ + WCHAR wszName[_1K]; + DWORD dwLen = _1K; + BOOL fRc; + if (g_pfnQueryFullProcessImageNameW) + fRc = g_pfnQueryFullProcessImageNameW(hProcess, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen); else - { - /* Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps cannot query 64-bit - apps and vice verse) we have to use a different code path for Vista and up. */ - WCHAR wszName[_1K]; - DWORD dwLen = sizeof(wszName); /** @todo r=bird: wrong? */ - - /* Use QueryFullProcessImageNameW if available (Vista+). */ - if (g_pfnQueryFullProcessImageNameW) - { - if (!g_pfnQueryFullProcessImageNameW(h, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen)) - rc = VERR_ACCESS_DENIED; - } - else if (!g_pfnGetModuleFileNameExW(h, NULL /* Get main executable */, wszName, dwLen)) - rc = VERR_ACCESS_DENIED; - - if ( RT_FAILURE(rc) - && g_cVerbosity > 3) - VGSvcError("Unable to retrieve process name for PID=%u, error=%u\n", pProc->id, GetLastError()); - else - { - PRTUTF16 pszName = RTUtf16Dup(wszName); - if (pszName) - *ppszName = pszName; - else - rc = VERR_NO_MEMORY; - } + fRc = g_pfnGetModuleFileNameExW(hProcess, NULL /* Get main executable */, wszName, dwLen); - CloseHandle(h); + int rc; + if (fRc) + rc = RTUtf16DupEx(ppszName, wszName, 0); + else + { + DWORD dwErr = GetLastError(); + if (g_cVerbosity > 3) + VGSvcError("Unable to retrieve process name for PID=%u, LastError=%Rwc\n", pProc->id, dwErr); + rc = RTErrConvertFromWin32(dwErr); } + CloseHandle(hProcess); return rc; } @@ -686,7 +663,7 @@ if (g_cVerbosity) { PRTUTF16 pszName; - int rc2 = vgsvcVMInfoWinProcessesGetModuleNameA(&paProcs[i], &pszName); + int rc2 = vgsvcVMInfoWinProcessesGetModuleNameW(&paProcs[i], &pszName); VGSvcVerbose(4, "Session %RU32: PID=%u (fInt=%RTbool): %ls\n", pSessionData->Session, paProcs[i].id, paProcs[i].fInteractive, RT_SUCCESS(rc2) ? pszName : L""); @@ -856,29 +833,30 @@ pUserInfo->wszUser, pSessionData->Session, pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage, pUserInfo->wszLogonDomain); - /** - * Note: On certain Windows OSes WTSQuerySessionInformation leaks memory when used - * under a heavy stress situation. There are hotfixes available from Microsoft. + /* KB970910 (check http://support.microsoft.com/kb/970910 on archive.org) + * indicates that WTSQuerySessionInformation may leak memory and return the + * wrong status code for WTSApplicationName and WTSInitialProgram queries. + * + * The system must be low on resources, and presumably some internal operation + * must fail because of this, triggering an error handling path that forgets + * to free memory and set last error. * - * See: http://support.microsoft.com/kb/970910 + * bird 2022-08-26: However, we do not query either of those info items. We + * query WTSConnectState, which is a rather simple affair. So, I've + * re-enabled the code for all systems that includes the API. */ if (!s_fSkipRDPDetection) { - /* Skip RDP detection on non-NT systems. */ - if (g_WinVersion.dwPlatformId != VER_PLATFORM_WIN32_NT) - s_fSkipRDPDetection = true; - - /* Skip RDP detection on Windows 2000. - * For Windows 2000 however we don't have any hotfixes, so just skip the - * RDP detection in any case. */ - if ( g_WinVersion.dwMajorVersion == 5 - && g_WinVersion.dwMinorVersion == 0) - s_fSkipRDPDetection = true; - /* Skip if we don't have the WTS API. */ if (!g_pfnWTSQuerySessionInformationA) s_fSkipRDPDetection = true; - +#if 0 /* bird: see above */ + /* Skip RDP detection on Windows 2000 and older. + For Windows 2000 however we don't have any hotfixes, so just skip the + RDP detection in any case. */ + else if (RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 1, 0)) /* older than XP */ + s_fSkipRDPDetection = true; +#endif if (s_fSkipRDPDetection) VGSvcVerbose(0, "Detection of logged-in users via RDP is disabled\n"); } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/darwin/Installer/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/Additions/darwin/Installer/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Additions/darwin/Installer/Makefile.kmk 2020-10-16 16:30:11.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/darwin/Installer/Makefile.kmk 2022-09-01 13:20:49.000000000 +0000 @@ -185,8 +185,18 @@ --resources $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res \ --identifier org.VirtualBox.mpkg.GuestAdditions \ --version $(VBOX_VERSION_MAJOR).$(VBOX_VERSION_MINOR).$(VBOX_VERSION_BUILD) \ - $(if $(VBOX_MACOSX_INSTALLER_SIGN),--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \ + $(if-expr defined(VBOX_MACOSX_INSTALLER_SIGN) && $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) == "",--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \ $@ +ifdef VBOX_SIGNING_MODE + if $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) + @# Sign the created pkg. + $(call VBOX_SIGN_PKG_FN,$@,org.VirtualBox.mpkg.GuestAdditions) + if $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) + @# Notarize the signed pkg (includes stapling). + $(call VBOX_NOTARIZE_FILE_FN,$@,org.virtualbox.VBoxGuestAdditions.$(VBOX_VERSION_MAJOR).$(VBOX_VERSION_MINOR).$(VBOX_VERSION_BUILD).$(VBOX_SVN_REV)) + endif + endif +endif @# Cleanup. sudo rm -Rf \ @@ -272,8 +282,14 @@ --version $(VBOX_VERSION_MAJOR).$(VBOX_VERSION_MINOR).$(VBOX_VERSION_BUILD) \ --install-location /Library/Extensions/ \ --ownership preserve \ - $(if $(VBOX_MACOSX_INSTALLER_SIGN),--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \ + $(if-expr defined(VBOX_MACOSX_INSTALLER_SIGN) && $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) == "",--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \ $@ +ifdef VBOX_SIGNING_MODE + if $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) + @# Sign the created pkg. + $(call VBOX_SIGN_PKG_FN,$@,org.virtualbox.pkg.vboxguestadditionskexts) + endif +endif @# Cleanup sudo rm -Rf \ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root \ @@ -332,6 +348,9 @@ endif # Add Uninstall.tool $(INSTALL) -m 0755 $(VBOX_ADD_PATH_DI_SRC)/DiskImage/Uninstall.tool "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/" +ifdef VBOX_SIGNING_MODE + $(call VBOX_SIGN_FILE_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/Uninstall.tool,org.virtualbox.app.guestadditions.uninstaller) +endif @# Install launchd stuff $(INSTALL) -m 0755 $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsToolsAndServices/VBoxServiceWrapper \ @@ -341,6 +360,23 @@ $(INSTALL) -m 644 $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxservice.plist \ "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/LaunchDaemons/" + @# Sign the binaries. +ifdef VBOX_SIGNING_MODE + ifdef VBOX_WITH_COMBINED_PACKAGE + $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \ + ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary),org.virtualbox.app.guestadditions.$(notdir $(binary))) ) + $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \ + ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-x86,org.virtualbox.app.guestadditions.$(notdir $(binary))-x86) ) + $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \ + ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-amd64,org.virtualbox.app.guestadditions.$(notdir $(binary))-amd64) ) + else + $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \ + ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary),org.virtualbox.app.guestadditions.$(notdir $(binary))) ) + $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \ + ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-$(KBUILD_TARGET_ARCH),org.virtualbox.app.guestadditions.$(notdir $(binary))-$(KBUILD_TARGET_ARCH)) ) + endif +endif + @# Correct ownership sudo chown -R root:wheel "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/" @@ -352,8 +388,14 @@ --version $(VBOX_VERSION_MAJOR).$(VBOX_VERSION_MINOR).$(VBOX_VERSION_BUILD) \ --install-location "/Library/" \ --ownership preserve \ - $(if $(VBOX_MACOSX_INSTALLER_SIGN),--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \ + $(if-expr defined(VBOX_MACOSX_INSTALLER_SIGN) && $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) == "",--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \ $@ +ifdef VBOX_SIGNING_MODE + if $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) + @# Sign the created pkg. + $(call VBOX_SIGN_PKG_FN,$@,org.virtualbox.pkg.vboxguestadditions) + endif +endif @# Cleanup sudo rm -Rf \ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root \ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_drv.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_drv.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_drv.c 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_drv.c 2022-09-01 13:20:52.000000000 +0000 @@ -43,9 +43,26 @@ # include #endif +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) +# include +#endif + #include "version-generated.h" #include "revision-generated.h" +/** Detect whether kernel mode setting is OFF. */ +#if defined(CONFIG_VGA_CONSOLE) +# if RTLNX_VER_MIN(5,17,0) +# define VBOX_VIDEO_NOMODESET() drm_firmware_drivers_only() && vbox_modeset == -1 +# elif RTLNX_VER_MIN(4,7,0) +# define VBOX_VIDEO_NOMODESET() vgacon_text_force() && vbox_modeset == -1 +# else /* < 4.7.0 */ +# define VBOX_VIDEO_NOMODESET() 0 +# endif /* < 4.7.0 */ +#else /* !CONFIG_VGA_CONSOLE */ +# define VBOX_VIDEO_NOMODESET() 0 +#endif /* !CONFIG_VGA_CONSOLE */ + static int vbox_modeset = -1; MODULE_PARM_DESC(modeset, "Disable/Enable modesetting"); @@ -65,12 +82,27 @@ struct drm_device *dev = NULL; int ret = 0; +# if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) +# if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_MIN(9,1) + ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &driver); +# else + ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "vboxvideofb"); +# endif + if (ret) + { + printk("unable to remove conflicting framebuffer devices\n"); + return ret; + } +# endif /* >= 5.14. */ + dev = drm_dev_alloc(&driver, &pdev->dev); if (IS_ERR(dev)) { ret = PTR_ERR(dev); goto err_drv_alloc; } +# if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) dev->pdev = pdev; +# endif pci_set_drvdata(pdev, dev); ret = vbox_driver_load(dev); @@ -125,7 +157,7 @@ drm_kms_helper_poll_disable(dev); - pci_save_state(dev->pdev); + pci_save_state(VBOX_DRM_TO_PCI_DEV(dev)); drm_fb_helper_set_suspend_unlocked(&vbox->fbdev->helper, true); @@ -147,7 +179,7 @@ { int ret; - if (pci_enable_device(dev->pdev)) + if (pci_enable_device(VBOX_DRM_TO_PCI_DEV(dev))) return -EIO; ret = vbox_drm_thaw(dev); @@ -262,7 +294,7 @@ .read = drm_read, }; -#if RTLNX_VER_MIN(5,9,0) +#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3) static void #else static int @@ -286,7 +318,7 @@ schedule_delayed_work(&vbox->refresh_work, VBOX_REFRESH_PERIOD); mutex_unlock(&vbox->hw_mutex); -#if RTLNX_VER_MAX(5,9,0) +#if RTLNX_VER_MAX(5,9,0) && !RTLNX_RHEL_MAJ_PREREQ(8,4) && !RTLNX_SUSE_MAJ_PREREQ(15,3) return 0; #endif } @@ -310,19 +342,18 @@ } static struct drm_driver driver = { -#if RTLNX_VER_MAX(5,4,0) && !RTLNX_RHEL_MAJ_PREREQ(8,3) +#if RTLNX_VER_MAX(5,4,0) && !RTLNX_RHEL_MAJ_PREREQ(8,3) && !RTLNX_SUSE_MAJ_PREREQ(15,3) .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ | # if RTLNX_VER_MAX(5,1,0) && !RTLNX_RHEL_MAJ_PREREQ(8,1) DRIVER_IRQ_SHARED | # endif DRIVER_PRIME, -#else /* >= 5.4.0 && RHEL >= 8.3 */ - .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ, +#else /* >= 5.4.0 && RHEL >= 8.3 && SLES >= 15-SP3 */ + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ, #endif /* < 5.4.0 */ - .dev_priv_size = 0, #if RTLNX_VER_MAX(4,19,0) && !RTLNX_RHEL_MAJ_PREREQ(8,3) - /* Legacy hooks, but still supported. */ + /* Legacy hooks, but still supported. */ .load = vbox_driver_load, .unload = vbox_driver_unload, #endif @@ -336,7 +367,9 @@ #endif .fops = &vbox_fops, +#if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_MAJ_PREREQ(9,1) .irq_handler = vbox_irq_handler, +#endif .name = DRIVER_NAME, .desc = DRIVER_DESC, .date = DRIVER_DATE, @@ -346,38 +379,51 @@ #if RTLNX_VER_MAX(4,7,0) .gem_free_object = vbox_gem_free_object, -#else - .gem_free_object_unlocked = vbox_gem_free_object, #endif .dumb_create = vbox_dumb_create, .dumb_map_offset = vbox_dumb_mmap_offset, #if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3) .dumb_destroy = vbox_dumb_destroy, -#else +#elif RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) .dumb_destroy = drm_gem_dumb_destroy, #endif .prime_handle_to_fd = drm_gem_prime_handle_to_fd, .prime_fd_to_handle = drm_gem_prime_fd_to_handle, - .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_import_sg_table = vbox_gem_prime_import_sg_table, + .gem_prime_mmap = vbox_gem_prime_mmap, + +#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) + .dev_priv_size = 0, +# if RTLNX_VER_MIN(4,7,0) + .gem_free_object_unlocked = vbox_gem_free_object, +# endif + .gem_prime_export = drm_gem_prime_export, .gem_prime_pin = vbox_gem_prime_pin, .gem_prime_unpin = vbox_gem_prime_unpin, .gem_prime_get_sg_table = vbox_gem_prime_get_sg_table, - .gem_prime_import_sg_table = vbox_gem_prime_import_sg_table, .gem_prime_vmap = vbox_gem_prime_vmap, .gem_prime_vunmap = vbox_gem_prime_vunmap, - .gem_prime_mmap = vbox_gem_prime_mmap, +#endif }; static int __init vbox_init(void) { -#if defined(CONFIG_VGA_CONSOLE) || RTLNX_VER_MIN(4,7,0) - if (vgacon_text_force() && vbox_modeset == -1) + printk("vboxvideo: loading version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n"); + if (VBOX_VIDEO_NOMODESET()) + { + printk("vboxvideo: kernel is running with *nomodeset* parameter,\n"); + printk("vboxvideo: please consider either to remove it or load driver\n"); + printk("vboxvideo: with parameter modeset=1, unloading\n"); return -EINVAL; -#endif + } if (vbox_modeset == 0) + { + printk("vboxvideo: driver loaded with modeset=0 parameter, unloading\n"); return -EINVAL; + } #if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,3) return pci_register_driver(&vbox_pci_driver); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_drv.h virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_drv.h --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_drv.h 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_drv.h 2022-09-01 13:20:53.000000000 +0000 @@ -151,15 +151,17 @@ #define S64_MIN ((s64)(-S64_MAX - 1)) #endif -#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) +#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3) # include # include # include # include # include -# include +# if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_MAJ_PREREQ(9,1) +# include +# endif # include -#else /* < 5.5.0 || RHEL < 8.3 */ +#else /* < 5.5.0 || RHEL < 8.3 || SLES < 15-SP3 */ # include #endif #if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5) @@ -173,8 +175,19 @@ #include #include #include -#include -#include +#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) +# include +#endif +#if RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) +# include +#endif +#if RTLNX_VER_MIN(5,10,0) +# include +#endif + +#if RTLNX_VER_MIN(6,0,0) +# include +#endif #include "vboxvideo_guest.h" #include "vboxvideo_vbe.h" @@ -220,6 +233,15 @@ sizeof(HGSMIHOSTFLAGS)) #define HOST_FLAGS_OFFSET GUEST_HEAP_USABLE_SIZE +/** Field "pdev" of struct drm_device was removed in 5.14. This macro + * transparently handles this change. Input argument is a pointer + * to struct drm_device. */ +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) +# define VBOX_DRM_TO_PCI_DEV(_dev) to_pci_dev(_dev->dev) +#else +# define VBOX_DRM_TO_PCI_DEV(_dev) _dev->pdev +#endif + /** How frequently we refresh if the guest is not providing dirty rectangles. */ #define VBOX_REFRESH_PERIOD (HZ / 2) @@ -258,7 +280,11 @@ struct drm_global_reference mem_global_ref; struct ttm_bo_global_ref bo_global_ref; #endif +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + struct ttm_device bdev; +#else struct ttm_bo_device bdev; +#endif bool mm_initialised; } ttm; @@ -444,7 +470,10 @@ int vbox_gem_create(struct drm_device *dev, u32 size, bool iskernel, struct drm_gem_object **obj); -int vbox_bo_pin(struct vbox_bo *bo, u32 pl_flag, u64 *gpu_addr); +#define VBOX_MEM_TYPE_VRAM 0x1 +#define VBOX_MEM_TYPE_SYSTEM 0x2 + +int vbox_bo_pin(struct vbox_bo *bo, u32 mem_type, u64 *gpu_addr); int vbox_bo_unpin(struct vbox_bo *bo); static inline int vbox_bo_reserve(struct vbox_bo *bo, bool no_wait) @@ -469,7 +498,7 @@ ttm_bo_unreserve(&bo->bo); } -void vbox_ttm_placement(struct vbox_bo *bo, int domain); +void vbox_ttm_placement(struct vbox_bo *bo, u32 mem_type); int vbox_bo_push_sysram(struct vbox_bo *bo); int vbox_mmap(struct file *filp, struct vm_area_struct *vma); @@ -494,7 +523,9 @@ int vbox_irq_init(struct vbox_private *vbox); void vbox_irq_fini(struct vbox_private *vbox); void vbox_report_hotplug(struct vbox_private *vbox); +#if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_MAJ_PREREQ(9,1) irqreturn_t vbox_irq_handler(int irq, void *arg); +#endif /* vbox_hgsmi.c */ void *hgsmi_buffer_alloc(struct gen_pool *guest_pool, size_t size, diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_fb.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_fb.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_fb.c 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_fb.c 2022-09-01 13:20:53.000000000 +0000 @@ -295,13 +295,20 @@ if (ret) return ret; - ret = vbox_bo_pin(bo, TTM_PL_FLAG_VRAM, NULL); + ret = vbox_bo_pin(bo, VBOX_MEM_TYPE_VRAM, NULL); if (ret) { vbox_bo_unreserve(bo); return ret; } +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.resource->num_pages, &bo->kmap); +#elif RTLNX_VER_MIN(5,12,0) || RTLNX_RHEL_MAJ_PREREQ(8,5) + ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.mem.num_pages, &bo->kmap); +#else ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap); +#endif + vbox_bo_unreserve(bo); if (ret) { DRM_ERROR("failed to kmap fbcon\n"); @@ -332,8 +339,8 @@ * This seems to be done for safety checking that the framebuffer * is not registered twice by different drivers. */ - info->apertures->ranges[0].base = pci_resource_start(dev->pdev, 0); - info->apertures->ranges[0].size = pci_resource_len(dev->pdev, 0); + info->apertures->ranges[0].base = pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0); + info->apertures->ranges[0].size = pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0); #if RTLNX_VER_MIN(5,2,0) || RTLNX_RHEL_MAJ_PREREQ(8,2) /* @@ -355,6 +362,9 @@ info->screen_size = size; #ifdef CONFIG_FB_DEFERRED_IO +# if RTLNX_VER_MIN(5,19,0) + info->fix.smem_len = info->screen_size; +# endif info->fbdefio = &vbox_defio; fb_deferred_io_init(info); #endif @@ -405,7 +415,7 @@ vbox_bo_unpin(bo); vbox_bo_unreserve(bo); } -#if RTLNX_VER_MIN(5,9,0) +#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3) drm_gem_object_put(afb->obj); #else drm_gem_object_put_unlocked(afb->obj); @@ -438,7 +448,7 @@ #else drm_fb_helper_prepare(dev, &fbdev->helper, &vbox_fb_helper_funcs); #endif -#if RTLNX_VER_MIN(5,7,0) +#if RTLNX_VER_MIN(5,7,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3) ret = drm_fb_helper_init(dev, &fbdev->helper); #elif RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5) ret = drm_fb_helper_init(dev, &fbdev->helper, vbox->num_crtcs); @@ -450,7 +460,7 @@ if (ret) return ret; -#if RTLNX_VER_MAX(5,7,0) +#if RTLNX_VER_MAX(5,7,0) && !RTLNX_RHEL_MAJ_PREREQ(8,4) && !RTLNX_SUSE_MAJ_PREREQ(15,3) ret = drm_fb_helper_single_add_all_connectors(&fbdev->helper); if (ret) goto err_fini; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_irq.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_irq.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_irq.c 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_irq.c 2022-09-01 13:20:53.000000000 +0000 @@ -205,8 +205,10 @@ { INIT_WORK(&vbox->hotplug_work, vbox_hotplug_worker); vbox_update_mode_hints(vbox); -#if RTLNX_VER_MIN(3,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,1) - return drm_irq_install(vbox->dev, vbox->dev->pdev->irq); +#if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_MAJ_PREREQ(9,1) + return request_irq(VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq, vbox_irq_handler, IRQF_SHARED, vbox->dev->driver->name, vbox->dev); +#elif RTLNX_VER_MIN(3,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,1) + return drm_irq_install(vbox->dev, VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq); #else return drm_irq_install(vbox->dev); #endif @@ -214,6 +216,10 @@ void vbox_irq_fini(struct vbox_private *vbox) { +#if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_MAJ_PREREQ(9,1) + free_irq(VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq, vbox->dev); +#else drm_irq_uninstall(vbox->dev); +#endif flush_work(&vbox->hotplug_work); } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_main.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_main.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_main.c 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_main.c 2022-09-01 13:20:53.000000000 +0000 @@ -46,7 +46,7 @@ struct vbox_framebuffer *vbox_fb = to_vbox_framebuffer(fb); if (vbox_fb->obj) -#if RTLNX_VER_MIN(5,9,0) +#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3) drm_gem_object_put(vbox_fb->obj); #else drm_gem_object_put_unlocked(vbox_fb->obj); @@ -225,7 +225,7 @@ err_free_vbox_fb: kfree(vbox_fb); err_unref_obj: -#if RTLNX_VER_MIN(5,9,0) +#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3) drm_gem_object_put(obj); #else drm_gem_object_put_unlocked(obj); @@ -290,7 +290,7 @@ /* Take a command buffer for each screen from the end of usable VRAM. */ vbox->available_vram_size -= vbox->num_crtcs * VBVA_MIN_BUFFER_SIZE; - vbox->vbva_buffers = pci_iomap_range(vbox->dev->pdev, 0, + vbox->vbva_buffers = pci_iomap_range(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0, vbox->available_vram_size, vbox->num_crtcs * VBVA_MIN_BUFFER_SIZE); @@ -311,14 +311,14 @@ return 0; err_pci_iounmap: - pci_iounmap(vbox->dev->pdev, vbox->vbva_buffers); + pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->vbva_buffers); return ret; } static void vbox_accel_fini(struct vbox_private *vbox) { vbox_disable_accel(vbox); - pci_iounmap(vbox->dev->pdev, vbox->vbva_buffers); + pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->vbva_buffers); } /** Do we support the 4.3 plus mode hint reporting interface? */ @@ -393,7 +393,7 @@ /* Map guest-heap at end of vram */ vbox->guest_heap = - pci_iomap_range(vbox->dev->pdev, 0, GUEST_HEAP_OFFSET(vbox), + pci_iomap_range(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0, GUEST_HEAP_OFFSET(vbox), GUEST_HEAP_SIZE); if (!vbox->guest_heap) return -ENOMEM; @@ -442,7 +442,7 @@ err_destroy_guest_pool: gen_pool_destroy(vbox->guest_pool); err_unmap_guest_heap: - pci_iounmap(vbox->dev->pdev, vbox->guest_heap); + pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->guest_heap); return ret; } @@ -452,7 +452,7 @@ cancel_delayed_work(&vbox->refresh_work); vbox_accel_fini(vbox); gen_pool_destroy(vbox->guest_pool); - pci_iounmap(vbox->dev->pdev, vbox->guest_heap); + pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->guest_heap); } #if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MIN(8,3) @@ -567,12 +567,16 @@ size = roundup(size, PAGE_SIZE); if (size == 0) + { + DRM_ERROR("bad size\n"); return -EINVAL; + } ret = vbox_bo_create(dev, size, 0, 0, &vboxbo); if (ret) { if (ret != -ERESTARTSYS) DRM_ERROR("failed to allocate GEM object\n"); + DRM_ERROR("failed to allocate GEM (%d)\n", ret); return ret; } @@ -596,7 +600,7 @@ return ret; ret = drm_gem_handle_create(file, gobj, &handle); -#if RTLNX_VER_MIN(5,9,0) +#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3) drm_gem_object_put(gobj); #else drm_gem_object_put_unlocked(gobj); @@ -628,12 +632,27 @@ { struct vbox_bo *vbox_bo = gem_to_vbox_bo(obj); +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + /* Starting from kernel 5.14, there is a warning appears in dmesg + * on attempt to desroy pinned buffer object. Make sure it is unpinned. */ + while (vbox_bo->bo.pin_count) + { + int ret; + ret = vbox_bo_unpin(vbox_bo); + if (ret) + { + DRM_ERROR("unable to unpin buffer object\n"); + break; + } + } +#endif + ttm_bo_put(&vbox_bo->bo); } static inline u64 vbox_bo_mmap_offset(struct vbox_bo *bo) { -#if RTLNX_VER_MIN(5,4,0) || RTLNX_RHEL_MIN(8,3) +#if RTLNX_VER_MIN(5,4,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3) return drm_vma_node_offset_addr(&bo->bo.base.vma_node); #elif RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,0) return bo->bo.addr_space_offset; @@ -648,7 +667,7 @@ u32 handle, u64 *offset) { struct drm_gem_object *obj; - int ret; + int ret = 0; struct vbox_bo *bo; mutex_lock(&dev->struct_mutex); @@ -665,8 +684,15 @@ bo = gem_to_vbox_bo(obj); *offset = vbox_bo_mmap_offset(bo); +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + ret = drm_vma_node_allow(&bo->bo.base.vma_node, file); + if (ret) + { + DRM_ERROR("unable to grant previladges to user"); + } +#endif + drm_gem_object_put(obj); - ret = 0; out_unlock: mutex_unlock(&dev->struct_mutex); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_mode.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_mode.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_mode.c 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_mode.c 2022-09-01 13:20:53.000000000 +0000 @@ -46,6 +46,10 @@ # include #endif +#if RTLNX_VER_MIN(6,0,0) +# include +#endif + #include "VBoxVideo.h" static int vbox_cursor_set2(struct drm_crtc *crtc, struct drm_file *file_priv, @@ -227,7 +231,7 @@ if (ret) return ret; - ret = vbox_bo_pin(bo, TTM_PL_FLAG_VRAM, &gpu_addr); + ret = vbox_bo_pin(bo, VBOX_MEM_TYPE_VRAM, &gpu_addr); vbox_bo_unreserve(bo); if (ret) return ret; @@ -245,6 +249,10 @@ vbox_bo_unpin(bo); vbox_bo_unreserve(bo); } + else + { + DRM_ERROR("unable to lock buffer object: error %d\n", ret); + } } if (&vbox->fbdev->afb == vbox_fb) @@ -398,7 +406,7 @@ static struct drm_encoder *vbox_best_single_encoder(struct drm_connector *connector) { -#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) +#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3) struct drm_encoder *encoder; /* There is only one encoder per connector */ @@ -856,7 +864,13 @@ vbox->cursor_data_size = data_size; dst = vbox->cursor_data; +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.resource->num_pages, &uobj_map); +#elif RTLNX_VER_MIN(5,12,0) || RTLNX_RHEL_MAJ_PREREQ(8,5) + ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.mem.num_pages, &uobj_map); +#else ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &uobj_map); +#endif if (ret) { vbox->cursor_data_size = 0; goto out_unreserve_bo; @@ -884,7 +898,7 @@ out_unreserve_bo: vbox_bo_unreserve(bo); out_unref_obj: -#if RTLNX_VER_MIN(5,9,0) +#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3) drm_gem_object_put(obj); #else drm_gem_object_put_unlocked(obj); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_ttm.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_ttm.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/drm/vbox_ttm.c 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/drm/vbox_ttm.c 2022-09-01 13:20:53.000000000 +0000 @@ -33,7 +33,17 @@ * Michael Thayer */ #include "vbox_drv.h" -#include +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_MAJ_PREREQ(8,5) +# include +# include +# include +#else +# include +#endif + +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) +# include +#endif #if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2) #define PLACEMENT_FLAGS(placement) (placement) @@ -41,7 +51,12 @@ #define PLACEMENT_FLAGS(placement) ((placement).flags) #endif + +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) +static inline struct vbox_private *vbox_bdev(struct ttm_device *bd) +#else static inline struct vbox_private *vbox_bdev(struct ttm_bo_device *bd) +#endif { return container_of(bd, struct vbox_private, ttm.bdev); } @@ -125,6 +140,7 @@ return false; } +#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) static int vbox_bo_init_mem_type(struct ttm_bo_device *bdev, u32 type, struct ttm_mem_type_manager *man) @@ -148,6 +164,7 @@ return 0; } +#endif static void vbox_bo_evict_flags(struct ttm_buffer_object *bo, struct ttm_placement *pl) @@ -157,21 +174,24 @@ if (!vbox_ttm_bo_is_vbox_bo(bo)) return; - vbox_ttm_placement(vboxbo, TTM_PL_FLAG_SYSTEM); + vbox_ttm_placement(vboxbo, VBOX_MEM_TYPE_SYSTEM); *pl = vboxbo->placement; } +#if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) static int vbox_bo_verify_access(struct ttm_buffer_object *bo, struct file *filp) { return 0; } +#endif +#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_RANGE(8,5, 8,99) static int vbox_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem) { - struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type]; struct vbox_private *vbox = vbox_bdev(bdev); + struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type]; mem->bus.addr = NULL; mem->bus.offset = 0; @@ -194,12 +214,78 @@ } return 0; } +#else +# if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) +static int vbox_ttm_io_mem_reserve(struct ttm_bo_device *bdev, + struct ttm_resource *mem) +# else /* > 5.13.0 */ +static int vbox_ttm_io_mem_reserve(struct ttm_device *bdev, + struct ttm_resource *mem) +# endif /* > 5.13.0 */ +{ + struct vbox_private *vbox = vbox_bdev(bdev); + mem->bus.addr = NULL; + mem->bus.offset = 0; +# if RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) + mem->size = mem->num_pages << PAGE_SHIFT; +# endif + mem->start = 0; + mem->bus.is_iomem = false; + switch (mem->mem_type) { + case TTM_PL_SYSTEM: + /* system memory */ + return 0; + case TTM_PL_VRAM: +# if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + mem->bus.caching = ttm_write_combined; +# endif +# if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + mem->bus.offset = (mem->start << PAGE_SHIFT) + pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0); +# else + mem->bus.offset = mem->start << PAGE_SHIFT; + mem->start = pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0); +# endif + mem->bus.is_iomem = true; + break; + default: + return -EINVAL; + } + return 0; +} +#endif + + +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) +static void vbox_ttm_io_mem_free(struct ttm_device *bdev, + struct ttm_resource *mem) +{ +} +#elif RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99) +static void vbox_ttm_io_mem_free(struct ttm_bo_device *bdev, + struct ttm_resource *mem) +{ +} +#else static void vbox_ttm_io_mem_free(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem) { } +#endif +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) +static void vbox_ttm_tt_destroy(struct ttm_device *bdev, struct ttm_tt *tt) +{ + ttm_tt_fini(tt); + kfree(tt); +} +#elif RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99) +static void vbox_ttm_tt_destroy(struct ttm_bo_device *bdev, struct ttm_tt *tt) +{ + ttm_tt_fini(tt); + kfree(tt); +} +#else static void vbox_ttm_backend_destroy(struct ttm_tt *tt) { ttm_tt_fini(tt); @@ -209,6 +295,7 @@ static struct ttm_backend_func vbox_tt_backend_func = { .destroy = &vbox_ttm_backend_destroy, }; +#endif #if RTLNX_VER_MAX(4,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5) static struct ttm_tt *vbox_ttm_tt_create(struct ttm_bo_device *bdev, @@ -226,11 +313,17 @@ if (!tt) return NULL; +#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_RANGE(8,5, 8,99) tt->func = &vbox_tt_backend_func; +#endif #if RTLNX_VER_MAX(4,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5) if (ttm_tt_init(tt, bdev, size, page_flags, dummy_read_page)) { -#else +#elif RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_RANGE(8,5, 8,99) if (ttm_tt_init(tt, bo, page_flags)) { +#elif RTLNX_VER_MAX(5,19,0) + if (ttm_tt_init(tt, bo, page_flags, ttm_write_combined)) { +#else + if (ttm_tt_init(tt, bo, page_flags, ttm_write_combined, 0)) { #endif kfree(tt); return NULL; @@ -259,18 +352,38 @@ } #endif +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) +static int vbox_bo_move(struct ttm_buffer_object *bo, bool evict, + struct ttm_operation_ctx *ctx, struct ttm_resource *new_mem, + struct ttm_place *hop) +{ + return ttm_bo_move_memcpy(bo, ctx, new_mem); +} +#endif + +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) +static struct ttm_device_funcs vbox_bo_driver = { +#else /* < 5.13.0 */ static struct ttm_bo_driver vbox_bo_driver = { +#endif /* < 5.13.0 */ .ttm_tt_create = vbox_ttm_tt_create, +#if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + .ttm_tt_destroy = vbox_ttm_tt_destroy, +#endif #if RTLNX_VER_MAX(4,17,0) .ttm_tt_populate = vbox_ttm_tt_populate, .ttm_tt_unpopulate = vbox_ttm_tt_unpopulate, #endif +#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) .init_mem_type = vbox_bo_init_mem_type, +#endif #if RTLNX_VER_MIN(4,10,0) || RTLNX_RHEL_MAJ_PREREQ(7,4) .eviction_valuable = ttm_bo_eviction_valuable, #endif .evict_flags = vbox_bo_evict_flags, +#if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) .verify_access = vbox_bo_verify_access, +#endif .io_mem_reserve = &vbox_ttm_io_mem_reserve, .io_mem_free = &vbox_ttm_io_mem_free, #if RTLNX_VER_MIN(4,12,0) || RTLNX_RHEL_MAJ_PREREQ(7,5) @@ -282,32 +395,49 @@ .lru_tail = &ttm_bo_default_lru_tail, .swap_lru_tail = &ttm_bo_default_swap_lru_tail, #endif +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + .move = &vbox_bo_move, +#endif }; int vbox_mm_init(struct vbox_private *vbox) { int ret; struct drm_device *dev = vbox->dev; +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + struct ttm_device *bdev = &vbox->ttm.bdev; +#else struct ttm_bo_device *bdev = &vbox->ttm.bdev; +#endif #if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1) ret = vbox_ttm_global_init(vbox); if (ret) return ret; #endif +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + ret = ttm_device_init(&vbox->ttm.bdev, +#else ret = ttm_bo_device_init(&vbox->ttm.bdev, +#endif #if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1) vbox->ttm.bo_global_ref.ref.object, #endif &vbox_bo_driver, +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + dev->dev, +#endif #if RTLNX_VER_MIN(3,15,0) || RTLNX_RHEL_MAJ_PREREQ(7,1) dev->anon_inode->i_mapping, #endif -#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) - dev->vma_offset_manager, +#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3) + dev->vma_offset_manager, #elif RTLNX_VER_MAX(5,2,0) && !RTLNX_RHEL_MAJ_PREREQ(8,2) DRM_FILE_PAGE_OFFSET, #endif +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + false, +#endif true); if (ret) { DRM_ERROR("Error initialising bo driver; %d\n", ret); @@ -318,25 +448,34 @@ #endif } +#if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + ret = ttm_range_man_init(bdev, TTM_PL_VRAM, false, + vbox->available_vram_size >> PAGE_SHIFT); +#else ret = ttm_bo_init_mm(bdev, TTM_PL_VRAM, vbox->available_vram_size >> PAGE_SHIFT); +#endif if (ret) { DRM_ERROR("Failed ttm VRAM init: %d\n", ret); goto err_device_release; } #ifdef DRM_MTRR_WC - vbox->fb_mtrr = drm_mtrr_add(pci_resource_start(dev->pdev, 0), - pci_resource_len(dev->pdev, 0), + vbox->fb_mtrr = drm_mtrr_add(pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0), + pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0), DRM_MTRR_WC); #else - vbox->fb_mtrr = arch_phys_wc_add(pci_resource_start(dev->pdev, 0), - pci_resource_len(dev->pdev, 0)); + vbox->fb_mtrr = arch_phys_wc_add(pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0), + pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0)); #endif return 0; err_device_release: +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + ttm_device_fini(&vbox->ttm.bdev); +#else ttm_bo_device_release(&vbox->ttm.bdev); +#endif #if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1) err_ttm_global_release: vbox_ttm_global_release(vbox); @@ -348,18 +487,22 @@ { #ifdef DRM_MTRR_WC drm_mtrr_del(vbox->fb_mtrr, - pci_resource_start(vbox->dev->pdev, 0), - pci_resource_len(vbox->dev->pdev, 0), DRM_MTRR_WC); + pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0), + pci_resource_len(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0), DRM_MTRR_WC); #else arch_phys_wc_del(vbox->fb_mtrr); #endif +#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + ttm_device_fini(&vbox->ttm.bdev); +#else ttm_bo_device_release(&vbox->ttm.bdev); +#endif #if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1) vbox_ttm_global_release(vbox); #endif } -void vbox_ttm_placement(struct vbox_bo *bo, int domain) +void vbox_ttm_placement(struct vbox_bo *bo, u32 mem_type) { u32 c = 0; #if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2) @@ -372,15 +515,45 @@ bo->placement.placement = bo->placements; bo->placement.busy_placement = bo->placements; - if (domain & TTM_PL_FLAG_VRAM) + if (mem_type & VBOX_MEM_TYPE_VRAM) { +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + bo->placements[c].mem_type = TTM_PL_VRAM; + PLACEMENT_FLAGS(bo->placements[c++]) = 0; +#elif RTLNX_VER_MIN(5,10,0) + bo->placements[c].mem_type = TTM_PL_VRAM; + PLACEMENT_FLAGS(bo->placements[c++]) = + TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED; +#else PLACEMENT_FLAGS(bo->placements[c++]) = TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_VRAM; - if (domain & TTM_PL_FLAG_SYSTEM) +#endif + } + if (mem_type & VBOX_MEM_TYPE_SYSTEM) { +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + bo->placements[c].mem_type = TTM_PL_SYSTEM; + PLACEMENT_FLAGS(bo->placements[c++]) = 0; +#elif RTLNX_VER_MIN(5,10,0) + bo->placements[c].mem_type = TTM_PL_SYSTEM; + PLACEMENT_FLAGS(bo->placements[c++]) = + TTM_PL_MASK_CACHING; +#else PLACEMENT_FLAGS(bo->placements[c++]) = TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM; - if (!c) +#endif + } + if (!c) { +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + bo->placements[c].mem_type = TTM_PL_SYSTEM; + PLACEMENT_FLAGS(bo->placements[c++]) = 0; +#elif RTLNX_VER_MIN(5,10,0) + bo->placements[c].mem_type = TTM_PL_SYSTEM; + PLACEMENT_FLAGS(bo->placements[c++]) = + TTM_PL_MASK_CACHING; +#else PLACEMENT_FLAGS(bo->placements[c++]) = TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM; +#endif + } bo->placement.num_placement = c; bo->placement.num_busy_placement = c; @@ -393,12 +566,24 @@ #endif } +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) +static const struct drm_gem_object_funcs vbox_drm_gem_object_funcs = { + .free = vbox_gem_free_object, + .print_info = drm_gem_ttm_print_info, +# if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + .mmap = drm_gem_ttm_mmap, +# endif +}; +#endif + int vbox_bo_create(struct drm_device *dev, int size, int align, u32 flags, struct vbox_bo **pvboxbo) { struct vbox_private *vbox = dev->dev_private; struct vbox_bo *vboxbo; +#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) size_t acc_size; +#endif int ret; vboxbo = kzalloc(sizeof(*vboxbo), GFP_KERNEL); @@ -409,30 +594,54 @@ if (ret) goto err_free_vboxbo; +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + if (!vboxbo->gem.funcs) { + vboxbo->gem.funcs = &vbox_drm_gem_object_funcs; + } +#endif vboxbo->bo.bdev = &vbox->ttm.bdev; #if RTLNX_VER_MAX(3,15,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1) vboxbo->bo.bdev->dev_mapping = dev->dev_mapping; #endif - vbox_ttm_placement(vboxbo, TTM_PL_FLAG_VRAM | TTM_PL_FLAG_SYSTEM); + vbox_ttm_placement(vboxbo, VBOX_MEM_TYPE_VRAM | VBOX_MEM_TYPE_SYSTEM); +#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) acc_size = ttm_bo_dma_acc_size(&vbox->ttm.bdev, size, sizeof(struct vbox_bo)); +#endif + +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + /* Initialization of the following was removed from DRM stack + * in 5.14, so we need to do it manually. */ + vboxbo->bo.base.funcs = &vbox_drm_gem_object_funcs; + kref_init(&vboxbo->bo.base.refcount); + vboxbo->bo.base.size = size; + vboxbo->bo.base.dev = dev; + dma_resv_init(&vboxbo->bo.base._resv); + drm_vma_node_reset(&vboxbo->bo.base.vma_node); +#endif ret = ttm_bo_init(&vbox->ttm.bdev, &vboxbo->bo, size, ttm_bo_type_device, &vboxbo->placement, #if RTLNX_VER_MAX(4,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5) align >> PAGE_SHIFT, false, NULL, acc_size, -#else +#elif RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) /* < 5.13.0, < RHEL(8.6, 8.99) */ align >> PAGE_SHIFT, false, acc_size, -#endif +#else /* > 5.13.0 */ + align >> PAGE_SHIFT, false, +#endif /* > 5.13.0 */ #if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2) NULL, NULL, vbox_bo_ttm_destroy); #else NULL, vbox_bo_ttm_destroy); #endif if (ret) - goto err_free_vboxbo; + { + /* In case of failure, ttm_bo_init() supposed to call + * vbox_bo_ttm_destroy() which in turn will free @vboxbo. */ + goto err_exit; + } *pvboxbo = vboxbo; @@ -440,24 +649,30 @@ err_free_vboxbo: kfree(vboxbo); +err_exit: return ret; } static inline u64 vbox_bo_gpu_offset(struct vbox_bo *bo) { -#if RTLNX_VER_MIN(5,9,0) +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + return bo->bo.resource->start << PAGE_SHIFT; +#elif RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3) return bo->bo.mem.start << PAGE_SHIFT; #else return bo->bo.offset; #endif } -int vbox_bo_pin(struct vbox_bo *bo, u32 pl_flag, u64 *gpu_addr) +int vbox_bo_pin(struct vbox_bo *bo, u32 mem_type, u64 *gpu_addr) { #if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5) struct ttm_operation_ctx ctx = { false, false }; #endif - int i, ret; + int ret; +#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) + int i; +#endif if (bo->pin_count) { bo->pin_count++; @@ -467,10 +682,12 @@ return 0; } - vbox_ttm_placement(bo, pl_flag); + vbox_ttm_placement(bo, mem_type); +#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) for (i = 0; i < bo->placement.num_placement; i++) PLACEMENT_FLAGS(bo->placements[i]) |= TTM_PL_FLAG_NO_EVICT; +#endif #if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5) ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false); @@ -482,6 +699,10 @@ bo->pin_count = 1; +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + ttm_bo_pin(&bo->bo); +#endif + if (gpu_addr) *gpu_addr = vbox_bo_gpu_offset(bo); @@ -491,9 +712,14 @@ int vbox_bo_unpin(struct vbox_bo *bo) { #if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5) +# if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) struct ttm_operation_ctx ctx = { false, false }; +# endif +#endif + int ret = 0; +#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) + int i; #endif - int i, ret; if (!bo->pin_count) { DRM_ERROR("unpin bad %p\n", bo); @@ -503,20 +729,27 @@ if (bo->pin_count) return 0; +#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) for (i = 0; i < bo->placement.num_placement; i++) PLACEMENT_FLAGS(bo->placements[i]) &= ~TTM_PL_FLAG_NO_EVICT; +#endif #if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5) ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false); -#else +#elif RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx); #endif if (ret) return ret; +#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + ttm_bo_unpin(&bo->bo); +#endif + return 0; } +#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5) /* * Move a vbox-owned buffer object to system memory if no one else has it * pinned. The caller must have pinned it previously, and this call will @@ -524,9 +757,9 @@ */ int vbox_bo_push_sysram(struct vbox_bo *bo) { -#if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5) +# if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5) struct ttm_operation_ctx ctx = { false, false }; -#endif +# endif int i, ret; if (!bo->pin_count) { @@ -540,16 +773,16 @@ if (bo->kmap.virtual) ttm_bo_kunmap(&bo->kmap); - vbox_ttm_placement(bo, TTM_PL_FLAG_SYSTEM); + vbox_ttm_placement(bo, VBOX_MEM_TYPE_SYSTEM); for (i = 0; i < bo->placement.num_placement; i++) PLACEMENT_FLAGS(bo->placements[i]) |= TTM_PL_FLAG_NO_EVICT; -#if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5) +# if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5) ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false); -#else +# else ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx); -#endif +# endif if (ret) { DRM_ERROR("pushing to VRAM failed\n"); return ret; @@ -557,11 +790,13 @@ return 0; } +#endif int vbox_mmap(struct file *filp, struct vm_area_struct *vma) { struct drm_file *file_priv; struct vbox_private *vbox; + int ret = -EINVAL; if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET)) return -EINVAL; @@ -569,5 +804,12 @@ file_priv = filp->private_data; vbox = file_priv->minor->dev->dev_private; - return ttm_bo_mmap(filp, vma, &vbox->ttm.bdev); +#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99) + if (drm_dev_is_unplugged(file_priv->minor->dev)) + return -ENODEV; + ret = drm_gem_mmap(filp, vma); +#else + ret = ttm_bo_mmap(filp, vma, &vbox->ttm.bdev); +#endif + return ret; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/installer/install.sh.in virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/installer/install.sh.in --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/installer/install.sh.in 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/installer/install.sh.in 2022-09-01 13:20:53.000000000 +0000 @@ -33,8 +33,8 @@ PUBLIC_UNINSTALL_HOOK="/usr/sbin/vbox-uninstall-guest-additions" ROUTINES="routines.sh" INSTALLATION_VER="_VERSION_" -BUILD_TYPE="_BUILDTYPE_" -USERNAME="_USERNAME_" +BUILD_VBOX_KBUILD_TYPE="_BUILDTYPE_" +BUILD_USERNAME="_USERNAME_" UNINSTALL_SCRIPTS="vboxadd-x11 vboxvfs vboxadd-timesync vboxadd-service vboxadd" INSTALLATION_DIR="/opt/$PACKAGE-$INSTALLATION_VER" @@ -448,8 +448,8 @@ # Package version INSTALL_VER='$INSTALLATION_VER' # Build type and user name for logging purposes -BUILD_TYPE='$BUILD_TYPE' -USERNAME='$USERNAME' +BUILD_VBOX_KBUILD_TYPE='$BUILD_BUILDTYPE' +BUILD_USERNAME='$USERNAME' EOF # Give the modules the chance to write their stuff diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/installer/vboxadd.sh virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/installer/vboxadd.sh --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/installer/vboxadd.sh 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/installer/vboxadd.sh 2022-09-01 13:20:53.000000000 +0000 @@ -1,7 +1,7 @@ #! /bin/sh # $Id: vboxadd.sh $ ## @file -# Linux Additions kernel module init script ($Revision: 135976 $) +# Linux Additions kernel module init script ($Revision: 152074 $) # # @@ -61,7 +61,7 @@ test -z "${TARGET_VER}" && TARGET_VER=`uname -r` # Marker to ignore a particular kernel version which was already installed. SKIPFILE_BASE=/var/lib/VBoxGuestAdditions/skip -export BUILD_TYPE +export VBOX_KBUILD_TYPE export USERNAME setup_log() @@ -135,6 +135,13 @@ MODULE_SRC="$INSTALL_DIR/src/vboxguest-$INSTALL_VER" BUILDINTMP="$MODULE_SRC/build_in_tmp" +# Attempt to detect VirtualBox Guest Additions version and revision information. +VBOXCLIENT="${INSTALL_DIR}/bin/VBoxClient" +VBOX_VERSION="`"$VBOXCLIENT" --version | cut -d r -f1`" +[ -n "$VBOX_VERSION" ] || VBOX_VERSION='unknown' +VBOX_REVISION="r`"$VBOXCLIENT" --version | cut -d r -f2`" +[ "$VBOX_REVISION" != "r" ] || VBOX_REVISION='unknown' + running_vboxguest() { lsmod | grep -q "vboxguest[^_-]" @@ -283,6 +290,16 @@ export KERN_VER info "Building the modules for kernel $KERN_VER." + # Detect if kernel was built with clang. + unset LLVM + vbox_cc_is_clang=$(/lib/modules/"$KERN_VER"/build/scripts/config \ + --file /lib/modules/"$KERN_VER"/build/.config \ + --state CONFIG_CC_IS_CLANG 2>/dev/null) + if test "${vbox_cc_is_clang}" = "y"; then + info "Using clang compiler." + export LLVM=1 + fi + log "Building the main Guest Additions $INSTALL_VER module for kernel $KERN_VER." if ! myerr=`$BUILDINTMP \ --save-module-symvers /tmp/vboxguest-Module.symvers \ @@ -403,6 +420,75 @@ fi } +# Returns path to module file as seen by modinfo(8) or empty string. +module_path() +{ + mod="$1" + [ -n "$mod" ] || return + + modinfo "$mod" 2>/dev/null | grep -e "^filename:" | tr -s ' ' | cut -d " " -f2 +} + +# Returns module version if module is available or empty string. +module_version() +{ + mod="$1" + [ -n "$mod" ] || return + + modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f2 +} + +# Returns module revision if module is available in the system or empty string. +module_revision() +{ + mod="$1" + [ -n "$mod" ] || return + + modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f3 +} + +# Returns "1" if externally built module is available in the system and its +# version and revision number do match to current VirtualBox installation. +# Or empty string otherwise. +module_available() +{ + mod="$1" + [ -n "$mod" ] || return + + [ "$VBOX_VERSION" = "$(module_version "$mod")" ] || return + [ "$VBOX_REVISION" = "$(module_revision "$mod")" ] || return + + # Check if module belongs to VirtualBox installation. + # + # We have a convention that only modules from /lib/modules/*/misc + # belong to us. Modules from other locations are treated as + # externally built. + mod_path="$(module_path "$mod")" + + # If module path points to a symbolic link, resolve actual file location. + [ -L "$mod_path" ] && mod_path="$(readlink -e -- "$mod_path")" + + # File exists? + [ -f "$mod_path" ] || return + + # Extract last component of module path and check whether it is located + # outside of /lib/modules/*/misc. + mod_dir="$(dirname "$mod_path" | sed 's;^.*/;;')" + [ "$mod_dir" = "misc" ] || return + + echo "1" +} + +# Check if required modules are installed in the system and versions match. +setup_complete() +{ + [ "$(module_available vboxguest)" = "1" ] || return + [ "$(module_available vboxsf)" = "1" ] || return + + # All modules are in place. + echo "1" +} + # setup_script setup() { @@ -411,18 +497,25 @@ chcon -t bin_t "$BUILDINTMP" 2>/dev/null if test -z "$INSTALL_NO_MODULE_BUILDS"; then - info "Building the VirtualBox Guest Additions kernel modules. This may take a while." - info "To build modules for other installed kernels, run" - info " /sbin/rcvboxadd quicksetup " - info "or" - info " /sbin/rcvboxadd quicksetup all" - if test -d /lib/modules/"$TARGET_VER"/build; then - setup_modules "$TARGET_VER" - depmod + # Check whether modules setup is already complete for currently running kernel. + # Prevent unnecessary rebuilding in order to speed up booting process. + if test "$(setup_complete)" = "1"; then + info "VirtualBox Guest Additions kernel modules $VBOX_VERSION $VBOX_REVISION are" + info "already available for kernel $TARGET_VER and do not require to be rebuilt." else - info "Kernel headers not found for target kernel $TARGET_VER. \ + info "Building the VirtualBox Guest Additions kernel modules. This may take a while." + info "To build modules for other installed kernels, run" + info " /sbin/rcvboxadd quicksetup " + info "or" + info " /sbin/rcvboxadd quicksetup all" + if test -d /lib/modules/"$TARGET_VER"/build; then + setup_modules "$TARGET_VER" + depmod + else + info "Kernel headers not found for target kernel $TARGET_VER. \ Please install them and execute /sbin/rcvboxadd setup" + fi fi fi create_vbox_user diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/dirops.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/dirops.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/dirops.c 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/dirops.c 2022-09-01 13:20:55.000000000 +0000 @@ -1038,13 +1038,16 @@ /** * Create a new regular file. * - * @param parent inode of the directory - * @param dentry directory cache entry - * @param mode file mode - * @param excl Possible O_EXCL... + * @param ns The name space. + * @param parent inode of the directory + * @param dentry directory cache entry + * @param mode file mode + * @param excl Possible O_EXCL... * @returns 0 on success, Linux error code otherwise */ -#if RTLNX_VER_MIN(3,6,0) || defined(DOXYGEN_RUNNING) +#if RTLNX_VER_MIN(5,12,0) || defined(DOXYGEN_RUNNING) +static int vbsf_inode_create(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, umode_t mode, bool excl) +#elif RTLNX_VER_MIN(3,6,0) static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, umode_t mode, bool excl) #elif RTLNX_VER_MIN(3,3,0) static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, umode_t mode, struct nameidata *nd) @@ -1076,12 +1079,15 @@ /** * Create a new directory. * - * @param parent inode of the directory - * @param dentry directory cache entry - * @param mode file mode + * @param ns The name space. + * @param parent inode of the directory + * @param dentry directory cache entry + * @param mode file mode * @returns 0 on success, Linux error code otherwise */ -#if RTLNX_VER_MIN(3,3,0) +#if RTLNX_VER_MIN(5,12,0) || defined(DOXYGEN_RUNNING) +static int vbsf_inode_mkdir(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, umode_t mode) +#elif RTLNX_VER_MIN(3,3,0) static int vbsf_inode_mkdir(struct inode *parent, struct dentry *dentry, umode_t mode) #else static int vbsf_inode_mkdir(struct inode *parent, struct dentry *dentry, int mode) @@ -1186,15 +1192,22 @@ /** * Rename a regular file / directory. * - * @param old_parent inode of the old parent directory - * @param old_dentry old directory cache entry - * @param new_parent inode of the new parent directory - * @param new_dentry new directory cache entry - * @param flags flags + * @param ns The name space. + * @param old_parent inode of the old parent directory + * @param old_dentry old directory cache entry + * @param new_parent inode of the new parent directory + * @param new_dentry new directory cache entry + * @param flags flags * @returns 0 on success, Linux error code otherwise */ +#if RTLNX_VER_MIN(5,12,0) || defined(DOXYGEN_RUNNING) +static int vbsf_inode_rename(struct user_namespace *ns, + struct inode *old_parent, struct dentry *old_dentry, + struct inode *new_parent, struct dentry *new_dentry, unsigned flags) +#else static int vbsf_inode_rename(struct inode *old_parent, struct dentry *old_dentry, struct inode *new_parent, struct dentry *new_dentry, unsigned flags) +#endif { /* * Deal with flags. @@ -1299,7 +1312,11 @@ /** * Create a symbolic link. */ +#if RTLNX_VER_MIN(5,12,0) +static int vbsf_inode_symlink(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, const char *target) +#else static int vbsf_inode_symlink(struct inode *parent, struct dentry *dentry, const char *target) +#endif { /* * Turn the target into a string (contiguous physcial memory). @@ -1383,7 +1400,9 @@ #if RTLNX_VER_MIN(4,9,0) .rename = vbsf_inode_rename, #else +# if RTLNX_VER_MAX(3,17,0) .rename = vbsf_inode_rename_no_flags, +# endif # if RTLNX_VER_MIN(3,15,0) .rename2 = vbsf_inode_rename, # endif diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/Makefile.module virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/Makefile.module --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/Makefile.module 2020-10-16 16:30:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/Makefile.module 2022-09-01 13:20:55.000000000 +0000 @@ -42,7 +42,7 @@ VBoxGuestR0LibInit.o \ VBoxGuestR0LibPhysHeap.o \ VBoxGuestR0LibSharedFolders.o -ifeq ($(BUILD_TARGET_ARCH),x86) +ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86) VBOXMOD_OBJS += \ divdi3.o \ moddi3.o \ @@ -66,7 +66,7 @@ IN_GUEST \ IN_GUEST_R0 \ RT_NO_EXPORT_SYMBOL -ifeq ($(BUILD_TARGET_ARCH),amd64) +ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64) VBOXMOD_DEFS += VBOX_WITH_64_BITS_GUESTS endif ifneq ($(filter %uek.x86_64,$(KERN_VER)),) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c 2022-09-01 13:20:55.000000000 +0000 @@ -43,14 +43,52 @@ #include #include #include +#include +#include #include "vbsfmount.h" -#include +#include +#include /* PAGE_SIZE (used by MAX_MNTOPT_STR) */ +#include +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ #define PANIC_ATTR __attribute ((noreturn, __format__ (__printf__, 1, 2))) + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +struct vbsf_mount_opts +{ + unsigned long fFlags; /**< MS_XXX */ + + /** @name Preformatted option=value or empty if not specified. + * Helps eliminate duplicate options as well as simplifying concatting. + * @{ */ + char szTTL[32]; + char szMsDirCacheTTL[32]; + char szMsInodeTTL[32]; + char szMaxIoPages[32]; + char szDirBuf[32]; + char szCacheMode[32]; + char szUid[32]; + char szGid[32]; + char szDMode[32]; + char szFMode[32]; + char szDMask[32]; + char szFMask[32]; + char szIoCharset[32]; + /** @} */ + + bool fSloppy; + char *pszConvertCp; +}; + + static void PANIC_ATTR panic(const char *fmt, ...) { @@ -133,6 +171,7 @@ HO_DMASK, HO_FMASK, HO_IOCHARSET, + HO_NLS, HO_CONVERTCP, HO_NOEXEC, HO_EXEC, @@ -163,6 +202,7 @@ {"dirbuf", HO_DIR_BUF, 1, "directory buffer size (0 for default)"}, {"cache", HO_CACHE, 1, "cache mode: none, strict (default), read, readwrite"}, {"iocharset", HO_IOCHARSET, 1, "i/o charset (default utf8)"}, + {"nls", HO_NLS, 1, "i/o charset (default utf8)"}, {"convertcp", HO_CONVERTCP, 1, "convert share name from given charset to utf8"}, {"dmode", HO_DMODE, 1, "mode of all directories"}, {"fmode", HO_FMODE, 1, "mode of all regular files"}, @@ -229,66 +269,71 @@ if (!(val && *val)) { panic("%.*s requires an argument (i.e. %.*s=)\n", - (int)len, s, (int)len, s); + (int)len, s, (int)len, s); } } switch (handler->opt) { case HO_RW: - opts->ronly = 0; + opts->fFlags &= ~MS_RDONLY; break; case HO_RO: - opts->ronly = 1; + opts->fFlags |= MS_RDONLY; break; case HO_NOEXEC: - opts->noexec = 1; + opts->fFlags |= MS_NOEXEC; break; case HO_EXEC: - opts->noexec = 0; + opts->fFlags &= ~MS_NOEXEC; break; case HO_NODEV: - opts->nodev = 1; + opts->fFlags |= MS_NODEV; break; case HO_DEV: - opts->nodev = 0; + opts->fFlags &= ~MS_NODEV; break; case HO_NOSUID: - opts->nosuid = 1; + opts->fFlags |= MS_NOSUID; break; case HO_SUID: - opts->nosuid = 0; + opts->fFlags &= ~MS_NOSUID; break; case HO_REMOUNT: - opts->remount = 1; + opts->fFlags |= MS_REMOUNT; break; case HO_TTL: - opts->ttl = safe_atoi(val, val_len, 10); + snprintf(opts->szTTL, sizeof(opts->szTTL), + "ttl=%d", safe_atoi(val, val_len, 10)); break; case HO_DENTRY_TTL: - opts->msDirCacheTTL = safe_atoi(val, val_len, 10); + snprintf(opts->szMsDirCacheTTL, sizeof(opts->szMsDirCacheTTL), + "dcachettl=%d", safe_atoi(val, val_len, 10)); break; case HO_INODE_TTL: - opts->msInodeTTL = safe_atoi(val, val_len, 10); + snprintf(opts->szMsInodeTTL, sizeof(opts->szMsInodeTTL), + "inodettl=%d", safe_atoi(val, val_len, 10)); break; case HO_MAX_IO_PAGES: - opts->cMaxIoPages = safe_atoiu(val, val_len, 10); + snprintf(opts->szMaxIoPages, sizeof(opts->szMaxIoPages), + "maxiopages=%d", safe_atoiu(val, val_len, 10)); break; case HO_DIR_BUF: - opts->cbDirBuf = safe_atoiu(val, val_len, 10); + snprintf(opts->szDirBuf, sizeof(opts->szDirBuf), + "dirbuf=%d", safe_atoiu(val, val_len, 10)); break; case HO_CACHE: #define IS_EQUAL(a_sz) (val_len == sizeof(a_sz) - 1U && strncmp(val, a_sz, sizeof(a_sz) - 1U) == 0) if (IS_EQUAL("default")) - opts->enmCacheMode = kVbsfCacheMode_Default; + strcpy(opts->szCacheMode, "cache=default"); else if (IS_EQUAL("none")) - opts->enmCacheMode = kVbsfCacheMode_None; + strcpy(opts->szCacheMode, "cache=none"); else if (IS_EQUAL("strict")) - opts->enmCacheMode = kVbsfCacheMode_Strict; + strcpy(opts->szCacheMode, "cache=strict"); else if (IS_EQUAL("read")) - opts->enmCacheMode = kVbsfCacheMode_Read; + strcpy(opts->szCacheMode, "cache=read"); else if (IS_EQUAL("readwrite")) - opts->enmCacheMode = kVbsfCacheMode_ReadWrite; + strcpy(opts->szCacheMode, "cache=readwrite"); else panic("invalid cache mode '%.*s'\n" "Valid cache modes are: default, none, strict, read, readwrite\n", @@ -296,43 +341,50 @@ break; case HO_UID: /** @todo convert string to id. */ - opts->uid = safe_atoi(val, val_len, 10); + snprintf(opts->szUid, sizeof(opts->szUid), + "uid=%d", safe_atoi(val, val_len, 10)); break; case HO_GID: /** @todo convert string to id. */ - opts->gid = safe_atoi(val, val_len, 10); + snprintf(opts->szGid, sizeof(opts->szGid), + "gid=%d", safe_atoi(val, val_len, 10)); break; case HO_DMODE: - opts->dmode = safe_atoi(val, val_len, 8); + snprintf(opts->szDMode, sizeof(opts->szDMode), + "dmode=0%o", safe_atoi(val, val_len, 8)); break; case HO_FMODE: - opts->fmode = safe_atoi(val, val_len, 8); + snprintf(opts->szFMode, sizeof(opts->szFMode), + "fmode=0%o", safe_atoi(val, val_len, 8)); break; case HO_UMASK: - opts->dmask = opts->fmask = safe_atoi(val, val_len, 8); + { + int fMask = safe_atoi(val, val_len, 8); + snprintf(opts->szDMask, sizeof(opts->szDMask), "dmask=0%o", fMask); + snprintf(opts->szFMask, sizeof(opts->szFMask), "fmask=0%o", fMask); break; + } case HO_DMASK: - opts->dmask = safe_atoi(val, val_len, 8); + snprintf(opts->szDMask, sizeof(opts->szDMask), + "dmask=0%o", safe_atoi(val, val_len, 8)); break; case HO_FMASK: - opts->fmask = safe_atoi(val, val_len, 8); + snprintf(opts->szFMask, sizeof(opts->szFMask), + "fmask=0%o", safe_atoi(val, val_len, 8)); break; case HO_IOCHARSET: - if (val_len + 1 > sizeof(opts->nls_name)) - { - panic("iocharset name too long\n"); - } - memcpy(opts->nls_name, val, val_len); - opts->nls_name[val_len] = 0; + case HO_NLS: + if (val_len >= MAX_NLS_NAME) + panic("the character set name for I/O is too long: %*.*s\n", (int)val_len, (int)val_len, val); + snprintf(opts->szIoCharset, sizeof(opts->szIoCharset), + "%s=%*.*s", handler->opt == HO_IOCHARSET ? "iocharset" : "nls", (int)val_len, (int)val_len, val); break; case HO_CONVERTCP: - opts->convertcp = malloc(val_len + 1); - if (!opts->convertcp) - { + opts->pszConvertCp = malloc(val_len + 1); + if (!opts->pszConvertCp) panic_err("could not allocate memory"); - } - memcpy(opts->convertcp, val, val_len); - opts->convertcp[val_len] = 0; + memcpy(opts->pszConvertCp, val, val_len); + opts->pszConvertCp[val_len] = '\0'; break; case HO_NOAUTO: case HO_NIGNORE: @@ -344,7 +396,7 @@ } if ( !handler->name - && !opts->sloppy) + && !opts->fSloppy) { fprintf(stderr, "unknown mount option `%.*s'\n", (int)len, s); fprintf(stderr, "valid options:\n"); @@ -353,24 +405,44 @@ { if (handler->desc) fprintf(stderr, " %-10s%s %s\n", handler->name, - handler->has_arg ? "=" : "", handler->desc); + handler->has_arg ? "=" : "", handler->desc); } exit(EXIT_FAILURE); } } } +/** Appends @a pszOptVal to pszOpts if not empty. */ +static size_t append_option(char *pszOpts, size_t cbOpts, size_t offOpts, const char *pszOptVal) +{ + if (*pszOptVal != '\0') + { + size_t cchOptVal = strlen(pszOptVal); + if (offOpts + (offOpts > 0) + cchOptVal < cbOpts) + { + if (offOpts) + pszOpts[offOpts++] = ','; + memcpy(&pszOpts[offOpts], pszOptVal, cchOptVal); + offOpts += cchOptVal; + pszOpts[offOpts] = '\0'; + } + else + panic("Too many options!"); + } + return offOpts; +} + static void -convertcp(char *in_codeset, char *host_name, struct vbsf_mount_info_new *info) +convertcp(char *in_codeset, char *pszSharedFolder, char *pszDst) { - char *i = host_name; - char *o = info->name; - size_t ib = strlen(host_name); - size_t ob = sizeof(info->name) - 1; + char *i = pszSharedFolder; + char *o = pszDst; + size_t ib = strlen(pszSharedFolder); + size_t ob = MAX_HOST_NAME - 1; iconv_t cd; cd = iconv_open("UTF-8", in_codeset); - if (cd == (iconv_t) -1) + if (cd == (iconv_t)-1) { panic_err("could not convert share name, iconv_open `%s' failed", in_codeset); @@ -379,10 +451,10 @@ while (ib) { size_t c = iconv(cd, &i, &ib, &o, &ob); - if (c == (size_t) -1) + if (c == (size_t)-1) { panic_err("could not convert share name(%s) at %d", - host_name, (int)(strlen (host_name) - ib)); + pszSharedFolder, (int)(strlen(pszSharedFolder) - ib)); } } *o = 0; @@ -453,50 +525,46 @@ int err; int saved_errno; int nomtab = 0; - unsigned long flags = MS_NODEV; - char *host_name; - char *mount_point; - struct vbsf_mount_info_new mntinf; + char *pszSharedFolder; + char *pszMountPoint; + struct utsname uts; + int major, minor, patch; + size_t offOpts; + static const char s_szSfNameOpt[] = "sf_name="; + char szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1 + MAX_HOST_NAME]; + char szOpts[MAX_MNTOPT_STR]; struct vbsf_mount_opts opts = { - -1, /* ttl */ - -1, /* msDirCacheTTL */ - -1, /* msInodeTTL */ - 0, /* cMaxIoPages */ - 0, /* cbDirBuf */ - kVbsfCacheMode_Default, - 0, /* uid */ - 0, /* gid */ - ~0U, /* dmode */ - ~0U, /* fmode*/ - 0, /* dmask */ - 0, /* fmask */ - 0, /* ronly */ - 0, /* sloppy */ - 0, /* noexec */ - 0, /* nodev */ - 0, /* nosuid */ - 0, /* remount */ - "\0", /* nls_name */ - NULL, /* convertcp */ + MS_NODEV, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + false, /*fSloppy*/ + NULL, }; + AssertCompile(sizeof(uid_t) == sizeof(int)); AssertCompile(sizeof(gid_t) == sizeof(int)); - memset(&mntinf, 0, sizeof(mntinf)); - mntinf.nullchar = '\0'; - mntinf.signature[0] = VBSF_MOUNT_SIGNATURE_BYTE_0; - mntinf.signature[1] = VBSF_MOUNT_SIGNATURE_BYTE_1; - mntinf.signature[2] = VBSF_MOUNT_SIGNATURE_BYTE_2; - mntinf.length = sizeof(mntinf); - mntinf.szTag[0] = '\0'; - if (getuid()) panic("Only root can mount shared folders from the host.\n"); if (!argv[0]) argv[0] = "mount.vboxsf"; + /* + * Parse options. + */ while ((c = getopt(argc, argv, "rwsno:h")) != -1) { switch (c) @@ -509,15 +577,15 @@ return usage(argv[0]); case 'r': - opts.ronly = 1; + opts.fFlags |= MS_RDONLY; break; case 'w': - opts.ronly = 0; + opts.fFlags &= ~MS_RDONLY; break; case 's': - opts.sloppy = 1; + opts.fSloppy = true; break; case 'o': @@ -533,100 +601,76 @@ if (argc - optind < 2) return usage(argv[0]); - host_name = argv[optind]; - mount_point = argv[optind + 1]; - - if (opts.convertcp) - convertcp(opts.convertcp, host_name, &mntinf); - else + pszSharedFolder = argv[optind]; + pszMountPoint = argv[optind + 1]; + if (opts.pszConvertCp) { - if (strlen(host_name) > MAX_HOST_NAME - 1) - panic("host name is too big\n"); - - strcpy(mntinf.name, host_name); + convertcp(opts.pszConvertCp, pszSharedFolder, &szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1]); + pszSharedFolder = &szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1]; } - if (strlen(opts.nls_name) > MAX_NLS_NAME - 1) - panic("%s: the character set name for I/O is too long.\n", argv[0]); - - strcpy(mntinf.nls_name, opts.nls_name); - - if (opts.ronly) - flags |= MS_RDONLY; - if (opts.noexec) - flags |= MS_NOEXEC; - if (opts.nodev) - flags |= MS_NODEV; - if (opts.remount) - flags |= MS_REMOUNT; - - mntinf.ttl = opts.ttl; - mntinf.msDirCacheTTL = opts.msDirCacheTTL; - mntinf.msInodeTTL = opts.msInodeTTL; - mntinf.cMaxIoPages = opts.cMaxIoPages; - mntinf.cbDirBuf = opts.cbDirBuf; - mntinf.enmCacheMode = opts.enmCacheMode; - - mntinf.uid = opts.uid; - mntinf.gid = opts.gid; - mntinf.dmode = opts.dmode; - mntinf.fmode = opts.fmode; - mntinf.dmask = opts.dmask; - mntinf.fmask = opts.fmask; - /* - * Note: When adding and/or modifying parameters of the vboxsf mounting - * options you also would have to adjust VBoxServiceAutoMount.cpp - * to keep this code here slick without having VbglR3. + * Concat option strings. */ - err = mount(host_name, mount_point, "vboxsf", flags, &mntinf); - saved_errno = errno; + offOpts = 0; + szOpts[0] = '\0'; + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szTTL); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMsDirCacheTTL); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMsInodeTTL); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMaxIoPages); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDirBuf); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szCacheMode); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szUid); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szGid); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDMode); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szFMode); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDMask); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szFMask); + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szIoCharset); + + /* For pre-2.6 kernels we have to supply the shared folder name as a + string option because the kernel hides the device name from us. */ + RT_ZERO(uts); + if ( uname(&uts) == -1 + || sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) + major = minor = patch = 5; - /* Some versions of the mount utility (unknown which, if any) will turn the - shared folder name into an absolute path. So, we check if it starts with - the CWD and removes it. We must do this after failing, because there is - not actual restrictions on the shared folder name wrt to slashes and such. */ - if (err == -1 && errno == ENXIO && host_name[0] == '/') + if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(2,6,0)) { - char szCWD[4096]; - if (getcwd(szCWD, sizeof(szCWD)) != NULL) + memcpy(szSharedFolderIconved, s_szSfNameOpt, sizeof(s_szSfNameOpt) - 1); + if (!opts.pszConvertCp) { - size_t cchCWD = strlen(szCWD); - if (!strncmp(host_name, szCWD, cchCWD)) - { - while (host_name[cchCWD] == '/') - ++cchCWD; - if (host_name[cchCWD]) - { - /* We checked before that we have enough space. */ - strcpy(mntinf.name, host_name + cchCWD); - err = mount(host_name, mount_point, "vboxsf", flags, &mntinf); - saved_errno = errno; - } - } + if (strlen(pszSharedFolder) >= MAX_HOST_NAME) + panic("%s: shared folder name is too long (max %d)", argv[0], (int)MAX_HOST_NAME - 1); + strcpy(&szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1], pszSharedFolder); } - else - fprintf(stderr, "%s: failed to get the current working directory: %s", argv[0], strerror(errno)); - errno = saved_errno; + offOpts = append_option(szOpts, sizeof(szOpts), offOpts, szSharedFolderIconved); } + + /* + * Do the actual mounting. + */ + err = mount(pszSharedFolder, pszMountPoint, "vboxsf", opts.fFlags, szOpts); + saved_errno = errno; + if (err) { if (saved_errno == ENXIO) - panic("%s: shared folder '%s' was not found (check VM settings / spelling)\n", argv[0], host_name); + panic("%s: shared folder '%s' was not found (check VM settings / spelling)\n", argv[0], pszSharedFolder); else panic_err("%s: mounting failed with the error", argv[0]); } if (!nomtab) { - err = vbsfmount_complete(host_name, mount_point, flags, &opts); + err = vbsfmount_complete(pszSharedFolder, pszMountPoint, opts.fFlags, szOpts); switch (err) { case 0: /* Success. */ break; case 1: - panic_err("%s: Could not update mount table (failed to create memstream).", argv[0]); + panic_err("%s: Could not update mount table (out of memory).", argv[0]); break; case 2: diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/regops.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/regops.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/regops.c 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/regops.c 2022-09-01 13:20:55.000000000 +0000 @@ -78,6 +78,18 @@ /********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def VBSF_GET_ITER_TYPE + * Accessor for getting iov iter type member which changed name in 5.14. */ +#if RTLNX_VER_MIN(5,14,0) +# define VBSF_GET_ITER_TYPE(a_pIter) ((a_pIter)->iter_type) +#else +# define VBSF_GET_ITER_TYPE(a_pIter) ((a_pIter)->type) +#endif + + +/********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ #if RTLNX_VER_MAX(3,16,0) @@ -147,7 +159,11 @@ while (cSegs-- > 0) { if (paIov->iov_len > 0) { if (access_ok(VERIFY_READ, paIov->iov_base, paIov->iov_len)) +#if RTLNX_VER_MIN(5,10,0) + return (uintptr_t)paIov->iov_base >= TASK_SIZE_MAX ? ITER_KVEC : 0; +#else return (uintptr_t)paIov->iov_base >= USER_DS.seg ? ITER_KVEC : 0; +#endif AssertMsgFailed(("%p LB %#zx\n", paIov->iov_base, paIov->iov_len)); break; } @@ -1401,7 +1417,10 @@ /* * Check that this is valid user memory that is actually in the kernel range. */ -#if RTLNX_VER_MIN(5,0,0) || RTLNX_RHEL_MIN(8,1) +#if RTLNX_VER_MIN(5,10,0) + if ( access_ok((void *)uPtrFrom, cPages << PAGE_SHIFT) + && uPtrFrom >= TASK_SIZE_MAX) +#elif RTLNX_VER_MIN(5,0,0) || RTLNX_RHEL_MIN(8,1) if ( access_ok((void *)uPtrFrom, cPages << PAGE_SHIFT) && uPtrFrom >= USER_DS.seg) #else @@ -1459,6 +1478,7 @@ return -EFAULT; } +#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */ /** * Read function used when accessing files that are memory mapped. @@ -1468,7 +1488,7 @@ */ static ssize_t vbsf_reg_read_mapped(struct file *file, char /*__user*/ *buf, size_t size, loff_t *off) { -#if RTLNX_VER_MIN(3,16,0) +# if RTLNX_VER_MIN(3,16,0) struct iovec iov = { .iov_base = buf, .iov_len = size }; struct iov_iter iter; struct kiocb kiocb; @@ -1483,7 +1503,7 @@ *off = kiocb.ki_pos; return cbRet; -#elif RTLNX_VER_MIN(2,6,19) +# elif RTLNX_VER_MIN(2,6,19) struct iovec iov = { .iov_base = buf, .iov_len = size }; struct kiocb kiocb; ssize_t cbRet; @@ -1498,9 +1518,9 @@ *off = kiocb.ki_pos; return cbRet; -#else /* 2.6.18 or earlier: */ +# else /* 2.6.18 or earlier: */ return generic_file_read(file, buf, size, off); -#endif +# endif } @@ -1684,7 +1704,7 @@ } } -#if 0 /* Turns out this is slightly slower than locking the pages even for 4KB reads (4.19/amd64). */ +# if 0 /* Turns out this is slightly slower than locking the pages even for 4KB reads (4.19/amd64). */ /* * For medium sized requests try use a bounce buffer. */ @@ -1713,11 +1733,12 @@ kfree(pvBounce); } } -#endif +# endif return vbsf_reg_read_locking(file, buf, size, off, pSuperInfo, sf_r); } +#endif /* < 5.10.0 */ /** * Helper the synchronizes the page cache content with something we just wrote @@ -1805,6 +1826,7 @@ RT_NOREF(cSrcPages); } +#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */ /** * Fallback case of vbsf_reg_write() that locks the user buffers and let the host @@ -1979,13 +2001,13 @@ if ( mapping && mapping->nrpages > 0 && mapping_writably_mapped(mapping)) { -#if RTLNX_VER_MIN(2,6,32) +# if RTLNX_VER_MIN(2,6,32) int err = filemap_fdatawait_range(mapping, pos, pos + size - 1); if (err) return err; -#else +# else /** @todo ... */ -#endif +# endif } /* @@ -2027,7 +2049,7 @@ VbglR0PhysHeapFree(pReq); } -#if 0 /* Turns out this is slightly slower than locking the pages even for 4KB reads (4.19/amd64). */ +# if 0 /* Turns out this is slightly slower than locking the pages even for 4KB reads (4.19/amd64). */ /* * For medium sized requests try use a bounce buffer. */ @@ -2066,12 +2088,19 @@ } } } -#endif +# endif return vbsf_reg_write_locking(file, buf, size, off, pos, inode, sf_i, pSuperInfo, sf_r); } +#endif /* < 5.10.0 */ #if RTLNX_VER_MIN(2,6,19) +/* See kernel 6.0.0 change eba2d3d798295dc43cae8fade102f9d083a2a741. */ +# if RTLNX_VER_MIN(6,0,0) +# define VBOX_IOV_GET_PAGES iov_iter_get_pages2 +# else +# define VBOX_IOV_GET_PAGES iov_iter_get_pages +# endif /** * Companion to vbsf_iter_lock_pages(). @@ -2115,7 +2144,7 @@ int rc = 0; Assert(iov_iter_count(iter) + pStash->cb > 0); - if (!(iter->type & ITER_KVEC)) { + if (!(VBSF_GET_ITER_TYPE(iter) & ITER_KVEC)) { /* * Do we have a stashed page? */ @@ -2162,9 +2191,11 @@ while (!iov_iter_single_seg_count(iter)) /* Old code didn't skip empty segments which caused EFAULTs. */ iov_iter_advance(iter, 0); # endif - cbSegRet = iov_iter_get_pages(iter, papPages, iov_iter_count(iter), cMaxPages, &offPage0); + cbSegRet = VBOX_IOV_GET_PAGES(iter, papPages, iov_iter_count(iter), cMaxPages, &offPage0); if (cbSegRet > 0) { +# if RTLNX_VER_MAX(6,0,0) iov_iter_advance(iter, cbSegRet); +#endif cbChunk = (size_t)cbSegRet; cPages = RT_ALIGN_Z(offPage0 + cbSegRet, PAGE_SIZE) >> PAGE_SHIFT; cMaxPages -= cPages; @@ -2188,9 +2219,11 @@ iov_iter_advance(iter, 0); cbSeg = iov_iter_single_seg_count(iter); } - cbSegRet = iov_iter_get_pages(iter, &papPages[cPages], iov_iter_count(iter), 1, &offPgProbe); + cbSegRet = VBOX_IOV_GET_PAGES(iter, &papPages[cPages], iov_iter_count(iter), 1, &offPgProbe); if (cbSegRet > 0) { +# if RTLNX_VER_MAX(6,0,0) iov_iter_advance(iter, cbSegRet); /** @todo maybe not do this if we stash the page? */ +#endif Assert(offPgProbe + cbSegRet <= PAGE_SIZE); if (offPgProbe == 0) { cbChunk += cbSegRet; @@ -2206,11 +2239,13 @@ */ cbSeg -= cbSegRet; if (cbSeg > 0) { - cbSegRet = iov_iter_get_pages(iter, &papPages[cPages], iov_iter_count(iter), cMaxPages, &offPgProbe); + cbSegRet = VBOX_IOV_GET_PAGES(iter, &papPages[cPages], iov_iter_count(iter), cMaxPages, &offPgProbe); if (cbSegRet > 0) { size_t const cPgRet = RT_ALIGN_Z((size_t)cbSegRet, PAGE_SIZE) >> PAGE_SHIFT; Assert(offPgProbe == 0); +# if RTLNX_VER_MAX(6,0,0) iov_iter_advance(iter, cbSegRet); +# endif SFLOG3(("vbsf_iter_lock_pages: iov_iter_get_pages() -> %#zx; %#zx pages\n", cbSegRet, cPgRet)); cPages += cPgRet; cMaxPages -= cPgRet; @@ -2362,8 +2397,8 @@ { size_t cPages; # if RTLNX_VER_MIN(3,16,0) - if (iter_is_iovec(iter) || (iter->type & ITER_KVEC)) { -#endif + if (iter_is_iovec(iter) || (VBSF_GET_ITER_TYPE(iter) & ITER_KVEC)) { +# endif const struct iovec *pCurIov = iter->iov; size_t cLeft = iter->nr_segs; size_t cPagesSpan = 0; @@ -2425,7 +2460,7 @@ } else { /* Won't bother with accurate counts for the next two types, just make some rough estimates (does pipes have segments?): */ - size_t cSegs = iter->type & ITER_BVEC ? RT_MAX(1, iter->nr_segs) : 1; + size_t cSegs = VBSF_GET_ITER_TYPE(iter) & ITER_BVEC ? RT_MAX(1, iter->nr_segs) : 1; cPages = (iov_iter_count(iter) + (PAGE_SIZE * 2 - 2) * cSegs) >> PAGE_SHIFT; } # endif @@ -2578,7 +2613,7 @@ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb); SFLOGFLOW(("vbsf_reg_read_iter: inode=%p file=%p size=%#zx off=%#llx type=%#x\n", - inode, kio->ki_filp, cbToRead, kio->ki_pos, iter->type)); + inode, kio->ki_filp, cbToRead, kio->ki_pos, VBSF_GET_ITER_TYPE(iter) )); AssertReturn(S_ISREG(inode->i_mode), -EINVAL); /* @@ -2814,7 +2849,7 @@ SFLOGFLOW(("vbsf_reg_write_iter: inode=%p file=%p size=%#zx off=%#llx type=%#x\n", - inode, kio->ki_filp, cbToWrite, offFile, iter->type)); + inode, kio->ki_filp, cbToWrite, offFile, VBSF_GET_ITER_TYPE(iter) )); AssertReturn(S_ISREG(inode->i_mode), -EINVAL); /* @@ -3399,7 +3434,16 @@ /* Special page fault callback for mapping pages: */ -# if RTLNX_VER_MIN(4,10,0) +# if RTLNX_VER_MIN(5,12,0) +static vm_fault_t vbsf_vmlog_map_pages(struct vm_fault *vmf, pgoff_t start, pgoff_t end) +{ + vm_fault_t rc; + SFLOGFLOW(("vbsf_vmlog_map_pages: vmf=%p (flags=%#x addr=%p) start=%p end=%p\n", vmf, vmf->flags, vmf->address, start, end)); + rc = g_pGenericFileVmOps->map_pages(vmf, start, end); + SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n")); + return rc; +} +# elif RTLNX_VER_MIN(4,10,0) static void vbsf_vmlog_map_pages(struct vm_fault *vmf, pgoff_t start, pgoff_t end) { SFLOGFLOW(("vbsf_vmlog_map_pages: vmf=%p (flags=%#x addr=%p) start=%p end=%p\n", vmf, vmf->flags, vmf->address, start, end)); @@ -3489,8 +3533,10 @@ */ struct file_operations vbsf_reg_fops = { .open = vbsf_reg_open, +#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */ .read = vbsf_reg_read, .write = vbsf_reg_write, +#endif #if RTLNX_VER_MIN(3,16,0) .read_iter = vbsf_reg_read_iter, .write_iter = vbsf_reg_write_iter, @@ -3547,8 +3593,14 @@ * Needed for mmap and reads+writes when the file is mmapped in a * shared+writeable fashion. */ +#if RTLNX_VER_MIN(5,19,0) +static int vbsf_read_folio(struct file *file, struct folio *folio) +{ + struct page *page = &folio->page; +#else static int vbsf_readpage(struct file *file, struct page *page) { +#endif struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode; int err; @@ -3694,8 +3746,7 @@ /** * Called when writing thru the page cache (which we shouldn't be doing). */ -int vbsf_write_begin(struct file *file, struct address_space *mapping, loff_t pos, - unsigned len, unsigned flags, struct page **pagep, void **fsdata) +static inline void vbsf_write_begin_warn(loff_t pos, unsigned len, unsigned flags) { /** @todo r=bird: We shouldn't ever get here, should we? Because we don't use * the page cache for any writes AFAIK. We could just as well use @@ -3711,10 +3762,49 @@ WARN_ON(1); # endif } +} + +# if RTLNX_VER_MIN(5,19,0) +int vbsf_write_begin(struct file *file, struct address_space *mapping, loff_t pos, + unsigned len, struct page **pagep, void **fsdata) +{ + vbsf_write_begin_warn(pos, len, 0); + return simple_write_begin(file, mapping, pos, len, pagep, fsdata); +} +# else +int vbsf_write_begin(struct file *file, struct address_space *mapping, loff_t pos, + unsigned len, unsigned flags, struct page **pagep, void **fsdata) +{ + vbsf_write_begin_warn(pos, len, flags); return simple_write_begin(file, mapping, pos, len, flags, pagep, fsdata); } +# endif + #endif /* KERNEL_VERSION >= 2.6.24 */ +#if RTLNX_VER_MIN(5,14,0) +/** + * Companion to vbsf_write_begin (i.e. shouldn't be called). + */ +static int vbsf_write_end(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int copied, + struct page *page, void *fsdata) +{ + static uint64_t volatile s_cCalls = 0; + if (s_cCalls++ < 16) + { + printk("vboxsf: Unexpected call to vbsf_write_end(pos=%#llx len=%#x)! Please report.\n", + (unsigned long long)pos, len); + RTLogBackdoorPrintf("vboxsf: Unexpected call to vbsf_write_end(pos=%#llx len=%#x)! Please report.\n", + (unsigned long long)pos, len); +# ifdef WARN_ON + WARN_ON(1); +# endif + } + return -ENOTSUPP; +} +#endif /* KERNEL_VERSION >= 5.14.0 */ + #if RTLNX_VER_MIN(2,4,10) @@ -3763,13 +3853,22 @@ * @todo the FsPerf touch/flush (mmap) test fails on 4.4.0 (ubuntu 16.04 lts). */ struct address_space_operations vbsf_reg_aops = { +#if RTLNX_VER_MIN(5,19,0) + .read_folio = vbsf_read_folio, +#else .readpage = vbsf_readpage, +#endif .writepage = vbsf_writepage, /** @todo Need .writepages if we want msync performance... */ -#if RTLNX_VER_MIN(2,5,12) +#if RTLNX_VER_MIN(5,18,0) + .dirty_folio = filemap_dirty_folio, +#elif RTLNX_VER_MIN(2,5,12) .set_page_dirty = __set_page_dirty_buffers, #endif -#if RTLNX_VER_MIN(2,6,24) +#if RTLNX_VER_MIN(5,14,0) + .write_begin = vbsf_write_begin, + .write_end = vbsf_write_end, +#elif RTLNX_VER_MIN(2,6,24) .write_begin = vbsf_write_begin, .write_end = simple_write_end, #elif RTLNX_VER_MIN(2,5,45) @@ -3780,4 +3879,3 @@ .direct_IO = vbsf_direct_IO, #endif }; - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/utils.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/utils.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/utils.c 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/utils.c 2022-09-01 13:20:55.000000000 +0000 @@ -700,7 +700,10 @@ [generic_fillattr] */ #if RTLNX_VER_MIN(2,5,18) -# if RTLNX_VER_MIN(4,11,0) +# if RTLNX_VER_MIN(5,12,0) +int vbsf_inode_getattr(struct user_namespace *ns, const struct path *path, + struct kstat *kstat, u32 request_mask, unsigned int flags) +# elif RTLNX_VER_MIN(4,11,0) int vbsf_inode_getattr(const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int flags) # else int vbsf_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *kstat) @@ -740,7 +743,11 @@ # endif if (rc == 0) { /* Do generic filling in of info. */ +# if RTLNX_VER_MIN(5,12,0) + generic_fillattr(ns, dentry->d_inode, kstat); +# else generic_fillattr(dentry->d_inode, kstat); +# endif /* Add birth time. */ # if RTLNX_VER_MIN(4,11,0) @@ -786,7 +793,11 @@ /** * Modify inode attributes. */ +#if RTLNX_VER_MIN(5,12,0) +int vbsf_inode_setattr(struct user_namespace *ns, struct dentry *dentry, struct iattr *iattr) +#else int vbsf_inode_setattr(struct dentry *dentry, struct iattr *iattr) +#endif { struct inode *pInode = dentry->d_inode; struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pInode->i_sb); @@ -805,8 +816,12 @@ * from futimes() when asked to preserve times, see ticketref:18569. */ iattr->ia_valid |= ATTR_FORCE; -#if (RTLNX_VER_RANGE(3,16,39, 3,17,0)) || RTLNX_VER_MIN(4,9,0) || (RTLNX_VER_RANGE(4,1,37, 4,2,0)) +#if (RTLNX_VER_RANGE(3,16,39, 3,17,0)) || RTLNX_VER_MIN(4,9,0) || (RTLNX_VER_RANGE(4,1,37, 4,2,0)) || RTLNX_UBUNTU_ABI_MIN(4,4,255,208) +# if RTLNX_VER_MIN(5,12,0) + rc = setattr_prepare(ns, dentry, iattr); +# else rc = setattr_prepare(dentry, iattr); +# endif #else rc = inode_change_ok(pInode, iattr); #endif diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/vbsfmount.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/vbsfmount.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/vbsfmount.c 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/vbsfmount.c 2022-09-01 13:20:55.000000000 +0000 @@ -16,9 +16,14 @@ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ #ifndef _GNU_SOURCE -#define _GNU_SOURCE +# define _GNU_SOURCE #endif +#include #include #include #include @@ -31,84 +36,68 @@ /** @todo Use defines for return values! */ -int vbsfmount_complete(const char *host_name, const char *mount_point, - unsigned long flags, struct vbsf_mount_opts *opts) +int vbsfmount_complete(const char *pszSharedFolder, const char *pszMountPoint, + unsigned long fFlags, const char *pszOpts) { - FILE *f, *m; - char *buf; - size_t size; - struct mntent e; - int rc = 0; - - m = open_memstream(&buf, &size); - if (!m) - return 1; /* Could not update mount table (failed to create memstream). */ - - if (opts->ttl != -1) - fprintf(m, "ttl=%d,", opts->ttl); - if (opts->msDirCacheTTL >= 0) - fprintf(m, "dcachettl=%d,", opts->msDirCacheTTL); - if (opts->msInodeTTL >= 0) - fprintf(m, "inodettl=%d,", opts->msInodeTTL); - if (opts->cMaxIoPages) - fprintf(m, "maxiopages=%u,", opts->cMaxIoPages); - if (opts->cbDirBuf) - fprintf(m, "dirbuf=%u,", opts->cbDirBuf); - switch (opts->enmCacheMode) + /* + * Combine pszOpts and fFlags. + */ + int rc; + size_t const cchFlags = (fFlags & MS_NOSUID ? strlen(MNTOPT_NOSUID) + 1 : 0) + + (fFlags & MS_RDONLY ? strlen(MNTOPT_RO) : strlen(MNTOPT_RW)); + size_t const cchOpts = pszOpts ? 1 + strlen(pszOpts) : 0; + char *pszBuf = (char *)malloc(cchFlags + cchOpts + 8); + if (pszBuf) { - default: - case kVbsfCacheMode_Default: - break; - case kVbsfCacheMode_None: fprintf(m, "cache=none,"); break; - case kVbsfCacheMode_Strict: fprintf(m, "cache=strict,"); break; - case kVbsfCacheMode_Read: fprintf(m, "cache=read,"); break; - case kVbsfCacheMode_ReadWrite: fprintf(m, "cache=readwrite,"); break; - } - if (opts->uid) - fprintf(m, "uid=%d,", opts->uid); - if (opts->gid) - fprintf(m, "gid=%d,", opts->gid); - if (*opts->nls_name) - fprintf(m, "iocharset=%s,", opts->nls_name); - if (flags & MS_NOSUID) - fprintf(m, "%s,", MNTOPT_NOSUID); - if (flags & MS_RDONLY) - fprintf(m, "%s,", MNTOPT_RO); - else - fprintf(m, "%s,", MNTOPT_RW); + char *psz = pszBuf; + FILE *pMTab; - fclose(m); + strcpy(psz, fFlags & MS_RDONLY ? MNTOPT_RO : MNTOPT_RW); + psz += strlen(psz); - if (size > 0) - buf[size - 1] = 0; - else - buf = "defaults"; + if (fFlags & MS_NOSUID) + { + *psz++ = ','; + strcpy(psz, MNTOPT_NOSUID); + psz += strlen(psz); + } + + if (cchOpts) + { + *psz++ = ','; + strcpy(psz, pszOpts); + } + + assert(strlen(pszBuf) <= cchFlags + cchOpts); + + /* + * Open the mtab and update it: + */ + pMTab = setmntent(MOUNTED, "a+"); + if (pMTab) + { + struct mntent Entry; + Entry.mnt_fsname = (char*)pszSharedFolder; + Entry.mnt_dir = (char *)pszMountPoint; + Entry.mnt_type = "vboxsf"; + Entry.mnt_opts = pszBuf; + Entry.mnt_freq = 0; + Entry.mnt_passno = 0; + + if (!addmntent(pMTab, &Entry)) + rc = 0; /* success. */ + else + rc = 3; /* Could not add an entry to the mount table. */ + + endmntent(pMTab); + } + else + rc = 2; /* Could not open mount table for update. */ - f = setmntent(MOUNTED, "a+"); - if (!f) - { - rc = 2; /* Could not open mount table for update. */ + free(pszBuf); } else - { - e.mnt_fsname = (char*)host_name; - e.mnt_dir = (char*)mount_point; - e.mnt_type = "vboxsf"; - e.mnt_opts = buf; - e.mnt_freq = 0; - e.mnt_passno = 0; - - if (addmntent(f, &e)) - rc = 3; /* Could not add an entry to the mount table. */ - - endmntent(f); - } - - if (size > 0) - { - memset(buf, 0, size); - free(buf); - } - + rc = 1; /* allocation error */ return rc; } + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/vbsfmount.h virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/vbsfmount.h --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/vbsfmount.h 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/vbsfmount.h 2022-09-01 13:20:55.000000000 +0000 @@ -34,9 +34,10 @@ # pragma once #endif -/* Linux constraints the size of data mount argument to PAGE_SIZE - 1. */ -#define MAX_HOST_NAME 256 -#define MAX_NLS_NAME 32 +/* Linux constrains the size of data mount argument to PAGE_SIZE - 1. */ +#define MAX_MNTOPT_STR PAGE_SIZE +#define MAX_HOST_NAME 256 +#define MAX_NLS_NAME 32 #define VBSF_DEFAULT_TTL_MS 200 #define VBSF_MOUNT_SIGNATURE_BYTE_0 '\377' @@ -134,34 +135,8 @@ AssertCompileSize(struct vbsf_mount_info_new, 2*4 + MAX_HOST_NAME + MAX_NLS_NAME + 7*4 + 32 + 5*4); #endif -/** - * For use with the vbsfmount_complete() helper. - */ -struct vbsf_mount_opts { - int ttl; - int32_t msDirCacheTTL; - int32_t msInodeTTL; - uint32_t cMaxIoPages; - uint32_t cbDirBuf; - enum vbsf_cache_mode enmCacheMode; - int uid; - int gid; - int dmode; - int fmode; - int dmask; - int fmask; - int ronly; - int sloppy; - int noexec; - int nodev; - int nosuid; - int remount; - char nls_name[MAX_NLS_NAME]; - char *convertcp; -}; - /** Completes the mount operation by adding the new mount point to mtab if required. */ -int vbsfmount_complete(const char *host_name, const char *mount_point, - unsigned long flags, struct vbsf_mount_opts *opts); +int vbsfmount_complete(const char *pszSharedFolder, const char *pszMountPoint, + unsigned long fFlags, const char *pszOpts); #endif /* !GA_INCLUDED_SRC_linux_sharedfolders_vbsfmount_h */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/vfsmod.c virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/vfsmod.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/vfsmod.c 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/vfsmod.c 2022-09-01 13:20:55.000000000 +0000 @@ -45,7 +45,7 @@ #include "version-generated.h" #include "revision-generated.h" #include "product-generated.h" -#if RTLNX_VER_MIN(5,0,0) +#if RTLNX_VER_MIN(5,0,0) || RTLNX_RHEL_MIN(8,4) # include /* for MS_REMOUNT */ #elif RTLNX_VER_MAX(3,3,0) # include @@ -57,6 +57,12 @@ #endif #include #include +#if RTLNX_VER_MIN(5,1,0) +# include +# include +#elif RTLNX_VER_MIN(2,6,0) +# include +#endif /********************************************************************************************************************************* @@ -215,17 +221,6 @@ TRACE(); *sf_gp = NULL; /* (old gcc maybe used initialized) */ - /* - * Validate info. - */ - if ( info->nullchar != '\0' - || info->signature[0] != VBSF_MOUNT_SIGNATURE_BYTE_0 - || info->signature[1] != VBSF_MOUNT_SIGNATURE_BYTE_1 - || info->signature[2] != VBSF_MOUNT_SIGNATURE_BYTE_2) { - SFLOGRELBOTH(("vboxsf: Invalid info signature: %#x %#x %#x %#x!\n", - info->nullchar, info->signature[0], info->signature[1], info->signature[2])); - return -EINVAL; - } name_len = RTStrNLen(info->name, sizeof(info->name)); if (name_len >= sizeof(info->name)) { SFLOGRELBOTH(("vboxsf: Specified shared folder name is not zero terminated!\n")); @@ -543,9 +538,321 @@ } +#if RTLNX_VER_MAX(5,1,0) +static void vbsf_init_mount_info(struct vbsf_mount_info_new *mount_info, + const char *sf_name) +{ + mount_info->ttl = mount_info->msDirCacheTTL = mount_info->msInodeTTL = -1; + mount_info->dmode = mount_info->fmode = ~0U; + mount_info->enmCacheMode = kVbsfCacheMode_Strict; + mount_info->length = sizeof(struct vbsf_mount_info_new); + if (sf_name) { +# if RTLNX_VER_MAX(2,5,69) + strncpy(mount_info->name, sf_name, sizeof(mount_info->name)); + mount_info->name[sizeof(mount_info->name)-1] = 0; +# else + strlcpy(mount_info->name, sf_name, sizeof(mount_info->name)); +# endif + } +} +#endif + +#if RTLNX_VER_RANGE(2,6,0, 5,1,0) +/** + * The following section of code uses the Linux match_token() family of + * routines to parse string-based mount options. + */ +enum { + Opt_iocharset, /* nls_name[] */ + Opt_nls, /* alias for iocharset */ + Opt_uid, + Opt_gid, + Opt_ttl, + Opt_dmode, + Opt_fmode, + Opt_dmask, + Opt_fmask, + Opt_umask, + Opt_maxiopages, + Opt_dirbuf, + Opt_dcachettl, + Opt_inodettl, + Opt_cachemode, /* enum vbsf_cache_mode */ + Opt_tag, + Opt_err +}; + +# if RTLNX_VER_MAX(2,6,28) +static match_table_t vbsf_tokens = { +# else +static const match_table_t vbsf_tokens = { +# endif + { Opt_iocharset, "iocharset=%s" }, + { Opt_nls, "nls=%s" }, + { Opt_uid, "uid=%u" }, + { Opt_gid, "gid=%u" }, + { Opt_ttl, "ttl=%u" }, + { Opt_dmode, "dmode=%o" }, + { Opt_fmode, "fmode=%o" }, + { Opt_dmask, "dmask=%o" }, + { Opt_fmask, "fmask=%o" }, + { Opt_umask, "umask=%o" }, + { Opt_maxiopages, "maxiopages=%u" }, + { Opt_dirbuf, "dirbuf=%u" }, + { Opt_dcachettl, "dcachettl=%u" }, + { Opt_inodettl, "inodettl=%u" }, + { Opt_cachemode, "cache=%s" }, + { Opt_tag, "tag=%s" }, /* private option for automounter */ + { Opt_err, NULL } +}; + +static int vbsf_parse_mount_options(char *options, + struct vbsf_mount_info_new *mount_info) +{ + substring_t args[MAX_OPT_ARGS]; + int option; + int token; + char *p; + char *iocharset; + char *cachemode; + char *tag; + + if (!options) + return -EINVAL; + + while ((p = strsep(&options, ",")) != NULL) { + if (!*p) + continue; + + token = match_token(p, vbsf_tokens, args); + switch (token) { + case Opt_iocharset: + case Opt_nls: + iocharset = match_strdup(&args[0]); + if (!iocharset) { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for iocharset!\n")); + return -ENOMEM; + } + strlcpy(mount_info->nls_name, iocharset, + sizeof(mount_info->nls_name)); + kfree(iocharset); + break; + case Opt_uid: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->uid = option; + break; + case Opt_gid: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->gid = option; + break; + case Opt_ttl: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->ttl = option; + break; + case Opt_dmode: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->dmode = option; + break; + case Opt_fmode: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->fmode = option; + break; + case Opt_dmask: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->dmask = option; + break; + case Opt_fmask: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->fmask = option; + break; + case Opt_umask: + if (match_octal(&args[0], &option)) + return -EINVAL; + mount_info->dmask = mount_info->fmask = option; + break; + case Opt_maxiopages: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->cMaxIoPages = option; + break; + case Opt_dirbuf: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->cbDirBuf = option; + break; + case Opt_dcachettl: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->msDirCacheTTL = option; + break; + case Opt_inodettl: + if (match_int(&args[0], &option)) + return -EINVAL; + mount_info->msInodeTTL = option; + break; + case Opt_cachemode: { + cachemode = match_strdup(&args[0]); + if (!cachemode) { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for cachemode!\n")); + return -ENOMEM; + } + if (!strcmp(cachemode, "default") || !strcmp(cachemode, "strict")) + mount_info->enmCacheMode = kVbsfCacheMode_Strict; + else if (!strcmp(cachemode, "none")) + mount_info->enmCacheMode = kVbsfCacheMode_None; + else if (!strcmp(cachemode, "read")) + mount_info->enmCacheMode = kVbsfCacheMode_Read; + else if (!strcmp(cachemode, "readwrite")) + mount_info->enmCacheMode = kVbsfCacheMode_ReadWrite; + else + printk(KERN_WARNING "vboxsf: cache mode (%s) is out of range, using default instead.\n", cachemode); + kfree(cachemode); + break; + } + case Opt_tag: + tag = match_strdup(&args[0]); + if (!tag) { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for automount tag!\n")); + return -ENOMEM; + } + strlcpy(mount_info->szTag, tag, sizeof(mount_info->szTag)); + kfree(tag); + break; + default: + printk(KERN_ERR "unrecognised mount option \"%s\"", p); + return -EINVAL; + } + } + + return 0; +} +#endif /* 5.1.0 > version >= 2.6.0 */ + + +#if RTLNX_VER_MAX(2,6,0) /** - * This is called by vbsf_read_super_24() and vbsf_read_super_26() when vfs mounts - * the fs and wants to read super_block. + * Linux kernel versions older than 2.6.0 don't have the match_token() routines + * so we parse the string-based mount options manually here. + */ +static int vbsf_parse_mount_options(char *options, + struct vbsf_mount_info_new *mount_info) +{ + char *value; + char *option; + + if (!options) + return -EINVAL; + +# if RTLNX_VER_MIN(2,3,9) + while ((option = strsep(&options, ",")) != NULL) { +# else + for (option = strtok(options, ","); option; option = strtok(NULL, ",")) { +# endif + if (!*option) + continue; + + value = strchr(option, '='); + if (value) + *value++ = '\0'; + + if (!strcmp(option, "iocharset") || !strcmp(option, "nls")) { + if (!value || !*value) + return -EINVAL; + strncpy(mount_info->nls_name, value, sizeof(mount_info->nls_name)); + mount_info->nls_name[sizeof(mount_info->nls_name)-1] = 0; + } else if (!strcmp(option, "uid")) { + mount_info->uid = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "gid")) { + mount_info->gid = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "ttl")) { + mount_info->ttl = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "dmode")) { + mount_info->dmode = simple_strtoul(value, &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "fmode")) { + mount_info->fmode = simple_strtoul(value, &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "dmask")) { + mount_info->dmask = simple_strtoul(value, &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "fmask")) { + mount_info->fmask = simple_strtoul(value, &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "umask")) { + mount_info->dmask = mount_info->fmask = simple_strtoul(value, + &value, 8); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "maxiopages")) { + mount_info->cMaxIoPages = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "dirbuf")) { + mount_info->cbDirBuf = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "dcachettl")) { + mount_info->msDirCacheTTL = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "inodettl")) { + mount_info->msInodeTTL = simple_strtoul(value, &value, 0); + if (*value) + return -EINVAL; + } else if (!strcmp(option, "cache")) { + if (!value || !*value) + return -EINVAL; + if (!strcmp(value, "default") || !strcmp(value, "strict")) + mount_info->enmCacheMode = kVbsfCacheMode_Strict; + else if (!strcmp(value, "none")) + mount_info->enmCacheMode = kVbsfCacheMode_None; + else if (!strcmp(value, "read")) + mount_info->enmCacheMode = kVbsfCacheMode_Read; + else if (!strcmp(value, "readwrite")) + mount_info->enmCacheMode = kVbsfCacheMode_ReadWrite; + else + printk(KERN_WARNING "vboxsf: cache mode (%s) is out of range, using default instead.\n", value); + } else if (!strcmp(option, "tag")) { + if (!value || !*value) + return -EINVAL; + strncpy(mount_info->szTag, value, sizeof(mount_info->szTag)); + mount_info->szTag[sizeof(mount_info->szTag)-1] = 0; + } else if (!strcmp(option, "sf_name")) { + if (!value || !*value) + return -EINVAL; + strncpy(mount_info->name, value, sizeof(mount_info->name)); + mount_info->name[sizeof(mount_info->name)-1] = 0; + } else { + printk(KERN_ERR "unrecognised mount option \"%s\"", option); + return -EINVAL; + } + } + + return 0; +} +#endif + + +/** + * This is called by vbsf_read_super_24(), vbsf_read_super_26(), and + * vbsf_get_tree() when vfs mounts the fs and wants to read the super_block. * * Calls vbsf_super_info_alloc_and_map_it() to map the folder and allocate super * information structure. @@ -554,12 +861,17 @@ * * Should respect @a flags. */ +#if RTLNX_VER_MIN(5,1,0) +static int vbsf_read_super_aux(struct super_block *sb, struct fs_context *fc) +#else static int vbsf_read_super_aux(struct super_block *sb, void *data, int flags) +#endif { int rc; struct vbsf_super_info *pSuperInfo; TRACE(); +#if RTLNX_VER_MAX(5,1,0) if (!data) { SFLOGRELBOTH(("vboxsf: No mount data. Is mount.vboxsf installed (typically in /sbin)?\n")); return -EINVAL; @@ -569,11 +881,17 @@ SFLOGRELBOTH(("vboxsf: Remounting is not supported!\n")); return -ENOSYS; } +#endif /* * Create our super info structure and map the shared folder. */ +#if RTLNX_VER_MIN(5,1,0) + struct vbsf_mount_info_new *info = fc->fs_private; + rc = vbsf_super_info_alloc_and_map_it(info, &pSuperInfo); +#else rc = vbsf_super_info_alloc_and_map_it((struct vbsf_mount_info_new *)data, &pSuperInfo); +#endif if (rc == 0) { /* * Initialize the super block structure (must be done before @@ -743,27 +1061,38 @@ return rc; } +#if RTLNX_VER_MIN(5,1,0) +static int vbsf_remount_fs(struct super_block *sb, + struct vbsf_mount_info_new *info) +#else static int vbsf_remount_fs(struct super_block *sb, int *flags, char *data) +#endif { #if RTLNX_VER_MIN(2,4,23) - struct vbsf_super_info *pSuperInfo = pSuperInfo = VBSF_GET_SUPER_INFO(sb); + struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb); struct vbsf_inode_info *sf_i; struct inode *iroot; SHFLFSOBJINFO fsinfo; int err; Assert(pSuperInfo); - if (data && data[0] != 0) { - struct vbsf_mount_info_new *info = (struct vbsf_mount_info_new *)data; - if ( info->nullchar == '\0' - && info->signature[0] == VBSF_MOUNT_SIGNATURE_BYTE_0 - && info->signature[1] == VBSF_MOUNT_SIGNATURE_BYTE_1 - && info->signature[2] == VBSF_MOUNT_SIGNATURE_BYTE_2) { - vbsf_super_info_copy_remount_options(pSuperInfo, info); - } +# if RTLNX_VER_MIN(5,1,0) + vbsf_super_info_copy_remount_options(pSuperInfo, info); +# else + if (VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + vbsf_super_info_copy_remount_options(pSuperInfo, (struct vbsf_mount_info_new *)data); + } else { + struct vbsf_mount_info_new mount_opts = { '\0' }; + vbsf_init_mount_info(&mount_opts, NULL); + err = vbsf_parse_mount_options(data, &mount_opts); + if (err) + return err; + vbsf_super_info_copy_remount_options(pSuperInfo, &mount_opts); } +# endif - iroot = ilookup(sb, 0); + /* '.' and '..' entries are st_ino == 0 so root is #1 */ + iroot = ilookup(sb, 1); if (!iroot) return -ENOSYS; @@ -771,7 +1100,7 @@ err = vbsf_stat(__func__, pSuperInfo, sf_i->path, &fsinfo, 0); BUG_ON(err != 0); vbsf_init_inode(iroot, sf_i, &fsinfo, pSuperInfo); - /*unlock_new_inode(iroot); */ + iput(iroot); return 0; #else /* < 2.4.23 */ return -ENOSYS; @@ -854,7 +1183,9 @@ #endif .put_super = vbsf_put_super, .statfs = vbsf_statfs, +#if RTLNX_VER_MAX(5,1,0) .remount_fs = vbsf_remount_fs, +#endif .show_options = vbsf_show_options }; @@ -864,7 +1195,7 @@ * File system type related stuff. * *********************************************************************************************************************************/ -#if RTLNX_VER_MIN(2,5,4) +#if RTLNX_VER_RANGE(2,5,4, 5,1,0) static int vbsf_read_super_26(struct super_block *sb, void *data, int flags) { @@ -882,44 +1213,80 @@ static struct dentry *sf_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { TRACE(); - return mount_nodev(fs_type, flags, data, vbsf_read_super_26); + + if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + int rc; + struct vbsf_mount_info_new mount_opts = { '\0' }; + + vbsf_init_mount_info(&mount_opts, dev_name); + rc = vbsf_parse_mount_options(data, &mount_opts); + if (rc) + return ERR_PTR(rc); + return mount_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26); + } else { + return mount_nodev(fs_type, flags, data, vbsf_read_super_26); + } } # elif RTLNX_VER_MIN(2,6,18) static int vbsf_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt) { TRACE(); - return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26, mnt); + + if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + int rc; + struct vbsf_mount_info_new mount_opts = { '\0' }; + + vbsf_init_mount_info(&mount_opts, dev_name); + rc = vbsf_parse_mount_options(data, &mount_opts); + if (rc) + return rc; + return get_sb_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26, + mnt); + } else { + return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26, mnt); + } } -# else /* < 2.6.18 */ +# else /* 2.6.18 > version >= 2.5.4 */ static struct super_block *vbsf_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { TRACE(); - return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26); -} -# endif -/** - * File system registration structure. - */ -static struct file_system_type g_vboxsf_fs_type = { - .owner = THIS_MODULE, - .name = "vboxsf", -# if RTLNX_VER_MIN(2,6,39) - .mount = sf_mount, -# else - .get_sb = vbsf_get_sb, + if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + int rc; + struct vbsf_mount_info_new mount_opts = { '\0' }; + + vbsf_init_mount_info(&mount_opts, dev_name); + rc = vbsf_parse_mount_options(data, &mount_opts); + if (rc) + return ERR_PTR(rc); + return get_sb_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26); + } else { + return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26); + } +} # endif - .kill_sb = kill_anon_super -}; +#endif /* 5.1.0 > version >= 2.5.4 */ -#else /* < 2.5.4 */ +#if RTLNX_VER_MAX(2,5,4) /* < 2.5.4 */ static struct super_block *vbsf_read_super_24(struct super_block *sb, void *data, int flags) { int err; TRACE(); - err = vbsf_read_super_aux(sb, data, flags); + + if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + int rc; + struct vbsf_mount_info_new mount_opts = { '\0' }; + + vbsf_init_mount_info(&mount_opts, NULL); + rc = vbsf_parse_mount_options(data, &mount_opts); + if (rc) + return ERR_PTR(rc); + err = vbsf_read_super_aux(sb, &mount_opts, flags); + } else { + err = vbsf_read_super_aux(sb, data, flags); + } if (err) { printk(KERN_DEBUG "vbsf_read_super_aux err=%d\n", err); return NULL; @@ -932,6 +1299,314 @@ #endif /* < 2.5.4 */ +#if RTLNX_VER_MIN(5,1,0) + +/** + * The following section of code uses the Linux filesystem mount API (also + * known as the "filesystem context API") to parse string-based mount options. + * The API is described here: + * https://www.kernel.org/doc/Documentation/filesystems/mount_api.txt + */ +enum vbsf_cache_modes { + VBSF_CACHE_DEFAULT, + VBSF_CACHE_NONE, + VBSF_CACHE_STRICT, + VBSF_CACHE_READ, + VBSF_CACHE_RW +}; + +static const struct constant_table vbsf_param_cache_mode[] = { + { "default", VBSF_CACHE_DEFAULT }, + { "none", VBSF_CACHE_NONE }, + { "strict", VBSF_CACHE_STRICT }, + { "read", VBSF_CACHE_READ }, + { "readwrite", VBSF_CACHE_RW }, + {} +}; + +enum { + Opt_iocharset, /* nls_name[] */ + Opt_nls, /* alias for iocharset */ + Opt_uid, + Opt_gid, + Opt_ttl, + Opt_dmode, + Opt_fmode, + Opt_dmask, + Opt_fmask, + Opt_umask, + Opt_maxiopages, + Opt_dirbuf, + Opt_dcachettl, + Opt_inodettl, + Opt_cachemode, /* enum vbsf_cache_mode */ + Opt_tag +}; + +# if RTLNX_VER_MAX(5,6,0) +static const struct fs_parameter_spec vbsf_fs_specs[] = { +# else +static const struct fs_parameter_spec vbsf_fs_parameters[] = { +# endif + fsparam_string("iocharset", Opt_iocharset), + fsparam_string("nls", Opt_nls), + fsparam_u32 ("uid", Opt_uid), + fsparam_u32 ("gid", Opt_gid), + fsparam_u32 ("ttl", Opt_ttl), + fsparam_u32oct("dmode", Opt_dmode), + fsparam_u32oct("fmode", Opt_fmode), + fsparam_u32oct("dmask", Opt_dmask), + fsparam_u32oct("fmask", Opt_fmask), + fsparam_u32oct("umask", Opt_umask), + fsparam_u32 ("maxiopages", Opt_maxiopages), + fsparam_u32 ("dirbuf", Opt_dirbuf), + fsparam_u32 ("dcachettl", Opt_dcachettl), + fsparam_u32 ("inodettl", Opt_inodettl), +# if RTLNX_VER_MAX(5,6,0) + fsparam_enum ("cache", Opt_cachemode), +# else + fsparam_enum ("cache", Opt_cachemode, vbsf_param_cache_mode), +# endif + fsparam_string("tag", Opt_tag), + {} +}; + +# if RTLNX_VER_MAX(5,6,0) +static const struct fs_parameter_enum vbsf_fs_enums[] = { + { Opt_cachemode, "default", VBSF_CACHE_DEFAULT }, + { Opt_cachemode, "none", VBSF_CACHE_NONE }, + { Opt_cachemode, "strict", VBSF_CACHE_STRICT }, + { Opt_cachemode, "read", VBSF_CACHE_READ }, + { Opt_cachemode, "readwrite", VBSF_CACHE_RW }, + {} +}; + +static const struct fs_parameter_description vbsf_fs_parameters = { + .name = "vboxsf", + .specs = vbsf_fs_specs, + .enums = vbsf_fs_enums +}; +# endif + +/** + * Parse the (string-based) mount options passed in as -o foo,bar=123,etc. + */ +static int vbsf_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct fs_parse_result result; + struct vbsf_mount_info_new *info = fc->fs_private; + int opt; + +# if RTLNX_VER_MAX(5,6,0) + opt = fs_parse(fc, &vbsf_fs_parameters, param, &result); +# else + opt = fs_parse(fc, vbsf_fs_parameters, param, &result); +# endif + if (opt < 0) + return opt; + + switch (opt) { + case Opt_iocharset: + case Opt_nls: + strlcpy(info->nls_name, param->string, sizeof(info->nls_name)); + break; + case Opt_uid: + info->uid = result.uint_32; + break; + case Opt_gid: + info->gid = result.uint_32; + break; + case Opt_ttl: + info->ttl = result.uint_32; + break; + case Opt_dmode: + if (result.uint_32 & ~0777) + return invalf(fc, "Invalid dmode specified: '%o'", result.uint_32); + info->dmode = result.uint_32; + break; + case Opt_fmode: + if (result.uint_32 & ~0777) + return invalf(fc, "Invalid fmode specified: '%o'", result.uint_32); + info->fmode = result.uint_32; + break; + case Opt_dmask: + if (result.uint_32 & ~07777) + return invalf(fc, "Invalid dmask specified: '%o'", result.uint_32); + info->dmask = result.uint_32; + break; + case Opt_fmask: + if (result.uint_32 & ~07777) + return invalf(fc, "Invalid fmask specified: '%o'", result.uint_32); + info->fmask = result.uint_32; + break; + case Opt_umask: + if (result.uint_32 & ~07777) + return invalf(fc, "Invalid umask specified: '%o'", result.uint_32); + info->dmask = info->fmask = result.uint_32; + break; + case Opt_maxiopages: + info->cMaxIoPages = result.uint_32; + break; + case Opt_dirbuf: + info->cbDirBuf = result.uint_32; + break; + case Opt_dcachettl: + info->msDirCacheTTL = result.uint_32; + break; + case Opt_inodettl: + info->msInodeTTL = result.uint_32; + break; + case Opt_cachemode: + if (result.uint_32 == VBSF_CACHE_DEFAULT || result.uint_32 == VBSF_CACHE_STRICT) + info->enmCacheMode = kVbsfCacheMode_Strict; + else if (result.uint_32 == VBSF_CACHE_NONE) + info->enmCacheMode = kVbsfCacheMode_None; + else if (result.uint_32 == VBSF_CACHE_READ) + info->enmCacheMode = kVbsfCacheMode_Read; + else if (result.uint_32 == VBSF_CACHE_RW) + info->enmCacheMode = kVbsfCacheMode_ReadWrite; + else + printk(KERN_WARNING "vboxsf: cache mode (%u) is out of range, using default instead.\n", result.uint_32); + break; + case Opt_tag: + strlcpy(info->szTag, param->string, sizeof(info->szTag)); + break; + default: + return invalf(fc, "Invalid mount option: '%s'", param->key); + } + + return 0; +} + +/** + * Parse the mount options provided whether by the mount.vboxsf utility + * which supplies the mount information as a page of data or else as a + * string in the following format: key[=val][,key[=val]]*. + */ +static int vbsf_parse_monolithic(struct fs_context *fc, void *data) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + + if (data) { + if (VBSF_IS_MOUNT_VBOXSF_DATA(data)) { + memcpy(info, data, sizeof(struct vbsf_mount_info_new)); + } else { + /* this will call vbsf_parse_param() */ + return generic_parse_monolithic(fc, data); + } + } + + return 0; +} + +/** + * Clean up the filesystem-specific part of the filesystem context. + */ +static void vbsf_free_ctx(struct fs_context *fc) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + + if (info) { + kfree(info); + fc->fs_private = NULL; + } +} + +/** + * Create the mountable root and superblock which can then be used later for + * mounting the shared folder. The superblock is populated by + * vbsf_read_super_aux() which also sets up the shared folder mapping and the + * related paperwork in preparation for mounting the shared folder. + */ +static int vbsf_get_tree(struct fs_context *fc) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + + if (!fc->source) { + SFLOGRELBOTH(("vboxsf: No shared folder specified\n")); + return invalf(fc, "vboxsf: No shared folder specified"); + } + + /* fc->source (the shared folder name) is set after vbsf_init_fs_ctx() */ + strlcpy(info->name, fc->source, sizeof(info->name)); + +# if RTLNX_VER_MAX(5,3,0) + return vfs_get_super(fc, vfs_get_independent_super, vbsf_read_super_aux); +# else + return get_tree_nodev(fc, vbsf_read_super_aux); +# endif +} + +/** + * Reconfigures the superblock based on the mount information stored in the + * filesystem context. Called via '-o remount' (aka mount(2) with MS_REMOUNT) + * and is the equivalent of .fs_remount. + */ +static int vbsf_reconfigure(struct fs_context *fc) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + struct super_block *sb = fc->root->d_sb; + + return vbsf_remount_fs(sb, info); +} + +static const struct fs_context_operations vbsf_context_ops = { + .parse_param = vbsf_parse_param, + .parse_monolithic = vbsf_parse_monolithic, + .free = vbsf_free_ctx, + .get_tree = vbsf_get_tree, + .reconfigure = vbsf_reconfigure +}; + +/** + * Set up the filesystem mount context. + */ +static int vbsf_init_fs_context(struct fs_context *fc) +{ + struct vbsf_mount_info_new *info = fc->fs_private; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + SFLOGRELBOTH(("vboxsf: Could not allocate memory for mount options\n")); + return -ENOMEM; + } + + /* set default values for the mount information structure */ + info->ttl = info->msDirCacheTTL = info->msInodeTTL = -1; + info->dmode = info->fmode = ~0U; + info->enmCacheMode = kVbsfCacheMode_Strict; + info->length = sizeof(struct vbsf_mount_info_new); + + fc->fs_private = info; + fc->ops = &vbsf_context_ops; + + return 0; +} +#endif /* >= 5.1.0 */ + + +#if RTLNX_VER_MIN(2,5,4) +/** + * File system registration structure. + */ +static struct file_system_type g_vboxsf_fs_type = { + .owner = THIS_MODULE, + .name = "vboxsf", +# if RTLNX_VER_MIN(5,1,0) + .init_fs_context = vbsf_init_fs_context, +# if RTLNX_VER_MAX(5,6,0) + .parameters = &vbsf_fs_parameters, +# else + .parameters = vbsf_fs_parameters, +# endif +# elif RTLNX_VER_MIN(2,6,39) + .mount = sf_mount, +# else + .get_sb = vbsf_get_sb, +# endif + .kill_sb = kill_anon_super +}; +#endif /* >= 2.5.4 */ /********************************************************************************************************************************* @@ -1002,15 +1677,15 @@ */ rc = register_filesystem(&g_vboxsf_fs_type); if (rc == 0) { - printk(KERN_INFO "vboxsf: Successfully loaded version " VBOX_VERSION_STRING "\n"); + printk(KERN_INFO "vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n"); #ifdef VERMAGIC_STRING - LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " on %s (LINUX_VERSION_CODE=%#x)\n", + LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " on %s (LINUX_VERSION_CODE=%#x)\n", VERMAGIC_STRING, LINUX_VERSION_CODE)); #elif defined(UTS_RELEASE) - LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " on %s (LINUX_VERSION_CODE=%#x)\n", + LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " on %s (LINUX_VERSION_CODE=%#x)\n", UTS_RELEASE, LINUX_VERSION_CODE)); #else - LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " (LINUX_VERSION_CODE=%#x)\n", LINUX_VERSION_CODE)); + LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " (LINUX_VERSION_CODE=%#x)\n", LINUX_VERSION_CODE)); #endif return 0; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/vfsmod.h virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/vfsmod.h --- virtualbox-6.1.16-dfsg/src/VBox/Additions/linux/sharedfolders/vfsmod.h 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/linux/sharedfolders/vfsmod.h 2022-09-01 13:20:55.000000000 +0000 @@ -264,7 +264,10 @@ extern int vbsf_inode_revalidate_worker(struct dentry *dentry, bool fForced, bool fInodeLocked); extern int vbsf_inode_revalidate_with_handle(struct dentry *dentry, SHFLHANDLE hHostFile, bool fForced, bool fInodeLocked); #if RTLNX_VER_MIN(2,5,18) -# if RTLNX_VER_MIN(4,11,0) +# if RTLNX_VER_MIN(5,12,0) +extern int vbsf_inode_getattr(struct user_namespace *ns, const struct path *path, + struct kstat *kstat, u32 request_mask, unsigned int query_flags); +# elif RTLNX_VER_MIN(4,11,0) extern int vbsf_inode_getattr(const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int query_flags); # else extern int vbsf_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *kstat); @@ -272,7 +275,11 @@ #else /* < 2.5.44 */ extern int vbsf_inode_revalidate(struct dentry *dentry); #endif /* < 2.5.44 */ +#if RTLNX_VER_MIN(5,12,0) +extern int vbsf_inode_setattr(struct user_namespace *ns, struct dentry *dentry, struct iattr *iattr); +#else extern int vbsf_inode_setattr(struct dentry *dentry, struct iattr *iattr); +#endif extern void vbsf_handle_drop_chain(struct vbsf_inode_info *pInodeInfo); @@ -426,6 +433,17 @@ # define VBSF_GET_F_DENTRY(f) (f->f_dentry) #endif +/** + * Macro for checking if the 'data' argument passed in via mount(2) was supplied + * by the mount.vboxsf command line utility as a page of data containing the + * vbsf_mount_info_new structure. + */ +#define VBSF_IS_MOUNT_VBOXSF_DATA(data) \ + (((struct vbsf_mount_info_new *)data)->nullchar == '\0' && \ + ((struct vbsf_mount_info_new *)data)->signature[0] == VBSF_MOUNT_SIGNATURE_BYTE_0 && \ + ((struct vbsf_mount_info_new *)data)->signature[1] == VBSF_MOUNT_SIGNATURE_BYTE_1 && \ + ((struct vbsf_mount_info_new *)data)->signature[2] == VBSF_MOUNT_SIGNATURE_BYTE_2) + extern int vbsf_stat(const char *caller, struct vbsf_super_info *pSuperInfo, SHFLSTRING * path, PSHFLFSOBJINFO result, int ok_to_fail); extern int vbsf_path_from_dentry(struct vbsf_super_info *pSuperInfo, struct vbsf_inode_info *sf_i, struct dentry *dentry, diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/Additions/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Additions/Makefile.kmk 2020-10-16 16:30:02.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/Makefile.kmk 2022-09-01 13:20:32.000000000 +0000 @@ -47,69 +47,71 @@ # Include sub-makefiles. include $(PATH_SUB_CURRENT)/common/Makefile.kmk -ifndef VBOX_ONLY_VALIDATIONKIT - ifdef VBOX_WITH_X11_ADDITIONS - include $(PATH_SUB_CURRENT)/x11/Makefile.kmk - endif +ifdef VBOX_WITH_X11_ADDITIONS + include $(PATH_SUB_CURRENT)/x11/Makefile.kmk +endif - ifeq ($(KBUILD_TARGET),freebsd) - include $(PATH_SUB_CURRENT)/freebsd/Makefile.kmk - endif - ifeq ($(KBUILD_TARGET),linux) - include $(PATH_SUB_CURRENT)/linux/Makefile.kmk - endif - ifeq ($(KBUILD_TARGET),os2) - include $(PATH_SUB_CURRENT)/os2/Makefile.kmk - endif - ifeq ($(KBUILD_TARGET),solaris) - include $(PATH_SUB_CURRENT)/solaris/Makefile.kmk - endif - ifeq ($(KBUILD_TARGET),win) - ifdef VBOX_WITH_MESA3D - include $(PATH_SUB_CURRENT)/3D/Makefile.kmk - endif - include $(PATH_SUB_CURRENT)/WINNT/Makefile.kmk - endif - ifeq ($(KBUILD_TARGET),darwin) - include $(PATH_SUB_CURRENT)/darwin/Makefile.kmk - endif - ifeq ($(KBUILD_TARGET),haiku) - include $(PATH_SUB_CURRENT)/haiku/Makefile.kmk +ifeq ($(KBUILD_TARGET),freebsd) + include $(PATH_SUB_CURRENT)/freebsd/Makefile.kmk +endif +ifeq ($(KBUILD_TARGET),linux) + include $(PATH_SUB_CURRENT)/linux/Makefile.kmk +endif +ifeq ($(KBUILD_TARGET),os2) + include $(PATH_SUB_CURRENT)/os2/Makefile.kmk +endif +ifeq ($(KBUILD_TARGET),solaris) + include $(PATH_SUB_CURRENT)/solaris/Makefile.kmk +endif +ifeq ($(KBUILD_TARGET),win) + ifdef VBOX_WITH_MESA3D + include $(PATH_SUB_CURRENT)/3D/Makefile.kmk endif + include $(PATH_SUB_CURRENT)/WINNT/Makefile.kmk +endif +ifeq ($(KBUILD_TARGET),darwin) + include $(PATH_SUB_CURRENT)/darwin/Makefile.kmk +endif +ifeq ($(KBUILD_TARGET),haiku) + include $(PATH_SUB_CURRENT)/haiku/Makefile.kmk +endif + +ifeq ($(KBUILD_TARGET),linux) - ifeq ($(KBUILD_TARGET),linux) - - INSTALLS += LnxAddIso-scripts - LnxAddIso-scripts_INST = $(INST_ADDITIONS) - LnxAddIso-scripts_MODE = a+rx,u+w - LnxAddIso-scripts_SOURCES = \ - ../Installer/linux/runasroot.sh \ - linux/installer/autorun.sh - - endif # KBUILD_TARGET == linux - ifeq ($(KBUILD_TARGET),win) - # - # Inf2Cat requires all the files referenced in the .inf file - # to be present in the directory, so we have to do this from here, - # since VBoxGuest.sys is being built from the common sources. - # - INSTALLS += VBoxGuest-inf - VBoxGuest-inf_INST = $(INST_ADDITIONS) - VBoxGuest-inf_MODE = a+r,u+w - VBoxGuest-inf_SOURCES = \ - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf \ - $(if-expr defined(VBOX_SIGN_ADDITIONS),$(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.cat,) \ - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.sys \ - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxControl.exe \ - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxTray.exe - VBoxGuest-inf_CLEAN = $(VBoxGuest-inf_SOURCES) - VBoxGuest-inf_BLDDIRS = \ - $(PATH_TARGET)/VBoxGuestCat.dir + INSTALLS += LnxAddIso-scripts + LnxAddIso-scripts_INST = $(INST_ADDITIONS) + LnxAddIso-scripts_MODE = a+rx,u+w + LnxAddIso-scripts_SOURCES = \ + ../Installer/linux/runasroot.sh \ + linux/installer/autorun.sh - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf: $(PATH_SUB_CURRENT)/common/VBoxGuest/win/VBoxGuest.inf $(MAKEFILE_CURRENT) | $$(dir $$@) +endif # KBUILD_TARGET == linux +ifeq ($(KBUILD_TARGET),win) + # + # Inf2Cat requires all the files referenced in the .inf file + # to be present in the directory, so we have to do this from here, + # since VBoxGuest.sys is being built from the common sources. + # + INSTALLS += VBoxGuest-inf + VBoxGuest-inf_INST = $(INST_ADDITIONS) + VBoxGuest-inf_MODE = a+r,u+w + VBoxGuest-inf_SOURCES = \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf + VBoxGuest-inf_CLEAN = $(VBoxGuest-inf_SOURCES) + VBoxGuest-inf_BLDDIRS = $(PATH_TARGET)/VBoxGuestCat.dir + + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf: $(PATH_SUB_CURRENT)/common/VBoxGuest/win/VBoxGuest.inf $(MAKEFILE_CURRENT) | $$(dir $$@) $(call MSG_GENERATE,VBoxGuest-inf,$@,$<) $(call VBOX_EDIT_INF_FN,$<,$@) +if defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS) + VBoxGuest-inf_SOURCES += \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.cat \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.cat=>VBoxGuest-PreW10.cat \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.sys \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxControl.exe \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxTray.exe + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.sys: $$(VBoxGuest_1_TARGET) | $$(dir $$@) $(INSTALL) -m 644 $< $(@D) @@ -120,20 +122,20 @@ $(INSTALL) -m 755 $< $(@D) $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.cat: \ - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf \ - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.sys \ - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxControl.exe \ - $(PATH_TARGET)/VBoxGuestCat.dir/VBoxTray.exe + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.sys \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxControl.exe \ + $(PATH_TARGET)/VBoxGuestCat.dir/VBoxTray.exe $(call MSG_TOOL,Inf2Cat,VBoxGuest-inf,$@,$<) $(call VBOX_MAKE_CAT_FN, $(@D),$@) - endif # KBUILD_TARGET == win + endif # signing +endif # KBUILD_TARGET == win - # The packing target rule, but only if we're on the local build box. - # (VBOX_WITHOUT_ADDITIONS_ISO is used by the additions build box, see the root makefile.) - ifndef VBOX_WITHOUT_ADDITIONS_ISO - PACKING += $(VBOX_PATH_ADDITIONS_ISO)/VBoxGuestAdditions.iso - endif -endif # !VBOX_ONLY_VALIDATIONKIT +# The packing target rule, but only if we're on the local build box. +# (VBOX_WITHOUT_ADDITIONS_ISO is used by the additions build box, see the root makefile.) +ifndef VBOX_WITHOUT_ADDITIONS_ISO + PACKING += $(VBOX_PATH_ADDITIONS_ISO)/VBoxGuestAdditions.iso +endif include $(FILE_KBUILD_SUB_FOOTER) @@ -292,6 +294,10 @@ GUESTADDITIONS_FILESPEC.win += cert/vbox-sha256-r3.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha256-r3.cer endif endif + ifdef VBOX_WITH_VBOX_LEGACY_TS_CA + GUESTADDITIONS_FILESPEC.win += cert/vbox-legacy-timestamp-ca.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-legacy-timestamp-ca.cer + endif + GUESTADDITIONS_FILESPEC.win += windows11-bypass.reg=$(VBOX_PATH_ADDITIONS_SRC)/WINNT/tools/windows11-bypass.reg endif # haiku diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/DRM/include/drmP.h virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/DRM/include/drmP.h --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/DRM/include/drmP.h 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/DRM/include/drmP.h 2022-09-01 13:20:56.000000000 +0000 @@ -57,7 +57,9 @@ #include #include "drm_atomic.h" #include "drm.h" -#include "queue.h" +#if !defined(VBOX_WITH_SYSTEM_QUEUE_H) +# include "queue.h" +#endif #include "drm_linux_list.h" #ifndef __inline__ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/DRM/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/DRM/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/DRM/Makefile.kmk 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/DRM/Makefile.kmk 2022-09-01 13:20:56.000000000 +0000 @@ -39,6 +39,9 @@ vboxvideo_TEMPLATE = VBOXGUESTR0 vboxvideo_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV) vboxvideo_DEPS += $(VBOX_SVN_REV_KMK) +if ($(VBOX_SOLARIS_11_UPDATE_VERSION) > 3) +vboxvideo_DEFS += VBOX_WITH_SYSTEM_QUEUE_H +endif vboxvideo_INCS := \ include/ vboxvideo_SOURCES = \ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/DRM/vboxvideo_drm.c virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/DRM/vboxvideo_drm.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/DRM/vboxvideo_drm.c 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/DRM/vboxvideo_drm.c 2022-09-01 13:20:56.000000000 +0000 @@ -126,9 +126,6 @@ /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ -/** Device handle (we support only one instance). */ -static dev_info_t *g_pDip; - /** Soft state. */ static void *g_pVBoxVideoSolarisState; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Installer/postinstall.sh virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Installer/postinstall.sh --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Installer/postinstall.sh 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Installer/postinstall.sh 2022-09-01 13:20:56.000000000 +0000 @@ -97,8 +97,23 @@ $vboxadditions_path/vboxguest.sh stopall silentunload $vboxadditions_path/vboxguest.sh start + # Figure out group to use for /etc/devlink.tab (before Solaris 11 SRU6 + # it was always using group sys) + group=sys + if [ -f /etc/dev/reserved_devnames ]; then + # Solaris 11 SRU6 and later use group root (check a file which isn't + # tainted by VirtualBox install scripts and allow no other group) + refgroup=`LC_ALL=C /usr/bin/ls -lL /etc/dev/reserved_devnames | awk '{ print $4 }' 2>/dev/null` + if [ $? -eq 0 -a "x$refgroup" = "xroot" ]; then + group=root + fi + unset refgroup + fi + sed -e '/name=vboxguest/d' /etc/devlink.tab > /etc/devlink.vbox echo "type=ddi_pseudo;name=vboxguest \D" >> /etc/devlink.vbox + chmod 0644 /etc/devlink.vbox + chown root:$group /etc/devlink.vbox mv -f /etc/devlink.vbox /etc/devlink.tab # create the device link @@ -352,7 +367,7 @@ # take a while to complete, using disable/enable -s doesn't work either. So we restart it, and poll in # 1 second intervals to see if our service has been successfully imported and timeout after 'cmax' seconds. /usr/sbin/svcadm restart svc:system/manifest-import:default - /usr/bin/svcs virtualbox/vboxservice virtualbox/vboxmslnk >/dev/null 2>&1 + /usr/bin/svcs virtualbox/vboxservice >/dev/null 2>&1 && /usr/bin/svcs virtualbox/vboxmslnk >/dev/null 2>&1 while test "$?" -ne 0; do sleep 1 @@ -361,7 +376,7 @@ success=1 break fi - /usr/bin/svcs virtualbox/vboxservice virtualbox/vboxmslnk >/dev/null 2>&1 + /usr/bin/svcs virtualbox/vboxservice >/dev/null 2>&1 && /usr/bin/svcs virtualbox/vboxmslnk >/dev/null 2>&1 done if test "$success" -eq 0; then echo "Enabling services..." diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Installer/preremove.sh virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Installer/preremove.sh --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Installer/preremove.sh 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Installer/preremove.sh 2022-09-01 13:20:56.000000000 +0000 @@ -47,9 +47,23 @@ # vboxguest.sh would've been installed, we just need to call it. /opt/VirtualBoxAdditions/vboxguest.sh stopall silentunload +# Figure out group to use for /etc/devlink.tab (before Solaris 11 SRU6 +# it was always using group sys) +group=sys +if [ -f /etc/dev/reserved_devnames ]; then + # Solaris 11 SRU6 and later use group root (check a file which isn't + # tainted by VirtualBox install scripts and allow no other group) + refgroup=`LC_ALL=C /usr/bin/ls -lL /etc/dev/reserved_devnames | awk '{ print $4 }' 2>/dev/null` + if [ $? -eq 0 -a "x$refgroup" = "xroot" ]; then + group=root + fi + unset refgroup +fi + # remove devlink.tab entry for vboxguest -sed -e ' -/name=vboxguest/d' /etc/devlink.tab > /etc/devlink.vbox +sed -e '/name=vboxguest/d' /etc/devlink.tab > /etc/devlink.vbox +chmod 0644 /etc/devlink.vbox +chown root:$group /etc/devlink.vbox mv -f /etc/devlink.vbox /etc/devlink.tab # remove the link diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Installer/vboxdevlink.sed virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Installer/vboxdevlink.sed --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Installer/vboxdevlink.sed 2020-10-16 16:30:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Installer/vboxdevlink.sed 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -# $Id$ -## @file -# sed class script to modify /etc/devlink.tab -# - -# -# Copyright (C) 2008-2020 Oracle Corporation -# -# This file is part of VirtualBox Open Source Edition (OSE), as -# available from http://www.virtualbox.org. This file is free software; -# you can redistribute it and/or modify it under the terms of the GNU -# General Public License (GPL) as published by the Free Software -# Foundation, in version 2 as it comes in the "COPYING" file of the -# VirtualBox OSE distribution. VirtualBox OSE is distributed in the -# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. -# -# The contents of this file may alternatively be used under the terms -# of the Common Development and Distribution License Version 1.0 -# (CDDL) only, as it comes in the "COPYING.CDDL" file of the -# VirtualBox OSE distribution, in which case the provisions of the -# CDDL are applicable instead of those of the GPL. -# -# You may elect to license modified versions of this file under the -# terms and conditions of either the GPL or the CDDL or both. -# - -!install -/name=vboxguest/d -$i\ -type=ddi_pseudo;name=vboxguest \\D - -!remove -/name=vboxguest/d - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Makefile.kmk 2020-10-16 16:30:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Makefile.kmk 2022-09-01 13:20:56.000000000 +0000 @@ -101,7 +101,7 @@ # # VBoxAddISAExec # -VBoxAddISAExec_TEMPLATE = VBOXR3EXE +VBoxAddISAExec_TEMPLATE = VBoxGuestR3Exe VBoxAddISAExec_NAME = VBoxISAExec VBoxAddISAExec_INST = $(INST_ADDITIONS) VBoxAddISAExec_DEPS = $(VBOX_SVN_REV_KMK) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Mouse/vboxms.c virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Mouse/vboxms.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/Mouse/vboxms.c 2020-10-16 16:30:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/Mouse/vboxms.c 2022-09-01 13:20:57.000000000 +0000 @@ -328,11 +328,10 @@ case DDI_ATTACH: { int rc; - int instance = ddi_get_instance(pDip); /* Only one instance supported. */ if (!ASMAtomicCmpXchgPtr(&g_OpenNodeState.pDip, pDip, NULL)) return DDI_FAILURE; - rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, instance, DDI_PSEUDO, 0); + rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO, 0 /* flags */); if (rc == DDI_SUCCESS) return DDI_SUCCESS; ASMAtomicWritePtr(&g_OpenNodeState.pDip, NULL); @@ -402,12 +401,19 @@ switch (enmCmd) { case DDI_INFO_DEVT2DEVINFO: + { *ppvResult = (void *)g_OpenNodeState.pDip; + if (!*ppvResult) + rc = DDI_FAILURE; break; + } case DDI_INFO_DEVT2INSTANCE: - *ppvResult = (void *)(uintptr_t)ddi_get_instance(g_OpenNodeState.pDip); + { + /* There can only be a single-instance of this driver and thus its instance number is 0. */ + *ppvResult = (void *)0; break; + } default: rc = DDI_FAILURE; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk 2020-10-16 16:30:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk 2022-09-01 13:20:57.000000000 +0000 @@ -52,10 +52,8 @@ vboxfs_SOURCES += deps.asm vboxfs_deps.asm_ASFLAGS = -f bin -g null endif -if $(VBOX_SOLARIS_11_VERSION) > 175 \ - || ( $(VBOX_SOLARIS_11_VERSION) == 175 \ - && ( $(VBOX_SOLARIS_11_UPDATE_VERSION) > 1 \ - || ($(VBOX_SOLARIS_11_UPDATE_VERSION) == 1 && $(VBOX_SOLARIS_11_BUILD_VERSION) >= 10) ) ) +if ($(VBOX_SOLARIS_11_UPDATE_VERSION) > 1 \ + || ($(VBOX_SOLARIS_11_UPDATE_VERSION) == 1 && $(VBOX_SOLARIS_11_BUILD_VERSION) >= 10)) vboxfs_DEFS += VBOX_VFS_EXTENDED_POLICY endif diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c 2020-10-16 16:30:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c 2022-09-01 13:20:57.000000000 +0000 @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ #endif #include #include +#include #undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ #include "vboxfs_prov.h" @@ -103,6 +105,11 @@ kmutex_t sffs_minor_lock; int sffs_minor; /* minor number for device */ +/** Whether to use the old-style map_addr()/choose_addr() routines. */ +bool g_fVBoxVFS_SolOldAddrMap; +/** The map_addr()/choose_addr() hooks callout table structure. */ +VBoxVFS_SolAddrMap g_VBoxVFS_SolAddrMap; + /* * Module linkage information */ @@ -121,6 +128,37 @@ int _init() { + RTDBGKRNLINFO hKrnlDbgInfo; + int rc = RTR0DbgKrnlInfoOpen(&hKrnlDbgInfo, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + { + rc = RTR0DbgKrnlInfoQuerySymbol(hKrnlDbgInfo, NULL /* pszModule */, "plat_map_align_amount", NULL /* ppvSymbol */); + if (RT_SUCCESS(rc)) + { +#if defined(VBOX_VFS_SOLARIS_10U6) + g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr = (void *)map_addr; +#else + g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr = (void *)choose_addr; +#endif + } + else + { + g_fVBoxVFS_SolOldAddrMap = true; +#if defined(VBOX_VFS_SOLARIS_10U6) + g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr_old = (void *)map_addr; +#else + g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr_old = (void *)choose_addr; +#endif + } + + RTR0DbgKrnlInfoRelease(hKrnlDbgInfo); + } + else + { + cmn_err(CE_NOTE, "RTR0DbgKrnlInfoOpen failed. rc=%d\n", rc); + return rc; + } + return (mod_install(&modlinkage)); } @@ -240,6 +278,7 @@ return (0); } +#ifdef DEBUG_ramshankar static void sffs_print(sffs_data_t *sffs) { @@ -256,6 +295,7 @@ cmn_err(CE_NOTE, " char *sf_mntpath = %s\n", sffs->sf_mntpath); cmn_err(CE_NOTE, " sfp_mount_t *sf_handle = 0x%p\n", sffs->sf_handle); } +#endif static int sffs_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h 2020-10-16 16:30:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h 2022-09-01 13:20:57.000000000 +0000 @@ -48,6 +48,29 @@ uint64_t sf_ino; /* per FS ino generator */ } sffs_data_t; +/* + * Workaround for older Solaris versions which called map_addr()/choose_addr()/ + * map_addr_proc() with an 'alignment' argument that was removed in Solaris + * 11.4. + */ +typedef struct VBoxVFS_SolAddrMap +{ + union + { + void *(*pfnSol_map_addr) (caddr_t *, size_t, offset_t, uint_t); + void *(*pfnSol_map_addr_old) (caddr_t *, size_t, offset_t, int, uint_t); + } MapAddr; + + union + { + int (*pfnSol_choose_addr) (struct as *, caddr_t *, size_t, offset_t, uint_t); + int (*pfnSol_choose_addr_old) (struct as *, caddr_t *, size_t, offset_t, int, uint_t); + } ChooseAddr; +} VBoxVFS_SolAddrMap; +typedef VBoxVFS_SolAddrMap *pVBoxVFS_SolAddrMap; + +extern bool g_fVBoxVFS_SolOldAddrMap; +extern VBoxVFS_SolAddrMap g_VBoxVFS_SolAddrMap; #ifdef __cplusplus } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c --- virtualbox-6.1.16-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c 2020-10-16 16:30:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c 2022-09-01 13:20:57.000000000 +0000 @@ -1766,14 +1766,20 @@ #if defined(VBOX_VFS_SOLARIS_10U6) if ((flags & MAP_FIXED) == 0) { - map_addr(addrp, len, off, 1, flags); + if (g_fVBoxVFS_SolOldAddrMap) + g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr_old(addrp, len, off, 1, flags); + else + g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr(addrp, len, off, flags); if (*addrp == NULL) error = ENOMEM; } else as_unmap(asp, *addrp, len); /* User specified address, remove any previous mappings */ #else - error = choose_addr(asp, addrp, len, off, ADDR_VACALIGN, flags); + if (g_fVBoxVFS_SolOldAddrMap) + error = g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr_old(asp, addrp, len, off, 1, flags); + else + error = g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr(asp, addrp, len, off, flags); #endif if (error) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/undefined_xorg virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/undefined_xorg --- virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/undefined_xorg 2020-10-16 16:30:16.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/undefined_xorg 2022-09-01 13:20:59.000000000 +0000 @@ -181,3 +181,4 @@ xf86UpdateDesktopDimensions XNFcallocarray __xstat64 +__assert_c99 diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/clipboard.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/clipboard.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/clipboard.cpp 2020-10-16 16:30:16.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/clipboard.cpp 2022-09-01 13:20:58.000000000 +0000 @@ -203,14 +203,10 @@ int rcCompletion, CLIPREADCBREQ *pReq, void *pv, uint32_t cb) { LogFlowFunc(("rcCompletion=%Rrc, Format=0x%x, pv=%p, cb=%RU32\n", rcCompletion, pReq->Format, pv, cb)); + RT_NOREF(rcCompletion); - if (RT_SUCCESS(rcCompletion)) /* Only write data if the request succeeded. */ - { - AssertPtrReturnVoid(pv); - AssertReturnVoid(pv); - - rcCompletion = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pReq->Format, pv, cb); - } + Assert((cb == 0 && pv == NULL) || (cb != 0 && pv != NULL)); + rcCompletion = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pReq->Format, pv, cb); RTMemFree(pReq); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/display.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/display.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/display.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/display.cpp 2022-09-01 13:20:58.000000000 +0000 @@ -0,0 +1,318 @@ +/* $Id: display.cpp $ */ +/** @file + * X11 guest client - display management. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "VBoxClient.h" + +#include +#include +#include +#include + +#include +#include +#include + +/** @todo this should probably be replaced by something IPRT */ +/* For system() and WEXITSTATUS() */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* TESTING: Dynamic resizing and mouse integration toggling should work + * correctly with a range of X servers (pre-1.3, 1.3 and later under Linux, 1.3 + * and later under Solaris) with Guest Additions installed. Switching to a + * virtual terminal while a user session is in place should disable dynamic + * resizing and cursor integration, switching back should re-enable them. */ + +/** Display magic number, start of a UUID. */ +#define DISPLAYSTATE_MAGIC UINT32_C(0xf0029993) + +/** State information needed for the service. The main VBoxClient code provides + * the daemon logic needed by all services. */ +struct DISPLAYSTATE +{ + /** The service interface. */ + struct VBCLSERVICE *pInterface; + /** Magic number for sanity checks. */ + uint32_t magic; + /** Are we initialised yet? */ + bool mfInit; + /** The connection to the server. */ + Display *pDisplay; + /** The RandR extension base event number. */ + int cRREventBase; + /** Can we use version 1.2 or later of the RandR protocol here? */ + bool fHaveRandR12; + /** The command argument to use for the xrandr binary. Currently only + * used to support the non-standard location on some Solaris systems - + * would it make sense to use absolute paths on all systems? */ + const char *pcszXrandr; + /** Was there a recent mode hint with no following root window resize, and + * if so, have we waited for a reasonable time? */ + time_t timeLastModeHint; + /** Handle to libXrandr. */ + void *pRandLibraryHandle; + /** Handle to pXRRSelectInput. */ + void (*pXRRSelectInput) (Display *, Window, int); + /** Handle to pXRRQueryExtension. */ + Bool (*pXRRQueryExtension) (Display *, int *, int *); +}; + +static unsigned char *getRootProperty(struct DISPLAYSTATE *pState, const char *pszName, + long cItems, Atom type) +{ + Atom actualType = None; + int iFormat = 0; + unsigned long cReturned = 0; + unsigned long cAfter = 0; + unsigned char *pData = 0; + + if (XGetWindowProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), + XInternAtom(pState->pDisplay, pszName, 0), 0, cItems, + False /* delete */, type, &actualType, &iFormat, + &cReturned, &cAfter, &pData)) + return NULL; + return pData; +} + +static void doResize(struct DISPLAYSTATE *pState) +{ + /** @note The xrandr command can fail if something else accesses RandR at + * the same time. We just ignore failure for now as we do not know what + * someone else is doing. */ + if (!pState->fHaveRandR12) + { + char szCommand[256]; + unsigned char *pData; + + pData = getRootProperty(pState, "VBOXVIDEO_PREFERRED_MODE", 1, XA_INTEGER); + if (pData != NULL) + { + RTStrPrintf(szCommand, sizeof(szCommand), "%s -s %ux%u", + pState->pcszXrandr, ((unsigned long *)pData)[0] >> 16, ((unsigned long *)pData)[0] & 0xFFFF); + int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc); + XFree(pData); + } + } + else + { + const char szCommandBase[] = + "%s --output VGA-0 --auto --output VGA-1 --auto --right-of VGA-0 " + "--output VGA-2 --auto --right-of VGA-1 --output VGA-3 --auto --right-of VGA-2 " + "--output VGA-4 --auto --right-of VGA-3 --output VGA-5 --auto --right-of VGA-4 " + "--output VGA-6 --auto --right-of VGA-5 --output VGA-7 --auto --right-of VGA-6 " + "--output VGA-8 --auto --right-of VGA-7 --output VGA-9 --auto --right-of VGA-8 " + "--output VGA-10 --auto --right-of VGA-9 --output VGA-11 --auto --right-of VGA-10 " + "--output VGA-12 --auto --right-of VGA-11 --output VGA-13 --auto --right-of VGA-12 " + "--output VGA-14 --auto --right-of VGA-13 --output VGA-15 --auto --right-of VGA-14 " + "--output VGA-16 --auto --right-of VGA-15 --output VGA-17 --auto --right-of VGA-16 " + "--output VGA-18 --auto --right-of VGA-17 --output VGA-19 --auto --right-of VGA-18 " + "--output VGA-20 --auto --right-of VGA-19 --output VGA-21 --auto --right-of VGA-20 " + "--output VGA-22 --auto --right-of VGA-21 --output VGA-23 --auto --right-of VGA-22 " + "--output VGA-24 --auto --right-of VGA-23 --output VGA-25 --auto --right-of VGA-24 " + "--output VGA-26 --auto --right-of VGA-25 --output VGA-27 --auto --right-of VGA-26 " + "--output VGA-28 --auto --right-of VGA-27 --output VGA-29 --auto --right-of VGA-28 " + "--output VGA-30 --auto --right-of VGA-29 --output VGA-31 --auto --right-of VGA-30"; + char szCommand[sizeof(szCommandBase) + 256]; + RTStrPrintf(szCommand, sizeof(szCommand), szCommandBase, pState->pcszXrandr); + int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc); + } +} + +/** Main loop: handle display hot-plug events, property updates (which can + * signal VT switches hot-plug in old X servers). */ +static void runDisplay(struct DISPLAYSTATE *pState) +{ + Display *pDisplay = pState->pDisplay; + long cValue = 1; + + /* One way or another we want the preferred mode at server start-up. */ + doResize(pState); + XSelectInput(pDisplay, DefaultRootWindow(pDisplay), PropertyChangeMask | StructureNotifyMask); + if (pState->fHaveRandR12) + pState->pXRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask); + /* Semantics: when VBOXCLIENT_STARTED is set, pre-1.3 X.Org Server driver + * assumes that a client capable of handling mode hints will be present for the + * rest of the X session. If we crash things will not work as they should. + * I thought that preferable to implementing complex crash-handling logic. + */ + XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), XInternAtom(pState->pDisplay, "VBOXCLIENT_STARTED", 0), + XA_INTEGER, 32, PropModeReplace, (unsigned char *)&cValue, 1); + /* Interrupting this cleanly will be more work than making it robust + * against spontaneous termination, especially as it will never get + * properly tested, so I will go for the second. */ + while (true) + { + XEvent event; + struct pollfd PollFd; + int pollTimeOut = -1; + int cFds; + + /* Do not handle overflow. */ + if (pState->timeLastModeHint > 0 && pState->timeLastModeHint < INT_MAX - 2) + pollTimeOut = 2 - (time(0) - pState->timeLastModeHint); + PollFd.fd = ConnectionNumber(pDisplay); + PollFd.events = POLLIN; /* Hang-up is always reported. */ + XFlush(pDisplay); + cFds = poll(&PollFd, 1, pollTimeOut >= 0 ? pollTimeOut * 1000 : -1); + while (XPending(pDisplay)) + { + XNextEvent(pDisplay, &event); + /* This property is deleted when the server regains the virtual + * terminal. Force the main thread to call xrandr again, as old X + * servers could not handle it while switched out. */ + if ( !pState->fHaveRandR12 + && event.type == PropertyNotify + && event.xproperty.state == PropertyDelete + && event.xproperty.window == DefaultRootWindow(pDisplay) + && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_NO_VT", False)) + doResize(pState); + if ( !pState->fHaveRandR12 + && event.type == PropertyNotify + && event.xproperty.state == PropertyNewValue + && event.xproperty.window == DefaultRootWindow(pDisplay) + && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_PREFERRED_MODE", False)) + doResize(pState); + if ( pState->fHaveRandR12 + && event.type == pState->cRREventBase + RRScreenChangeNotify) + pState->timeLastModeHint = time(0); + if ( event.type == ConfigureNotify + && event.xproperty.window == DefaultRootWindow(pDisplay)) + pState->timeLastModeHint = 0; + } + if (cFds == 0 && pState->timeLastModeHint > 0) + doResize(pState); + } +} + +static int initDisplay(struct DISPLAYSTATE *pState) +{ + char szCommand[256]; + int status; + + pState->pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */); + if (!pState->pRandLibraryHandle) + pState->pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */); + if (!pState->pRandLibraryHandle) + pState->pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */); + + if (!RT_VALID_PTR(pState->pRandLibraryHandle)) + { + VBClLogFatalError("Could not locate libXrandr for dlopen\n"); + return VERR_NOT_FOUND; + } + + *(void **)(&pState->pXRRSelectInput) = dlsym(pState->pRandLibraryHandle, "XRRSelectInput"); + *(void **)(&pState->pXRRQueryExtension) = dlsym(pState->pRandLibraryHandle, "XRRQueryExtension"); + + if ( !RT_VALID_PTR(pState->pXRRSelectInput) + || !RT_VALID_PTR(pState->pXRRQueryExtension)) + { + VBClLogFatalError("Could not load required libXrandr symbols\n"); + dlclose(pState->pRandLibraryHandle); + pState->pRandLibraryHandle = NULL; + return VERR_NOT_FOUND; + } + + pState->pDisplay = XOpenDisplay(NULL); + if (!pState->pDisplay) + return VERR_NOT_FOUND; + if (!pState->pXRRQueryExtension(pState->pDisplay, &pState->cRREventBase, &status)) + return VERR_NOT_FOUND; + pState->fHaveRandR12 = false; + pState->pcszXrandr = "xrandr"; + if (RTFileExists("/usr/X11/bin/xrandr")) + pState->pcszXrandr = "/usr/X11/bin/xrandr"; + status = system(pState->pcszXrandr); + if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */ + VBClLogFatalError("Failed to execute the xrandr utility\n"); + RTStrPrintf(szCommand, sizeof(szCommand), "%s --q12", pState->pcszXrandr); + status = system(szCommand); + if (WEXITSTATUS(status) == 0) + pState->fHaveRandR12 = true; + return VINF_SUCCESS; +} + +static const char *getName() +{ + return "Display"; +} + +static const char *getPidFilePath() +{ + return ".vboxclient-display.pid"; +} + +static struct DISPLAYSTATE *getStateFromInterface(struct VBCLSERVICE **ppInterface) +{ + struct DISPLAYSTATE *pSelf = (struct DISPLAYSTATE *)ppInterface; + if (pSelf->magic != DISPLAYSTATE_MAGIC) + VBClLogFatalError("Bad display service object!\n"); + return pSelf; +} + +static int init(struct VBCLSERVICE **ppInterface) +{ + struct DISPLAYSTATE *pSelf = getStateFromInterface(ppInterface); + int rc; + + if (pSelf->mfInit) + return VERR_WRONG_ORDER; + rc = initDisplay(pSelf); + if (RT_FAILURE(rc)) + return rc; + if (RT_SUCCESS(rc)) + pSelf->mfInit = true; + return rc; +} + +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + RT_NOREF1(fDaemonised); + struct DISPLAYSTATE *pSelf = getStateFromInterface(ppInterface); + + if (!pSelf->mfInit) + return VERR_WRONG_ORDER; + runDisplay(pSelf); + return VERR_INTERNAL_ERROR; /* "Should never reach here." */ +} + +struct VBCLSERVICE vbclDisplayInterface = +{ + getName, + getPidFilePath, + init, + run, + VBClServiceDefaultCleanup +}; + +struct VBCLSERVICE **VBClGetDisplayService() +{ + struct DISPLAYSTATE *pService = (struct DISPLAYSTATE *)RTMemAlloc(sizeof(*pService)); + + if (!pService) + VBClLogFatalError("Out of memory\n"); + pService->pInterface = &vbclDisplayInterface; + pService->magic = DISPLAYSTATE_MAGIC; + pService->mfInit = false; + return &pService->pInterface; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/display-drm.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/display-drm.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/display-drm.cpp 2020-10-16 16:30:16.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/display-drm.cpp 2022-09-01 13:20:58.000000000 +0000 @@ -194,6 +194,24 @@ /* Do not acknowledge the first event we query for to pick up old events, * e.g. from before a guest reboot. */ bool fAck = false; + + /** The name and handle of the PID file. */ + static const char szPidFile[RTPATH_MAX] = "/var/run/VBoxDRMClient"; + RTFILE hPidFile; + + /* Check PID file before attempting to initialize anything. */ + rc = VbglR3PidFile(szPidFile, &hPidFile); + if (rc == VERR_FILE_LOCK_VIOLATION) + { + VBClLogInfo("VBoxDRMClient: already running, exiting\n"); + return RTEXITCODE_SUCCESS; + } + else if (RT_FAILURE(rc)) + { + VBClLogError("VBoxDRMClient: unable to lock PID file (%Rrc), exiting\n", rc); + return RTEXITCODE_FAILURE; + } + drmConnect(&drmContext); if (drmContext.hDevice == NIL_RTFILE) return VERR_OPEN_FAILED; @@ -224,9 +242,9 @@ rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck); fAck = true; if (RT_FAILURE(rc)) - VBClLogFatalError("Failed to get display change request, rc=%Rrc\n", rc); + VBClLogError("Failed to get display change request, rc=%Rrc\n", rc); if (cDisplaysOut > VMW_MAX_HEADS) - VBClLogFatalError("Display change request contained, rc=%Rrc\n", rc); + VBClLogError("Display change request contained, rc=%Rrc\n", rc); if (cDisplaysOut > 0) { for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i) @@ -277,5 +295,11 @@ if (RT_FAILURE(rc)) VBClLogFatalError("Failure waiting for event, rc=%Rrc\n", rc); } + + /** @todo this code never executed since we do not have yet a clean way to exit + * main event loop above. */ + VBClLogInfo("VBoxDRMClient: releasing PID file lock\n"); + VbglR3ClosePidFile(szPidFile, hPidFile); + return 0; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp 2020-10-16 16:30:16.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp 2022-09-01 13:20:58.000000000 +0000 @@ -59,6 +59,8 @@ #include #include #include +#include +#include #include #include @@ -78,6 +80,33 @@ /** Shutdown indicator for the monitor thread. */ static bool g_fMonitorThreadShutdown = false; +#define X_VMwareCtrlSetRes 1 + +typedef struct +{ + CARD8 reqType; + CARD8 VMwareCtrlReqType; + CARD16 length B16; + CARD32 screen B32; + CARD32 x B32; + CARD32 y B32; +} xVMwareCtrlSetResReq; +#define sz_xVMwareCtrlSetResReq 16 + +typedef struct +{ + BYTE type; + BYTE pad1; + CARD16 sequenceNumber B16; + CARD32 length B32; + CARD32 screen B32; + CARD32 x B32; + CARD32 y B32; + CARD32 pad2 B32; + CARD32 pad3 B32; + CARD32 pad4 B32; +} xVMwareCtrlSetResReply; +#define sz_xVMwareCtrlSetResReply 32 typedef struct { CARD8 reqType; /* always X_VMwareCtrlReqCode */ @@ -152,7 +181,11 @@ RRMode (*pXRRCreateMode) (Display *, Window, XRRModeInfo *); XRROutputInfo* (*pXRRGetOutputInfo) (Display *, XRRScreenResources *, RROutput); XRRCrtcInfo* (*pXRRGetCrtcInfo) (Display *, XRRScreenResources *, RRCrtc crtc); + void (*pXRRFreeCrtcInfo)(XRRCrtcInfo *); void (*pXRRAddOutputMode)(Display *, RROutput, RRMode); + void (*pXRRDeleteOutputMode)(Display *, RROutput, RRMode); + void (*pXRRDestroyMode)(Display *, RRMode); + void (*pXRRSetOutputPrimary)(Display *, Window, RROutput); }; static X11CONTEXT x11Context; @@ -164,6 +197,7 @@ uint32_t width; uint32_t height; bool fEnabled; + bool fPrimary; }; struct DisplayModeR { @@ -202,7 +236,7 @@ #define checkFunctionPtr(pFunction) \ do { \ if (!pFunction) \ - VBClLogFatalError("Could not find symbol address (%s)\n", #pFunction);\ + VBClLogError("Could not find symbol address (%s)\n", #pFunction);\ } while (0) @@ -430,6 +464,33 @@ return Mode; } +#ifdef RT_OS_SOLARIS +static bool VMwareCtrlSetRes( + Display *dpy, int hExtensionMajorOpcode, int screen, int x, int y) +{ + xVMwareCtrlSetResReply rep; + xVMwareCtrlSetResReq *pReq; + bool fResult = false; + + LockDisplay(dpy); + + GetReq(VMwareCtrlSetRes, pReq); + AssertPtrReturn(pReq, false); + + pReq->reqType = hExtensionMajorOpcode; + pReq->VMwareCtrlReqType = X_VMwareCtrlSetRes; + pReq->screen = screen; + pReq->x = x; + pReq->y = y; + + fResult = !!_XReply(dpy, (xReply *)&rep, (SIZEOF(xVMwareCtrlSetResReply) - SIZEOF(xReply)) >> 2, xFalse); + + UnlockDisplay(dpy); + + return fResult; +} +#endif /* RT_OS_SOLARIS */ + /** Makes a call to vmwarectrl extension. This updates the * connection information and possible resolutions (modes) * of each monitor on the driver. Also sets the preferred mode @@ -473,6 +534,10 @@ { if (!sMonitorName) return -1; +#ifdef RT_OS_SOLARIS + if (!strcmp(sMonitorName, "default")) + return 1; +#endif int iLen = strlen(sMonitorName); if (iLen <= 0) return -1; @@ -498,7 +563,7 @@ } int rc = VbglR3SeamlessSendMonitorPositions(cPositions, pPositions); if (RT_SUCCESS(rc)) - VBClLogError("Sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc); + VBClLogInfo("Sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc); else VBClLogError("Error during sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc); } @@ -537,9 +602,26 @@ } for (int i = 0; i < iMonitorCount; ++i) { - int iMonitorID = getMonitorIdFromName(XGetAtomName(x11Context.pDisplayRandRMonitoring, pMonitorInfo[i].name)) - 1; + char *pszMonitorName = XGetAtomName(x11Context.pDisplayRandRMonitoring, pMonitorInfo[i].name); + if (!pszMonitorName) + { + VBClLogError("queryMonitorPositions: skip monitor with unknown name %d\n", i); + continue; + } + + int iMonitorID = getMonitorIdFromName(pszMonitorName) - 1; + XFree((void *)pszMonitorName); + pszMonitorName = NULL; + if (iMonitorID >= x11Context.hOutputCount || iMonitorID == -1) + { + VBClLogInfo("queryMonitorPositions: skip monitor %d (id %d) (w,h)=(%d,%d) (x,y)=(%d,%d)\n", + i, iMonitorID, + pMonitorInfo[i].width, pMonitorInfo[i].height, + pMonitorInfo[i].x, pMonitorInfo[i].y); continue; + } + VBClLogInfo("Monitor %d (w,h)=(%d,%d) (x,y)=(%d,%d)\n", i, pMonitorInfo[i].width, pMonitorInfo[i].height, @@ -628,6 +710,11 @@ { int hHeight = 600; int hWidth = 800; + bool fResult = false; + int idxDefaultScreen = DefaultScreen(x11Context.pDisplay); + + AssertReturn(idxDefaultScreen >= 0, false); + AssertReturn(idxDefaultScreen < x11Context.hOutputCount, false); xXineramaScreenInfo *extents = (xXineramaScreenInfo *)malloc(x11Context.hOutputCount * sizeof(xXineramaScreenInfo)); if (!extents) @@ -651,10 +738,17 @@ extents[i].height = hHeight; hRunningOffset += hWidth; } - return VMwareCtrlSetTopology(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode, - DefaultScreen(x11Context.pDisplay), - extents, x11Context.hOutputCount); +#ifdef RT_OS_SOLARIS + fResult = VMwareCtrlSetRes(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode, + idxDefaultScreen, extents[idxDefaultScreen].width, + extents[idxDefaultScreen].height); +#else + fResult = VMwareCtrlSetTopology(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode, + idxDefaultScreen, extents, x11Context.hOutputCount); +#endif free(extents); + + return fResult; } /** @@ -753,17 +847,31 @@ VBClLogInfo("Starting DRM client.\n"); int rc = execve(szDRMClientPath, argv, env); if (rc == -1) - VBClLogFatalError("execve for % returns the following error %d %s\n", szDRMClientPath, errno, strerror(errno)); + VBClLogFatalError("execve for %s returns the following error %d %s\n", szDRMClientPath, errno, strerror(errno)); /* This is reached only when execve fails. */ return false; } +static bool legacyX11AgentStart() +{ +#if defined(RT_OS_LINUX) +# define VBOX_DRMCLIENT_LEGACY_EXECUTABLE "/usr/bin/VBoxClient" + const char *apszArgs[3] = { VBOX_DRMCLIENT_LEGACY_EXECUTABLE, "--display", NULL }; + + int rc = RTProcCreate(VBOX_DRMCLIENT_LEGACY_EXECUTABLE, apszArgs, RTENV_DEFAULT, + RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SEARCH_PATH, NULL); + return RT_SUCCESS(rc); +#else + return false; +#endif +} + static bool init() { /* If DRM client is already running don't start this service. */ if (checkDRMClient()) { - VBClLogFatalError("DRM resizing is already running. Exiting this service\n"); + VBClLogInfo("DRM resizing is already running. Exiting this service\n"); return false; } if (isXwayland()) @@ -839,40 +947,52 @@ x11Context.fMonitorInfoAvailable = x11Context.pXRRGetMonitors && x11Context.pXRRFreeMonitors; *(void **)(&x11Context.pXRRGetScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRGetScreenResources"); - checkFunctionPtrReturn(x11Context.pXRRGetScreenResources); + checkFunctionPtr(x11Context.pXRRGetScreenResources); *(void **)(&x11Context.pXRRSetCrtcConfig) = dlsym(x11Context.pRandLibraryHandle, "XRRSetCrtcConfig"); - checkFunctionPtrReturn(x11Context.pXRRSetCrtcConfig); + checkFunctionPtr(x11Context.pXRRSetCrtcConfig); *(void **)(&x11Context.pXRRFreeScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeScreenResources"); - checkFunctionPtrReturn(x11Context.pXRRFreeScreenResources); + checkFunctionPtr(x11Context.pXRRFreeScreenResources); *(void **)(&x11Context.pXRRFreeModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeModeInfo"); - checkFunctionPtrReturn(x11Context.pXRRFreeModeInfo); + checkFunctionPtr(x11Context.pXRRFreeModeInfo); *(void **)(&x11Context.pXRRFreeOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeOutputInfo"); - checkFunctionPtrReturn(x11Context.pXRRFreeOutputInfo); + checkFunctionPtr(x11Context.pXRRFreeOutputInfo); *(void **)(&x11Context.pXRRSetScreenSize) = dlsym(x11Context.pRandLibraryHandle, "XRRSetScreenSize"); - checkFunctionPtrReturn(x11Context.pXRRSetScreenSize); + checkFunctionPtr(x11Context.pXRRSetScreenSize); *(void **)(&x11Context.pXRRUpdateConfiguration) = dlsym(x11Context.pRandLibraryHandle, "XRRUpdateConfiguration"); - checkFunctionPtrReturn(x11Context.pXRRUpdateConfiguration); + checkFunctionPtr(x11Context.pXRRUpdateConfiguration); *(void **)(&x11Context.pXRRAllocModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRAllocModeInfo"); - checkFunctionPtrReturn(x11Context.pXRRAllocModeInfo); + checkFunctionPtr(x11Context.pXRRAllocModeInfo); *(void **)(&x11Context.pXRRCreateMode) = dlsym(x11Context.pRandLibraryHandle, "XRRCreateMode"); - checkFunctionPtrReturn(x11Context.pXRRCreateMode); + checkFunctionPtr(x11Context.pXRRCreateMode); *(void **)(&x11Context.pXRRGetOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetOutputInfo"); - checkFunctionPtrReturn(x11Context.pXRRGetOutputInfo); + checkFunctionPtr(x11Context.pXRRGetOutputInfo); *(void **)(&x11Context.pXRRGetCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetCrtcInfo"); - checkFunctionPtrReturn(x11Context.pXRRGetCrtcInfo); + checkFunctionPtr(x11Context.pXRRGetCrtcInfo); + + *(void **)(&x11Context.pXRRFreeCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeCrtcInfo"); + checkFunctionPtr(x11Context.pXRRFreeCrtcInfo); *(void **)(&x11Context.pXRRAddOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRAddOutputMode"); - checkFunctionPtrReturn(x11Context.pXRRAddOutputMode); + checkFunctionPtr(x11Context.pXRRAddOutputMode); + + *(void **)(&x11Context.pXRRDeleteOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDeleteOutputMode"); + checkFunctionPtr(x11Context.pXRRDeleteOutputMode); + + *(void **)(&x11Context.pXRRDestroyMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDestroyMode"); + checkFunctionPtr(x11Context.pXRRDestroyMode); + + *(void **)(&x11Context.pXRRSetOutputPrimary) = dlsym(x11Context.pRandLibraryHandle, "XRRSetOutputPrimary"); + checkFunctionPtrReturn(x11Context.pXRRSetOutputPrimary); return VINF_SUCCESS; } @@ -898,7 +1018,11 @@ x11Context.pXRRCreateMode = NULL; x11Context.pXRRGetOutputInfo = NULL; x11Context.pXRRGetCrtcInfo = NULL; + x11Context.pXRRFreeCrtcInfo = NULL; x11Context.pXRRAddOutputMode = NULL; + x11Context.pXRRDeleteOutputMode = NULL; + x11Context.pXRRDestroyMode = NULL; + x11Context.pXRRSetOutputPrimary = NULL; x11Context.fWmwareCtrlExtention = false; x11Context.fMonitorInfoAvailable = false; x11Context.hRandRMajor = 0; @@ -954,11 +1078,17 @@ } if (x11Context.hRandRMajor < 1 || x11Context.hRandRMinor <= 3) { - VBClLogFatalError("Resizing service requires libXrandr Version >= 1.4. Detected version is %d.%d\n", x11Context.hRandRMajor, x11Context.hRandRMinor); + VBClLogError("Resizing service requires libXrandr Version >= 1.4. Detected version is %d.%d\n", x11Context.hRandRMajor, x11Context.hRandRMinor); XCloseDisplay(x11Context.pDisplay); x11Context.pDisplay = NULL; + + bool rc = legacyX11AgentStart(); + VBClLogInfo("Attempt to start legacy X11 resize agent has %s\n", rc ? "succeeded" : "failed"); + return; } + else + VBClLogInfo("Found libXrandr %d.%d\n", x11Context.hRandRMajor, x11Context.hRandRMinor); } x11Context.rootWindow = DefaultRootWindow(x11Context.pDisplay); x11Context.hEventMask = RRScreenChangeNotifyMask; @@ -978,9 +1108,7 @@ if (x11Context.pXRRGetScreenResources) x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow); #endif - /* Currently without the VMWARE_CTRL extension we cannot connect outputs and set outputs' preferred mode. - * So we set the output count to 1 to get the 1st output position correct. */ - x11Context.hOutputCount = x11Context.fWmwareCtrlExtention ? determineOutputCount() : 1; + x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0; #ifdef WITH_DISTRO_XRAND_XINERAMA XRRFreeScreenResources(x11Context.pScreenResources); #else @@ -1031,6 +1159,14 @@ ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); #endif + +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeCrtcInfo(pCrctInfo); +#else + if (x11Context.pXRRFreeCrtcInfo) + x11Context.pXRRFreeCrtcInfo(pCrctInfo); +#endif + /** @todo In case of unsuccesful crtc config set we have to revert frame buffer size and crtc sizes. */ if (ret == Success) return true; @@ -1107,9 +1243,17 @@ #endif XRRScreenSize newSize = currentSize(); - if (!event || newSize.width != (int)iXRes || newSize.height != (int)iYRes) + /* On Solaris guest, new screen size is not reported properly despite + * RRScreenChangeNotify event arrives. Hense, only check for event here. + * Linux guests do report new size correctly. */ + if ( !event +#ifndef RT_OS_SOLARIS + || newSize.width != (int)iXRes || newSize.height != (int)iYRes +#endif + ) { - VBClLogError("Resizing frame buffer to %d %d has failed\n", iXRes, iYRes); + VBClLogError("Resizing frame buffer to %d %d has failed, current mode %d %d\n", + iXRes, iYRes, newSize.width, newSize.height); return false; } return true; @@ -1131,7 +1275,10 @@ DisplayModeR mode = f86CVTMode(iXRes, iYRes, 60 /*VRefresh */, true /*Reduced */, false /* Interlaced */); - pModeInfo->dotClock = mode.Clock; + /* Convert kHz to Hz: f86CVTMode returns clock value in units of kHz, + * XRRCreateMode will expect it in units of Hz. */ + pModeInfo->dotClock = mode.Clock * 1000; + pModeInfo->hSyncStart = mode.HSyncStart; pModeInfo->hSyncEnd = mode.HSyncEnd; pModeInfo->hTotal = mode.HTotal; @@ -1168,6 +1315,14 @@ VBClLogError("Output index %d is greater than # of oputputs %d\n", iOutputIndex, x11Context.hOutputCount); return false; } + + AssertReturn(iOutputIndex >= 0, false); + AssertReturn(iOutputIndex < VMW_MAX_HEADS, false); + + /* Remember the last instantiated display mode ID here. This mode will be replaced with the + * new one on the next guest screen resize event. */ + static RRMode aPrevMode[VMW_MAX_HEADS]; + RROutput outputId = x11Context.pScreenResources->outputs[iOutputIndex]; XRROutputInfo *pOutputInfo = NULL; #ifdef WITH_DISTRO_XRAND_XINERAMA @@ -1188,6 +1343,7 @@ { /* A mode with required size was not found. Create a new one. */ pModeInfo = createMode(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height); + VBClLogInfo("create mode %s (%u) on output %d\n", pModeInfo->name, pModeInfo->id, iOutputIndex); fNewMode = true; } if (!pModeInfo) @@ -1203,6 +1359,41 @@ if (x11Context.pXRRAddOutputMode) x11Context.pXRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id); #endif + + /* If mode has been newly created, destroy and forget mode created on previous guest screen resize event. */ + if ( aPrevMode[iOutputIndex] > 0 + && pModeInfo->id != aPrevMode[iOutputIndex] + && fNewMode) + { + VBClLogInfo("removing unused mode %u from output %d\n", aPrevMode[iOutputIndex], iOutputIndex); +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]); + XRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]); +#else + if (x11Context.pXRRDeleteOutputMode) + x11Context.pXRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]); + if (x11Context.pXRRDestroyMode) + x11Context.pXRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]); +#endif + /* Forget destroyed mode. */ + aPrevMode[iOutputIndex] = 0; + } + + /* Only cache modes created "by us". XRRDestroyMode will complain if provided mode + * was not created by XRRCreateMode call. */ + if (fNewMode) + aPrevMode[iOutputIndex] = pModeInfo->id; + + if (paOutputs[iOutputIndex].fPrimary) + { +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId); +#else + if (x11Context.pXRRSetOutputPrimary) + x11Context.pXRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId); +#endif + } + /* Make sure outputs crtc is set. */ pOutputInfo->crtc = pOutputInfo->crtcs[0]; @@ -1243,6 +1434,12 @@ /** Construct the xrandr command which sets the whole monitor topology each time. */ static void setXrandrTopology(struct RANDROUTPUT *paOutputs) { + if (!x11Context.pDisplay) + { + VBClLogInfo("not connected to X11\n"); + return; + } + XGrabServer(x11Context.pDisplay); if (x11Context.fWmwareCtrlExtention) callVMWCTRL(paOutputs); @@ -1253,11 +1450,12 @@ if (x11Context.pXRRGetScreenResources) x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow); #endif - x11Context.hOutputCount = x11Context.fWmwareCtrlExtention ? determineOutputCount() : 1; + x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0; if (!x11Context.pScreenResources) { XUngrabServer(x11Context.pDisplay); + XFlush(x11Context.pDisplay); return; } @@ -1280,12 +1478,14 @@ { VBClLogFatalError("Crtc disable failed %lu\n", pOutputInfo->crtc); XUngrabServer(x11Context.pDisplay); + XSync(x11Context.pDisplay, False); #ifdef WITH_DISTRO_XRAND_XINERAMA XRRFreeScreenResources(x11Context.pScreenResources); #else if (x11Context.pXRRFreeScreenResources) x11Context.pXRRFreeScreenResources(x11Context.pScreenResources); #endif + XFlush(x11Context.pDisplay); return; } #ifdef WITH_DISTRO_XRAND_XINERAMA @@ -1299,12 +1499,14 @@ if (!resizeFrameBuffer(paOutputs)) { XUngrabServer(x11Context.pDisplay); + XSync(x11Context.pDisplay, False); #ifdef WITH_DISTRO_XRAND_XINERAMA XRRFreeScreenResources(x11Context.pScreenResources); #else if (x11Context.pXRRFreeScreenResources) x11Context.pXRRFreeScreenResources(x11Context.pScreenResources); #endif + XFlush(x11Context.pDisplay); return; } @@ -1316,7 +1518,10 @@ break; if (!paOutputs[i].fEnabled) continue; - configureOutput(i, paOutputs); + if (configureOutput(i, paOutputs)) + VBClLogInfo("output[%d] successfully configured\n", i); + else + VBClLogError("failed to configure output[%d]\n", i); } XSync(x11Context.pDisplay, False); #ifdef WITH_DISTRO_XRAND_XINERAMA @@ -1376,9 +1581,9 @@ rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck); fAck = true; if (RT_FAILURE(rc)) - VBClLogFatalError("Failed to get display change request, rc=%Rrc\n", rc); + VBClLogError("Failed to get display change request, rc=%Rrc\n", rc); if (cDisplaysOut > VMW_MAX_HEADS) - VBClLogFatalError("Display change request contained, rc=%Rrc\n", rc); + VBClLogError("Display change request contained, rc=%Rrc\n", rc); if (cDisplaysOut > 0) { for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i) @@ -1411,6 +1616,7 @@ aOutputs[j].width = aMonitors[j].cx; aOutputs[j].height = aMonitors[j].cy; aOutputs[j].fEnabled = !(aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_DISABLED); + aOutputs[j].fPrimary = (aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY); if (aOutputs[j].fEnabled) iRunningX += aOutputs[j].width; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/main.cpp virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/main.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/main.cpp 2020-10-16 16:30:16.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/main.cpp 2022-09-01 13:20:58.000000000 +0000 @@ -47,6 +47,7 @@ #define VBOXCLIENT_OPT_DRAGANDDROP VBOXCLIENT_OPT_SERVICES + 2 #define VBOXCLIENT_OPT_SEAMLESS VBOXCLIENT_OPT_SERVICES + 3 #define VBOXCLIENT_OPT_VMSVGA VBOXCLIENT_OPT_SERVICES + 4 +#define VBOXCLIENT_OPT_DISPLAY VBOXCLIENT_OPT_SERVICES + 5 /********************************************************************************************************************************* @@ -188,6 +189,7 @@ #endif RTPrintf(" --seamless starts the seamless windows service\n"); RTPrintf(" --vmsvga starts VMSVGA dynamic resizing for x11/Wayland guests\n"); + RTPrintf(" --display starts VMSVGA dynamic resizing for legacy guests\n"); RTPrintf(" -f, --foreground run in the foreground (no daemonizing)\n"); RTPrintf(" -d, --nodaemon continues running as a system service\n"); RTPrintf(" -h, --help shows this help text\n"); @@ -249,6 +251,7 @@ #endif { "--seamless", VBOXCLIENT_OPT_SEAMLESS, RTGETOPT_REQ_NOTHING }, { "--vmsvga", VBOXCLIENT_OPT_VMSVGA, RTGETOPT_REQ_NOTHING }, + { "--display", VBOXCLIENT_OPT_DISPLAY, RTGETOPT_REQ_NOTHING }, }; int ch; @@ -354,6 +357,14 @@ break; } + case VBOXCLIENT_OPT_DISPLAY: + { + if (g_pService) + return vbclSyntaxOnlyOneService(); + g_pService = VBClGetDisplayService(); + break; + } + case VERR_GETOPT_UNKNOWN_OPTION: { RTMsgError("unrecognized option '%s'", ValueUnion.psz); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/Makefile.kmk 2020-10-16 16:30:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/Makefile.kmk 2022-09-01 13:20:58.000000000 +0000 @@ -45,6 +45,7 @@ VBoxClient_INCS += ../x11include/randrproto-1.6.0 VBoxClient_SOURCES = \ main.cpp \ + display.cpp \ display-svga-x11.cpp \ seamless.cpp \ seamless-x11.cpp \ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/VBoxClient.h virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/VBoxClient.h --- virtualbox-6.1.16-dfsg/src/VBox/Additions/x11/VBoxClient/VBoxClient.h 2020-10-16 16:30:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Additions/x11/VBoxClient/VBoxClient.h 2022-09-01 13:20:58.000000000 +0000 @@ -80,5 +80,6 @@ extern struct VBCLSERVICE **VBClCheck3DService(); extern struct VBCLSERVICE **VBClDisplaySVGAService(); extern struct VBCLSERVICE **VBClDisplaySVGAX11Service(); +extern struct VBCLSERVICE **VBClGetDisplayService(); #endif /* !GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Debugger/DBGCEmulateCodeView.cpp virtualbox-6.1.38-dfsg/src/VBox/Debugger/DBGCEmulateCodeView.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Debugger/DBGCEmulateCodeView.cpp 2020-10-16 16:32:54.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Debugger/DBGCEmulateCodeView.cpp 2022-09-01 13:23:46.000000000 +0000 @@ -629,6 +629,7 @@ { DBGFEVENT_EXIT_SVM_VMSAVE, "exit_svm_vmsave", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL }, { DBGFEVENT_EXIT_SVM_STGI, "exit_svm_stgi", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL }, { DBGFEVENT_EXIT_SVM_CLGI, "exit_svm_clgi", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL }, + { DBGFEVENT_VMX_SPLIT_LOCK, "vmx_split_lock", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL }, { DBGFEVENT_IOPORT_UNASSIGNED, "pio_unassigned", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL }, { DBGFEVENT_IOPORT_UNUSED, "pio_unused", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL }, { DBGFEVENT_MEMORY_UNASSIGNED, "mmio_unassigned", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL }, diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/alsa_mangling.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/alsa_mangling.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/alsa_mangling.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/alsa_mangling.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -/* $Id: alsa_mangling.h $ */ -/** @file - * Mangle libasound symbols. - * - * This is necessary on hosts which don't support the -fvisibility gcc switch. - */ - -/* - * Copyright (C) 2013-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_alsa_mangling_h -#define VBOX_INCLUDED_SRC_Audio_alsa_mangling_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#define ALSA_MANGLER(symbol) VBox_##symbol - -#define snd_lib_error_set_handler ALSA_MANGLER(snd_lib_error_set_handler) -#define snd_strerror ALSA_MANGLER(snd_strerror) - -#define snd_device_name_hint ALSA_MANGLER(snd_device_name_hint) -#define snd_device_name_get_hint ALSA_MANGLER(snd_device_name_get_hint) -#define snd_device_name_free_hint ALSA_MANGLER(snd_device_name_free_hint) - -#define snd_pcm_avail_update ALSA_MANGLER(snd_pcm_avail_update) -#define snd_pcm_close ALSA_MANGLER(snd_pcm_close) -#define snd_pcm_delay ALSA_MANGLER(snd_pcm_delay) -#define snd_pcm_drain ALSA_MANGLER(snd_pcm_drain) -#define snd_pcm_drop ALSA_MANGLER(snd_pcm_drop) -#define snd_pcm_nonblock ALSA_MANGLER(snd_pcm_nonblock) -#define snd_pcm_open ALSA_MANGLER(snd_pcm_open) -#define snd_pcm_prepare ALSA_MANGLER(snd_pcm_prepare) -#define snd_pcm_readi ALSA_MANGLER(snd_pcm_readi) -#define snd_pcm_resume ALSA_MANGLER(snd_pcm_resume) -#define snd_pcm_start ALSA_MANGLER(snd_pcm_start) -#define snd_pcm_state ALSA_MANGLER(snd_pcm_state) -#define snd_pcm_writei ALSA_MANGLER(snd_pcm_writei) - -#define snd_pcm_hw_params ALSA_MANGLER(snd_pcm_hw_params) -#define snd_pcm_hw_params_any ALSA_MANGLER(snd_pcm_hw_params_any) -#define snd_pcm_hw_params_sizeof ALSA_MANGLER(snd_pcm_hw_params_sizeof) -#define snd_pcm_hw_params_get_buffer_size ALSA_MANGLER(snd_pcm_hw_params_get_buffer_size) -#define snd_pcm_hw_params_get_period_size_min ALSA_MANGLER(snd_pcm_hw_params_get_period_size_min) -#define snd_pcm_hw_params_set_rate_near ALSA_MANGLER(snd_pcm_hw_params_set_rate_near) -#define snd_pcm_hw_params_set_access ALSA_MANGLER(snd_pcm_hw_params_set_access) -#define snd_pcm_hw_params_set_buffer_time_near ALSA_MANGLER(snd_pcm_hw_params_set_buffer_time_near) -#define snd_pcm_hw_params_set_buffer_size_near ALSA_MANGLER(snd_pcm_hw_params_set_buffer_size_near) -#define snd_pcm_hw_params_get_buffer_size_min ALSA_MANGLER(snd_pcm_hw_params_get_buffer_size_min) -#define snd_pcm_hw_params_set_channels_near ALSA_MANGLER(snd_pcm_hw_params_set_channels_near) -#define snd_pcm_hw_params_set_format ALSA_MANGLER(snd_pcm_hw_params_set_format) -#define snd_pcm_hw_params_get_period_size ALSA_MANGLER(snd_pcm_hw_params_get_period_size) -#define snd_pcm_hw_params_set_period_size_near ALSA_MANGLER(snd_pcm_hw_params_set_period_size_near) -#define snd_pcm_hw_params_set_period_time_near ALSA_MANGLER(snd_pcm_hw_params_set_period_time_near) - -#define snd_pcm_sw_params ALSA_MANGLER(snd_pcm_sw_params) -#define snd_pcm_sw_params_current ALSA_MANGLER(snd_pcm_sw_params_current) -#define snd_pcm_sw_params_get_start_threshold ALSA_MANGLER(snd_pcm_sw_params_get_start_threshold) -#define snd_pcm_sw_params_set_avail_min ALSA_MANGLER(snd_pcm_sw_params_set_avail_min) -#define snd_pcm_sw_params_set_start_threshold ALSA_MANGLER(snd_pcm_sw_params_set_start_threshold) -#define snd_pcm_sw_params_sizeof ALSA_MANGLER(snd_pcm_sw_params_sizeof) - -#endif /* !VBOX_INCLUDED_SRC_Audio_alsa_mangling_h */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/alsa_stubs.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/alsa_stubs.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/alsa_stubs.c 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/alsa_stubs.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,243 +0,0 @@ -/* $Id: alsa_stubs.c $ */ -/** @file - * Stubs for libasound. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include -#include -#include - -#include - -#include "alsa_stubs.h" - -#define VBOX_ALSA_LIB "libasound.so.2" - -#define PROXY_STUB(function, rettype, signature, shortsig) \ - static rettype (*pfn_ ## function) signature; \ - \ - rettype VBox_##function signature; \ - rettype VBox_##function signature \ - { \ - return pfn_ ## function shortsig; \ - } - -PROXY_STUB(snd_lib_error_set_handler, int, (snd_lib_error_handler_t handler), - (handler)) -PROXY_STUB(snd_strerror, const char *, (int errnum), (errnum)) - -PROXY_STUB(snd_device_name_hint, int, - (int card, const char *iface, void ***hints), - (card, iface, hints)) -PROXY_STUB(snd_device_name_free_hint, int, - (void **hints), - (hints)) -PROXY_STUB(snd_device_name_get_hint, char *, - (const void *hint, const char *id), - (hint, id)) - -/* - * PCM - */ - -PROXY_STUB(snd_pcm_avail_update, snd_pcm_sframes_t, (snd_pcm_t *pcm), - (pcm)) -PROXY_STUB(snd_pcm_close, int, (snd_pcm_t *pcm), (pcm)) -PROXY_STUB(snd_pcm_delay, int, (snd_pcm_t *pcm, snd_pcm_sframes_t *frames), - (pcm, frames)) -PROXY_STUB(snd_pcm_nonblock, int, (snd_pcm_t *pcm, int *onoff), - (pcm, onoff)) -PROXY_STUB(snd_pcm_drain, int, (snd_pcm_t *pcm), - (pcm)) -PROXY_STUB(snd_pcm_drop, int, (snd_pcm_t *pcm), (pcm)) -PROXY_STUB(snd_pcm_open, int, - (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode), - (pcm, name, stream, mode)) -PROXY_STUB(snd_pcm_prepare, int, (snd_pcm_t *pcm), (pcm)) -PROXY_STUB(snd_pcm_readi, snd_pcm_sframes_t, - (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size), - (pcm, buffer, size)) -PROXY_STUB(snd_pcm_resume, int, (snd_pcm_t *pcm), (pcm)) -PROXY_STUB(snd_pcm_state, snd_pcm_state_t, (snd_pcm_t *pcm), (pcm)) -PROXY_STUB(snd_pcm_writei, snd_pcm_sframes_t, - (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size), - (pcm, buffer, size)) -PROXY_STUB(snd_pcm_start, int, (snd_pcm_t *pcm), (pcm)) - -/* - * HW - */ - -PROXY_STUB(snd_pcm_hw_params, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params), - (pcm, params)) -PROXY_STUB(snd_pcm_hw_params_any, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params), - (pcm, params)) -PROXY_STUB(snd_pcm_hw_params_get_buffer_size, int, - (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), - (params, val)) -PROXY_STUB(snd_pcm_hw_params_get_buffer_size_min, int, - (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), - (params, val)) -PROXY_STUB(snd_pcm_hw_params_get_period_size, int, - (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir), - (params, frames, dir)) -PROXY_STUB(snd_pcm_hw_params_get_period_size_min, int, - (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir), - (params, frames, dir)) -PROXY_STUB(snd_pcm_hw_params_set_rate_near, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), - (pcm, params, val, dir)) -PROXY_STUB(snd_pcm_hw_params_set_access, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access), - (pcm, params, _access)) -PROXY_STUB(snd_pcm_hw_params_set_buffer_time_near, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), - (pcm, params, val, dir)) -PROXY_STUB(snd_pcm_hw_params_set_buffer_size_near, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), - (pcm, params, val)) -PROXY_STUB(snd_pcm_hw_params_set_channels_near, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val), - (pcm, params, val)) -PROXY_STUB(snd_pcm_hw_params_set_period_size_near, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir), - (pcm, params, val, dir)) -PROXY_STUB(snd_pcm_hw_params_set_period_time_near, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), - (pcm, params, val, dir)) -PROXY_STUB(snd_pcm_hw_params_sizeof, size_t, (void), ()) -PROXY_STUB(snd_pcm_hw_params_set_format, int, - (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val), - (pcm, params, val)) - -/* - * SW - */ - -PROXY_STUB(snd_pcm_sw_params, int, - (snd_pcm_t *pcm, snd_pcm_sw_params_t *params), - (pcm, params)) -PROXY_STUB(snd_pcm_sw_params_current, int, - (snd_pcm_t *pcm, snd_pcm_sw_params_t *params), - (pcm, params)) -PROXY_STUB(snd_pcm_sw_params_get_start_threshold, int, - (const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val), - (params, val)) -PROXY_STUB(snd_pcm_sw_params_set_avail_min, int, - (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val), - (pcm, params, val)) -PROXY_STUB(snd_pcm_sw_params_set_start_threshold, int, - (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val), - (pcm, params, val)) -PROXY_STUB(snd_pcm_sw_params_sizeof, size_t, (void), ()) - -typedef struct -{ - const char *name; - void (**fn)(void); -} SHARED_FUNC; - -#define ELEMENT(function) { #function , (void (**)(void)) & pfn_ ## function } -static SHARED_FUNC SharedFuncs[] = -{ - ELEMENT(snd_lib_error_set_handler), - ELEMENT(snd_strerror), - - ELEMENT(snd_device_name_hint), - ELEMENT(snd_device_name_get_hint), - ELEMENT(snd_device_name_free_hint), - - ELEMENT(snd_pcm_avail_update), - ELEMENT(snd_pcm_close), - ELEMENT(snd_pcm_delay), - ELEMENT(snd_pcm_drain), - ELEMENT(snd_pcm_drop), - ELEMENT(snd_pcm_nonblock), - ELEMENT(snd_pcm_open), - ELEMENT(snd_pcm_prepare), - ELEMENT(snd_pcm_resume), - ELEMENT(snd_pcm_state), - - ELEMENT(snd_pcm_readi), - ELEMENT(snd_pcm_start), - ELEMENT(snd_pcm_writei), - - ELEMENT(snd_pcm_hw_params), - ELEMENT(snd_pcm_hw_params_any), - ELEMENT(snd_pcm_hw_params_sizeof), - ELEMENT(snd_pcm_hw_params_get_buffer_size), - ELEMENT(snd_pcm_hw_params_get_buffer_size_min), - ELEMENT(snd_pcm_hw_params_get_period_size_min), - ELEMENT(snd_pcm_hw_params_set_access), - ELEMENT(snd_pcm_hw_params_set_buffer_size_near), - ELEMENT(snd_pcm_hw_params_set_buffer_time_near), - ELEMENT(snd_pcm_hw_params_set_channels_near), - ELEMENT(snd_pcm_hw_params_set_format), - ELEMENT(snd_pcm_hw_params_get_period_size), - ELEMENT(snd_pcm_hw_params_set_period_size_near), - ELEMENT(snd_pcm_hw_params_set_period_time_near), - ELEMENT(snd_pcm_hw_params_set_rate_near), - - ELEMENT(snd_pcm_sw_params), - ELEMENT(snd_pcm_sw_params_current), - ELEMENT(snd_pcm_sw_params_get_start_threshold), - ELEMENT(snd_pcm_sw_params_set_avail_min), - ELEMENT(snd_pcm_sw_params_set_start_threshold), - ELEMENT(snd_pcm_sw_params_sizeof), -}; -#undef ELEMENT - -/** - * Try to dynamically load the ALSA libraries. This function is not - * thread-safe, and should be called before attempting to use any of the - * ALSA functions. - * - * @returns iprt status code - */ -int audioLoadAlsaLib(void) -{ - int rc = VINF_SUCCESS; - unsigned i; - static enum { NO = 0, YES, FAIL } isLibLoaded = NO; - RTLDRMOD hLib; - - LogFlowFunc(("\n")); - /* If this is not NO then the function has obviously been called twice, - which is likely to be a bug. */ - if (NO != isLibLoaded) - { - AssertMsgFailed(("isLibLoaded == %s\n", YES == isLibLoaded ? "YES" : "NO")); - return YES == isLibLoaded ? VINF_SUCCESS : VERR_NOT_SUPPORTED; - } - isLibLoaded = FAIL; - rc = RTLdrLoad(VBOX_ALSA_LIB, &hLib); - if (RT_FAILURE(rc)) - { - LogRelFunc(("Failed to load library %s\n", VBOX_ALSA_LIB)); - return rc; - } - for (i=0; i +#include +#include +#include +#include +#include +#include + +#define LOG_GROUP LOG_GROUP_DRV_AUDIO +#include + +#include +#include +#include +#include + +#include "AudioHlp.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct AUDIOWAVEFILEHDR +{ + RTRIFFHDR Hdr; + RTRIFFWAVEFMTEXTCHUNK FmtExt; + RTRIFFCHUNK Data; +} AUDIOWAVEFILEHDR; + + +#if 0 /* unused, no header prototypes */ + +/** + * Retrieves the matching PDMAUDIOFMT for the given bits + signing flag. + * + * @return Matching PDMAUDIOFMT value. + * @retval PDMAUDIOFMT_INVALID if unsupported @a cBits value. + * + * @param cBits The number of bits in the audio format. + * @param fSigned Whether the audio format is signed @c true or not. + */ +PDMAUDIOFMT DrvAudioAudFmtBitsToFormat(uint8_t cBits, bool fSigned) +{ + if (fSigned) + { + switch (cBits) + { + case 8: return PDMAUDIOFMT_S8; + case 16: return PDMAUDIOFMT_S16; + case 32: return PDMAUDIOFMT_S32; + default: AssertMsgFailedReturn(("Bogus audio bits %RU8\n", cBits), PDMAUDIOFMT_INVALID); + } + } + else + { + switch (cBits) + { + case 8: return PDMAUDIOFMT_U8; + case 16: return PDMAUDIOFMT_U16; + case 32: return PDMAUDIOFMT_U32; + default: AssertMsgFailedReturn(("Bogus audio bits %RU8\n", cBits), PDMAUDIOFMT_INVALID); + } + } +} + +/** + * Returns an unique file name for this given audio connector instance. + * + * @return Allocated file name. Must be free'd using RTStrFree(). + * @param uInstance Driver / device instance. + * @param pszPath Path name of the file to delete. The path must exist. + * @param pszSuffix File name suffix to use. + */ +char *DrvAudioDbgGetFileNameA(uint8_t uInstance, const char *pszPath, const char *pszSuffix) +{ + char szFileName[64]; + RTStrPrintf(szFileName, sizeof(szFileName), "drvAudio%RU8-%s", uInstance, pszSuffix); + + char szFilePath[RTPATH_MAX]; + int rc2 = RTStrCopy(szFilePath, sizeof(szFilePath), pszPath); + AssertRC(rc2); + rc2 = RTPathAppend(szFilePath, sizeof(szFilePath), szFileName); + AssertRC(rc2); + + return RTStrDup(szFilePath); +} + +#endif /* unused */ + +/** + * Checks whether a given stream configuration is valid or not. + * + * @note See notes on AudioHlpPcmPropsAreValid(). + * + * Returns @c true if configuration is valid, @c false if not. + * @param pCfg Stream configuration to check. + */ +bool AudioHlpStreamCfgIsValid(PCPDMAUDIOSTREAMCFG pCfg) +{ + /* Ugly! HDA attach code calls us with uninitialized (all zero) config. */ + if (PDMAudioPropsHz(&pCfg->Props) != 0) + { + if (PDMAudioStrmCfgIsValid(pCfg)) + { + if ( pCfg->enmDir == PDMAUDIODIR_IN + || pCfg->enmDir == PDMAUDIODIR_OUT) + return AudioHlpPcmPropsAreValid(&pCfg->Props); + } + } + return false; +} + +/** + * Calculates the audio bit rate of the given bits per sample, the Hz and the number + * of audio channels. + * + * Divide the result by 8 to get the byte rate. + * + * @returns Bitrate. + * @param cBits Number of bits per sample. + * @param uHz Hz (Hertz) rate. + * @param cChannels Number of audio channels. + */ +uint32_t AudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels) +{ + return cBits * uHz * cChannels; +} + + +/** + * Checks whether given PCM properties are valid or not. + * + * @note This is more of a supported than valid check. There is code for + * unsigned samples elsewhere (like DrvAudioHlpClearBuf()), but this + * function will flag such properties as not valid. + * + * @todo r=bird: See note and explain properly. Perhaps rename to + * AudioHlpPcmPropsAreValidAndSupported? + * + * @returns @c true if the properties are valid, @c false if not. + * @param pProps The PCM properties to check. + */ +bool AudioHlpPcmPropsAreValid(PCPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, false); + AssertReturn(PDMAudioPropsAreValid(pProps), false); + + switch (PDMAudioPropsSampleSize(pProps)) + { + case 1: /* 8 bit */ + if (PDMAudioPropsIsSigned(pProps)) + return false; + break; + case 2: /* 16 bit */ + if (!PDMAudioPropsIsSigned(pProps)) + return false; + break; + /** @todo Do we need support for 24 bit samples? */ + case 4: /* 32 bit */ + if (!PDMAudioPropsIsSigned(pProps)) + return false; + break; + case 8: /* 64-bit raw */ + if ( !PDMAudioPropsIsSigned(pProps) + || !pProps->fRaw) + return false; + break; + default: + return false; + } + + if (!pProps->fSwapEndian) /** @todo Handling Big Endian audio data is not supported yet. */ + return true; + return false; +} + + +/********************************************************************************************************************************* +* Audio File Helpers * +*********************************************************************************************************************************/ + +/** + * Constructs an unique file name, based on the given path and the audio file type. + * + * @returns VBox status code. + * @param pszDst Where to store the constructed file name. + * @param cbDst Size of the destination buffer (bytes; incl terminator). + * @param pszPath Base path to use. If NULL or empty, the user's + * temporary directory will be used. + * @param pszNameFmt A name for better identifying the file. + * @param va Arguments for @a pszNameFmt. + * @param uInstance Device / driver instance which is using this file. + * @param enmType Audio file type to construct file name for. + * @param fFlags File naming flags, AUDIOHLPFILENAME_FLAGS_XXX. + * @param chTweak Retry tweak character. + */ +static int audioHlpConstructPathWorker(char *pszDst, size_t cbDst, const char *pszPath, const char *pszNameFmt, va_list va, + uint32_t uInstance, AUDIOHLPFILETYPE enmType, uint32_t fFlags, char chTweak) +{ + /* + * Validate input. + */ + AssertPtrNullReturn(pszPath, VERR_INVALID_POINTER); + AssertPtrReturn(pszNameFmt, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~AUDIOHLPFILENAME_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + + /* Validate the type and translate it into a suffix. */ + const char *pszSuffix = NULL; + switch (enmType) + { + case AUDIOHLPFILETYPE_RAW: pszSuffix = ".pcm"; break; + case AUDIOHLPFILETYPE_WAV: pszSuffix = ".wav"; break; + case AUDIOHLPFILETYPE_INVALID: + case AUDIOHLPFILETYPE_32BIT_HACK: + break; /* no default */ + } + AssertMsgReturn(pszSuffix, ("enmType=%d\n", enmType), VERR_INVALID_PARAMETER); + + /* + * The directory. Make sure it exists and ends with a path separator. + */ + int rc; + if (!pszPath || !*pszPath) + rc = RTPathTemp(pszDst, cbDst); + else + { + AssertPtrReturn(pszDst, VERR_INVALID_POINTER); + rc = RTStrCopy(pszDst, cbDst, pszPath); + } + AssertRCReturn(rc, rc); + + if (!RTDirExists(pszDst)) + { + rc = RTDirCreateFullPath(pszDst, RTFS_UNIX_IRWXU); + AssertRCReturn(rc, rc); + } + + size_t offDst = RTPathEnsureTrailingSeparator(pszDst, cbDst); + AssertReturn(offDst > 0, VERR_BUFFER_OVERFLOW); + Assert(offDst < cbDst); + + /* + * The filename. + */ + /* Start with a ISO timestamp w/ colons replaced by dashes if requested. */ + if (fFlags & AUDIOHLPFILENAME_FLAGS_TS) + { + RTTIMESPEC NowTimeSpec; + RTTIME NowUtc; + AssertReturn(RTTimeToString(RTTimeExplode(&NowUtc, RTTimeNow(&NowTimeSpec)), &pszDst[offDst], cbDst - offDst), + VERR_BUFFER_OVERFLOW); + + /* Change the two colons in the time part to dashes. */ + char *pchColon = &pszDst[offDst]; + while ((pchColon = strchr(pchColon, ':')) != NULL) + *pchColon++ = '-'; + + offDst += strlen(&pszDst[offDst]); + Assert(pszDst[offDst - 1] == 'Z'); + + /* Append a dash to separate the timestamp from the name. */ + AssertReturn(offDst + 2 <= cbDst, VERR_BUFFER_OVERFLOW); + pszDst[offDst++] = '-'; + pszDst[offDst] = '\0'; + } + + /* Append the filename, instance, retry-tweak and suffix. */ + va_list vaCopy; + va_copy(vaCopy, va); + ssize_t cchTail; + if (chTweak == '\0') + cchTail = RTStrPrintf2(&pszDst[offDst], cbDst - offDst, "%N-%u%s", pszNameFmt, &vaCopy, uInstance, pszSuffix); + else + cchTail = RTStrPrintf2(&pszDst[offDst], cbDst - offDst, "%N-%u%c%s", pszNameFmt, &vaCopy, uInstance, chTweak, pszSuffix); + va_end(vaCopy); + AssertReturn(cchTail > 0, VERR_BUFFER_OVERFLOW); + + return VINF_SUCCESS; +} + + +/** + * Worker for AudioHlpFileCreateF and AudioHlpFileCreateAndOpenEx that allocates + * and initializes a AUDIOHLPFILE instance. + */ +static int audioHlpFileCreateWorker(PAUDIOHLPFILE *ppFile, uint32_t fFlags, AUDIOHLPFILETYPE enmType, const char *pszPath) +{ + AssertReturn(!(fFlags & ~AUDIOHLPFILE_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + + size_t const cbPath = strlen(pszPath) + 1; + PAUDIOHLPFILE pFile = (PAUDIOHLPFILE)RTMemAllocVar(RT_UOFFSETOF_DYN(AUDIOHLPFILE, szName[cbPath])); + AssertPtrReturn(pFile, VERR_NO_MEMORY); + + pFile->enmType = enmType; + pFile->fFlags = fFlags; + pFile->cbWaveData = 0; + pFile->hFile = NIL_RTFILE; + memcpy(pFile->szName, pszPath, cbPath); + + *ppFile = pFile; + return VINF_SUCCESS; +} + + +/** + * Creates an instance of AUDIOHLPFILE with the given filename and type. + * + * @note This does NOT create the file, see AudioHlpFileOpen for that. + * + * @returns VBox status code. + * @param ppFile Where to return the pointer to the audio debug file + * instance on success. + * @param fFlags AUDIOHLPFILE_FLAGS_XXX. + * @param enmType The audio file type to produce. + * @param pszPath The directory path. The temporary directory will be + * used if NULL or empty. + * @param fFilename AUDIOHLPFILENAME_FLAGS_XXX. + * @param uInstance The instance number (will be appended to the filename + * with a dash inbetween). + * @param pszNameFmt The filename format string. + * @param ... Arguments to the filename format string. + */ +int AudioHlpFileCreateF(PAUDIOHLPFILE *ppFile, uint32_t fFlags, AUDIOHLPFILETYPE enmType, + const char *pszPath, uint32_t fFilename, uint32_t uInstance, const char *pszNameFmt, ...) +{ + *ppFile = NULL; + + /* + * Construct the filename first. + */ + char szPath[RTPATH_MAX]; + va_list va; + va_start(va, pszNameFmt); + int rc = audioHlpConstructPathWorker(szPath, sizeof(szPath), pszPath, pszNameFmt, va, uInstance, enmType, fFilename, '\0'); + va_end(va); + AssertRCReturn(rc, rc); + + /* + * Allocate and initializes a debug file instance with that filename path. + */ + return audioHlpFileCreateWorker(ppFile, fFlags, enmType, szPath); +} + + +/** + * Destroys a formerly created audio file. + * + * @param pFile Audio file (object) to destroy. + */ +void AudioHlpFileDestroy(PAUDIOHLPFILE pFile) +{ + if (pFile) + { + AudioHlpFileClose(pFile); + RTMemFree(pFile); + } +} + + +/** + * Opens or creates an audio file. + * + * @returns VBox status code. + * @param pFile Pointer to audio file handle to use. + * @param fOpen Open flags. + * Use AUDIOHLPFILE_DEFAULT_OPEN_FLAGS for the default open flags. + * @param pProps PCM properties to use. + */ +int AudioHlpFileOpen(PAUDIOHLPFILE pFile, uint64_t fOpen, PCPDMAUDIOPCMPROPS pProps) +{ + int rc; + + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + /** @todo Validate fOpen flags. */ + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + Assert(PDMAudioPropsAreValid(pProps)); + + /* + * Raw files just needs to be opened. + */ + if (pFile->enmType == AUDIOHLPFILETYPE_RAW) + rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen); + /* + * Wave files needs a header to be constructed and we need to take note of where + * there are sizes to update later when closing the file. + */ + else if (pFile->enmType == AUDIOHLPFILETYPE_WAV) + { + /* Construct the header. */ + AUDIOWAVEFILEHDR FileHdr; + FileHdr.Hdr.uMagic = RTRIFFHDR_MAGIC; + FileHdr.Hdr.cbFile = 0; /* need to update this later */ + FileHdr.Hdr.uFileType = RTRIFF_FILE_TYPE_WAVE; + FileHdr.FmtExt.Chunk.uMagic = RTRIFFWAVEFMT_MAGIC; + FileHdr.FmtExt.Chunk.cbChunk = sizeof(RTRIFFWAVEFMTEXTCHUNK) - sizeof(RTRIFFCHUNK); + FileHdr.FmtExt.Data.Core.uFormatTag = RTRIFFWAVEFMT_TAG_EXTENSIBLE; + FileHdr.FmtExt.Data.Core.cChannels = PDMAudioPropsChannels(pProps); + FileHdr.FmtExt.Data.Core.uHz = PDMAudioPropsHz(pProps); + FileHdr.FmtExt.Data.Core.cbRate = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps)); + FileHdr.FmtExt.Data.Core.cbFrame = PDMAudioPropsFrameSize(pProps); + FileHdr.FmtExt.Data.Core.cBitsPerSample = PDMAudioPropsSampleBits(pProps); + FileHdr.FmtExt.Data.cbExtra = sizeof(FileHdr.FmtExt.Data) - sizeof(FileHdr.FmtExt.Data.Core); + FileHdr.FmtExt.Data.cValidBitsPerSample = PDMAudioPropsSampleBits(pProps); + FileHdr.FmtExt.Data.fChannelMask = 0; + for (uintptr_t idxCh = 0; idxCh < FileHdr.FmtExt.Data.Core.cChannels; idxCh++) + { + PDMAUDIOCHANNELID const idCh = (PDMAUDIOCHANNELID)pProps->aidChannels[idxCh]; + AssertLogRelMsgReturn(idCh >= PDMAUDIOCHANNELID_FIRST_STANDARD && idCh < PDMAUDIOCHANNELID_END_STANDARD, + ("Invalid channel ID %d for channel #%u", idCh, idxCh), VERR_INVALID_PARAMETER); + AssertLogRelMsgReturn(!(FileHdr.FmtExt.Data.fChannelMask & RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD)), + ("Channel #%u repeats channel ID %d", idxCh, idCh), VERR_INVALID_PARAMETER); + FileHdr.FmtExt.Data.fChannelMask |= RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD); + } + + RTUUID UuidTmp; + rc = RTUuidFromStr(&UuidTmp, RTRIFFWAVEFMTEXT_SUBTYPE_PCM); + AssertRCReturn(rc, rc); + FileHdr.FmtExt.Data.SubFormat = UuidTmp; /* (64-bit field maybe unaligned) */ + + FileHdr.Data.uMagic = RTRIFFWAVEDATACHUNK_MAGIC; + FileHdr.Data.cbChunk = 0; /* need to update this later */ + + /* Open the file and write out the header. */ + rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(pFile->hFile, &FileHdr, sizeof(FileHdr), NULL); + if (RT_FAILURE(rc)) + { + RTFileClose(pFile->hFile); + pFile->hFile = NIL_RTFILE; + } + } + } + else + AssertFailedStmt(rc = VERR_INTERNAL_ERROR_3); + if (RT_SUCCESS(rc)) + { + pFile->cbWaveData = 0; + LogRel2(("Audio: Opened file '%s'\n", pFile->szName)); + } + else + LogRel(("Audio: Failed opening file '%s': %Rrc\n", pFile->szName, rc)); + return rc; +} + + +/** + * Creates a debug file structure and opens a file for it, extended version. + * + * @returns VBox status code. + * @param ppFile Where to return the debug file instance on success. + * @param enmType The file type. + * @param pszDir The directory to open the file in. + * @param iInstance The device/driver instance. + * @param fFilename AUDIOHLPFILENAME_FLAGS_XXX. + * @param fCreate AUDIOHLPFILE_FLAGS_XXX. + * @param pProps PCM audio properties for the file. + * @param fOpen RTFILE_O_XXX or AUDIOHLPFILE_DEFAULT_OPEN_FLAGS. + * @param pszNameFmt The base filename. + * @param ... Filename format arguments. + */ +int AudioHlpFileCreateAndOpenEx(PAUDIOHLPFILE *ppFile, AUDIOHLPFILETYPE enmType, const char *pszDir, + uint32_t iInstance, uint32_t fFilename, uint32_t fCreate, + PCPDMAUDIOPCMPROPS pProps, uint64_t fOpen, const char *pszNameFmt, ...) +{ + *ppFile = NULL; + + for (uint32_t iTry = 0; ; iTry++) + { + /* Format the path to the filename. */ + char szFile[RTPATH_MAX]; + va_list va; + va_start(va, pszNameFmt); + int rc = audioHlpConstructPathWorker(szFile, sizeof(szFile), pszDir, pszNameFmt, va, iInstance, enmType, fFilename, + iTry == 0 ? '\0' : iTry + 'a'); + va_end(va); + AssertRCReturn(rc, rc); + + /* Create an debug audio file instance with the filename path. */ + PAUDIOHLPFILE pFile = NULL; + rc = audioHlpFileCreateWorker(&pFile, fCreate, enmType, szFile); + AssertRCReturn(rc, rc); + + /* Try open it. */ + rc = AudioHlpFileOpen(pFile, fOpen, pProps); + if (RT_SUCCESS(rc)) + { + *ppFile = pFile; + return rc; + } + AudioHlpFileDestroy(pFile); + + AssertReturn(iTry < 16, rc); + } +} + + +/** + * Creates a debug wav-file structure and opens a file for it, default flags. + * + * @returns VBox status code. + * @param ppFile Where to return the debug file instance on success. + * @param pszDir The directory to open the file in. + * @param pszName The base filename. + * @param iInstance The device/driver instance. + * @param pProps PCM audio properties for the file. + */ +int AudioHlpFileCreateAndOpen(PAUDIOHLPFILE *ppFile, const char *pszDir, const char *pszName, + uint32_t iInstance, PCPDMAUDIOPCMPROPS pProps) +{ + return AudioHlpFileCreateAndOpenEx(ppFile, AUDIOHLPFILETYPE_WAV, pszDir, iInstance, + AUDIOHLPFILENAME_FLAGS_NONE, AUDIOHLPFILE_FLAGS_NONE, + pProps, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS, "%s", pszName); +} + + +/** + * Closes an audio file. + * + * @returns VBox status code. + * @param pFile Audio file handle to close. + */ +int AudioHlpFileClose(PAUDIOHLPFILE pFile) +{ + if (!pFile || pFile->hFile == NIL_RTFILE) + return VINF_SUCCESS; + + /* + * Wave files needs to update the data size and file size in the header. + */ + if (pFile->enmType == AUDIOHLPFILETYPE_WAV) + { + uint32_t const cbFile = sizeof(AUDIOWAVEFILEHDR) - sizeof(RTRIFFCHUNK) + (uint32_t)pFile->cbWaveData; + uint32_t const cbData = (uint32_t)pFile->cbWaveData; + + int rc2; + rc2 = RTFileWriteAt(pFile->hFile, RT_UOFFSETOF(AUDIOWAVEFILEHDR, Hdr.cbFile), &cbFile, sizeof(cbFile), NULL); + AssertRC(rc2); + rc2 = RTFileWriteAt(pFile->hFile, RT_UOFFSETOF(AUDIOWAVEFILEHDR, Data.cbChunk), &cbData, sizeof(cbData), NULL); + AssertRC(rc2); + } + + /* + * Do the closing. + */ + int rc = RTFileClose(pFile->hFile); + if (RT_SUCCESS(rc) || rc == VERR_INVALID_HANDLE) + pFile->hFile = NIL_RTFILE; + + if (RT_SUCCESS(rc)) + LogRel2(("Audio: Closed file '%s' (%'RU64 bytes PCM data)\n", pFile->szName, pFile->cbWaveData)); + else + LogRel(("Audio: Failed closing file '%s': %Rrc\n", pFile->szName, rc)); + + /* + * Delete empty file if requested. + */ + if ( !(pFile->fFlags & AUDIOHLPFILE_FLAGS_KEEP_IF_EMPTY) + && pFile->cbWaveData == 0 + && RT_SUCCESS(rc)) + AudioHlpFileDelete(pFile); + + return rc; +} + + +/** + * Deletes an audio file. + * + * @returns VBox status code. + * @param pFile Audio file to delete. + */ +int AudioHlpFileDelete(PAUDIOHLPFILE pFile) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + + int rc = RTFileDelete(pFile->szName); + if (RT_SUCCESS(rc)) + LogRel2(("Audio: Deleted file '%s'\n", pFile->szName)); + else if (rc == VERR_FILE_NOT_FOUND) /* Don't bitch if the file is not around anymore. */ + rc = VINF_SUCCESS; + + if (RT_FAILURE(rc)) + LogRel(("Audio: Failed deleting file '%s', rc=%Rrc\n", pFile->szName, rc)); + + return rc; +} + + +/** + * Returns whether the given audio file is open and in use or not. + * + * @returns True if open, false if not. + * @param pFile Audio file to check open status for. + */ +bool AudioHlpFileIsOpen(PAUDIOHLPFILE pFile) +{ + if (!pFile || pFile->hFile == NIL_RTFILE) + return false; + + return RTFileIsValid(pFile->hFile); +} + + +/** + * Write PCM data to a wave (.WAV) file. + * + * @returns VBox status code. + * @param pFile Audio file to write PCM data to. + * @param pvBuf Audio data to write. + * @param cbBuf Size (in bytes) of audio data to write. + */ +int AudioHlpFileWrite(PAUDIOHLPFILE pFile, const void *pvBuf, size_t cbBuf) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + + if (!cbBuf) + return VINF_SUCCESS; + + int rc = RTFileWrite(pFile->hFile, pvBuf, cbBuf, NULL); + if (RT_SUCCESS(rc)) + pFile->cbWaveData += cbBuf; + + return rc; +} + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioHlp.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioHlp.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioHlp.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioHlp.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,128 @@ +/* $Id: AudioHlp.h $ */ +/** @file + * Audio helper routines. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_AudioHlp_h +#define VBOX_INCLUDED_SRC_Audio_AudioHlp_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +#include +#include +#include +#include + +#include +#include +#include + +/** @name Audio calculation helper methods. + * @{ */ +uint32_t AudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels); +/** @} */ + +/** @name Audio PCM properties helper methods. + * @{ */ +bool AudioHlpPcmPropsAreValid(PCPDMAUDIOPCMPROPS pProps); +/** @} */ + +/** @name Audio configuration helper methods. + * @{ */ +bool AudioHlpStreamCfgIsValid(PCPDMAUDIOSTREAMCFG pCfg); +/** @} */ + + +/** @name AUDIOHLPFILE_FLAGS_XXX + * @{ */ +/** No flags defined. */ +#define AUDIOHLPFILE_FLAGS_NONE UINT32_C(0) +/** Keep the audio file even if it contains no audio data. */ +#define AUDIOHLPFILE_FLAGS_KEEP_IF_EMPTY RT_BIT_32(0) +/** Audio file flag validation mask. */ +#define AUDIOHLPFILE_FLAGS_VALID_MASK UINT32_C(0x1) +/** @} */ + +/** Audio file default open flags. */ +#define AUDIOHLPFILE_DEFAULT_OPEN_FLAGS (RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE) + +/** + * Audio file types. + */ +typedef enum AUDIOHLPFILETYPE +{ + /** The customary invalid zero value. */ + AUDIOHLPFILETYPE_INVALID = 0, + /** Raw (PCM) file. */ + AUDIOHLPFILETYPE_RAW, + /** Wave (.WAV) file. */ + AUDIOHLPFILETYPE_WAV, + /** Hack to blow the type up to 32-bit. */ + AUDIOHLPFILETYPE_32BIT_HACK = 0x7fffffff +} AUDIOHLPFILETYPE; + +/** @name AUDIOHLPFILENAME_FLAGS_XXX + * @{ */ +/** No flags defined. */ +#define AUDIOHLPFILENAME_FLAGS_NONE UINT32_C(0) +/** Adds an ISO timestamp to the file name. */ +#define AUDIOHLPFILENAME_FLAGS_TS RT_BIT_32(0) +/** Valid flag mask. */ +#define AUDIOHLPFILENAME_FLAGS_VALID_MASK AUDIOHLPFILENAME_FLAGS_TS +/** @} */ + +/** + * Audio file handle. + */ +typedef struct AUDIOHLPFILE +{ + /** Type of the audio file. */ + AUDIOHLPFILETYPE enmType; + /** Audio file flags, AUDIOHLPFILE_FLAGS_XXX. */ + uint32_t fFlags; + /** Amount of wave data written. */ + uint64_t cbWaveData; + /** Actual file handle. */ + RTFILE hFile; + /** File name and path. */ + RT_FLEXIBLE_ARRAY_EXTENSION + char szName[RT_FLEXIBLE_ARRAY]; +} AUDIOHLPFILE; +/** Pointer to an audio file handle. */ +typedef AUDIOHLPFILE *PAUDIOHLPFILE; + +/** @name Audio file methods. + * @{ */ +int AudioHlpFileCreateAndOpen(PAUDIOHLPFILE *ppFile, const char *pszDir, const char *pszName, + uint32_t iInstance, PCPDMAUDIOPCMPROPS pProps); +int AudioHlpFileCreateAndOpenEx(PAUDIOHLPFILE *ppFile, AUDIOHLPFILETYPE enmType, const char *pszDir, + uint32_t iInstance, uint32_t fFilename, uint32_t fCreate, + PCPDMAUDIOPCMPROPS pProps, uint64_t fOpen, const char *pszName, ...); +int AudioHlpFileCreateF(PAUDIOHLPFILE *ppFile, uint32_t fFlags, AUDIOHLPFILETYPE enmType, + const char *pszPath, uint32_t fFilename, uint32_t uInstance, const char *pszFileFmt, ...); + +void AudioHlpFileDestroy(PAUDIOHLPFILE pFile); +int AudioHlpFileOpen(PAUDIOHLPFILE pFile, uint64_t fOpen, PCPDMAUDIOPCMPROPS pProps); +int AudioHlpFileClose(PAUDIOHLPFILE pFile); +int AudioHlpFileDelete(PAUDIOHLPFILE pFile); +bool AudioHlpFileIsOpen(PAUDIOHLPFILE pFile); +int AudioHlpFileWrite(PAUDIOHLPFILE pFile, const void *pvBuf, size_t cbBuf); +/** @} */ + +#endif /* !VBOX_INCLUDED_SRC_Audio_AudioHlp_h */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixBuffer-Convert.cpp.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixBuffer-Convert.cpp.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixBuffer-Convert.cpp.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixBuffer-Convert.cpp.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,305 @@ +/* $Id: AudioMixBuffer-Convert.cpp.h $ */ +/** @file + * Audio mixing buffer - Format conversion template. + */ + +/* + * Copyright (C) 2014-2021 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/* used to be: #define AUDMIXBUF_CONVERT(a_Name, a_Type, a_Min, a_Max, a_fSigned, a_cShift) */ + +/* Clips a specific output value to a single sample value. */ +DECLINLINE(int32_t) RT_CONCAT(audioMixBufSampleFrom,a_Name)(a_Type aVal) +{ + /* left shifting of signed values is not defined, therefore the intermediate uint64_t cast */ + if (a_fSigned) + return (int32_t) (((uint32_t) ((int32_t) aVal )) << (32 - a_cShift)); + return (int32_t) (((uint32_t) ((int32_t) aVal - ((a_Max >> 1) + 1))) << (32 - a_cShift)); +} + +/* Clips a single sample value to a specific output value. */ +DECLINLINE(a_Type) RT_CONCAT(audioMixBufSampleTo,a_Name)(int32_t iVal) +{ + if (a_fSigned) + return (a_Type) (iVal >> (32 - a_cShift)); + return (a_Type) ((iVal >> (32 - a_cShift)) + ((a_Max >> 1) + 1)); +} + +/* Encoders for peek: */ + +/* Generic */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncodeGeneric,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames, + PAUDIOMIXBUFPEEKSTATE pState) +{ + RT_NOREF_PV(pState); + uintptr_t const cSrcChannels = pState->cSrcChannels; + uintptr_t const cDstChannels = pState->cDstChannels; + a_Type *pDst = (a_Type *)pvDst; + while (cFrames-- > 0) + { + uintptr_t idxDst = cDstChannels; + while (idxDst-- > 0) + { + int8_t idxSrc = pState->aidxChannelMap[idxDst]; + if (idxSrc >= 0) + pDst[idxDst] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[idxSrc]); + else if (idxSrc != -2) + pDst[idxDst] = (a_fSigned) ? 0 : (a_Max >> 1); + else + pDst[idxDst] = 0; + } + pDst += cDstChannels; + pi32Src += cSrcChannels; + } +} + +/* 2ch -> 2ch */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncode2ChTo2Ch,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames, + PAUDIOMIXBUFPEEKSTATE pState) +{ + RT_NOREF_PV(pState); + a_Type *pDst = (a_Type *)pvDst; + while (cFrames-- > 0) + { + pDst[0] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[0]); + pDst[1] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[1]); + AUDMIXBUF_MACRO_LOG(("%p: %RI32 / %RI32 => %RI32 / %RI32\n", + &pi32Src[0], pi32Src[0], pi32Src[1], (int32_t)pDst[0], (int32_t)pDst[1])); + pDst += 2; + pi32Src += 2; + } +} + +/* 2ch -> 1ch */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncode2ChTo1Ch,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames, + PAUDIOMIXBUFPEEKSTATE pState) +{ + RT_NOREF_PV(pState); + a_Type *pDst = (a_Type *)pvDst; + while (cFrames-- > 0) + { + pDst[0] = RT_CONCAT(audioMixBufSampleTo,a_Name)(audioMixBufBlendSampleRet(pi32Src[0], pi32Src[1])); + pDst += 1; + pi32Src += 2; + } +} + +/* 1ch -> 2ch */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncode1ChTo2Ch,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames, + PAUDIOMIXBUFPEEKSTATE pState) +{ + RT_NOREF_PV(pState); + a_Type *pDst = (a_Type *)pvDst; + while (cFrames-- > 0) + { + pDst[0] = pDst[1] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[0]); + pDst += 2; + pi32Src += 1; + } +} +/* 1ch -> 1ch */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncode1ChTo1Ch,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames, + PAUDIOMIXBUFPEEKSTATE pState) +{ + RT_NOREF_PV(pState); + a_Type *pDst = (a_Type *)pvDst; + while (cFrames-- > 0) + { + pDst[0] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[0]); + pDst += 1; + pi32Src += 1; + } +} + +/* Decoders for write: */ + +/* Generic */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecodeGeneric,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames, + PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + uintptr_t const cSrcChannels = pState->cSrcChannels; + uintptr_t const cDstChannels = pState->cDstChannels; + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + uintptr_t idxDst = cDstChannels; + while (idxDst-- > 0) + { + int8_t idxSrc = pState->aidxChannelMap[idxDst]; + if (idxSrc >= 0) + pi32Dst[idxDst] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[idxSrc]); + else if (idxSrc != -2) + pi32Dst[idxDst] = (a_fSigned) ? 0 : (a_Max >> 1); + else + pi32Dst[idxDst] = 0; + } + pi32Dst += cDstChannels; + pSrc += cSrcChannels; + } +} + +/* 2ch -> 2ch */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecode2ChTo2Ch,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames, + PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + pi32Dst[0] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]); + pi32Dst[1] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[1]); + AUDMIXBUF_MACRO_LOG(("%p: %RI32 / %RI32 => %RI32 / %RI32\n", + &pSrc[0], (int32_t)pSrc[0], (int32_t)pSrc[1], pi32Dst[0], pi32Dst[1])); + pi32Dst += 2; + pSrc += 2; + } +} + +/* 2ch -> 1ch */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecode2ChTo1Ch,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames, + PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + pi32Dst[0] = audioMixBufBlendSampleRet(RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]), + RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[1])); + pi32Dst += 1; + pSrc += 2; + } +} + +/* 1ch -> 2ch */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecode1ChTo2Ch,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames, + PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + pi32Dst[1] = pi32Dst[0] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]); + pi32Dst += 2; + pSrc += 1; + } +} + +/* 1ch -> 1ch */ +static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecode1ChTo1Ch,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames, + PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + pi32Dst[0] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]); + pi32Dst += 1; + pSrc += 1; + } +} + +/* Decoders for blending: */ + +/* Generic */ +static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecodeGeneric,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc, + uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + uintptr_t const cSrcChannels = pState->cSrcChannels; + uintptr_t const cDstChannels = pState->cDstChannels; + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + uintptr_t idxDst = cDstChannels; + while (idxDst-- > 0) + { + int8_t idxSrc = pState->aidxChannelMap[idxDst]; + if (idxSrc >= 0) + audioMixBufBlendSample(&pi32Dst[idxDst], RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[idxSrc])); + } + pi32Dst += cDstChannels; + pSrc += cSrcChannels; + } +} + +/* 2ch -> 2ch */ +static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecode2ChTo2Ch,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc, + uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + audioMixBufBlendSample(&pi32Dst[0], RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0])); + audioMixBufBlendSample(&pi32Dst[1], RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[1])); + AUDMIXBUF_MACRO_LOG(("%p: %RI32 / %RI32 => %RI32 / %RI32\n", + &pSrc[0], (int32_t)pSrc[0], (int32_t)pSrc[1], pi32Dst[0], pi32Dst[1])); + pi32Dst += 2; + pSrc += 2; + } +} + +/* 2ch -> 1ch */ +static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecode2ChTo1Ch,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc, + uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + audioMixBufBlendSample(&pi32Dst[0], audioMixBufBlendSampleRet(RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]), + RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[1]))); + pi32Dst += 1; + pSrc += 2; + } +} + +/* 1ch -> 2ch */ +static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecode1ChTo2Ch,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc, + uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + int32_t const i32Src = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]); + audioMixBufBlendSample(&pi32Dst[0], i32Src); + audioMixBufBlendSample(&pi32Dst[1], i32Src); + pi32Dst += 2; + pSrc += 1; + } +} + +/* 1ch -> 1ch */ +static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecode1ChTo1Ch,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc, + uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState) +{ + RT_NOREF_PV(pState); + a_Type const *pSrc = (a_Type const *)pvSrc; + while (cFrames-- > 0) + { + audioMixBufBlendSample(&pi32Dst[0], RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0])); + pi32Dst += 1; + pSrc += 1; + } +} + + +#undef a_Name +#undef a_Type +#undef a_Min +#undef a_Max +#undef a_fSigned +#undef a_cShift + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixBuffer.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixBuffer.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixBuffer.cpp 2020-10-16 16:32:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixBuffer.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -14,7 +14,39 @@ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ + +/** @page pg_audio_mixing_buffers Audio Mixer Buffer + * + * @section sec_audio_mixing_buffers_volume Soft Volume Control + * + * The external code supplies an 8-bit volume (attenuation) value in the + * 0 .. 255 range. This represents 0 to -96dB attenuation where an input + * value of 0 corresponds to -96dB and 255 corresponds to 0dB (unchanged). + * + * Each step thus corresponds to 96 / 256 or 0.375dB. Every 6dB (16 steps) + * represents doubling the sample value. + * + * For internal use, the volume control needs to be converted to a 16-bit + * (sort of) exponential value between 1 and 65536. This is used with fixed + * point arithmetic such that 65536 means 1.0 and 1 means 1/65536. + * + * For actual volume calculation, 33.31 fixed point is used. Maximum (or + * unattenuated) volume is represented as 0x40000000; conveniently, this + * value fits into a uint32_t. + * + * To enable fast processing, the maximum volume must be a power of two + * and must not have a sign when converted to int32_t. While 0x80000000 + * violates these constraints, 0x40000000 does not. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_AUDIO_MIXER_BUFFER +#if defined(VBOX_AUDIO_MIX_BUFFER_TESTCASE) && !defined(RT_STRICT) +# define RT_STRICT /* Run the testcase with assertions because the main functions doesn't return on invalid input. */ +#endif #include #if 0 @@ -45,51 +77,47 @@ # define LOG_ENABLED # include #endif -#include +#include +#include #include "AudioMixBuffer.h" + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ #ifndef VBOX_AUDIO_TESTCASE # ifdef DEBUG # define AUDMIXBUF_LOG(x) LogFlowFunc(x) +# define AUDMIXBUF_LOG_ENABLED # else -# define AUDMIXBUF_LOG(x) do {} while (0) +# define AUDMIXBUF_LOG(x) do {} while (0) # endif #else /* VBOX_AUDIO_TESTCASE */ # define AUDMIXBUF_LOG(x) RTPrintf x +# define AUDMIXBUF_LOG_ENABLED #endif -#ifdef DEBUG -DECLINLINE(void) audioMixBufDbgPrintInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc); -DECL_FORCE_INLINE(bool) audioMixBufDbgValidate(PPDMAUDIOMIXBUF pMixBuf); -#endif -/* - * Soft Volume Control - * - * The external code supplies an 8-bit volume (attenuation) value in the - * 0 .. 255 range. This represents 0 to -96dB attenuation where an input - * value of 0 corresponds to -96dB and 255 corresponds to 0dB (unchanged). - * - * Each step thus corresponds to 96 / 256 or 0.375dB. Every 6dB (16 steps) - * represents doubling the sample value. - * - * For internal use, the volume control needs to be converted to a 16-bit - * (sort of) exponential value between 1 and 65536. This is used with fixed - * point arithmetic such that 65536 means 1.0 and 1 means 1/65536. - * - * For actual volume calculation, 33.31 fixed point is used. Maximum (or - * unattenuated) volume is represented as 0x40000000; conveniently, this - * value fits into a uint32_t. - * - * To enable fast processing, the maximum volume must be a power of two - * and must not have a sign when converted to int32_t. While 0x80000000 - * violates these constraints, 0x40000000 does not. - */ +/** Bit shift for fixed point conversion. + * @sa @ref sec_audio_mixing_buffers_volume */ +#define AUDIOMIXBUF_VOL_SHIFT 30 + +/** Internal representation of 0dB volume (1.0 in fixed point). + * @sa @ref sec_audio_mixing_buffers_volume */ +#define AUDIOMIXBUF_VOL_0DB (1 << AUDIOMIXBUF_VOL_SHIFT) +AssertCompile(AUDIOMIXBUF_VOL_0DB <= 0x40000000); /* Must always hold. */ +AssertCompile(AUDIOMIXBUF_VOL_0DB == 0x40000000); /* For now -- when only attenuation is used. */ -/** Logarithmic/exponential volume conversion table. */ -static uint32_t s_aVolumeConv[256] = { +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Logarithmic/exponential volume conversion table. + * @sa @ref sec_audio_mixing_buffers_volume + */ +static uint32_t const s_aVolumeConv[256] = +{ 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ 1, 2, 2, 2, 2, 2, 2, 2, /* 15 */ 2, 2, 2, 2, 2, 3, 3, 3, /* 23 */ @@ -124,215 +152,685 @@ 48393, 50535, 52773, 55109, 57549, 60097, 62757, 65536, /* 255 */ }; -/* Bit shift for fixed point conversion. */ -#define AUDIOMIXBUF_VOL_SHIFT 30 - -/* Internal representation of 0dB volume (1.0 in fixed point). */ -#define AUDIOMIXBUF_VOL_0DB (1 << AUDIOMIXBUF_VOL_SHIFT) -AssertCompile(AUDIOMIXBUF_VOL_0DB <= 0x40000000); /* Must always hold. */ -AssertCompile(AUDIOMIXBUF_VOL_0DB == 0x40000000); /* For now -- when only attenuation is used. */ +#ifdef VBOX_STRICT +# ifdef UNUSED /** - * Peeks for audio frames without any conversion done. - * This will get the raw frame data out of a mixing buffer. + * Prints a single mixing buffer. + * Internal helper function for debugging. Do not use directly. * - * @return IPRT status code or VINF_AUDIO_MORE_DATA_AVAILABLE if more data is available to read. + * @returns VBox status code. + * @param pMixBuf Mixing buffer to print. + * @param pszFunc Function name to log this for. + * @param uIdtLvl Indention level to use. + */ +static void audioMixBufDbgPrintSingle(PAUDIOMIXBUF pMixBuf, const char *pszFunc, uint16_t uIdtLvl) +{ + Log(("%s: %*s %s: offRead=%RU32, offWrite=%RU32 -> %RU32/%RU32\n", + pszFunc, uIdtLvl * 4, "", + pMixBuf->pszName, pMixBuf->offRead, pMixBuf->offWrite, pMixBuf->cUsed, pMixBuf->cFrames)); +} + +static void audioMixBufDbgPrintInternal(PAUDIOMIXBUF pMixBuf, const char *pszFunc) +{ + audioMixBufDbgPrintSingle(pMixBuf, pszFunc, 0 /* iIdtLevel */); +} + +/** + * Validates a single mixing buffer. * - * @param pMixBuf Mixing buffer to acquire audio frames from. - * @param cFramesToRead Number of audio frames to read. - * @param paFrameBuf Buffer where to store the returned audio frames. - * @param cFrameBuf Size (in frames) of the buffer to store audio frames into. - * @param pcFramesRead Returns number of read audio frames. Optional. - * - * @remark This function is not thread safe! - */ -int AudioMixBufPeek(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead, - PPDMAUDIOFRAME paFrameBuf, uint32_t cFrameBuf, uint32_t *pcFramesRead) -{ - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertPtrReturn(paFrameBuf, VERR_INVALID_POINTER); - AssertReturn(cFrameBuf, VERR_INVALID_PARAMETER); - /* pcRead is optional. */ + * @return @true if the buffer state is valid or @false if not. + * @param pMixBuf Mixing buffer to validate. + */ +static bool audioMixBufDbgValidate(PAUDIOMIXBUF pMixBuf) +{ + //const uint32_t offReadEnd = (pMixBuf->offRead + pMixBuf->cUsed) % pMixBuf->cFrames; + //const uint32_t offWriteEnd = (pMixBuf->offWrite + (pMixBuf->cFrames - pMixBuf->cUsed)) % pMixBuf->cFrames; - int rc; + bool fValid = true; - if (!cFramesToRead) - { - if (pcFramesRead) - *pcFramesRead = 0; - return VINF_SUCCESS; - } + AssertStmt(pMixBuf->offRead <= pMixBuf->cFrames, fValid = false); + AssertStmt(pMixBuf->offWrite <= pMixBuf->cFrames, fValid = false); + AssertStmt(pMixBuf->cUsed <= pMixBuf->cFrames, fValid = false); - uint32_t cRead; - if (pMixBuf->offRead + cFramesToRead > pMixBuf->cFrames) + if (pMixBuf->offWrite > pMixBuf->offRead) { - cRead = pMixBuf->cFrames - pMixBuf->offRead; - rc = VINF_AUDIO_MORE_DATA_AVAILABLE; + if (pMixBuf->offWrite - pMixBuf->offRead != pMixBuf->cUsed) + fValid = false; } - else + else if (pMixBuf->offWrite < pMixBuf->offRead) { - cRead = cFramesToRead; - rc = VINF_SUCCESS; + if (pMixBuf->offWrite + pMixBuf->cFrames - pMixBuf->offRead != pMixBuf->cUsed) + fValid = false; } - if (cRead > cFrameBuf) + if (!fValid) { - cRead = cFrameBuf; - rc = VINF_AUDIO_MORE_DATA_AVAILABLE; + audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); + AssertFailed(); } - if (cRead) - { - memcpy(paFrameBuf, &pMixBuf->pFrames[pMixBuf->offRead], sizeof(PDMAUDIOFRAME) * cRead); + return fValid; +} - pMixBuf->offRead = (pMixBuf->offRead + cRead) % pMixBuf->cFrames; - Assert(pMixBuf->offRead <= pMixBuf->cFrames); - pMixBuf->cUsed -= RT_MIN(cRead, pMixBuf->cUsed); - } +# endif /* UNUSED */ +#endif /* VBOX_STRICT */ - if (pcFramesRead) - *pcFramesRead = cRead; - return rc; +/** + * Merges @a i32Src into the value stored at @a pi32Dst. + * + * @param pi32Dst The value to merge @a i32Src into. + * @param i32Src The new value to add. + */ +DECL_FORCE_INLINE(void) audioMixBufBlendSample(int32_t *pi32Dst, int32_t i32Src) +{ + if (i32Src) + { + int64_t const i32Dst = *pi32Dst; + if (!i32Dst) + *pi32Dst = i32Src; + else + *pi32Dst = (int32_t)(((int64_t)i32Dst + i32Src) / 2); + } } + /** - * Returns a mutable pointer to the mixing buffer's audio frame buffer for writing raw - * audio frames. + * Variant of audioMixBufBlendSample that returns the result rather than storing it. * - * @return IPRT status code. VINF_TRY_AGAIN for getting next pointer at beginning (circular). - * @param pMixBuf Mixing buffer to acquire audio frames from. - * @param cFrames Number of requested audio frames to write. - * @param ppvFrames Returns a mutable pointer to the buffer's audio frame data. - * @param pcFramesToWrite Number of available audio frames to write. - * - * @remark This function is not thread safe! - */ -int AudioMixBufPeekMutable(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFrames, - PPDMAUDIOFRAME *ppvFrames, uint32_t *pcFramesToWrite) -{ - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertPtrReturn(ppvFrames, VERR_INVALID_POINTER); - AssertPtrReturn(pcFramesToWrite, VERR_INVALID_POINTER); + * This is used for stereo -> mono. + */ +DECL_FORCE_INLINE(int32_t) audioMixBufBlendSampleRet(int32_t i32Sample1, int32_t i32Sample2) +{ + if (!i32Sample1) + return i32Sample2; + if (!i32Sample2) + return i32Sample1; + return (int32_t)(((int64_t)i32Sample1 + i32Sample2) / 2); +} - int rc; - if (!cFrames) +/** + * Blends (merges) the source buffer into the destination buffer. + * + * We're taking a very simple approach here, working sample by sample: + * - if one is silent, use the other one. + * - otherwise sum and divide by two. + * + * @param pi32Dst The destination stream buffer (input and output). + * @param pi32Src The source stream buffer. + * @param cFrames Number of frames to process. + * @param cChannels Number of channels. + */ +static void audioMixBufBlendBuffer(int32_t *pi32Dst, int32_t const *pi32Src, uint32_t cFrames, uint8_t cChannels) +{ + switch (cChannels) { - *pcFramesToWrite = 0; - return VINF_SUCCESS; - } + case 2: + while (cFrames-- > 0) + { + audioMixBufBlendSample(&pi32Dst[0], pi32Src[0]); + audioMixBufBlendSample(&pi32Dst[1], pi32Src[1]); + pi32Dst += 2; + pi32Src += 2; + } + break; - uint32_t cFramesToWrite; - if (pMixBuf->offWrite + cFrames > pMixBuf->cFrames) - { - cFramesToWrite = pMixBuf->cFrames - pMixBuf->offWrite; - rc = VINF_TRY_AGAIN; - } - else - { - cFramesToWrite = cFrames; - rc = VINF_SUCCESS; + default: + cFrames *= cChannels; + RT_FALL_THROUGH(); + case 1: + while (cFrames-- > 0) + { + audioMixBufBlendSample(pi32Dst, pi32Src[0]); + pi32Dst++; + pi32Src++; + } + break; } +} + - *ppvFrames = &pMixBuf->pFrames[pMixBuf->offWrite]; - AssertPtr(ppvFrames); +#ifdef AUDIOMIXBUF_DEBUG_MACROS +# define AUDMIXBUF_MACRO_LOG(x) AUDMIXBUF_LOG(x) +#elif defined(VBOX_AUDIO_TESTCASE_VERBOSE) /* Warning: VBOX_AUDIO_TESTCASE_VERBOSE will generate huge logs! */ +# define AUDMIXBUF_MACRO_LOG(x) RTPrintf x +#else +# define AUDMIXBUF_MACRO_LOG(x) do {} while (0) +#endif - pMixBuf->offWrite = (pMixBuf->offWrite + cFramesToWrite) % pMixBuf->cFrames; - Assert(pMixBuf->offWrite <= pMixBuf->cFrames); - pMixBuf->cUsed += RT_MIN(cFramesToWrite, pMixBuf->cUsed); +/* + * Instantiate format conversion (in and out of the mixer buffer.) + */ +/** @todo Currently does not handle any endianness conversion yet! */ - *pcFramesToWrite = cFramesToWrite; +/* audioMixBufConvXXXS8: 8-bit, signed. */ +#define a_Name S8 +#define a_Type int8_t +#define a_Min INT8_MIN +#define a_Max INT8_MAX +#define a_fSigned 1 +#define a_cShift 8 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXU8: 8-bit, unsigned. */ +#define a_Name U8 +#define a_Type uint8_t +#define a_Min 0 +#define a_Max UINT8_MAX +#define a_fSigned 0 +#define a_cShift 8 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXS16: 16-bit, signed. */ +#define a_Name S16 +#define a_Type int16_t +#define a_Min INT16_MIN +#define a_Max INT16_MAX +#define a_fSigned 1 +#define a_cShift 16 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXU16: 16-bit, unsigned. */ +#define a_Name U16 +#define a_Type uint16_t +#define a_Min 0 +#define a_Max UINT16_MAX +#define a_fSigned 0 +#define a_cShift 16 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXS32: 32-bit, signed. */ +#define a_Name S32 +#define a_Type int32_t +#define a_Min INT32_MIN +#define a_Max INT32_MAX +#define a_fSigned 1 +#define a_cShift 32 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXU32: 32-bit, unsigned. */ +#define a_Name U32 +#define a_Type uint32_t +#define a_Min 0 +#define a_Max UINT32_MAX +#define a_fSigned 0 +#define a_cShift 32 +#include "AudioMixBuffer-Convert.cpp.h" + +/* audioMixBufConvXXXRaw: 32-bit stored as 64-bit, signed. */ +#define a_Name Raw +#define a_Type int64_t +#define a_Min INT64_MIN +#define a_Max INT64_MAX +#define a_fSigned 1 +#define a_cShift 32 /* Yes, 32! */ +#include "AudioMixBuffer-Convert.cpp.h" + +#undef AUDMIXBUF_CONVERT +#undef AUDMIXBUF_MACRO_LOG + + +/* + * Resampling core. + */ +/** @todo Separate down- and up-sampling, borrow filter code from RDP. */ +#define COPY_LAST_FRAME_1CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + } while (0) +#define COPY_LAST_FRAME_2CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + } while (0) +#define COPY_LAST_FRAME_3CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + } while (0) +#define COPY_LAST_FRAME_4CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + } while (0) +#define COPY_LAST_FRAME_5CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + } while (0) +#define COPY_LAST_FRAME_6CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + } while (0) +#define COPY_LAST_FRAME_7CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + } while (0) +#define COPY_LAST_FRAME_8CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + } while (0) +#define COPY_LAST_FRAME_9CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + (a_pi32Dst)[8] = (a_pi32Src)[8]; \ + } while (0) +#define COPY_LAST_FRAME_10CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + (a_pi32Dst)[8] = (a_pi32Src)[8]; \ + (a_pi32Dst)[9] = (a_pi32Src)[9]; \ + } while (0) +#define COPY_LAST_FRAME_11CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + (a_pi32Dst)[8] = (a_pi32Src)[8]; \ + (a_pi32Dst)[9] = (a_pi32Src)[9]; \ + (a_pi32Dst)[10] = (a_pi32Src)[10]; \ + } while (0) +#define COPY_LAST_FRAME_12CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \ + (a_pi32Dst)[0] = (a_pi32Src)[0]; \ + (a_pi32Dst)[1] = (a_pi32Src)[1]; \ + (a_pi32Dst)[2] = (a_pi32Src)[2]; \ + (a_pi32Dst)[3] = (a_pi32Src)[3]; \ + (a_pi32Dst)[4] = (a_pi32Src)[4]; \ + (a_pi32Dst)[5] = (a_pi32Src)[5]; \ + (a_pi32Dst)[6] = (a_pi32Src)[6]; \ + (a_pi32Dst)[7] = (a_pi32Src)[7]; \ + (a_pi32Dst)[8] = (a_pi32Src)[8]; \ + (a_pi32Dst)[9] = (a_pi32Src)[9]; \ + (a_pi32Dst)[10] = (a_pi32Src)[10]; \ + (a_pi32Dst)[11] = (a_pi32Src)[11]; \ + } while (0) + +#define INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_iCh) \ + (a_pi32Dst)[a_iCh] = ((a_pi32Last)[a_iCh] * a_i64FactorLast + (a_pi32Src)[a_iCh] * a_i64FactorCur) >> 32 +#define INTERPOLATE_1CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + } while (0) +#define INTERPOLATE_2CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + } while (0) +#define INTERPOLATE_3CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + } while (0) +#define INTERPOLATE_4CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + } while (0) +#define INTERPOLATE_5CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + } while (0) +#define INTERPOLATE_6CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + } while (0) +#define INTERPOLATE_7CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + } while (0) +#define INTERPOLATE_8CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + } while (0) +#define INTERPOLATE_9CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \ + } while (0) +#define INTERPOLATE_10CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \ + } while (0) +#define INTERPOLATE_11CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 10); \ + } while (0) +#define INTERPOLATE_12CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 10); \ + INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 11); \ + } while (0) + +#define AUDIOMIXBUF_RESAMPLE(a_cChannels, a_Suffix) \ + /** @returns Number of destination frames written. */ \ + static DECLCALLBACK(uint32_t) \ + audioMixBufResample##a_cChannels##Ch##a_Suffix(int32_t *pi32Dst, uint32_t cDstFrames, \ + int32_t const *pi32Src, uint32_t cSrcFrames, uint32_t *pcSrcFramesRead, \ + PAUDIOSTREAMRATE pRate) \ + { \ + Log5(("Src: %RU32 L %RU32; Dst: %RU32 L%RU32; uDstInc=%#RX64\n", \ + pRate->offSrc, cSrcFrames, RT_HI_U32(pRate->offDst), cDstFrames, pRate->uDstInc)); \ + int32_t * const pi32DstStart = pi32Dst; \ + int32_t const * const pi32SrcStart = pi32Src; \ + \ + int32_t ai32LastFrame[a_cChannels]; \ + COPY_LAST_FRAME_##a_cChannels##CH(ai32LastFrame, pRate->SrcLast.ai32Samples, a_cChannels); \ + \ + while (cDstFrames > 0 && cSrcFrames > 0) \ + { \ + int32_t const cSrcNeeded = RT_HI_U32(pRate->offDst) - pRate->offSrc + 1; \ + if (cSrcNeeded > 0) \ + { \ + if ((uint32_t)cSrcNeeded + 1 < cSrcFrames) \ + { \ + pRate->offSrc += (uint32_t)cSrcNeeded; \ + cSrcFrames -= (uint32_t)cSrcNeeded; \ + pi32Src += (uint32_t)cSrcNeeded * a_cChannels; \ + COPY_LAST_FRAME_##a_cChannels##CH(ai32LastFrame, &pi32Src[-a_cChannels], a_cChannels); \ + } \ + else \ + { \ + pi32Src += cSrcFrames * a_cChannels; \ + pRate->offSrc += cSrcFrames; \ + COPY_LAST_FRAME_##a_cChannels##CH(pRate->SrcLast.ai32Samples, &pi32Src[-a_cChannels], a_cChannels); \ + *pcSrcFramesRead = (pi32Src - pi32SrcStart) / a_cChannels; \ + return (pi32Dst - pi32DstStart) / a_cChannels; \ + } \ + } \ + \ + /* Interpolate. */ \ + int64_t const offFactorCur = pRate->offDst & UINT32_MAX; \ + int64_t const offFactorLast = (int64_t)_4G - offFactorCur; \ + INTERPOLATE_##a_cChannels##CH(pi32Dst, pi32Src, ai32LastFrame, offFactorCur, offFactorLast, a_cChannels); \ + \ + /* Advance. */ \ + pRate->offDst += pRate->uDstInc; \ + pi32Dst += a_cChannels; \ + cDstFrames -= 1; \ + } \ + \ + COPY_LAST_FRAME_##a_cChannels##CH(pRate->SrcLast.ai32Samples, ai32LastFrame, a_cChannels); \ + *pcSrcFramesRead = (pi32Src - pi32SrcStart) / a_cChannels; \ + return (pi32Dst - pi32DstStart) / a_cChannels; \ + } + +AUDIOMIXBUF_RESAMPLE(1,Generic) +AUDIOMIXBUF_RESAMPLE(2,Generic) +AUDIOMIXBUF_RESAMPLE(3,Generic) +AUDIOMIXBUF_RESAMPLE(4,Generic) +AUDIOMIXBUF_RESAMPLE(5,Generic) +AUDIOMIXBUF_RESAMPLE(6,Generic) +AUDIOMIXBUF_RESAMPLE(7,Generic) +AUDIOMIXBUF_RESAMPLE(8,Generic) +AUDIOMIXBUF_RESAMPLE(9,Generic) +AUDIOMIXBUF_RESAMPLE(10,Generic) +AUDIOMIXBUF_RESAMPLE(11,Generic) +AUDIOMIXBUF_RESAMPLE(12,Generic) - return rc; -} /** - * Clears the entire frame buffer. - * - * @param pMixBuf Mixing buffer to clear. + * Resets the resampling state unconditionally. * + * @param pRate The state to reset. */ -void AudioMixBufClear(PPDMAUDIOMIXBUF pMixBuf) +static void audioMixBufRateResetAlways(PAUDIOSTREAMRATE pRate) { - AssertPtrReturnVoid(pMixBuf); - - if (pMixBuf->cFrames) - RT_BZERO(pMixBuf->pFrames, pMixBuf->cFrames * sizeof(PDMAUDIOFRAME)); + pRate->offDst = 0; + pRate->offSrc = 0; + for (uintptr_t i = 0; i < RT_ELEMENTS(pRate->SrcLast.ai32Samples); i++) + pRate->SrcLast.ai32Samples[0] = 0; } + /** - * Clears (zeroes) the buffer by a certain amount of (used) frames and - * keeps track to eventually assigned children buffers. + * Resets the resampling state. * - * @param pMixBuf Mixing buffer to clear. - * @param cFramesToClear Number of audio frames to clear. + * @param pRate The state to reset. */ -void AudioMixBufFinish(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToClear) +DECLINLINE(void) audioMixBufRateReset(PAUDIOSTREAMRATE pRate) { - AUDMIXBUF_LOG(("cFramesToClear=%RU32\n", cFramesToClear)); - AUDMIXBUF_LOG(("%s: offRead=%RU32, cUsed=%RU32\n", - pMixBuf->pszName, pMixBuf->offRead, pMixBuf->cUsed)); - - AssertStmt(cFramesToClear <= pMixBuf->cFrames, cFramesToClear = pMixBuf->cFrames); - - PPDMAUDIOMIXBUF pIter; - RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node) + if (pRate->offDst == 0) + { /* likely */ } + else { - AUDMIXBUF_LOG(("\t%s: cMixed=%RU32 -> %RU32\n", - pIter->pszName, pIter->cMixed, pIter->cMixed - cFramesToClear)); - - pIter->cMixed -= RT_MIN(pIter->cMixed, cFramesToClear); - /* Note: Do not increment pIter->cUsed here, as this gets done when reading from that buffer using AudioMixBufReadXXX. */ + Assert(!pRate->fNoConversionNeeded); + audioMixBufRateResetAlways(pRate); } +} - uint32_t cClearOff; - uint32_t cClearLen; - /* Clear end of buffer (wrap around). */ - if (cFramesToClear > pMixBuf->offRead) +/** + * Initializes the frame rate converter state. + * + * @returns VBox status code. + * @param pRate The state to initialize. + * @param uSrcHz The source frame rate. + * @param uDstHz The destination frame rate. + * @param cChannels The number of channels in a frame. + */ +DECLINLINE(int) audioMixBufRateInit(PAUDIOSTREAMRATE pRate, uint32_t uSrcHz, uint32_t uDstHz, uint8_t cChannels) +{ + /* + * Do we need to set up frequency conversion? + * + * Some examples to get an idea of what uDstInc holds: + * 44100 to 44100 -> (44100<<32) / 44100 = 0x01'00000000 (4294967296) + * 22050 to 44100 -> (22050<<32) / 44100 = 0x00'80000000 (2147483648) + * 44100 to 22050 -> (44100<<32) / 22050 = 0x02'00000000 (8589934592) + * 44100 to 48000 -> (44100<<32) / 48000 = 0x00'EB333333 (3946001203.2) + * 48000 to 44100 -> (48000<<32) / 44100 = 0x01'16A3B35F (4674794335.7823129251700680272109) + */ + audioMixBufRateResetAlways(pRate); + if (uSrcHz == uDstHz) { - cClearOff = pMixBuf->cFrames - (cFramesToClear - pMixBuf->offRead); - cClearLen = pMixBuf->cFrames - cClearOff; - - AUDMIXBUF_LOG(("Clearing1: %RU32 - %RU32\n", cClearOff, cClearOff + cClearLen)); - - RT_BZERO(pMixBuf->pFrames + cClearOff, cClearLen * sizeof(PDMAUDIOFRAME)); - - Assert(cFramesToClear >= cClearLen); - cFramesToClear -= cClearLen; + pRate->fNoConversionNeeded = true; + pRate->uDstInc = RT_BIT_64(32); + pRate->pfnResample = NULL; } - - /* Clear beginning of buffer. */ - if ( cFramesToClear - && pMixBuf->offRead) + else { - Assert(pMixBuf->offRead >= cFramesToClear); + pRate->fNoConversionNeeded = false; + pRate->uDstInc = ((uint64_t)uSrcHz << 32) / uDstHz; + AssertReturn(uSrcHz != 0, VERR_INVALID_PARAMETER); + switch (cChannels) + { + case 1: pRate->pfnResample = audioMixBufResample1ChGeneric; break; + case 2: pRate->pfnResample = audioMixBufResample2ChGeneric; break; + case 3: pRate->pfnResample = audioMixBufResample3ChGeneric; break; + case 4: pRate->pfnResample = audioMixBufResample4ChGeneric; break; + case 5: pRate->pfnResample = audioMixBufResample5ChGeneric; break; + case 6: pRate->pfnResample = audioMixBufResample6ChGeneric; break; + case 7: pRate->pfnResample = audioMixBufResample7ChGeneric; break; + case 8: pRate->pfnResample = audioMixBufResample8ChGeneric; break; + case 9: pRate->pfnResample = audioMixBufResample9ChGeneric; break; + case 10: pRate->pfnResample = audioMixBufResample10ChGeneric; break; + case 11: pRate->pfnResample = audioMixBufResample11ChGeneric; break; + case 12: pRate->pfnResample = audioMixBufResample12ChGeneric; break; + default: + AssertMsgFailedReturn(("resampling %u changes is not implemented yet\n", cChannels), VERR_OUT_OF_RANGE); + } + } + return VINF_SUCCESS; +} - cClearOff = pMixBuf->offRead - cFramesToClear; - cClearLen = cFramesToClear; - Assert(cClearOff + cClearLen <= pMixBuf->cFrames); +/** + * Initializes a mixing buffer. + * + * @returns VBox status code. + * @param pMixBuf Mixing buffer to initialize. + * @param pszName Name of mixing buffer for easier identification. Optional. + * @param pProps PCM audio properties to use for the mixing buffer. + * @param cFrames Maximum number of audio frames the mixing buffer can hold. + */ +int AudioMixBufInit(PAUDIOMIXBUF pMixBuf, const char *pszName, PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + Assert(PDMAudioPropsAreValid(pProps)); - AUDMIXBUF_LOG(("Clearing2: %RU32 - %RU32\n", cClearOff, cClearOff + cClearLen)); + /* + * Initialize all members, setting the volume to max (0dB). + */ + pMixBuf->cFrames = 0; + pMixBuf->pi32Samples = NULL; + pMixBuf->cChannels = 0; + pMixBuf->cbFrame = 0; + pMixBuf->offRead = 0; + pMixBuf->offWrite = 0; + pMixBuf->cUsed = 0; + pMixBuf->Props = *pProps; + pMixBuf->Volume.fMuted = false; + pMixBuf->Volume.fAllMax = true; + for (uintptr_t i = 0; i < RT_ELEMENTS(pMixBuf->Volume.auChannels); i++) + pMixBuf->Volume.auChannels[i] = AUDIOMIXBUF_VOL_0DB; - RT_BZERO(pMixBuf->pFrames + cClearOff, cClearLen * sizeof(PDMAUDIOFRAME)); + int rc; + uint8_t const cChannels = PDMAudioPropsChannels(pProps); + if (cChannels >= 1 && cChannels <= PDMAUDIO_MAX_CHANNELS) + { + pMixBuf->pszName = RTStrDup(pszName); + if (pMixBuf->pszName) + { + pMixBuf->pi32Samples = (int32_t *)RTMemAllocZ(cFrames * cChannels * sizeof(pMixBuf->pi32Samples[0])); + if (pMixBuf->pi32Samples) + { + pMixBuf->cFrames = cFrames; + pMixBuf->cChannels = cChannels; + pMixBuf->cbFrame = cChannels * sizeof(pMixBuf->pi32Samples[0]); + pMixBuf->uMagic = AUDIOMIXBUF_MAGIC; +#ifdef AUDMIXBUF_LOG_ENABLED + char szTmp[PDMAUDIOPROPSTOSTRING_MAX]; + AUDMIXBUF_LOG(("%s: %s - cFrames=%#x (%d)\n", + pMixBuf->pszName, PDMAudioPropsToString(pProps, szTmp, sizeof(szTmp)), cFrames, cFrames)); +#endif + return VINF_SUCCESS; + } + RTStrFree(pMixBuf->pszName); + pMixBuf->pszName = NULL; + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NO_STR_MEMORY; + } + else + { + LogRelMaxFunc(64, ("cChannels=%d pszName=%s\n", cChannels, pszName)); + rc = VERR_OUT_OF_RANGE; } + pMixBuf->uMagic = AUDIOMIXBUF_MAGIC_DEAD; + return rc; } /** - * Destroys (uninitializes) a mixing buffer. + * Terminates (uninitializes) a mixing buffer. * - * @param pMixBuf Mixing buffer to destroy. + * @param pMixBuf The mixing buffer. Uninitialized mixer buffers will be + * quietly ignored. As will NULL. */ -void AudioMixBufDestroy(PPDMAUDIOMIXBUF pMixBuf) +void AudioMixBufTerm(PAUDIOMIXBUF pMixBuf) { if (!pMixBuf) return; - AudioMixBufUnlink(pMixBuf); + /* Ignore calls for an uninitialized (zeroed) or already destroyed instance. Happens a lot. */ + if ( pMixBuf->uMagic == 0 + || pMixBuf->uMagic == AUDIOMIXBUF_MAGIC_DEAD) + { + Assert(!pMixBuf->pszName); + Assert(!pMixBuf->pi32Samples); + Assert(!pMixBuf->cFrames); + return; + } + + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + pMixBuf->uMagic = ~AUDIOMIXBUF_MAGIC; if (pMixBuf->pszName) { @@ -342,1699 +840,1275 @@ pMixBuf->pszName = NULL; } - if (pMixBuf->pRate) - { - RTMemFree(pMixBuf->pRate); - pMixBuf->pRate = NULL; - } - - if (pMixBuf->pFrames) + if (pMixBuf->pi32Samples) { Assert(pMixBuf->cFrames); - - RTMemFree(pMixBuf->pFrames); - pMixBuf->pFrames = NULL; + RTMemFree(pMixBuf->pi32Samples); + pMixBuf->pi32Samples = NULL; } - pMixBuf->cFrames = 0; + pMixBuf->cFrames = 0; + pMixBuf->cChannels = 0; } + /** - * Returns the size (in audio frames) of free audio buffer space. + * Drops all the frames in the given mixing buffer + * + * This will reset the read and write offsets to zero. * - * @return uint32_t Size (in audio frames) of free audio buffer space. - * @param pMixBuf Mixing buffer to return free size for. + * @param pMixBuf The mixing buffer. Uninitialized mixer buffers will be + * quietly ignored. */ -uint32_t AudioMixBufFree(PPDMAUDIOMIXBUF pMixBuf) +void AudioMixBufDrop(PAUDIOMIXBUF pMixBuf) { - AssertPtrReturn(pMixBuf, 0); + AssertPtrReturnVoid(pMixBuf); - uint32_t cFrames, cFramesFree; - if (pMixBuf->pParent) - { - /* - * As a linked child buffer we want to know how many frames - * already have been consumed by the parent. - */ - cFrames = pMixBuf->pParent->cFrames; - - Assert(pMixBuf->cMixed <= cFrames); - cFramesFree = cFrames - pMixBuf->cMixed; - } - else /* As a parent. */ - { - cFrames = pMixBuf->cFrames; - Assert(cFrames >= pMixBuf->cUsed); - cFramesFree = pMixBuf->cFrames - pMixBuf->cUsed; - } + /* Ignore uninitialized (zeroed) mixer sink buffers (happens with AC'97 during VM construction). */ + if ( pMixBuf->uMagic == 0 + || pMixBuf->uMagic == AUDIOMIXBUF_MAGIC_DEAD) + return; - AUDMIXBUF_LOG(("%s: %RU32 of %RU32\n", pMixBuf->pszName, cFramesFree, cFrames)); - return cFramesFree; + AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName)); + + pMixBuf->offRead = 0; + pMixBuf->offWrite = 0; + pMixBuf->cUsed = 0; } + /** - * Returns the size (in bytes) of free audio buffer space. + * Gets the maximum number of audio frames this buffer can hold. * - * @return uint32_t Size (in bytes) of free audio buffer space. - * @param pMixBuf Mixing buffer to return free size for. + * @returns Number of frames. + * @param pMixBuf The mixing buffer. */ -uint32_t AudioMixBufFreeBytes(PPDMAUDIOMIXBUF pMixBuf) +uint32_t AudioMixBufSize(PCAUDIOMIXBUF pMixBuf) { - return AUDIOMIXBUF_F2B(pMixBuf, AudioMixBufFree(pMixBuf)); + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return pMixBuf->cFrames; } + /** - * Allocates the internal audio frame buffer. + * Gets the maximum number of bytes this buffer can hold. * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to allocate frame buffer for. - * @param cFrames Number of audio frames to allocate. + * @returns Number of bytes. + * @param pMixBuf The mixing buffer. */ -static int audioMixBufAlloc(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFrames) +uint32_t AudioMixBufSizeBytes(PCAUDIOMIXBUF pMixBuf) { - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertReturn(cFrames, VERR_INVALID_PARAMETER); - - AUDMIXBUF_LOG(("%s: cFrames=%RU32\n", pMixBuf->pszName, cFrames)); - - size_t cbFrames = cFrames * sizeof(PDMAUDIOFRAME); - pMixBuf->pFrames = (PPDMAUDIOFRAME)RTMemAllocZ(cbFrames); - if (pMixBuf->pFrames) - { - pMixBuf->cFrames = cFrames; - return VINF_SUCCESS; - } - return VERR_NO_MEMORY; + AssertPtrReturn(pMixBuf, 0); + AssertReturn(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC, 0); + return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cFrames); } -#ifdef AUDIOMIXBUF_DEBUG_MACROS -# define AUDMIXBUF_MACRO_LOG(x) AUDMIXBUF_LOG(x) -#elif defined(VBOX_AUDIO_TESTCASE_VERBOSE) /* Warning: VBOX_AUDIO_TESTCASE_VERBOSE will generate huge logs! */ -# define AUDMIXBUF_MACRO_LOG(x) RTPrintf x -#else -# define AUDMIXBUF_MACRO_LOG(x) do {} while (0) -#endif /** - * Macro for generating the conversion routines from/to different formats. - * Be careful what to pass in/out, as most of the macros are optimized for speed and - * thus don't do any bounds checking! - * - * Note: Currently does not handle any endianness conversion yet! - */ -#define AUDMIXBUF_CONVERT(_aName, _aType, _aMin, _aMax, _aSigned, _aShift) \ - /* Clips a specific output value to a single sample value. */ \ - DECLCALLBACK(int64_t) audioMixBufClipFrom##_aName(_aType aVal) \ - { \ - /* left shifting of signed values is not defined, therefore the intermediate uint64_t cast */ \ - if (_aSigned) \ - return (int64_t) (((uint64_t) ((int64_t) aVal )) << (32 - _aShift)); \ - return (int64_t) (((uint64_t) ((int64_t) aVal - ((_aMax >> 1) + 1))) << (32 - _aShift)); \ - } \ - \ - /* Clips a single sample value to a specific output value. */ \ - DECLCALLBACK(_aType) audioMixBufClipTo##_aName(int64_t iVal) \ - { \ - if (iVal >= 0x7fffffff) \ - return _aMax; \ - if (iVal < -INT64_C(0x80000000)) \ - return _aMin; \ - \ - if (_aSigned) \ - return (_aType) (iVal >> (32 - _aShift)); \ - return ((_aType) ((iVal >> (32 - _aShift)) + ((_aMax >> 1) + 1))); \ - } \ - \ - DECLCALLBACK(uint32_t) audioMixBufConvFrom##_aName##Stereo(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, \ - PCPDMAUDMIXBUFCONVOPTS pOpts) \ - { \ - _aType const *pSrc = (_aType const *)pvSrc; \ - uint32_t cFrames = RT_MIN(pOpts->cFrames, cbSrc / sizeof(_aType)); \ - AUDMIXBUF_MACRO_LOG(("cFrames=%RU32, BpS=%zu, lVol=%RU32, rVol=%RU32\n", \ - pOpts->cFrames, sizeof(_aType), pOpts->From.Volume.uLeft, pOpts->From.Volume.uRight)); \ - for (uint32_t i = 0; i < cFrames; i++) \ - { \ - paDst->i64LSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc++), pOpts->From.Volume.uLeft ) >> AUDIOMIXBUF_VOL_SHIFT; \ - paDst->i64RSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc++), pOpts->From.Volume.uRight) >> AUDIOMIXBUF_VOL_SHIFT; \ - paDst++; \ - } \ - \ - return cFrames; \ - } \ - \ - DECLCALLBACK(uint32_t) audioMixBufConvFrom##_aName##Mono(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, \ - PCPDMAUDMIXBUFCONVOPTS pOpts) \ - { \ - _aType const *pSrc = (_aType const *)pvSrc; \ - const uint32_t cFrames = RT_MIN(pOpts->cFrames, cbSrc / sizeof(_aType)); \ - AUDMIXBUF_MACRO_LOG(("cFrames=%RU32, BpS=%zu, lVol=%RU32, rVol=%RU32\n", \ - cFrames, sizeof(_aType), pOpts->From.Volume.uLeft, pOpts->From.Volume.uRight)); \ - for (uint32_t i = 0; i < cFrames; i++) \ - { \ - paDst->i64LSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc), pOpts->From.Volume.uLeft) >> AUDIOMIXBUF_VOL_SHIFT; \ - paDst->i64RSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc), pOpts->From.Volume.uRight) >> AUDIOMIXBUF_VOL_SHIFT; \ - pSrc++; \ - paDst++; \ - } \ - \ - return cFrames; \ - } \ - \ - DECLCALLBACK(void) audioMixBufConvTo##_aName##Stereo(void *pvDst, PCPDMAUDIOFRAME paSrc, PCPDMAUDMIXBUFCONVOPTS pOpts) \ - { \ - PCPDMAUDIOFRAME pSrc = paSrc; \ - _aType *pDst = (_aType *)pvDst; \ - _aType l, r; \ - uint32_t cFrames = pOpts->cFrames; \ - while (cFrames--) \ - { \ - AUDMIXBUF_MACRO_LOG(("%p: l=%RI64, r=%RI64\n", pSrc, pSrc->i64LSample, pSrc->i64RSample)); \ - l = audioMixBufClipTo##_aName(pSrc->i64LSample); \ - r = audioMixBufClipTo##_aName(pSrc->i64RSample); \ - AUDMIXBUF_MACRO_LOG(("\t-> l=%RI16, r=%RI16\n", l, r)); \ - *pDst++ = l; \ - *pDst++ = r; \ - pSrc++; \ - } \ - } \ - \ - DECLCALLBACK(void) audioMixBufConvTo##_aName##Mono(void *pvDst, PCPDMAUDIOFRAME paSrc, PCPDMAUDMIXBUFCONVOPTS pOpts) \ - { \ - PCPDMAUDIOFRAME pSrc = paSrc; \ - _aType *pDst = (_aType *)pvDst; \ - uint32_t cFrames = pOpts->cFrames; \ - while (cFrames--) \ - { \ - *pDst++ = audioMixBufClipTo##_aName((pSrc->i64LSample + pSrc->i64RSample) / 2); \ - pSrc++; \ - } \ - } - -/* audioMixBufConvXXXS8: 8 bit, signed. */ -AUDMIXBUF_CONVERT(S8 /* Name */, int8_t, INT8_MIN /* Min */, INT8_MAX /* Max */, true /* fSigned */, 8 /* cShift */) -/* audioMixBufConvXXXU8: 8 bit, unsigned. */ -AUDMIXBUF_CONVERT(U8 /* Name */, uint8_t, 0 /* Min */, UINT8_MAX /* Max */, false /* fSigned */, 8 /* cShift */) -/* audioMixBufConvXXXS16: 16 bit, signed. */ -AUDMIXBUF_CONVERT(S16 /* Name */, int16_t, INT16_MIN /* Min */, INT16_MAX /* Max */, true /* fSigned */, 16 /* cShift */) -/* audioMixBufConvXXXU16: 16 bit, unsigned. */ -AUDMIXBUF_CONVERT(U16 /* Name */, uint16_t, 0 /* Min */, UINT16_MAX /* Max */, false /* fSigned */, 16 /* cShift */) -/* audioMixBufConvXXXS32: 32 bit, signed. */ -AUDMIXBUF_CONVERT(S32 /* Name */, int32_t, INT32_MIN /* Min */, INT32_MAX /* Max */, true /* fSigned */, 32 /* cShift */) -/* audioMixBufConvXXXU32: 32 bit, unsigned. */ -AUDMIXBUF_CONVERT(U32 /* Name */, uint32_t, 0 /* Min */, UINT32_MAX /* Max */, false /* fSigned */, 32 /* cShift */) - -#undef AUDMIXBUF_CONVERT - -#define AUDMIXBUF_MIXOP(_aName, _aOp) \ - static void audioMixBufOp##_aName(PPDMAUDIOFRAME paDst, uint32_t cDstFrames, \ - PPDMAUDIOFRAME paSrc, uint32_t cSrcFrames, \ - PPDMAUDIOSTREAMRATE pRate, \ - uint32_t *pcDstWritten, uint32_t *pcSrcRead) \ - { \ - AUDMIXBUF_MACRO_LOG(("cSrcFrames=%RU32, cDstFrames=%RU32\n", cSrcFrames, cDstFrames)); \ - AUDMIXBUF_MACRO_LOG(("Rate: offSrc=%RU32, offDst=%RU32, uDstInc=%RU32\n", \ - pRate->offSrc, \ - (uint32_t)(pRate->offDst >> 32), (uint32_t)(pRate->uDstInc >> 32))); \ - \ - if (pRate->uDstInc == (UINT64_C(1) + UINT32_MAX)) /* No conversion needed? */ \ - { \ - uint32_t cFrames = RT_MIN(cSrcFrames, cDstFrames); \ - AUDMIXBUF_MACRO_LOG(("cFrames=%RU32\n", cFrames)); \ - for (uint32_t i = 0; i < cFrames; i++) \ - { \ - paDst[i].i64LSample _aOp paSrc[i].i64LSample; \ - paDst[i].i64RSample _aOp paSrc[i].i64RSample; \ - } \ - \ - if (pcDstWritten) \ - *pcDstWritten = cFrames; \ - if (pcSrcRead) \ - *pcSrcRead = cFrames; \ - return; \ - } \ - \ - PPDMAUDIOFRAME paSrcStart = paSrc; \ - PPDMAUDIOFRAME paSrcEnd = paSrc + cSrcFrames; \ - PPDMAUDIOFRAME paDstStart = paDst; \ - PPDMAUDIOFRAME paDstEnd = paDst + cDstFrames; \ - PDMAUDIOFRAME frameCur = { 0 }; \ - PDMAUDIOFRAME frameOut; \ - PDMAUDIOFRAME frameLast = pRate->SrcFrameLast; \ - \ - while (paDst < paDstEnd) \ - { \ - Assert(paSrc <= paSrcEnd); \ - Assert(paDst <= paDstEnd); \ - if (paSrc >= paSrcEnd) \ - break; \ - \ - while (pRate->offSrc <= (pRate->offDst >> 32)) \ - { \ - Assert(paSrc <= paSrcEnd); \ - frameLast = *paSrc++; \ - pRate->offSrc++; \ - if (paSrc == paSrcEnd) \ - break; \ - } \ - \ - Assert(paSrc <= paSrcEnd); \ - if (paSrc == paSrcEnd) \ - break; \ - \ - frameCur = *paSrc; \ - \ - /* Interpolate. */ \ - int64_t iDstOffInt = pRate->offDst & UINT32_MAX; \ - \ - frameOut.i64LSample = (frameLast.i64LSample * ((int64_t) (INT64_C(1) << 32) - iDstOffInt) + frameCur.i64LSample * iDstOffInt) >> 32; \ - frameOut.i64RSample = (frameLast.i64RSample * ((int64_t) (INT64_C(1) << 32) - iDstOffInt) + frameCur.i64RSample * iDstOffInt) >> 32; \ - \ - paDst->i64LSample _aOp frameOut.i64LSample; \ - paDst->i64RSample _aOp frameOut.i64RSample; \ - \ - AUDMIXBUF_MACRO_LOG(("\tiDstOffInt=%RI64, l=%RI64, r=%RI64 (cur l=%RI64, r=%RI64)\n", \ - iDstOffInt, \ - paDst->i64LSample >> 32, paDst->i64RSample >> 32, \ - frameCur.i64LSample >> 32, frameCur.i64RSample >> 32)); \ - \ - paDst++; \ - pRate->offDst += pRate->uDstInc; \ - \ - AUDMIXBUF_MACRO_LOG(("\t\tpRate->offDst=%RU32\n", pRate->offDst >> 32)); \ - \ - } \ - \ - AUDMIXBUF_MACRO_LOG(("%zu source frames -> %zu dest frames\n", paSrc - paSrcStart, paDst - paDstStart)); \ - \ - pRate->SrcFrameLast = frameLast; \ - \ - AUDMIXBUF_MACRO_LOG(("pRate->srcSampleLast l=%RI64, r=%RI64\n", \ - pRate->SrcFrameLast.i64LSample, pRate->SrcFrameLast.i64RSample)); \ - \ - if (pcDstWritten) \ - *pcDstWritten = paDst - paDstStart; \ - if (pcSrcRead) \ - *pcSrcRead = paSrc - paSrcStart; \ - } - -/* audioMixBufOpAssign: Assigns values from source buffer to destination bufffer, overwriting the destination. */ -AUDMIXBUF_MIXOP(Assign /* Name */, = /* Operation */) -#if 0 /* unused */ -/* audioMixBufOpBlend: Blends together the values from both, the source and the destination buffer. */ -AUDMIXBUF_MIXOP(Blend /* Name */, += /* Operation */) -#endif - -#undef AUDMIXBUF_MIXOP -#undef AUDMIXBUF_MACRO_LOG - -/** Dummy conversion used when the source is muted. */ -static DECLCALLBACK(uint32_t) -audioMixBufConvFromSilence(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, PCPDMAUDMIXBUFCONVOPTS pOpts) + * Worker for AudioMixBufUsed and AudioMixBufUsedBytes. + */ +DECLINLINE(uint32_t) audioMixBufUsedInternal(PCAUDIOMIXBUF pMixBuf) { - RT_NOREF(cbSrc, pvSrc); - - /* Internally zero always corresponds to silence. */ - RT_BZERO(paDst, pOpts->cFrames * sizeof(paDst[0])); - return pOpts->cFrames; + uint32_t const cFrames = pMixBuf->cFrames; + uint32_t cUsed = pMixBuf->cUsed; + AssertStmt(cUsed <= cFrames, cUsed = cFrames); + return cUsed; } + /** - * Looks up the matching conversion (macro) routine for converting - * audio frames from a source format. - * - ** @todo Speed up the lookup by binding it to the actual stream state. + * Get the number of used (readable) frames in the buffer. * - * @return PAUDMIXBUF_FN_CONVFROM Function pointer to conversion macro if found, NULL if not supported. - * @param enmFmt Audio format to lookup conversion macro for. + * @returns Number of frames. + * @param pMixBuf The mixing buffer. */ -static PFNPDMAUDIOMIXBUFCONVFROM audioMixBufConvFromLookup(PDMAUDIOMIXBUFFMT enmFmt) +uint32_t AudioMixBufUsed(PCAUDIOMIXBUF pMixBuf) { - if (AUDMIXBUF_FMT_SIGNED(enmFmt)) - { - if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2) - { - switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) - { - case 8: return audioMixBufConvFromS8Stereo; - case 16: return audioMixBufConvFromS16Stereo; - case 32: return audioMixBufConvFromS32Stereo; - default: return NULL; - } - } - else - { - switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) - { - case 8: return audioMixBufConvFromS8Mono; - case 16: return audioMixBufConvFromS16Mono; - case 32: return audioMixBufConvFromS32Mono; - default: return NULL; - } - } - } - else /* Unsigned */ - { - if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2) - { - switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) - { - case 8: return audioMixBufConvFromU8Stereo; - case 16: return audioMixBufConvFromU16Stereo; - case 32: return audioMixBufConvFromU32Stereo; - default: return NULL; - } - } - else - { - switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) - { - case 8: return audioMixBufConvFromU8Mono; - case 16: return audioMixBufConvFromU16Mono; - case 32: return audioMixBufConvFromU32Mono; - default: return NULL; - } - } - } - /* not reached */ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return audioMixBufUsedInternal(pMixBuf); } + /** - * Looks up the matching conversion (macro) routine for converting - * audio frames to a destination format. + * Get the number of (readable) bytes in the buffer. * - ** @todo Speed up the lookup by binding it to the actual stream state. - * - * @return PAUDMIXBUF_FN_CONVTO Function pointer to conversion macro if found, NULL if not supported. - * @param enmFmt Audio format to lookup conversion macro for. + * @returns Number of bytes. + * @param pMixBuf The mixing buffer. */ -static PFNPDMAUDIOMIXBUFCONVTO audioMixBufConvToLookup(PDMAUDIOMIXBUFFMT enmFmt) +uint32_t AudioMixBufUsedBytes(PCAUDIOMIXBUF pMixBuf) { - if (AUDMIXBUF_FMT_SIGNED(enmFmt)) - { - if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2) - { - switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) - { - case 8: return audioMixBufConvToS8Stereo; - case 16: return audioMixBufConvToS16Stereo; - case 32: return audioMixBufConvToS32Stereo; - default: return NULL; - } - } - else - { - switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) - { - case 8: return audioMixBufConvToS8Mono; - case 16: return audioMixBufConvToS16Mono; - case 32: return audioMixBufConvToS32Mono; - default: return NULL; - } - } - } - else /* Unsigned */ - { - if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2) - { - switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) - { - case 8: return audioMixBufConvToU8Stereo; - case 16: return audioMixBufConvToU16Stereo; - case 32: return audioMixBufConvToU32Stereo; - default: return NULL; - } - } - else - { - switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) - { - case 8: return audioMixBufConvToU8Mono; - case 16: return audioMixBufConvToU16Mono; - case 32: return audioMixBufConvToU32Mono; - default: return NULL; - } - } - } - /* not reached */ + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return AUDIOMIXBUF_F2B(pMixBuf, audioMixBufUsedInternal(pMixBuf)); } + /** - * Converts a PDM audio volume to an internal mixing buffer volume. - * - * @returns IPRT status code. - * @param pVolDst Where to store the converted mixing buffer volume. - * @param pVolSrc Volume to convert. + * Worker for AudioMixBufFree and AudioMixBufFreeBytes. */ -static int audioMixBufConvVol(PPDMAUDMIXBUFVOL pVolDst, PPDMAUDIOVOLUME pVolSrc) +DECLINLINE(uint32_t) audioMixBufFreeInternal(PCAUDIOMIXBUF pMixBuf) { - if (!pVolSrc->fMuted) /* Only change/convert the volume value if we're not muted. */ - { - uint8_t uVolL = pVolSrc->uLeft & 0xFF; - uint8_t uVolR = pVolSrc->uRight & 0xFF; - - /** @todo Ensure that the input is in the correct range/initialized! */ - pVolDst->uLeft = s_aVolumeConv[uVolL] * (AUDIOMIXBUF_VOL_0DB >> 16); - pVolDst->uRight = s_aVolumeConv[uVolR] * (AUDIOMIXBUF_VOL_0DB >> 16); - } - - pVolDst->fMuted = pVolSrc->fMuted; + uint32_t const cFrames = pMixBuf->cFrames; + uint32_t cUsed = pMixBuf->cUsed; + AssertStmt(cUsed <= cFrames, cUsed = cFrames); + uint32_t const cFramesFree = cFrames - cUsed; - return VINF_SUCCESS; + AUDMIXBUF_LOG(("%s: %RU32 of %RU32\n", pMixBuf->pszName, cFramesFree, cFrames)); + return cFramesFree; } + /** - * Initializes a mixing buffer. + * Gets the free buffer space in frames. * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to initialize. - * @param pszName Name of mixing buffer for easier identification. Optional. - * @param pProps PCM audio properties to use for the mixing buffer. - * @param cFrames Maximum number of audio frames the mixing buffer can hold. + * @return Number of frames. + * @param pMixBuf The mixing buffer. */ -int AudioMixBufInit(PPDMAUDIOMIXBUF pMixBuf, const char *pszName, PPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +uint32_t AudioMixBufFree(PCAUDIOMIXBUF pMixBuf) { - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertPtrReturn(pszName, VERR_INVALID_POINTER); - AssertPtrReturn(pProps, VERR_INVALID_POINTER); - - pMixBuf->pParent = NULL; - - RTListInit(&pMixBuf->lstChildren); - pMixBuf->cChildren = 0; - - pMixBuf->pFrames = NULL; - pMixBuf->cFrames = 0; - - pMixBuf->offRead = 0; - pMixBuf->offWrite = 0; - pMixBuf->cMixed = 0; - pMixBuf->cUsed = 0; - - /* Set initial volume to max. */ - pMixBuf->Volume.fMuted = false; - pMixBuf->Volume.uLeft = AUDIOMIXBUF_VOL_0DB; - pMixBuf->Volume.uRight = AUDIOMIXBUF_VOL_0DB; - - /* Prevent division by zero. - * Do a 1:1 conversion according to AUDIOMIXBUF_S2B_RATIO. */ - pMixBuf->iFreqRatio = 1 << 20; - - pMixBuf->pRate = NULL; - - pMixBuf->uAudioFmt = AUDMIXBUF_AUDIO_FMT_MAKE(pProps->uHz, - pProps->cChannels, - pProps->cbSample * 8 /* Bit */, - pProps->fSigned); - - pMixBuf->pfnConvFrom = audioMixBufConvFromLookup(pMixBuf->uAudioFmt); - pMixBuf->pfnConvTo = audioMixBufConvToLookup(pMixBuf->uAudioFmt); - - pMixBuf->cShift = pProps->cShift; - pMixBuf->pszName = RTStrDup(pszName); - if (!pMixBuf->pszName) - return VERR_NO_MEMORY; - - AUDMIXBUF_LOG(("%s: uHz=%RU32, cChan=%RU8, cBits=%RU8, fSigned=%RTbool\n", - pMixBuf->pszName, - AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->uAudioFmt), - AUDMIXBUF_FMT_CHANNELS(pMixBuf->uAudioFmt), - AUDMIXBUF_FMT_BITS_PER_SAMPLE(pMixBuf->uAudioFmt), - RT_BOOL(AUDMIXBUF_FMT_SIGNED(pMixBuf->uAudioFmt)))); - - return audioMixBufAlloc(pMixBuf, cFrames); + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return audioMixBufFreeInternal(pMixBuf); } + /** - * Returns @c true if there are any audio frames available for processing, - * @c false if not. + * Gets the free buffer space in bytes. * - * @return bool @c true if there are any audio frames available for processing, @c false if not. - * @param pMixBuf Mixing buffer to return value for. + * @return Number of bytes. + * @param pMixBuf The mixing buffer. */ -bool AudioMixBufIsEmpty(PPDMAUDIOMIXBUF pMixBuf) +uint32_t AudioMixBufFreeBytes(PCAUDIOMIXBUF pMixBuf) { - AssertPtrReturn(pMixBuf, true); - - if (pMixBuf->pParent) - return (pMixBuf->cMixed == 0); - return (pMixBuf->cUsed == 0); + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return AUDIOMIXBUF_F2B(pMixBuf, audioMixBufFreeInternal(pMixBuf)); } + /** - * Calculates the frequency (sample rate) ratio of mixing buffer A in relation to mixing buffer B. + * Checks if the buffer is empty. * - * @returns Calculated frequency ratio. - * @param pMixBufA First mixing buffer. - * @param pMixBufB Second mixing buffer. + * @retval true if empty buffer. + * @retval false if not empty and there are frames to be processed. + * @param pMixBuf The mixing buffer. */ -static int64_t audioMixBufCalcFreqRatio(PPDMAUDIOMIXBUF pMixBufA, PPDMAUDIOMIXBUF pMixBufB) +bool AudioMixBufIsEmpty(PCAUDIOMIXBUF pMixBuf) { - int64_t iRatio = ((int64_t)AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBufA->uAudioFmt) << 32) - / AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBufB->uAudioFmt); - - if (iRatio == 0) /* Catch division by zero. */ - iRatio = 1 << 20; /* Do a 1:1 conversion instead. */ - - return iRatio; + AssertPtrReturn(pMixBuf, true); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return pMixBuf->cUsed == 0; } + /** - * Links an audio mixing buffer to a parent mixing buffer. A parent mixing - * buffer can have multiple children mixing buffers [1:N], whereas a child only can - * have one parent mixing buffer [N:1]. + * Get the current read position. * - * The mixing direction always goes from the child/children buffer(s) to the - * parent buffer. + * This is for the testcase. * - * For guest audio output the host backend owns the parent mixing buffer, the - * device emulation owns the child/children. - * - * The audio format of each mixing buffer can vary; the internal mixing code - * then will automatically do the (needed) conversion. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to link parent to. - * @param pParent Parent mixing buffer to use for linking. - * - * @remark Circular linking is not allowed. + * @returns Frame number. + * @param pMixBuf The mixing buffer. */ -int AudioMixBufLinkTo(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOMIXBUF pParent) +uint32_t AudioMixBufReadPos(PCAUDIOMIXBUF pMixBuf) { - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertPtrReturn(pParent, VERR_INVALID_POINTER); - - AssertMsgReturn(AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->uAudioFmt), - ("Parent frame frequency (Hz) not set\n"), VERR_INVALID_PARAMETER); - AssertMsgReturn(AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->uAudioFmt), - ("Buffer sample frequency (Hz) not set\n"), VERR_INVALID_PARAMETER); - AssertMsgReturn(pMixBuf != pParent, - ("Circular linking not allowed\n"), VERR_INVALID_PARAMETER); - - if (pMixBuf->pParent) /* Already linked? */ - { - AUDMIXBUF_LOG(("%s: Already linked to parent '%s'\n", - pMixBuf->pszName, pMixBuf->pParent->pszName)); - return VERR_ACCESS_DENIED; - } - - RTListAppend(&pParent->lstChildren, &pMixBuf->Node); - pParent->cChildren++; - - /* Set the parent. */ - pMixBuf->pParent = pParent; - - /* Calculate the frequency ratios. */ - pMixBuf->iFreqRatio = audioMixBufCalcFreqRatio(pParent, pMixBuf); - - int rc = VINF_SUCCESS; -#if 0 - uint32_t cFrames = (uint32_t)RT_MIN( ((uint64_t)pParent->cFrames << 32) - / pMixBuf->iFreqRatio, _64K /* 64K frames max. */); - if (!cFrames) - cFrames = pParent->cFrames; - - int rc = VINF_SUCCESS; - - if (cFrames != pMixBuf->cFrames) - { - AUDMIXBUF_LOG(("%s: Reallocating frames %RU32 -> %RU32\n", - pMixBuf->pszName, pMixBuf->cFrames, cFrames)); - - uint32_t cbSamples = cFrames * sizeof(PDMAUDIOSAMPLE); - Assert(cbSamples); - pMixBuf->pSamples = (PPDMAUDIOSAMPLE)RTMemRealloc(pMixBuf->pSamples, cbSamples); - if (!pMixBuf->pSamples) - rc = VERR_NO_MEMORY; - - if (RT_SUCCESS(rc)) - { - pMixBuf->cFrames = cFrames; - - /* Make sure to zero the reallocated buffer so that it can be - * used properly when blending with another buffer later. */ - RT_BZERO(pMixBuf->pSamples, cbSamples); - } - } -#endif - - if (RT_SUCCESS(rc)) - { - if (!pMixBuf->pRate) - { - /* Create rate conversion. */ - pMixBuf->pRate = (PPDMAUDIOSTREAMRATE)RTMemAllocZ(sizeof(PDMAUDIOSTREAMRATE)); - if (!pMixBuf->pRate) - return VERR_NO_MEMORY; - } - else - RT_BZERO(pMixBuf->pRate, sizeof(PDMAUDIOSTREAMRATE)); - - pMixBuf->pRate->uDstInc = ((uint64_t)AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->uAudioFmt) << 32) - / AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->uAudioFmt); - - AUDMIXBUF_LOG(("uThisHz=%RU32, uParentHz=%RU32, iFreqRatio=0x%RX64 (%RI64), uRateInc=0x%RX64 (%RU64), cFrames=%RU32 (%RU32 parent)\n", - AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->uAudioFmt), - AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->uAudioFmt), - pMixBuf->iFreqRatio, pMixBuf->iFreqRatio, - pMixBuf->pRate->uDstInc, pMixBuf->pRate->uDstInc, - pMixBuf->cFrames, - pParent->cFrames)); - AUDMIXBUF_LOG(("%s (%RU32Hz) -> %s (%RU32Hz)\n", - pMixBuf->pszName, AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->uAudioFmt), - pMixBuf->pParent->pszName, AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->uAudioFmt))); - } - - return rc; + AssertPtrReturn(pMixBuf, 0); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return pMixBuf->offRead; } + /** - * Returns number of available live frames, that is, frames that - * have been written into the mixing buffer but not have been processed yet. + * Gets the current write position. * - * For a parent buffer, this simply returns the currently used number of frames - * in the buffer. + * This is for the testcase. * - * For a child buffer, this returns the number of frames which have been mixed - * to the parent and were not processed by the parent yet. - * - * @return uint32_t Number of live frames available. - * @param pMixBuf Mixing buffer to return value for. + * @returns Frame number. + * @param pMixBuf The mixing buffer. */ -uint32_t AudioMixBufLive(PPDMAUDIOMIXBUF pMixBuf) +uint32_t AudioMixBufWritePos(PCAUDIOMIXBUF pMixBuf) { AssertPtrReturn(pMixBuf, 0); - -#ifdef RT_STRICT - uint32_t cFrames; -#endif - uint32_t cAvail; - if (pMixBuf->pParent) /* Is this a child buffer? */ - { -#ifdef RT_STRICT - /* Use the frame count from the parent, as - * pMixBuf->cMixed specifies the frame count - * in parent frames. */ - cFrames = pMixBuf->pParent->cFrames; -#endif - cAvail = pMixBuf->cMixed; - } - else - { -#ifdef RT_STRICT - cFrames = pMixBuf->cFrames; -#endif - cAvail = pMixBuf->cUsed; - } - - Assert(cAvail <= cFrames); - return cAvail; + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + return pMixBuf->offWrite; } + /** - * Mixes audio frames from a source mixing buffer to a destination mixing buffer. + * Creates a mapping between desination channels and source source channels. * - * @return IPRT status code. - * VERR_BUFFER_UNDERFLOW if the source did not have enough audio data. - * VERR_BUFFER_OVERFLOW if the destination did not have enough space to store the converted source audio data. - * - * @param pDst Destination mixing buffer. - * @param pSrc Source mixing buffer. - * @param cSrcOff Offset of source audio frames to mix. - * @param cSrcFrames Number of source audio frames to mix. - * @param pcSrcMixed Number of source audio frames successfully mixed. Optional. - */ -static int audioMixBufMixTo(PPDMAUDIOMIXBUF pDst, PPDMAUDIOMIXBUF pSrc, uint32_t cSrcOff, uint32_t cSrcFrames, - uint32_t *pcSrcMixed) -{ - AssertPtrReturn(pDst, VERR_INVALID_POINTER); - AssertPtrReturn(pSrc, VERR_INVALID_POINTER); - /* pcSrcMixed is optional. */ - - AssertMsgReturn(pDst == pSrc->pParent, ("Source buffer '%s' is not a child of destination '%s'\n", - pSrc->pszName, pDst->pszName), VERR_INVALID_PARAMETER); - uint32_t cReadTotal = 0; - uint32_t cWrittenTotal = 0; - - Assert(pSrc->cMixed <= pDst->cFrames); - - Assert(pSrc->cUsed >= pDst->cMixed); - Assert(pDst->cUsed <= pDst->cFrames); - - uint32_t offSrcRead = cSrcOff; - - uint32_t offDstWrite = pDst->offWrite; - uint32_t cDstMixed = pSrc->cMixed; - - uint32_t cSrcAvail = RT_MIN(cSrcFrames, pSrc->cUsed); - uint32_t cDstAvail = pDst->cFrames - pDst->cUsed; /** @todo Use pDst->cMixed later? */ - - AUDMIXBUF_LOG(("%s (%RU32 available) -> %s (%RU32 available)\n", - pSrc->pszName, cSrcAvail, pDst->pszName, cDstAvail)); -#ifdef DEBUG - audioMixBufDbgPrintInternal(pDst, __FUNCTION__); -#endif - - if (!cSrcAvail) - return VERR_BUFFER_UNDERFLOW; - - if (!cDstAvail) - return VERR_BUFFER_OVERFLOW; - - uint32_t cSrcToRead = 0; - uint32_t cSrcRead; - - uint32_t cDstToWrite; - uint32_t cDstWritten; - - int rc = VINF_SUCCESS; - - while (cSrcAvail && cDstAvail) + * @param paidxChannelMap Where to store the mapping. Indexed by + * destination channel. Entry is either source + * channel index or -1 for zero and -2 for silence. + * @param pSrcProps The source properties. + * @param pDstProps The desination properties. + */ +static void audioMixBufInitChannelMap(int8_t paidxChannelMap[PDMAUDIO_MAX_CHANNELS], + PCPDMAUDIOPCMPROPS pSrcProps, PCPDMAUDIOPCMPROPS pDstProps) +{ + uintptr_t const cDstChannels = PDMAudioPropsChannels(pDstProps); + uintptr_t const cSrcChannels = PDMAudioPropsChannels(pSrcProps); + uintptr_t idxDst; + for (idxDst = 0; idxDst < cDstChannels; idxDst++) { - cSrcToRead = RT_MIN(cSrcAvail, pSrc->cFrames - offSrcRead); - cDstToWrite = RT_MIN(cDstAvail, pDst->cFrames - offDstWrite); - - AUDMIXBUF_LOG(("\tSource: %RU32 @ %RU32 -> reading %RU32\n", cSrcAvail, offSrcRead, cSrcToRead)); - AUDMIXBUF_LOG(("\tDest : %RU32 @ %RU32 -> writing %RU32\n", cDstAvail, offDstWrite, cDstToWrite)); - - if ( !cDstToWrite - || !cSrcToRead) + uint8_t const idDstCh = pDstProps->aidChannels[idxDst]; + if (idDstCh >= PDMAUDIOCHANNELID_FRONT_LEFT && idDstCh < PDMAUDIOCHANNELID_END) { - break; + uintptr_t idxSrc; + for (idxSrc = 0; idxSrc < cSrcChannels; idxSrc++) + if (idDstCh == pSrcProps->aidChannels[idxSrc]) + { + paidxChannelMap[idxDst] = idxSrc; + break; + } + if (idxSrc >= cSrcChannels) + { + /** @todo deal with mono. */ + paidxChannelMap[idxDst] = -2; + } } - - cDstWritten = cSrcRead = 0; - - Assert(offSrcRead < pSrc->cFrames); - Assert(offSrcRead + cSrcToRead <= pSrc->cFrames); - - Assert(offDstWrite < pDst->cFrames); - Assert(offDstWrite + cDstToWrite <= pDst->cFrames); - - audioMixBufOpAssign(pDst->pFrames + offDstWrite, cDstToWrite, - pSrc->pFrames + offSrcRead, cSrcToRead, - pSrc->pRate, &cDstWritten, &cSrcRead); - - cReadTotal += cSrcRead; - cWrittenTotal += cDstWritten; - - offSrcRead = (offSrcRead + cSrcRead) % pSrc->cFrames; - offDstWrite = (offDstWrite + cDstWritten) % pDst->cFrames; - - cDstMixed += cDstWritten; - - Assert(cSrcAvail >= cSrcRead); - cSrcAvail -= cSrcRead; - - Assert(cDstAvail >= cDstWritten); - cDstAvail -= cDstWritten; - - AUDMIXBUF_LOG(("\t%RU32 read (%RU32 left @ %RU32), %RU32 written (%RU32 left @ %RU32)\n", - cSrcRead, cSrcAvail, offSrcRead, - cDstWritten, cDstAvail, offDstWrite)); - } - - pSrc->offRead = offSrcRead; - Assert(pSrc->cUsed >= cReadTotal); - pSrc->cUsed -= RT_MIN(pSrc->cUsed, cReadTotal); - - /* Note: Always count in parent frames, as the rate can differ! */ - pSrc->cMixed = RT_MIN(cDstMixed, pDst->cFrames); - - pDst->offWrite = offDstWrite; - Assert(pDst->offWrite <= pDst->cFrames); - Assert((pDst->cUsed + cWrittenTotal) <= pDst->cFrames); - pDst->cUsed += cWrittenTotal; - - /* If there are more used frames than fitting in the destination buffer, - * adjust the values accordingly. - * - * This can happen if this routine has been called too often without - * actually processing the destination buffer in between. */ - if (pDst->cUsed > pDst->cFrames) - { - LogFunc(("%s: Warning: Destination buffer used %RU32 / %RU32 frames\n", pDst->pszName, pDst->cUsed, pDst->cFrames)); - pDst->offWrite = 0; - pDst->cUsed = pDst->cFrames; - - rc = VERR_BUFFER_OVERFLOW; - } - -#ifdef DEBUG - audioMixBufDbgValidate(pSrc); - audioMixBufDbgValidate(pDst); - - Assert(pSrc->cMixed <= pDst->cFrames); -#endif - -#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA - uint32_t offRead = pDst->offRead; - - uint32_t cLeft = cWrittenTotal; - while (cLeft) - { - uint8_t auBuf[256]; - RT_ZERO(auBuf); - - Assert(sizeof(auBuf) >= 4); - Assert(sizeof(auBuf) % 4 == 0); - - uint32_t cToRead = RT_MIN(AUDIOMIXBUF_B2F(pDst, sizeof(auBuf)), RT_MIN(cLeft, pDst->cFrames - offRead)); - Assert(cToRead <= pDst->cUsed); - - PDMAUDMIXBUFCONVOPTS convOpts; - RT_ZERO(convOpts); - convOpts.cFrames = cToRead; - - pDst->pfnConvTo(auBuf, pDst->pFrames + offRead, &convOpts); - - RTFILE fh; - int rc2 = RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_mixto.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - if (RT_SUCCESS(rc2)) + else if (idDstCh == PDMAUDIOCHANNELID_UNKNOWN) { - RTFileWrite(fh, auBuf, AUDIOMIXBUF_F2B(pDst, cToRead), NULL); - RTFileClose(fh); + /** @todo What to do here? Pick unused source channels in order? */ + paidxChannelMap[idxDst] = -2; + } + else + { + AssertMsg(idDstCh == PDMAUDIOCHANNELID_UNUSED_SILENCE || idDstCh == PDMAUDIOCHANNELID_UNUSED_ZERO, + ("idxDst=%u idDstCh=%u\n", idxDst, idDstCh)); + paidxChannelMap[idxDst] = idDstCh == PDMAUDIOCHANNELID_UNUSED_SILENCE ? -2 : -1; } - - offRead = (offRead + cToRead) % pDst->cFrames; - cLeft -= cToRead; } -#endif /* AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA */ - -#ifdef DEBUG - audioMixBufDbgPrintInternal(pDst, __FUNCTION__); -#endif - - if (pcSrcMixed) - *pcSrcMixed = cReadTotal; - - AUDMIXBUF_LOG(("cReadTotal=%RU32, cWrittenTotal=%RU32, cSrcMixed=%RU32, cDstUsed=%RU32, rc=%Rrc\n", - cReadTotal, cWrittenTotal, pSrc->cMixed, pDst->cUsed, rc)); - return rc; -} - -/** - * Mixes audio frames down to the parent mixing buffer, extended version. - * - * @return IPRT status code. See audioMixBufMixTo() for a more detailed explanation. - * @param pMixBuf Source mixing buffer to mix to its parent. - * @param cSrcOffset Offset (in frames) of source mixing buffer. - * @param cSrcFrames Number of source audio frames to mix to its parent. - * @param pcSrcMixed Number of source audio frames successfully mixed. Optional. - */ -int AudioMixBufMixToParentEx(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcOffset, uint32_t cSrcFrames, uint32_t *pcSrcMixed) -{ - AssertMsgReturn(VALID_PTR(pMixBuf->pParent), - ("Buffer is not linked to a parent buffer\n"), - VERR_INVALID_PARAMETER); - - return audioMixBufMixTo(pMixBuf->pParent, pMixBuf, cSrcOffset, cSrcFrames, pcSrcMixed); -} -/** - * Mixes audio frames down to the parent mixing buffer. - * - * @return IPRT status code. See audioMixBufMixTo() for a more detailed explanation. - * @param pMixBuf Source mixing buffer to mix to its parent. - * @param cSrcFrames Number of source audio frames to mix to its parent. - * @param pcSrcMixed Number of source audio frames successfully mixed. Optional. - */ -int AudioMixBufMixToParent(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcFrames, uint32_t *pcSrcMixed) -{ - return audioMixBufMixTo(pMixBuf->pParent, pMixBuf, pMixBuf->offRead, cSrcFrames, pcSrcMixed); + /* Set the remainder to -1 just to be sure their are safe. */ + for (; idxDst < PDMAUDIO_MAX_CHANNELS; idxDst++) + paidxChannelMap[idxDst] = -1; } -#ifdef DEBUG -/** - * Prints a single mixing buffer. - * Internal helper function for debugging. Do not use directly. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to print. - * @param pszFunc Function name to log this for. - * @param fIsParent Whether this is a parent buffer or not. - * @param uIdtLvl Indention level to use. - */ -DECL_FORCE_INLINE(void) audioMixBufDbgPrintSingle(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc, bool fIsParent, uint16_t uIdtLvl) -{ - Log(("%s: %*s[%s] %s: offRead=%RU32, offWrite=%RU32, cMixed=%RU32 -> %RU32/%RU32\n", - pszFunc, uIdtLvl * 4, "", fIsParent ? "PARENT" : "CHILD", - pMixBuf->pszName, pMixBuf->offRead, pMixBuf->offWrite, pMixBuf->cMixed, pMixBuf->cUsed, pMixBuf->cFrames)); -} /** - * Validates a single mixing buffer. + * Initializes the peek state, setting up encoder and (if necessary) resampling. * - * @return @true if the buffer state is valid or @false if not. - * @param pMixBuf Mixing buffer to validate. + * @returns VBox status code. */ -DECL_FORCE_INLINE(bool) audioMixBufDbgValidate(PPDMAUDIOMIXBUF pMixBuf) +int AudioMixBufInitPeekState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFPEEKSTATE pState, PCPDMAUDIOPCMPROPS pProps) { - //const uint32_t offReadEnd = (pMixBuf->offRead + pMixBuf->cUsed) % pMixBuf->cFrames; - //const uint32_t offWriteEnd = (pMixBuf->offWrite + (pMixBuf->cFrames - pMixBuf->cUsed)) % pMixBuf->cFrames; - - bool fValid = true; - - AssertStmt(pMixBuf->offRead <= pMixBuf->cFrames, fValid = false); - AssertStmt(pMixBuf->offWrite <= pMixBuf->cFrames, fValid = false); - AssertStmt(pMixBuf->cUsed <= pMixBuf->cFrames, fValid = false); + AssertPtr(pMixBuf); + AssertPtr(pState); + AssertPtr(pProps); - if (pMixBuf->offWrite > pMixBuf->offRead) - { - if (pMixBuf->offWrite - pMixBuf->offRead != pMixBuf->cUsed) - fValid = false; - } - else if (pMixBuf->offWrite < pMixBuf->offRead) - { - if (pMixBuf->offWrite + pMixBuf->cFrames - pMixBuf->offRead != pMixBuf->cUsed) - fValid = false; - } + /* + * Pick the encoding function first. + */ + uint8_t const cbSample = PDMAudioPropsSampleSize(pProps); + uint8_t const cSrcCh = PDMAudioPropsChannels(&pMixBuf->Props); + uint8_t const cDstCh = PDMAudioPropsChannels(pProps); + pState->cSrcChannels = cSrcCh; + pState->cDstChannels = cDstCh; + pState->cbDstFrame = PDMAudioPropsFrameSize(pProps); + audioMixBufInitChannelMap(pState->aidxChannelMap, &pMixBuf->Props, pProps); + AssertReturn(cDstCh > 0 && cDstCh <= PDMAUDIO_MAX_CHANNELS, VERR_OUT_OF_RANGE); + AssertReturn(cSrcCh > 0 && cSrcCh <= PDMAUDIO_MAX_CHANNELS, VERR_OUT_OF_RANGE); - if (!fValid) + if (PDMAudioPropsIsSigned(pProps)) { - audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); - AssertFailed(); - } - - return fValid; -} + /* Assign generic encoder first. */ + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncodeGenericS8; break; + case 2: pState->pfnEncode = audioMixBufEncodeGenericS16; break; + case 4: pState->pfnEncode = audioMixBufEncodeGenericS32; break; + case 8: + AssertReturn(pProps->fRaw, VERR_DISK_INVALID_FORMAT); + pState->pfnEncode = audioMixBufEncodeGenericRaw; + break; + default: + AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE); + } -/** - * Internal helper function for audioMixBufPrintChain(). - * Do not use directly. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to print. - * @param pszFunc Function name to print the chain for. - * @param uIdtLvl Indention level to use. - * @param pcChildren Pointer to children counter. - */ -DECL_FORCE_INLINE(void) audioMixBufDbgPrintChainHelper(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc, uint16_t uIdtLvl, - size_t *pcChildren) -{ - PPDMAUDIOMIXBUF pIter; - RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node) - { - audioMixBufDbgPrintSingle(pIter, pszFunc, false /* ifIsParent */, uIdtLvl + 1); - *pcChildren++; + /* Any specializations available? */ + switch (cDstCh) + { + case 1: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode1ChTo1ChS8; break; + case 2: pState->pfnEncode = audioMixBufEncode1ChTo1ChS16; break; + case 4: pState->pfnEncode = audioMixBufEncode1ChTo1ChS32; break; + case 8: pState->pfnEncode = audioMixBufEncode1ChTo1ChRaw; break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode2ChTo1ChS8; break; + case 2: pState->pfnEncode = audioMixBufEncode2ChTo1ChS16; break; + case 4: pState->pfnEncode = audioMixBufEncode2ChTo1ChS32; break; + case 8: pState->pfnEncode = audioMixBufEncode2ChTo1ChRaw; break; + } + break; + + case 2: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode1ChTo2ChS8; break; + case 2: pState->pfnEncode = audioMixBufEncode1ChTo2ChS16; break; + case 4: pState->pfnEncode = audioMixBufEncode1ChTo2ChS32; break; + case 8: pState->pfnEncode = audioMixBufEncode1ChTo2ChRaw; break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode2ChTo2ChS8; break; + case 2: pState->pfnEncode = audioMixBufEncode2ChTo2ChS16; break; + case 4: pState->pfnEncode = audioMixBufEncode2ChTo2ChS32; break; + case 8: pState->pfnEncode = audioMixBufEncode2ChTo2ChRaw; break; + } + break; + } } -} - -DECL_FORCE_INLINE(void) audioMixBufDbgPrintChainInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc) -{ - PPDMAUDIOMIXBUF pParent = pMixBuf->pParent; - while (pParent) + else { - if (!pParent->pParent) - break; - - pParent = pParent->pParent; - } - - if (!pParent) - pParent = pMixBuf; - - audioMixBufDbgPrintSingle(pParent, pszFunc, true /* fIsParent */, 0 /* uIdtLvl */); - - /* Recursively iterate children. */ - size_t cChildren = 0; - audioMixBufDbgPrintChainHelper(pParent, pszFunc, 0 /* uIdtLvl */, &cChildren); - - Log(("%s: Children: %zu\n", pszFunc, cChildren)); -} - -/** - * Prints statistics and status of the full chain of a mixing buffer to the logger, - * starting from the top root mixing buffer. - * For debug versions only. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to print. - */ -void AudioMixBufDbgPrintChain(PPDMAUDIOMIXBUF pMixBuf) -{ - audioMixBufDbgPrintChainInternal(pMixBuf, __FUNCTION__); -} - -DECL_FORCE_INLINE(void) audioMixBufDbgPrintInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc) -{ - PPDMAUDIOMIXBUF pParent = pMixBuf; - if (pMixBuf->pParent) - pParent = pMixBuf->pParent; - - audioMixBufDbgPrintSingle(pMixBuf, pszFunc, pParent == pMixBuf /* fIsParent */, 0 /* iIdtLevel */); + /* Assign generic encoder first. */ + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncodeGenericU8; break; + case 2: pState->pfnEncode = audioMixBufEncodeGenericU16; break; + case 4: pState->pfnEncode = audioMixBufEncodeGenericU32; break; + default: + AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE); + } - PPDMAUDIOMIXBUF pIter; - RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node) - { - if (pIter == pMixBuf) - continue; - audioMixBufDbgPrintSingle(pIter, pszFunc, false /* fIsParent */, 1 /* iIdtLevel */); + /* Any specializations available? */ + switch (cDstCh) + { + case 1: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode1ChTo1ChU8; break; + case 2: pState->pfnEncode = audioMixBufEncode1ChTo1ChU16; break; + case 4: pState->pfnEncode = audioMixBufEncode1ChTo1ChU32; break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode2ChTo1ChU8; break; + case 2: pState->pfnEncode = audioMixBufEncode2ChTo1ChU16; break; + case 4: pState->pfnEncode = audioMixBufEncode2ChTo1ChU32; break; + } + break; + + case 2: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode1ChTo2ChU8; break; + case 2: pState->pfnEncode = audioMixBufEncode1ChTo2ChU16; break; + case 4: pState->pfnEncode = audioMixBufEncode1ChTo2ChU32; break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: pState->pfnEncode = audioMixBufEncode2ChTo2ChU8; break; + case 2: pState->pfnEncode = audioMixBufEncode2ChTo2ChU16; break; + case 4: pState->pfnEncode = audioMixBufEncode2ChTo2ChU32; break; + } + break; + } } -} - -/** - * Prints statistics and status of a mixing buffer to the logger. - * For debug versions only. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to print. - */ -void AudioMixBufDbgPrint(PPDMAUDIOMIXBUF pMixBuf) -{ - audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); -} -#endif /* DEBUG */ - -/** - * Returns the total number of audio frames used. - * - * @return uint32_t - * @param pMixBuf - */ -uint32_t AudioMixBufUsed(PPDMAUDIOMIXBUF pMixBuf) -{ - AssertPtrReturn(pMixBuf, 0); - return pMixBuf->cUsed; -} -/** - * Returns the total number of bytes used. - * - * @return uint32_t - * @param pMixBuf - */ -uint32_t AudioMixBufUsedBytes(PPDMAUDIOMIXBUF pMixBuf) -{ - AssertPtrReturn(pMixBuf, 0); - return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cUsed); + int rc = audioMixBufRateInit(&pState->Rate, PDMAudioPropsHz(&pMixBuf->Props), PDMAudioPropsHz(pProps), cSrcCh); + AUDMIXBUF_LOG(("%s: %RU32 Hz to %RU32 Hz => uDstInc=0x%'RX64\n", pMixBuf->pszName, PDMAudioPropsHz(&pMixBuf->Props), + PDMAudioPropsHz(pProps), pState->Rate.uDstInc)); + return rc; } -/** - * Reads audio frames at a specific offset. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to read audio frames from. - * @param offFrames Offset (in audio frames) to start reading from. - * @param pvBuf Pointer to buffer to write output to. - * @param cbBuf Size (in bytes) of buffer to write to. - * @param pcbRead Size (in bytes) of data read. Optional. - */ -int AudioMixBufReadAt(PPDMAUDIOMIXBUF pMixBuf, - uint32_t offFrames, - void *pvBuf, uint32_t cbBuf, - uint32_t *pcbRead) -{ - return AudioMixBufReadAtEx(pMixBuf, pMixBuf->uAudioFmt, - offFrames, pvBuf, cbBuf, pcbRead); -} /** - * Reads audio frames at a specific offset. - * If the audio format of the mixing buffer and the requested audio format do - * not match the output will be converted accordingly. + * Initializes the write/blend state, setting up decoders and (if necessary) + * resampling. * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to read audio frames from. - * @param enmFmt Audio format to use for output. - * @param offFrames Offset (in audio frames) to start reading from. - * @param pvBuf Pointer to buffer to write output to. - * @param cbBuf Size (in bytes) of buffer to write to. - * @param pcbRead Size (in bytes) of data read. Optional. + * @returns VBox status code. */ -int AudioMixBufReadAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, - uint32_t offFrames, - void *pvBuf, uint32_t cbBuf, - uint32_t *pcbRead) -{ - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - /* pcbRead is optional. */ - - uint32_t cDstFrames = pMixBuf->cFrames; - uint32_t cLive = pMixBuf->cUsed; - - uint32_t cDead = cDstFrames - cLive; - uint32_t cToProcess = (uint32_t)AUDIOMIXBUF_F2F_RATIO(pMixBuf, cDead); - cToProcess = RT_MIN(cToProcess, AUDIOMIXBUF_B2F(pMixBuf, cbBuf)); - - AUDMIXBUF_LOG(("%s: offFrames=%RU32, cLive=%RU32, cDead=%RU32, cToProcess=%RU32\n", - pMixBuf->pszName, offFrames, cLive, cDead, cToProcess)); - - int rc; - if (cToProcess) - { - PFNPDMAUDIOMIXBUFCONVTO pfnConvTo = NULL; - if (pMixBuf->uAudioFmt != enmFmt) - pfnConvTo = audioMixBufConvToLookup(enmFmt); - else - pfnConvTo = pMixBuf->pfnConvTo; - - if (pfnConvTo) - { - PDMAUDMIXBUFCONVOPTS convOpts; - RT_ZERO(convOpts); - /* Note: No volume handling/conversion done in the conversion-to macros (yet). */ - - convOpts.cFrames = cToProcess; +int AudioMixBufInitWriteState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, PCPDMAUDIOPCMPROPS pProps) +{ + AssertPtr(pMixBuf); + AssertPtr(pState); + AssertPtr(pProps); - pfnConvTo(pvBuf, pMixBuf->pFrames + offFrames, &convOpts); + /* + * Pick the encoding function first. + */ + uint8_t const cbSample = PDMAudioPropsSampleSize(pProps); + uint8_t const cSrcCh = PDMAudioPropsChannels(pProps); + uint8_t const cDstCh = PDMAudioPropsChannels(&pMixBuf->Props); + pState->cSrcChannels = cSrcCh; + pState->cDstChannels = cDstCh; + pState->cbSrcFrame = PDMAudioPropsFrameSize(pProps); + audioMixBufInitChannelMap(pState->aidxChannelMap, pProps, &pMixBuf->Props); -#ifdef DEBUG - AudioMixBufDbgPrint(pMixBuf); -#endif - rc = VINF_SUCCESS; + if (PDMAudioPropsIsSigned(pProps)) + { + /* Assign generic decoders first. */ + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecodeGenericS8; + pState->pfnDecodeBlend = audioMixBufDecodeGenericS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecodeGenericS16; + pState->pfnDecodeBlend = audioMixBufDecodeGenericS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecodeGenericS32; + pState->pfnDecodeBlend = audioMixBufDecodeGenericS32Blend; + break; + case 8: + AssertReturn(pProps->fRaw, VERR_DISK_INVALID_FORMAT); + pState->pfnDecode = audioMixBufDecodeGenericRaw; + pState->pfnDecodeBlend = audioMixBufDecodeGenericRawBlend; + break; + default: + AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE); } - else + + /* Any specializations available? */ + switch (cDstCh) { - AssertFailed(); - rc = VERR_NOT_SUPPORTED; + case 1: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode1ChTo1ChS8; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode1ChTo1ChS16; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode1ChTo1ChS32; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS32Blend; + break; + case 8: + pState->pfnDecode = audioMixBufDecode1ChTo1ChRaw; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChRawBlend; + break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode2ChTo1ChS8; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode2ChTo1ChS16; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode2ChTo1ChS32; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS32Blend; + break; + case 8: + pState->pfnDecode = audioMixBufDecode2ChTo1ChRaw; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChRawBlend; + break; + } + break; + + case 2: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode1ChTo2ChS8; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode1ChTo2ChS16; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode1ChTo2ChS32; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS32Blend; + break; + case 8: + pState->pfnDecode = audioMixBufDecode1ChTo2ChRaw; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChRawBlend; + break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode2ChTo2ChS8; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode2ChTo2ChS16; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode2ChTo2ChS32; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS32Blend; + break; + case 8: + pState->pfnDecode = audioMixBufDecode2ChTo2ChRaw; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChRawBlend; + break; + } + break; } } else - rc = VINF_SUCCESS; - - if (RT_SUCCESS(rc)) { - if (pcbRead) - *pcbRead = AUDIOMIXBUF_F2B(pMixBuf, cToProcess); + /* Assign generic decoders first. */ + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecodeGenericU8; + pState->pfnDecodeBlend = audioMixBufDecodeGenericU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecodeGenericU16; + pState->pfnDecodeBlend = audioMixBufDecodeGenericU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecodeGenericU32; + pState->pfnDecodeBlend = audioMixBufDecodeGenericU32Blend; + break; + default: + AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE); + } + + /* Any specializations available? */ + switch (cDstCh) + { + case 1: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode1ChTo1ChU8; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode1ChTo1ChU16; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode1ChTo1ChU32; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU32Blend; + break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode2ChTo1ChU8; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode2ChTo1ChU16; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode2ChTo1ChU32; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU32Blend; + break; + } + break; + + case 2: + if (cSrcCh == 1) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode1ChTo2ChU8; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode1ChTo2ChU16; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode1ChTo2ChU32; + pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU32Blend; + break; + } + else if (cSrcCh == 2) + switch (cbSample) + { + case 1: + pState->pfnDecode = audioMixBufDecode2ChTo2ChU8; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU8Blend; + break; + case 2: + pState->pfnDecode = audioMixBufDecode2ChTo2ChU16; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU16Blend; + break; + case 4: + pState->pfnDecode = audioMixBufDecode2ChTo2ChU32; + pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU32Blend; + break; + } + break; + } } - AUDMIXBUF_LOG(("cbRead=%RU32, rc=%Rrc\n", AUDIOMIXBUF_F2B(pMixBuf, cToProcess), rc)); + int rc = audioMixBufRateInit(&pState->Rate, PDMAudioPropsHz(pProps), PDMAudioPropsHz(&pMixBuf->Props), cDstCh); + AUDMIXBUF_LOG(("%s: %RU32 Hz to %RU32 Hz => uDstInc=0x%'RX64\n", pMixBuf->pszName, PDMAudioPropsHz(pProps), + PDMAudioPropsHz(&pMixBuf->Props), pState->Rate.uDstInc)); return rc; } + /** - * Reads audio frames. The audio format of the mixing buffer will be used. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to read audio frames from. - * @param pvBuf Pointer to buffer to write output to. - * @param cbBuf Size (in bytes) of buffer to write to. - * @param pcBlock Returns acquired block to read (in audio frames). + * Worker for AudioMixBufPeek that handles the rate conversion case. */ -int AudioMixBufAcquireReadBlock(PPDMAUDIOMIXBUF pMixBuf, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock) -{ - return AudioMixBufAcquireReadBlockEx(pMixBuf, pMixBuf->uAudioFmt, pvBuf, cbBuf, pcBlock); +DECL_NO_INLINE(static, void) +audioMixBufPeekResampling(PCAUDIOMIXBUF pMixBuf, uint32_t offSrcFrame, uint32_t cMaxSrcFrames, uint32_t *pcSrcFramesPeeked, + PAUDIOMIXBUFPEEKSTATE pState, void *pvDst, uint32_t cbDst, uint32_t *pcbDstPeeked) +{ + *pcSrcFramesPeeked = 0; + *pcbDstPeeked = 0; + while (cMaxSrcFrames > 0 && cbDst >= pState->cbDstFrame) + { + /* Rate conversion into temporary buffer. */ + int32_t ai32DstRate[1024]; + uint32_t cSrcFrames = RT_MIN(pMixBuf->cFrames - offSrcFrame, cMaxSrcFrames); + uint32_t cDstMaxFrames = RT_MIN(RT_ELEMENTS(ai32DstRate) / pState->cSrcChannels, cbDst / pState->cbDstFrame); + uint32_t const cDstFrames = pState->Rate.pfnResample(ai32DstRate, cDstMaxFrames, + &pMixBuf->pi32Samples[offSrcFrame * pMixBuf->cChannels], + cSrcFrames, &cSrcFrames, &pState->Rate); + *pcSrcFramesPeeked += cSrcFrames; + cMaxSrcFrames -= cSrcFrames; + offSrcFrame = (offSrcFrame + cSrcFrames) % pMixBuf->cFrames; + + /* Encode the converted frames. */ + uint32_t const cbDstEncoded = cDstFrames * pState->cbDstFrame; + pState->pfnEncode(pvDst, ai32DstRate, cDstFrames, pState); + *pcbDstPeeked += cbDstEncoded; + cbDst -= cbDstEncoded; + pvDst = (uint8_t *)pvDst + cbDstEncoded; + } } + /** - * Reads audio frames in a specific audio format. - * If the audio format of the mixing buffer and the requested audio format do - * not match the output will be converted accordingly. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to read audio frames from. - * @param enmFmt Audio format to use for output. - * @param pvBuf Pointer to buffer to write output to. - * @param cbBuf Size (in bytes) of buffer to write to. - * @param pcBlock Returns acquired block to read (in audio frames). + * Copies data out of the mixing buffer, converting it if needed, but leaves the + * read offset untouched. + * + * @param pMixBuf The mixing buffer. + * @param offSrcFrame The offset to start reading at relative to + * current read position (offRead). The caller has + * made sure there is at least this number of + * frames available in the buffer before calling. + * @param cMaxSrcFrames Maximum number of frames to read. + * @param pcSrcFramesPeeked Where to return the actual number of frames read + * from the mixing buffer. + * @param pState Output configuration & conversion state. + * @param pvDst The destination buffer. + * @param cbDst The size of the destination buffer in bytes. + * @param pcbDstPeeked Where to put the actual number of bytes + * returned. */ -int AudioMixBufAcquireReadBlockEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, void *pvBuf, uint32_t cbBuf, - uint32_t *pcBlock) +void AudioMixBufPeek(PCAUDIOMIXBUF pMixBuf, uint32_t offSrcFrame, uint32_t cMaxSrcFrames, uint32_t *pcSrcFramesPeeked, + PAUDIOMIXBUFPEEKSTATE pState, void *pvDst, uint32_t cbDst, uint32_t *pcbDstPeeked) { - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertReturn(cbBuf, VERR_INVALID_PARAMETER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertPtrReturn(pcBlock, VERR_INVALID_POINTER); - - /* Make sure that we at least have space for a full audio frame. */ - AssertReturn(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), VERR_INVALID_PARAMETER); - - uint32_t cToRead = RT_MIN(pMixBuf->cUsed, AUDIOMIXBUF_B2F(pMixBuf, cbBuf)); + /* + * Check inputs. + */ + AssertPtr(pMixBuf); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + AssertPtr(pState); + AssertPtr(pState->pfnEncode); + Assert(pState->cSrcChannels == PDMAudioPropsChannels(&pMixBuf->Props)); + Assert(cMaxSrcFrames > 0); + Assert(cMaxSrcFrames <= pMixBuf->cFrames); + Assert(offSrcFrame <= pMixBuf->cFrames); + Assert(offSrcFrame + cMaxSrcFrames <= pMixBuf->cUsed); + AssertPtr(pcSrcFramesPeeked); + AssertPtr(pvDst); + Assert(cbDst >= pState->cbDstFrame); + AssertPtr(pcbDstPeeked); - AUDMIXBUF_LOG(("%s: cbBuf=%RU32 (%RU32 frames), cToRead=%RU32, fmtSrc=0x%x, fmtDst=0x%x\n", - pMixBuf->pszName, cbBuf, AUDIOMIXBUF_B2F(pMixBuf, cbBuf), cToRead, pMixBuf->uAudioFmt, enmFmt)); + /* + * Make start frame absolute. + */ + offSrcFrame = (pMixBuf->offRead + offSrcFrame) % pMixBuf->cFrames; - if (!cToRead) + /* + * Hopefully no sample rate conversion is necessary... + */ + if (pState->Rate.fNoConversionNeeded) { -#ifdef DEBUG - audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); -#endif - *pcBlock = 0; - return VINF_SUCCESS; - } + /* Figure out how much we should convert. */ + cMaxSrcFrames = RT_MIN(cMaxSrcFrames, cbDst / pState->cbDstFrame); + *pcSrcFramesPeeked = cMaxSrcFrames; + *pcbDstPeeked = cMaxSrcFrames * pState->cbDstFrame; + + /* First chunk. */ + uint32_t const cSrcFrames1 = RT_MIN(pMixBuf->cFrames - offSrcFrame, cMaxSrcFrames); + pState->pfnEncode(pvDst, &pMixBuf->pi32Samples[offSrcFrame * pMixBuf->cChannels], cSrcFrames1, pState); + + /* Another chunk from the start of the mixing buffer? */ + if (cMaxSrcFrames > cSrcFrames1) + pState->pfnEncode((uint8_t *)pvDst + cSrcFrames1 * pState->cbDstFrame, + &pMixBuf->pi32Samples[0], cMaxSrcFrames - cSrcFrames1, pState); - PFNPDMAUDIOMIXBUFCONVTO pfnConvTo = NULL; - if (pMixBuf->uAudioFmt != enmFmt) - pfnConvTo = audioMixBufConvToLookup(enmFmt); + //Log9Func(("*pcbDstPeeked=%#x\n%32.*Rhxd\n", *pcbDstPeeked, *pcbDstPeeked, pvDst)); + } else - pfnConvTo = pMixBuf->pfnConvTo; + audioMixBufPeekResampling(pMixBuf, offSrcFrame, cMaxSrcFrames, pcSrcFramesPeeked, pState, pvDst, cbDst, pcbDstPeeked); +} - if (!pfnConvTo) /* Audio format not supported. */ - { - AssertFailed(); - return VERR_NOT_SUPPORTED; - } - cToRead = RT_MIN(cToRead, pMixBuf->cFrames - pMixBuf->offRead); - if (cToRead) +/** + * Worker for AudioMixBufWrite that handles the rate conversion case. + */ +DECL_NO_INLINE(static, void) +audioMixBufWriteResampling(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesWritten) +{ + *pcDstFramesWritten = 0; + while (cDstMaxFrames > 0 && cbSrcBuf >= pState->cbSrcFrame) { - PDMAUDMIXBUFCONVOPTS convOpts; - RT_ZERO(convOpts); - convOpts.cFrames = cToRead; + /* Decode into temporary buffer. */ + int32_t ai32Decoded[1024]; + uint32_t cFramesDecoded = RT_MIN(RT_ELEMENTS(ai32Decoded) / pState->cDstChannels, cbSrcBuf / pState->cbSrcFrame); + pState->pfnDecode(ai32Decoded, pvSrcBuf, cFramesDecoded, pState); + cbSrcBuf -= cFramesDecoded * pState->cbSrcFrame; + pvSrcBuf = (uint8_t const *)pvSrcBuf + cFramesDecoded * pState->cbSrcFrame; - AUDMIXBUF_LOG(("cToRead=%RU32\n", cToRead)); - - pfnConvTo(pvBuf, pMixBuf->pFrames + pMixBuf->offRead, &convOpts); - -#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA - RTFILE fh; - int rc2 = RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_readcirc.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - if (RT_SUCCESS(rc2)) + /* Rate convert that into the mixer. */ + uint32_t iFrameDecoded = 0; + while (iFrameDecoded < cFramesDecoded) { - RTFileWrite(fh, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToRead), NULL); - RTFileClose(fh); + uint32_t cDstMaxFramesNow = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames); + uint32_t cSrcFrames = cFramesDecoded - iFrameDecoded; + uint32_t const cDstFrames = pState->Rate.pfnResample(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], + cDstMaxFramesNow, + &ai32Decoded[iFrameDecoded * pState->cDstChannels], + cSrcFrames, &cSrcFrames, &pState->Rate); + + iFrameDecoded += cSrcFrames; + *pcDstFramesWritten += cDstFrames; + offDstFrame = (offDstFrame + cDstFrames) % pMixBuf->cFrames; } -#endif } - *pcBlock = cToRead; - -#ifdef DEBUG - audioMixBufDbgValidate(pMixBuf); -#endif - - AUDMIXBUF_LOG(("cRead=%RU32 (%RU32 bytes)\n", cToRead, AUDIOMIXBUF_F2B(pMixBuf, cToRead))); - return VINF_SUCCESS; + /** @todo How to squeeze odd frames out of 22050 => 44100 conversion? */ } + /** - * Releases a formerly acquired read block again. + * Writes @a cbSrcBuf bytes to the mixer buffer starting at @a offDstFrame, + * converting it as needed, leaving the write offset untouched. + * + * @param pMixBuf The mixing buffer. + * @param pState Source configuration & conversion state. + * @param pvSrcBuf The source frames. + * @param cbSrcBuf Number of bytes of source frames. This will be + * convered in full. + * @param offDstFrame Mixing buffer offset relative to the write + * position. + * @param cDstMaxFrames Max number of frames to write. + * @param pcDstFramesWritten Where to return the number of frames actually + * written. * - * @param pMixBuf Mixing buffer to release acquired read block for. - * @param cBlock Size of the block to release (in audio frames). + * @note Does not advance the write position, please call AudioMixBufCommit() + * to do that. */ -void AudioMixBufReleaseReadBlock(PPDMAUDIOMIXBUF pMixBuf, uint32_t cBlock) +void AudioMixBufWrite(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesWritten) { - AssertPtrReturnVoid(pMixBuf); + /* + * Check inputs. + */ + AssertPtr(pMixBuf); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + AssertPtr(pState); + AssertPtr(pState->pfnDecode); + AssertPtr(pState->pfnDecodeBlend); + Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props)); + Assert(cDstMaxFrames > 0); + Assert(cDstMaxFrames <= pMixBuf->cFrames - pMixBuf->cUsed); + Assert(offDstFrame <= pMixBuf->cFrames); + AssertPtr(pvSrcBuf); + Assert(!(cbSrcBuf % pState->cbSrcFrame)); + AssertPtr(pcDstFramesWritten); - if (!cBlock) - return; + /* + * Make start frame absolute. + */ + offDstFrame = (pMixBuf->offWrite + offDstFrame) % pMixBuf->cFrames; - pMixBuf->offRead = (pMixBuf->offRead + cBlock) % pMixBuf->cFrames; - Assert(pMixBuf->cUsed >= cBlock); - pMixBuf->cUsed -= RT_MIN(cBlock, pMixBuf->cUsed); + /* + * Hopefully no sample rate conversion is necessary... + */ + if (pState->Rate.fNoConversionNeeded) + { + /* Figure out how much we should convert. */ + Assert(cDstMaxFrames >= cbSrcBuf / pState->cbSrcFrame); + cDstMaxFrames = RT_MIN(cDstMaxFrames, cbSrcBuf / pState->cbSrcFrame); + *pcDstFramesWritten = cDstMaxFrames; + + //Log10Func(("cbSrc=%#x\n%32.*Rhxd\n", pState->cbSrcFrame * cDstMaxFrames, pState->cbSrcFrame * cDstMaxFrames, pvSrcBuf)); + + /* First chunk. */ + uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames); + pState->pfnDecode(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], pvSrcBuf, cDstFrames1, pState); + //Log8Func(("offDstFrame=%#x cDstFrames1=%#x\n%32.*Rhxd\n", offDstFrame, cDstFrames1, + // cDstFrames1 * pMixBuf->cbFrame, &pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels])); + + /* Another chunk from the start of the mixing buffer? */ + if (cDstMaxFrames > cDstFrames1) + { + pState->pfnDecode(&pMixBuf->pi32Samples[0], (uint8_t *)pvSrcBuf + cDstFrames1 * pState->cbSrcFrame, + cDstMaxFrames - cDstFrames1, pState); + //Log8Func(("cDstFrames2=%#x\n%32.*Rhxd\n", cDstMaxFrames - cDstFrames1, + // (cDstMaxFrames - cDstFrames1) * pMixBuf->cbFrame, &pMixBuf->pi32Samples[0])); + } + } + else + audioMixBufWriteResampling(pMixBuf, pState, pvSrcBuf, cbSrcBuf, offDstFrame, cDstMaxFrames, pcDstFramesWritten); } + /** - * Returns the current read position of a mixing buffer. - * - * @returns IPRT status code. - * @param pMixBuf Mixing buffer to return position for. + * Worker for AudioMixBufBlend that handles the rate conversion case. */ -uint32_t AudioMixBufReadPos(PPDMAUDIOMIXBUF pMixBuf) +DECL_NO_INLINE(static, void) +audioMixBufBlendResampling(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesBlended) { - AssertPtrReturn(pMixBuf, 0); + *pcDstFramesBlended = 0; + while (cDstMaxFrames > 0 && cbSrcBuf >= pState->cbSrcFrame) + { + /* Decode into temporary buffer. This then has the destination channel count. */ + int32_t ai32Decoded[1024]; + uint32_t cFramesDecoded = RT_MIN(RT_ELEMENTS(ai32Decoded) / pState->cDstChannels, cbSrcBuf / pState->cbSrcFrame); + pState->pfnDecode(ai32Decoded, pvSrcBuf, cFramesDecoded, pState); + cbSrcBuf -= cFramesDecoded * pState->cbSrcFrame; + pvSrcBuf = (uint8_t const *)pvSrcBuf + cFramesDecoded * pState->cbSrcFrame; - return pMixBuf->offRead; + /* Rate convert that into another temporary buffer and then blend that into the mixer. */ + uint32_t iFrameDecoded = 0; + while (iFrameDecoded < cFramesDecoded) + { + int32_t ai32Rate[1024]; + uint32_t cDstMaxFramesNow = RT_MIN(RT_ELEMENTS(ai32Rate) / pState->cDstChannels, cDstMaxFrames); + uint32_t cSrcFrames = cFramesDecoded - iFrameDecoded; + uint32_t const cDstFrames = pState->Rate.pfnResample(&ai32Rate[0], cDstMaxFramesNow, + &ai32Decoded[iFrameDecoded * pState->cDstChannels], + cSrcFrames, &cSrcFrames, &pState->Rate); + + /* First chunk.*/ + uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstFrames); + audioMixBufBlendBuffer(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], + ai32Rate, cDstFrames1, pState->cDstChannels); + + /* Another chunk from the start of the mixing buffer? */ + if (cDstFrames > cDstFrames1) + audioMixBufBlendBuffer(&pMixBuf->pi32Samples[0], &ai32Rate[cDstFrames1 * pState->cDstChannels], + cDstFrames - cDstFrames1, pState->cDstChannels); + + /* Advance */ + iFrameDecoded += cSrcFrames; + *pcDstFramesBlended += cDstFrames; + offDstFrame = (offDstFrame + cDstFrames) % pMixBuf->cFrames; + } + } + + /** @todo How to squeeze odd frames out of 22050 => 44100 conversion? */ } + /** - * Resets a mixing buffer. - * - * @param pMixBuf Mixing buffer to reset. + * @todo not sure if 'blend' is the appropriate term here, but you know what + * we mean. */ -void AudioMixBufReset(PPDMAUDIOMIXBUF pMixBuf) +void AudioMixBufBlend(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesBlended) { - AssertPtrReturnVoid(pMixBuf); - - AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName)); + /* + * Check inputs. + */ + AssertPtr(pMixBuf); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + AssertPtr(pState); + AssertPtr(pState->pfnDecode); + AssertPtr(pState->pfnDecodeBlend); + Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props)); + Assert(cDstMaxFrames > 0); + Assert(cDstMaxFrames <= pMixBuf->cFrames - pMixBuf->cUsed); + Assert(offDstFrame <= pMixBuf->cFrames); + AssertPtr(pvSrcBuf); + Assert(!(cbSrcBuf % pState->cbSrcFrame)); + AssertPtr(pcDstFramesBlended); - pMixBuf->offRead = 0; - pMixBuf->offWrite = 0; - pMixBuf->cMixed = 0; - pMixBuf->cUsed = 0; + /* + * Make start frame absolute. + */ + offDstFrame = (pMixBuf->offWrite + offDstFrame) % pMixBuf->cFrames; - AudioMixBufClear(pMixBuf); + /* + * Hopefully no sample rate conversion is necessary... + */ + if (pState->Rate.fNoConversionNeeded) + { + /* Figure out how much we should convert. */ + Assert(cDstMaxFrames >= cbSrcBuf / pState->cbSrcFrame); + cDstMaxFrames = RT_MIN(cDstMaxFrames, cbSrcBuf / pState->cbSrcFrame); + *pcDstFramesBlended = cDstMaxFrames; + + /* First chunk. */ + uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames); + pState->pfnDecodeBlend(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], pvSrcBuf, cDstFrames1, pState); + + /* Another chunk from the start of the mixing buffer? */ + if (cDstMaxFrames > cDstFrames1) + pState->pfnDecodeBlend(&pMixBuf->pi32Samples[0], (uint8_t *)pvSrcBuf + cDstFrames1 * pState->cbSrcFrame, + cDstMaxFrames - cDstFrames1, pState); + } + else + audioMixBufBlendResampling(pMixBuf, pState, pvSrcBuf, cbSrcBuf, offDstFrame, cDstMaxFrames, pcDstFramesBlended); } + /** - * Sets the overall (master) volume. + * Writes @a cFrames of silence at @a offFrame relative to current write pos. + * + * This will also adjust the resampling state. * - * @param pMixBuf Mixing buffer to set volume for. - * @param pVol Pointer to volume structure to set. + * @param pMixBuf The mixing buffer. + * @param pState The write state. + * @param offFrame Where to start writing silence relative to the current + * write position. + * @param cFrames Number of frames of silence. + * @sa AudioMixBufWrite + * + * @note Does not advance the write position, please call AudioMixBufCommit() + * to do that. */ -void AudioMixBufSetVolume(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOVOLUME pVol) +void AudioMixBufSilence(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t offFrame, uint32_t cFrames) { - AssertPtrReturnVoid(pMixBuf); - AssertPtrReturnVoid(pVol); + /* + * Check inputs. + */ + AssertPtr(pMixBuf); + Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + AssertPtr(pState); + AssertPtr(pState->pfnDecode); + AssertPtr(pState->pfnDecodeBlend); + Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props)); + Assert(cFrames > 0); +#ifdef VBOX_STRICT + uint32_t const cMixBufFree = pMixBuf->cFrames - pMixBuf->cUsed; +#endif + Assert(cFrames <= cMixBufFree); + Assert(offFrame < cMixBufFree); + Assert(offFrame + cFrames <= cMixBufFree); + + /* + * Make start frame absolute. + */ + offFrame = (pMixBuf->offWrite + offFrame) % pMixBuf->cFrames; - LogFlowFunc(("%s: lVol=%RU8, rVol=%RU8, fMuted=%RTbool\n", pMixBuf->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted)); + /* + * First chunk. + */ + uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFrame, cFrames); + RT_BZERO(&pMixBuf->pi32Samples[offFrame * pMixBuf->cChannels], cFramesChunk1 * pMixBuf->cbFrame); + + /* + * Second chunk, if needed. + */ + if (cFrames > cFramesChunk1) + { + cFrames -= cFramesChunk1; + AssertStmt(cFrames <= pMixBuf->cFrames, cFrames = pMixBuf->cFrames); + RT_BZERO(&pMixBuf->pi32Samples[0], cFrames * pMixBuf->cbFrame); + } - int rc2 = audioMixBufConvVol(&pMixBuf->Volume /* Dest */, pVol /* Source */); - AssertRC(rc2); + /* + * Reset the resampling state. + */ + audioMixBufRateReset(&pState->Rate); } + /** - * Returns the maximum amount of audio frames this buffer can hold. + * Records a blending gap (silence) of @a cFrames. * - * @return uint32_t Size (in audio frames) the mixing buffer can hold. - * @param pMixBuf Mixing buffer to retrieve maximum for. + * This is used to adjust or reset the resampling state so we start from a + * silence state the next time we need to blend or write using @a pState. + * + * @param pMixBuf The mixing buffer. + * @param pState The write state. + * @param cFrames Number of frames of silence. + * @sa AudioMixBufSilence */ -uint32_t AudioMixBufSize(PPDMAUDIOMIXBUF pMixBuf) +void AudioMixBufBlendGap(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t cFrames) { - AssertPtrReturn(pMixBuf, 0); - return pMixBuf->cFrames; + /* + * For now we'll just reset the resampling state regardless of how many + * frames of silence there is. + */ + audioMixBufRateReset(&pState->Rate); + RT_NOREF(pMixBuf, cFrames); } + /** - * Returns the maximum amount of bytes this buffer can hold. + * Advances the read position of the buffer. * - * @return uint32_t Size (in bytes) the mixing buffer can hold. - * @param pMixBuf Mixing buffer to retrieve maximum for. + * For use after done peeking with AudioMixBufPeek(). + * + * @param pMixBuf The mixing buffer. + * @param cFrames Number of frames to advance. + * @sa AudioMixBufCommit */ -uint32_t AudioMixBufSizeBytes(PPDMAUDIOMIXBUF pMixBuf) +void AudioMixBufAdvance(PAUDIOMIXBUF pMixBuf, uint32_t cFrames) { - AssertPtrReturn(pMixBuf, 0); - return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cFrames); + AssertPtrReturnVoid(pMixBuf); + AssertReturnVoid(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + + AssertStmt(cFrames <= pMixBuf->cUsed, cFrames = pMixBuf->cUsed); + pMixBuf->cUsed -= cFrames; + pMixBuf->offRead = (pMixBuf->offRead + cFrames) % pMixBuf->cFrames; + LogFlowFunc(("%s: Advanced %u frames: offRead=%u cUsed=%u\n", pMixBuf->pszName, cFrames, pMixBuf->offRead, pMixBuf->cUsed)); } + /** - * Unlinks a mixing buffer from its parent, if any. - * - * @return IPRT status code. - * @param pMixBuf Mixing buffer to unlink from parent. + * Worker for audioMixAdjustVolume that adjust one contiguous chunk. */ -void AudioMixBufUnlink(PPDMAUDIOMIXBUF pMixBuf) +static void audioMixAdjustVolumeWorker(PAUDIOMIXBUF pMixBuf, uint32_t off, uint32_t cFrames) { - if (!pMixBuf || !pMixBuf->pszName) - return; - - AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName)); - - if (pMixBuf->pParent) /* IS this a children buffer? */ - { - AUDMIXBUF_LOG(("%s: Unlinking from parent \"%s\"\n", - pMixBuf->pszName, pMixBuf->pParent->pszName)); - - RTListNodeRemove(&pMixBuf->Node); - - /* Decrease the paren't children count. */ - Assert(pMixBuf->pParent->cChildren); - pMixBuf->pParent->cChildren--; - - /* Make sure to reset the parent mixing buffer each time it gets linked - * to a new child. */ - AudioMixBufReset(pMixBuf->pParent); - pMixBuf->pParent = NULL; - } - - PPDMAUDIOMIXBUF pChild, pChildNext; - RTListForEachSafe(&pMixBuf->lstChildren, pChild, pChildNext, PDMAUDIOMIXBUF, Node) + int32_t *pi32Samples = &pMixBuf->pi32Samples[off * pMixBuf->cChannels]; + switch (pMixBuf->cChannels) { - AUDMIXBUF_LOG(("\tUnlinking \"%s\"\n", pChild->pszName)); - - AudioMixBufReset(pChild); + case 1: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + while (cFrames-- > 0) + { + *pi32Samples = (int32_t)(ASMMult2xS32RetS64(*pi32Samples, uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples++; + } + break; + } - Assert(pChild->pParent == pMixBuf); - pChild->pParent = NULL; + case 2: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 2; + } + break; + } - RTListNodeRemove(&pChild->Node); + case 3: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 3; + } + break; + } - /* Decrease the children count. */ - Assert(pMixBuf->cChildren); - pMixBuf->cChildren--; - } + case 4: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2]; + uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 4; + } + break; + } - Assert(RTListIsEmpty(&pMixBuf->lstChildren)); - Assert(pMixBuf->cChildren == 0); + case 5: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2]; + uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3]; + uint32_t const uFactorCh4 = pMixBuf->Volume.auChannels[4]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[4] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[4], uFactorCh4) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 5; + } + break; + } - AudioMixBufReset(pMixBuf); + case 6: + { + uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0]; + uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1]; + uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2]; + uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3]; + uint32_t const uFactorCh4 = pMixBuf->Volume.auChannels[4]; + uint32_t const uFactorCh5 = pMixBuf->Volume.auChannels[5]; + while (cFrames-- > 0) + { + pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[4] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[4], uFactorCh4) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples[5] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[5], uFactorCh5) >> AUDIOMIXBUF_VOL_SHIFT); + pi32Samples += 6; + } + break; + } - if (pMixBuf->pRate) - { - pMixBuf->pRate->offDst = pMixBuf->pRate->offSrc = 0; - pMixBuf->pRate->uDstInc = 0; + default: + while (cFrames-- > 0) + for (uint32_t iCh = 0; iCh < pMixBuf->cChannels; iCh++, pi32Samples++) + *pi32Samples = ASMMult2xS32RetS64(*pi32Samples, pMixBuf->Volume.auChannels[iCh]) >> AUDIOMIXBUF_VOL_SHIFT; + break; } - - pMixBuf->iFreqRatio = 1; /* Prevent division by zero. */ } -/** - * Writes audio frames at a specific offset. - * The sample format being written must match the format of the mixing buffer. - * - * @return IPRT status code. - * @param pMixBuf Pointer to mixing buffer to write to. - * @param offFrames Offset (in frames) starting to write at. - * @param pvBuf Pointer to audio buffer to be written. - * @param cbBuf Size (in bytes) of audio buffer. - * @param pcWritten Returns number of audio frames written. Optional. - */ -int AudioMixBufWriteAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offFrames, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten) -{ - return AudioMixBufWriteAtEx(pMixBuf, pMixBuf->uAudioFmt, offFrames, pvBuf, cbBuf, pcWritten); -} /** - * Writes audio frames at a specific offset. - * - * Note that this operation also modifies the current read and write position - * to \a offFrames + written frames on success. + * Does volume adjustments for the given stretch of the buffer. * - * The audio sample format to be written can be different from the audio format - * the mixing buffer operates on. - * - * @return IPRT status code. - * @param pMixBuf Pointer to mixing buffer to write to. - * @param enmFmt Audio format supplied in the buffer. - * @param offFrames Offset (in frames) starting to write at. - * @param pvBuf Pointer to audio buffer to be written. - * @param cbBuf Size (in bytes) of audio buffer. - * @param pcWritten Returns number of audio frames written. Optional. + * @param pMixBuf The mixing buffer. + * @param offFirst Where to start (validated). + * @param cFrames How many frames (validated). */ -int AudioMixBufWriteAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, - uint32_t offFrames, const void *pvBuf, uint32_t cbBuf, - uint32_t *pcWritten) +static void audioMixAdjustVolume(PAUDIOMIXBUF pMixBuf, uint32_t offFirst, uint32_t cFrames) { - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertReturn(cbBuf, VERR_INVALID_PARAMETER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - /* pcbWritten is optional. */ + /* Caller has already validated these, so we don't need to repeat that in non-strict builds. */ + Assert(offFirst < pMixBuf->cFrames); + Assert(cFrames <= pMixBuf->cFrames); - if (offFrames >= pMixBuf->cFrames) - { - if (pcWritten) - *pcWritten = 0; - return VERR_BUFFER_OVERFLOW; - } - - /* - * Adjust cToWrite so we don't overflow our buffers. - */ - uint32_t cToWrite = RT_MIN(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), pMixBuf->cFrames - offFrames); - -#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA /* - * Now that we know how much we'll be converting we can log it. + * Muted? */ - RTFILE hFile; - int rc2 = RTFileOpen(&hFile, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_writeat.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - if (RT_SUCCESS(rc2)) + if (pMixBuf->Volume.fMuted) { - RTFileWrite(hFile, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), NULL); - RTFileClose(hFile); + /* first chunk */ + uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFirst, cFrames); + RT_BZERO(&pMixBuf->pi32Samples[offFirst * pMixBuf->cChannels], pMixBuf->cbFrame * cFramesChunk1); + + /* second chunk */ + if (cFramesChunk1 < cFrames) + RT_BZERO(&pMixBuf->pi32Samples[0], pMixBuf->cbFrame * (cFrames - cFramesChunk1)); } -#endif - /* - * Pick the conversion function and do the conversion. + * Less than max volume? */ - PFNPDMAUDIOMIXBUFCONVFROM pfnConvFrom = NULL; - if (!pMixBuf->Volume.fMuted) - { - if (pMixBuf->uAudioFmt != enmFmt) - pfnConvFrom = audioMixBufConvFromLookup(enmFmt); - else - pfnConvFrom = pMixBuf->pfnConvFrom; - } - else - pfnConvFrom = &audioMixBufConvFromSilence; - - int rc = VINF_SUCCESS; - - uint32_t cWritten; - if ( pfnConvFrom - && cToWrite) - { - PDMAUDMIXBUFCONVOPTS convOpts; - - convOpts.cFrames = cToWrite; - convOpts.From.Volume.fMuted = pMixBuf->Volume.fMuted; - convOpts.From.Volume.uLeft = pMixBuf->Volume.uLeft; - convOpts.From.Volume.uRight = pMixBuf->Volume.uRight; - - cWritten = pfnConvFrom(pMixBuf->pFrames + offFrames, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), &convOpts); - } - else - { - cWritten = 0; - if (!pfnConvFrom) - { - AssertFailed(); - rc = VERR_NOT_SUPPORTED; - } - } - - AUDMIXBUF_LOG(("%s: offFrames=%RU32, cbBuf=%RU32, cToWrite=%RU32 (%zu bytes), cWritten=%RU32 (%zu bytes), rc=%Rrc\n", - pMixBuf->pszName, offFrames, cbBuf, - cToWrite, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), - cWritten, AUDIOMIXBUF_F2B(pMixBuf, cWritten), rc)); - - if (RT_SUCCESS(rc)) + else if (!pMixBuf->Volume.fAllMax) { - pMixBuf->offRead = offFrames % pMixBuf->cFrames; - pMixBuf->offWrite = (offFrames + cWritten) % pMixBuf->cFrames; - pMixBuf->cUsed = cWritten; - pMixBuf->cMixed = 0; + /* first chunk */ + uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFirst, cFrames); + audioMixAdjustVolumeWorker(pMixBuf, offFirst, cFramesChunk1); -#ifdef DEBUG - audioMixBufDbgValidate(pMixBuf); -#endif - if (pcWritten) - *pcWritten = cWritten; + /* second chunk */ + if (cFramesChunk1 < cFrames) + audioMixAdjustVolumeWorker(pMixBuf, 0, cFrames - cFramesChunk1); } - else - AUDMIXBUF_LOG(("%s: Failed with %Rrc\n", pMixBuf->pszName, rc)); - - return rc; } + /** - * Writes audio frames. + * Adjust for volume settings and advances the write position of the buffer. * - * The sample format being written must match the format of the mixing buffer. + * For use after done peeking with AudioMixBufWrite(), AudioMixBufSilence(), + * AudioMixBufBlend() and AudioMixBufBlendGap(). * - * @return IPRT status code, or VERR_BUFFER_OVERFLOW if frames which not have - * been processed yet have been overwritten (due to cyclic buffer). - * @param pMixBuf Pointer to mixing buffer to write to. - * @param pvBuf Pointer to audio buffer to be written. - * @param cbBuf Size (in bytes) of audio buffer. - * @param pcWritten Returns number of audio frames written. Optional. + * @param pMixBuf The mixing buffer. + * @param cFrames Number of frames to advance. + * @sa AudioMixBufAdvance, AudioMixBufSetVolume */ -int AudioMixBufWriteCirc(PPDMAUDIOMIXBUF pMixBuf, - const void *pvBuf, uint32_t cbBuf, - uint32_t *pcWritten) +void AudioMixBufCommit(PAUDIOMIXBUF pMixBuf, uint32_t cFrames) { - return AudioMixBufWriteCircEx(pMixBuf, pMixBuf->uAudioFmt, pvBuf, cbBuf, pcWritten); + AssertPtrReturnVoid(pMixBuf); + AssertReturnVoid(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC); + + AssertStmt(cFrames <= pMixBuf->cFrames - pMixBuf->cUsed, cFrames = pMixBuf->cFrames - pMixBuf->cUsed); + + audioMixAdjustVolume(pMixBuf, pMixBuf->offWrite, cFrames); + + pMixBuf->cUsed += cFrames; + pMixBuf->offWrite = (pMixBuf->offWrite + cFrames) % pMixBuf->cFrames; + LogFlowFunc(("%s: Advanced %u frames: offWrite=%u cUsed=%u\n", pMixBuf->pszName, cFrames, pMixBuf->offWrite, pMixBuf->cUsed)); } + /** - * Writes audio frames of a specific format. - * This function might write less data at once than requested. + * Sets the volume. * - * @return IPRT status code, or VERR_BUFFER_OVERFLOW no space is available for writing anymore. - * @param pMixBuf Pointer to mixing buffer to write to. - * @param enmFmt Audio format supplied in the buffer. - * @param pvBuf Pointer to audio buffer to be written. - * @param cbBuf Size (in bytes) of audio buffer. - * @param pcWritten Returns number of audio frames written. Optional. + * The volume adjustments are applied by AudioMixBufCommit(). + * + * @param pMixBuf Mixing buffer to set volume for. + * @param pVol Pointer to volume structure to set. */ -int AudioMixBufWriteCircEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, - const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten) +void AudioMixBufSetVolume(PAUDIOMIXBUF pMixBuf, PCPDMAUDIOVOLUME pVol) { - AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - /* pcbWritten is optional. */ + AssertPtrReturnVoid(pMixBuf); + AssertPtrReturnVoid(pVol); - if (!cbBuf) - { - if (pcWritten) - *pcWritten = 0; - return VINF_SUCCESS; - } + LogFlowFunc(("%s: fMuted=%RTbool auChannels=%.*Rhxs\n", + pMixBuf->pszName, pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels)); - /* Make sure that we at least write a full audio frame. */ - AssertReturn(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), VERR_INVALID_PARAMETER); + /* + * Convert PDM audio volume to the internal format. + */ + if (!pVol->fMuted) + { + pMixBuf->Volume.fMuted = false; - Assert(pMixBuf->cFrames); - AssertPtr(pMixBuf->pFrames); + AssertCompileSize(pVol->auChannels[0], sizeof(uint8_t)); + for (uintptr_t i = 0; i < pMixBuf->cChannels; i++) + pMixBuf->Volume.auChannels[i] = s_aVolumeConv[pVol->auChannels[i]] * (AUDIOMIXBUF_VOL_0DB >> 16); - PFNPDMAUDIOMIXBUFCONVFROM pfnConvFrom = NULL; - if (!pMixBuf->Volume.fMuted) - { - if (pMixBuf->uAudioFmt != enmFmt) - pfnConvFrom = audioMixBufConvFromLookup(enmFmt); - else - pfnConvFrom = pMixBuf->pfnConvFrom; + pMixBuf->Volume.fAllMax = true; + for (uintptr_t i = 0; i < pMixBuf->cChannels; i++) + if (pMixBuf->Volume.auChannels[i] != AUDIOMIXBUF_VOL_0DB) + { + pMixBuf->Volume.fAllMax = false; + break; + } } else - pfnConvFrom = &audioMixBufConvFromSilence; - - if (!pfnConvFrom) - { - AssertFailed(); - return VERR_NOT_SUPPORTED; - } - - int rc = VINF_SUCCESS; - - uint32_t cWritten = 0; - - uint32_t cFree = pMixBuf->cFrames - pMixBuf->cUsed; - if (cFree) { - if ((pMixBuf->cFrames - pMixBuf->offWrite) == 0) - pMixBuf->offWrite = 0; - - uint32_t cToWrite = RT_MIN(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), RT_MIN(pMixBuf->cFrames - pMixBuf->offWrite, cFree)); - Assert(cToWrite); - - PDMAUDMIXBUFCONVOPTS convOpts; - RT_ZERO(convOpts); - - convOpts.From.Volume.fMuted = pMixBuf->Volume.fMuted; - convOpts.From.Volume.uLeft = pMixBuf->Volume.uLeft; - convOpts.From.Volume.uRight = pMixBuf->Volume.uRight; - - convOpts.cFrames = cToWrite; - - cWritten = pfnConvFrom(pMixBuf->pFrames + pMixBuf->offWrite, - pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), &convOpts); - Assert(cWritten == cToWrite); - -#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA - RTFILE fh; - RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_writecirc_ex.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTFileWrite(fh, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), NULL); - RTFileClose(fh); -#endif - pMixBuf->cUsed += cWritten; - Assert(pMixBuf->cUsed <= pMixBuf->cFrames); - - pMixBuf->offWrite = (pMixBuf->offWrite + cWritten) % pMixBuf->cFrames; - Assert(pMixBuf->offWrite <= pMixBuf->cFrames); + pMixBuf->Volume.fMuted = true; + pMixBuf->Volume.fAllMax = false; + for (uintptr_t i = 0; i < RT_ELEMENTS(pMixBuf->Volume.auChannels); i++) + pMixBuf->Volume.auChannels[i] = 0; } - else - rc = VERR_BUFFER_OVERFLOW; - -#ifdef DEBUG - audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); - audioMixBufDbgValidate(pMixBuf); -#endif - - if (pcWritten) - *pcWritten = cWritten; - - AUDMIXBUF_LOG(("%s: enmFmt=0x%x, cbBuf=%RU32 (%RU32 frames), cWritten=%RU32, rc=%Rrc\n", - pMixBuf->pszName, enmFmt, cbBuf, AUDIOMIXBUF_B2F(pMixBuf, cbBuf), cWritten, rc)); - return rc; -} - -/** - * Returns the current write position of a mixing buffer. - * - * @returns IPRT status code. - * @param pMixBuf Mixing buffer to return position for. - */ -uint32_t AudioMixBufWritePos(PPDMAUDIOMIXBUF pMixBuf) -{ - AssertPtrReturn(pMixBuf, 0); - - return pMixBuf->offWrite; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixBuffer.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixBuffer.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixBuffer.h 2020-10-16 16:32:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixBuffer.h 2022-09-01 13:23:47.000000000 +0000 @@ -24,70 +24,219 @@ #include #include -/** Constructs 32 bit value for given frequency, number of channels, bits per sample and signed bit. - * Note: This currently matches 1:1 the VRDE encoding -- this might change in the future, so better don't rely on this fact! */ -#define AUDMIXBUF_AUDIO_FMT_MAKE(freq, c, bps, s) ((((s) & 0x1) << 28) + (((bps) & 0xFF) << 20) + (((c) & 0xF) << 16) + ((freq) & 0xFFFF)) - -/** Decodes frequency (Hz). */ -#define AUDMIXBUF_FMT_SAMPLE_FREQ(a) ((a) & 0xFFFF) -/** Decodes number of channels. */ -#define AUDMIXBUF_FMT_CHANNELS(a) (((a) >> 16) & 0xF) -/** Decodes signed bit. */ -#define AUDMIXBUF_FMT_SIGNED(a) (((a) >> 28) & 0x1) -/** Decodes number of bits per sample. */ -#define AUDMIXBUF_FMT_BITS_PER_SAMPLE(a) (((a) >> 20) & 0xFF) -/** Decodes number of bytes per sample. */ -#define AUDMIXBUF_FMT_BYTES_PER_SAMPLE(a) ((AUDMIXBUF_AUDIO_FMT_BITS_PER_SAMPLE(a) + 7) / 8) - -/** Converts frames to bytes. */ -#define AUDIOMIXBUF_F2B(pBuf, frames) ((frames) << (pBuf)->cShift) -/** Converts frames to bytes, respecting the conversion ratio to - * a linked buffer. */ -#define AUDIOMIXBUF_F2B_RATIO(pBuf, frames) ((((int64_t) frames << 32) / (pBuf)->iFreqRatio) << (pBuf)->cShift) -/** Converts bytes to frames, *not* taking the conversion ratio - * into account. */ -#define AUDIOMIXBUF_B2F(pBuf, cb) (cb >> (pBuf)->cShift) -/** Converts number of frames according to the buffer's ratio. */ -#define AUDIOMIXBUF_F2F_RATIO(pBuf, frames) (((int64_t) frames << 32) / (pBuf)->iFreqRatio) - - -inline uint32_t AudioMixBufBytesToSamples(PPDMAUDIOMIXBUF pMixBuf); -void AudioMixBufClear(PPDMAUDIOMIXBUF pMixBuf); -void AudioMixBufDestroy(PPDMAUDIOMIXBUF pMixBuf); -void AudioMixBufFinish(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToClear); -uint32_t AudioMixBufFree(PPDMAUDIOMIXBUF pMixBuf); -uint32_t AudioMixBufFreeBytes(PPDMAUDIOMIXBUF pMixBuf); -int AudioMixBufInit(PPDMAUDIOMIXBUF pMixBuf, const char *pszName, PPDMAUDIOPCMPROPS pProps, uint32_t cFrames); -bool AudioMixBufIsEmpty(PPDMAUDIOMIXBUF pMixBuf); -int AudioMixBufLinkTo(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOMIXBUF pParent); -uint32_t AudioMixBufLive(PPDMAUDIOMIXBUF pMixBuf); -int AudioMixBufMixToParent(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcFrames, uint32_t *pcSrcMixed); -int AudioMixBufMixToParentEx(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcOffset, uint32_t cSrcFrames, uint32_t *pcSrcMixed); -int AudioMixBufPeek(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead, PPDMAUDIOFRAME paSampleBuf, uint32_t cSampleBuf, uint32_t *pcFramesRead); -int AudioMixBufPeekMutable(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead, PPDMAUDIOFRAME *ppvSamples, uint32_t *pcFramesRead); -uint32_t AudioMixBufUsed(PPDMAUDIOMIXBUF pMixBuf); -uint32_t AudioMixBufUsedBytes(PPDMAUDIOMIXBUF pMixBuf); -int AudioMixBufReadAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offSamples, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead); -int AudioMixBufReadAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, uint32_t offSamples, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead); -int AudioMixBufAcquireReadBlock(PPDMAUDIOMIXBUF pMixBuf, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock); -int AudioMixBufAcquireReadBlockEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock); -void AudioMixBufReleaseReadBlock(PPDMAUDIOMIXBUF pMixBuf, uint32_t cBlock); -uint32_t AudioMixBufReadPos(PPDMAUDIOMIXBUF pMixBuf); -void AudioMixBufReset(PPDMAUDIOMIXBUF pMixBuf); -void AudioMixBufSetVolume(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOVOLUME pVol); -uint32_t AudioMixBufSize(PPDMAUDIOMIXBUF pMixBuf); -uint32_t AudioMixBufSizeBytes(PPDMAUDIOMIXBUF pMixBuf); -void AudioMixBufUnlink(PPDMAUDIOMIXBUF pMixBuf); -int AudioMixBufWriteAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offSamples, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten); -int AudioMixBufWriteAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, uint32_t offSamples, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten); -int AudioMixBufWriteCirc(PPDMAUDIOMIXBUF pMixBuf, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten); -int AudioMixBufWriteCircEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten); -uint32_t AudioMixBufWritePos(PPDMAUDIOMIXBUF pMixBuf); - -#ifdef DEBUG -void AudioMixBufDbgPrint(PPDMAUDIOMIXBUF pMixBuf); -void AudioMixBufDbgPrintChain(PPDMAUDIOMIXBUF pMixBuf); -#endif +/** @defgroup grp_pdm_ifs_audio_mixing_buffers Audio Mixing Buffers + * @ingroup grp_pdm_ifs_audio_mixing + * + * @note This is currently placed under PDM Audio Interface as that seemed like + * the best place for it. + * + * @{ + */ + + +/** + * Rate processing information of a source & destination audio stream. + * + * This is needed because both streams can differ regarding their rates and + * therefore need to be treated accordingly. + */ +typedef struct AUDIOSTREAMRATE +{ + /** Current (absolute) offset in the output (destination) stream. + * @todo r=bird: Please reveal which unit these members are given in. */ + uint64_t offDst; + /** Increment for moving offDst for the destination stream. + * This is needed because the source <-> destination rate might be different. */ + uint64_t uDstInc; + /** Current (absolute) offset in the input stream. */ + uint32_t offSrc; + /** Set if no conversion is necessary. */ + bool fNoConversionNeeded; + bool afPadding[3]; + + /** Last processed frame of the input stream. + * Needed for interpolation. */ + union + { + int32_t ai32Samples[PDMAUDIO_MAX_CHANNELS]; + } SrcLast; + + /** + * Resampling function. + * @returns Number of destination frames written. + */ + DECLR3CALLBACKMEMBER(uint32_t, pfnResample, (int32_t *pi32Dst, uint32_t cDstFrames, + int32_t const *pi32Src, uint32_t cSrcFrames, uint32_t *pcSrcFramesRead, + struct AUDIOSTREAMRATE *pRate)); + +} AUDIOSTREAMRATE; +/** Pointer to rate processing information of a stream. */ +typedef AUDIOSTREAMRATE *PAUDIOSTREAMRATE; + +/** + * Mixing buffer volume parameters. + * + * The volume values are in fixed point style and must be converted to/from + * before using with e.g. PDMAUDIOVOLUME. + */ +typedef struct AUDMIXBUFVOL +{ + /** Set to @c true if this stream is muted, @c false if not. */ + bool fMuted; + /** Set if all (relevant) channels are at max. */ + bool fAllMax; + /** The per-channels values. */ + uint32_t auChannels[PDMAUDIO_MAX_CHANNELS]; +} AUDMIXBUFVOL; +/** Pointer to mixing buffer volument parameters. */ +typedef AUDMIXBUFVOL *PAUDMIXBUFVOL; + + +/** Pointer to audio mixing buffer. */ +typedef struct AUDIOMIXBUF *PAUDIOMIXBUF; +/** Pointer to a const audio mixing buffer. */ +typedef struct AUDIOMIXBUF const *PCAUDIOMIXBUF; + + +/** + * State & config for AudioMixBufPeek created by AudioMixBufInitPeekState. + */ +typedef struct AUDIOMIXBUFPEEKSTATE +{ + /** Encodes @a cFrames from @a paSrc to @a pvDst. */ + DECLR3CALLBACKMEMBER(void, pfnEncode,(void *pvDst, int32_t const *paSrc, uint32_t cFrames, struct AUDIOMIXBUFPEEKSTATE *pState)); + /** Sample rate conversion state (only used when needed). */ + AUDIOSTREAMRATE Rate; + /** Source (mixer) channels. */ + uint8_t cSrcChannels; + /** Destination channels. */ + uint8_t cDstChannels; + /** Destination frame size. */ + uint8_t cbDstFrame; + /** The destination frame layout described as indexes into the source frame. + * This ASSUMES that all channels uses the same sample size, so one sample per + * channel if you like. + * Negative values are special: -1 for zero, -2 for silence. + * @note Blending stereo into mono is not really expressible here. */ + int8_t aidxChannelMap[PDMAUDIO_MAX_CHANNELS]; +} AUDIOMIXBUFPEEKSTATE; +/** Pointer to peek state & config. */ +typedef AUDIOMIXBUFPEEKSTATE *PAUDIOMIXBUFPEEKSTATE; + + +/** + * State & config for AudioMixBufWrite, AudioMixBufSilence, AudioMixBufBlend and + * AudioMixBufBlendGap, created by AudioMixBufInitWriteState. + */ +typedef struct AUDIOMIXBUFWRITESTATE +{ + /** Encodes @a cFrames from @a pvSrc to @a paDst. */ + DECLR3CALLBACKMEMBER(void, pfnDecode,(int32_t *paDst, const void *pvSrc, uint32_t cFrames, struct AUDIOMIXBUFWRITESTATE *pState)); + /** Encodes @a cFrames from @a pvSrc blending into @a paDst. */ + DECLR3CALLBACKMEMBER(void, pfnDecodeBlend,(int32_t *paDst, const void *pvSrc, uint32_t cFrames, struct AUDIOMIXBUFWRITESTATE *pState)); + /** Sample rate conversion state (only used when needed). */ + AUDIOSTREAMRATE Rate; + /** Destination (mixer) channels. */ + uint8_t cDstChannels; + /** Source hannels. */ + uint8_t cSrcChannels; + /** Source frame size. */ + uint8_t cbSrcFrame; + /** The destination frame layout described as indexes into the source frame. + * This ASSUMES that all channels uses the same sample size, so one sample per + * channel if you like. + * Negative values are special: -1 for zero, -2 for silence. + * @note Blending stereo into mono is not really expressible here. */ + int8_t aidxChannelMap[PDMAUDIO_MAX_CHANNELS]; +} AUDIOMIXBUFWRITESTATE; +/** Pointer to write state & config. */ +typedef AUDIOMIXBUFWRITESTATE *PAUDIOMIXBUFWRITESTATE; + + +/** + * Audio mixing buffer. + */ +typedef struct AUDIOMIXBUF +{ + /** Magic value (AUDIOMIXBUF_MAGIC). */ + uint32_t uMagic; + /** Size of the frame buffer (in audio frames). */ + uint32_t cFrames; + /** The frame buffer. + * This is a two dimensional array consisting of cFrames rows and + * cChannels columns. */ + int32_t *pi32Samples; + /** The number of channels. */ + uint8_t cChannels; + /** The frame size (row size if you like). */ + uint8_t cbFrame; + uint8_t abPadding[2]; + /** The current read position (in frames). */ + uint32_t offRead; + /** The current write position (in frames). */ + uint32_t offWrite; + /** How much audio frames are currently being used in this buffer. + * @note This also is known as the distance in ring buffer terms. */ + uint32_t cUsed; + /** Audio properties for the buffer content - for frequency and channel count. + * (This is the guest side PCM properties.) */ + PDMAUDIOPCMPROPS Props; + /** Internal representation of current volume used for mixing. */ + AUDMIXBUFVOL Volume; + /** Name of the buffer. */ + char *pszName; +} AUDIOMIXBUF; + +/** Magic value for AUDIOMIXBUF (Antonio Lucio Vivaldi). */ +#define AUDIOMIXBUF_MAGIC UINT32_C(0x16780304) +/** Dead mixer buffer magic. */ +#define AUDIOMIXBUF_MAGIC_DEAD UINT32_C(0x17410728) + +/** Converts (audio) frames to bytes. */ +#define AUDIOMIXBUF_F2B(a_pMixBuf, a_cFrames) PDMAUDIOPCMPROPS_F2B(&(a_pMixBuf)->Props, a_cFrames) +/** Converts bytes to (audio) frames. + * @note Does *not* take the conversion ratio into account. */ +#define AUDIOMIXBUF_B2F(a_pMixBuf, a_cb) PDMAUDIOPCMPROPS_B2F(&(a_pMixBuf)->Props, a_cb) + + +int AudioMixBufInit(PAUDIOMIXBUF pMixBuf, const char *pszName, PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames); +void AudioMixBufTerm(PAUDIOMIXBUF pMixBuf); +void AudioMixBufDrop(PAUDIOMIXBUF pMixBuf); +void AudioMixBufSetVolume(PAUDIOMIXBUF pMixBuf, PCPDMAUDIOVOLUME pVol); + +/** @name Mixer buffer getters + * @{ */ +uint32_t AudioMixBufSize(PCAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufSizeBytes(PCAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufUsed(PCAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufUsedBytes(PCAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufFree(PCAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufFreeBytes(PCAUDIOMIXBUF pMixBuf); +bool AudioMixBufIsEmpty(PCAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufReadPos(PCAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufWritePos(PCAUDIOMIXBUF pMixBuf); +/** @} */ + +/** @name Mixer buffer reading + * @{ */ +int AudioMixBufInitPeekState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFPEEKSTATE pState, PCPDMAUDIOPCMPROPS pDstProps); +void AudioMixBufPeek(PCAUDIOMIXBUF pMixBuf, uint32_t offSrcFrame, uint32_t cMaxSrcFrames, uint32_t *pcSrcFramesPeeked, + PAUDIOMIXBUFPEEKSTATE pState, void *pvDst, uint32_t cbDst, uint32_t *pcbDstPeeked); +void AudioMixBufAdvance(PAUDIOMIXBUF pMixBuf, uint32_t cFrames); +/** @} */ + +/** @name Mixer buffer writing + * @{ */ +int AudioMixBufInitWriteState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, PCPDMAUDIOPCMPROPS pSrcProps); +void AudioMixBufWrite(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cMaxDstFrames, uint32_t *pcDstFramesWritten); +void AudioMixBufSilence(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t offFrame, uint32_t cFrames); +void AudioMixBufBlend(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf, + uint32_t offDstFrame, uint32_t cMaxDstFrames, uint32_t *pcDstFramesBlended); +void AudioMixBufBlendGap(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t cFrames); +void AudioMixBufCommit(PAUDIOMIXBUF pMixBuf, uint32_t cFrames); +/** @} */ +/** @} */ #endif /* !VBOX_INCLUDED_SRC_Audio_AudioMixBuffer_h */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixer.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixer.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixer.cpp 2020-10-16 16:32:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixer.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -1,56 +1,6 @@ /* $Id: AudioMixer.cpp $ */ /** @file * Audio mixing routines for multiplexing audio sources in device emulations. - * - * == Overview - * - * This mixer acts as a layer between the audio connector interface and - * the actual device emulation, providing mechanisms for audio sources (input) - * and audio sinks (output). - * - * Think of this mixer as kind of a high(er) level interface for the audio - * connector interface, abstracting common tasks such as creating and managing - * various audio sources and sinks. This mixer class is purely optional and can - * be left out when implementing a new device emulation, using only the audi - * connector interface instead. For example, the SB16 emulation does not use - * this mixer and does all its stream management on its own. - * - * As audio driver instances are handled as LUNs on the device level, this - * audio mixer then can take care of e.g. mixing various inputs/outputs to/from - * a specific source/sink. - * - * How and which audio streams are connected to sinks/sources depends on how - * the audio mixer has been set up. - * - * A sink can connect multiple output streams together, whereas a source - * does this with input streams. Each sink / source consists of one or more - * so-called mixer streams, which then in turn have pointers to the actual - * PDM audio input/output streams. - * - * == Playback - * - * For output sinks there can be one or more mixing stream attached. - * As the host sets the overall pace for the device emulation (virtual time - * in the guest OS vs. real time on the host OS), an output mixing sink - * needs to make sure that all connected output streams are able to accept - * all the same amount of data at a time. - * - * This is called synchronous multiplexing. - * - * A mixing sink employs an own audio mixing buffer, which in turn can convert - * the audio (output) data supplied from the device emulation into the sink's - * audio format. As all connected mixing streams in theory could have the same - * audio format as the mixing sink (parent), this can save processing time when - * it comes to serving a lot of mixing streams at once. That way only one - * conversion must be done, instead of each stream having to iterate over the - * data. - * - * == Recording - * - * For input sinks only one mixing stream at a time can be the recording - * source currently. A recording source is optional, e.g. it is possible to - * have no current recording source set. Switching to a different recording - * source at runtime is possible. */ /* @@ -65,6 +15,56 @@ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ +/** @page pg_audio_mixer Audio Mixer + * + * @section sec_audio_mixer_overview Overview + * + * This mixer acts as a layer between the audio connector interface and the + * actual device emulation, providing mechanisms for audio input sinks (sometime + * referred to as audio sources) and audio output sinks. + * + * Think of this mixer as kind of a higher level interface for the audio device + * to use in steado of PDMIAUDIOCONNECTOR, where it works with sinks rather than + * individual PDMAUDIOSTREAM instances. + * + * How and which audio streams are connected to the sinks depends on how the + * audio mixer has been set up by the device. Though, generally, each driver + * chain (LUN) has a mixer stream for each sink. + * + * An output sink can connect multiple output streams together, whereas an input + * sink (source) does this with input streams. Each of these mixer stream will + * in turn point to actual PDMAUDIOSTREAM instances. + * + * A mixing sink employs an own audio mixing buffer in a standard format (32-bit + * signed) with the virtual device's rate and channel configuration. The mixer + * streams will convert to/from this as they write and read from it. + * + * + * @section sec_audio_mixer_playback Playback + * + * For output sinks there can be one or more mixing stream attached. + * + * The backends are the consumers here and if they don't get samples when then + * need them we'll be having cracles, distortion and/or bits of silence in the + * actual output. The guest runs independently at it's on speed (see @ref + * sec_pdm_audio_timing for more details) and we're just inbetween trying to + * shuffle the data along as best as we can. If one or more of the backends + * for some reason isn't able to process data at a nominal speed (as defined by + * the others), we'll try detect this, mark it as bad and disregard it when + * calculating how much we can write to the backends in a buffer update call. + * + * This is called synchronous multiplexing. + * + * + * @section sec_audio_mixer_recording Recording + * + * For input sinks (sources) we blend the samples of all mixing streams + * together, however ignoring silent ones to avoid too much of a hit on the + * volume level. It is otherwise very similar to playback, only the direction + * is different and we don't multicast but blend. + * + */ + /********************************************************************************************************************************* * Header Files * @@ -73,362 +73,269 @@ #include #include "AudioMixer.h" #include "AudioMixBuffer.h" -#include "DrvAudio.h" +#include "AudioHlp.h" #include #include #include #include +#include #include #include #include +#include #include +#include + +#ifdef VBOX_WITH_DTRACE +# include "dtrace/VBoxDD.h" +#endif /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ -static int audioMixerAddSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink); static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink); -static int audioMixerSinkInit(PAUDMIXSINK pSink, PAUDIOMIXER pMixer, const char *pcszName, AUDMIXSINKDIR enmDir); -static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink); -static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster); -static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink); +static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink, PPDMDEVINS pDevIns); +static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVolMaster); static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); -static void audioMixerSinkReset(PAUDMIXSINK pSink); -static int audioMixerSinkSetRecSourceInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); -static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink); -static int audioMixerSinkMultiplexSync(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWrittenMin); -static int audioMixerSinkWriteToStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream); -static int audioMixerSinkWriteToStreamEx(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream, uint32_t cbToWrite, uint32_t *pcbWritten); +static void audioMixerSinkResetInternal(PAUDMIXSINK pSink); -static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl); -static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pStream); +static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd); +static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pStream, PPDMDEVINS pDevIns, bool fImmediate); static int audioMixerStreamUpdateStatus(PAUDMIXSTREAM pMixStream); -/** - * Converts a mixer sink status to a string. - * - * @returns Stringified mixer sink status flags. Must be free'd with RTStrFree(). - * "NONE" if no flags set. - * @param fStatus Mixer sink status to convert. - */ -static char *dbgAudioMixerSinkStatusToStr(AUDMIXSINKSTS fStatus) -{ -#define APPEND_FLAG_TO_STR(_aFlag) \ - if (fStatus & AUDMIXSINK_STS_##_aFlag) \ - { \ - if (pszFlags) \ - { \ - rc2 = RTStrAAppend(&pszFlags, " "); \ - if (RT_FAILURE(rc2)) \ - break; \ - } \ - \ - rc2 = RTStrAAppend(&pszFlags, #_aFlag); \ - if (RT_FAILURE(rc2)) \ - break; \ - } \ - - char *pszFlags = NULL; - int rc2 = VINF_SUCCESS; - - if (fStatus == AUDMIXSINK_STS_NONE) /* This is special, as this is value 0. */ - { - rc2 = RTStrAAppend(&pszFlags, "NONE"); - } - else - { - do - { - APPEND_FLAG_TO_STR(RUNNING); - APPEND_FLAG_TO_STR(PENDING_DISABLE); - APPEND_FLAG_TO_STR(DIRTY); - - } while (0); - } - - if ( RT_FAILURE(rc2) - && pszFlags) - { - RTStrFree(pszFlags); - pszFlags = NULL; - } - -#undef APPEND_FLAG_TO_STR - - return pszFlags; -} +/** size of output buffer for dbgAudioMixerSinkStatusToStr. */ +#define AUDIOMIXERSINK_STATUS_STR_MAX sizeof("RUNNING DRAINING DRAINED_DMA DRAINED_MIXBUF DIRTY 0x12345678") /** - * Creates an audio sink and attaches it to the given mixer. + * Converts a mixer sink status to a string. * - * @returns IPRT status code. - * @param pMixer Mixer to attach created sink to. - * @param pszName Name of the sink to create. - * @param enmDir Direction of the sink to create. - * @param ppSink Pointer which returns the created sink on success. - */ -int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink) -{ - AssertPtrReturn(pMixer, VERR_INVALID_POINTER); - AssertPtrReturn(pszName, VERR_INVALID_POINTER); - /* ppSink is optional. */ - - int rc = RTCritSectEnter(&pMixer->CritSect); - if (RT_FAILURE(rc)) - return rc; - - PAUDMIXSINK pSink = (PAUDMIXSINK)RTMemAllocZ(sizeof(AUDMIXSINK)); - if (pSink) - { - rc = audioMixerSinkInit(pSink, pMixer, pszName, enmDir); - if (RT_SUCCESS(rc)) - { - rc = audioMixerAddSinkInternal(pMixer, pSink); - if (RT_SUCCESS(rc)) + * @returns pszDst + * @param fStatus The mixer sink status. + * @param pszDst The output buffer. Must be at least + * AUDIOMIXERSINK_STATUS_STR_MAX in length. + */ +static const char *dbgAudioMixerSinkStatusToStr(uint32_t fStatus, char pszDst[AUDIOMIXERSINK_STATUS_STR_MAX]) +{ + if (!fStatus) + return strcpy(pszDst, "NONE"); + static const struct + { + const char *pszMnemonic; + uint32_t cchMnemonic; + uint32_t fStatus; + } s_aFlags[] = + { + { RT_STR_TUPLE("RUNNING "), AUDMIXSINK_STS_RUNNING }, + { RT_STR_TUPLE("DRAINING "), AUDMIXSINK_STS_DRAINING }, + { RT_STR_TUPLE("DRAINED_DMA "), AUDMIXSINK_STS_DRAINED_DMA }, + { RT_STR_TUPLE("DRAINED_MIXBUF "), AUDMIXSINK_STS_DRAINED_MIXBUF }, + { RT_STR_TUPLE("DIRTY "), AUDMIXSINK_STS_DIRTY }, + }; + char *psz = pszDst; + for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++) + if (fStatus & s_aFlags[i].fStatus) + { + memcpy(psz, s_aFlags[i].pszMnemonic, s_aFlags[i].cchMnemonic); + psz += s_aFlags[i].cchMnemonic; + fStatus &= ~s_aFlags[i].fStatus; + if (!fStatus) { - if (ppSink) - *ppSink = pSink; + psz[-1] = '\0'; + return pszDst; } } - - if (RT_FAILURE(rc)) - { - audioMixerSinkDestroyInternal(pSink); - - RTMemFree(pSink); - pSink = NULL; - } - } - else - rc = VERR_NO_MEMORY; - - int rc2 = RTCritSectLeave(&pMixer->CritSect); - AssertRC(rc2); - - return rc; + RTStrPrintf(psz, AUDIOMIXERSINK_STATUS_STR_MAX - (psz - pszDst), "%#x", fStatus); + return pszDst; } + /** * Creates an audio mixer. * - * @returns IPRT status code. - * @param pcszName Name of the audio mixer. - * @param fFlags Creation flags. Not used at the moment and must be 0. - * @param ppMixer Pointer which returns the created mixer object. - */ -int AudioMixerCreate(const char *pcszName, uint32_t fFlags, PAUDIOMIXER *ppMixer) -{ - RT_NOREF(fFlags); - AssertPtrReturn(pcszName, VERR_INVALID_POINTER); - /** @todo Add fFlags validation. */ + * @returns VBox status code. + * @param pszName Name of the audio mixer. + * @param fFlags Creation flags - AUDMIXER_FLAGS_XXX. + * @param ppMixer Pointer which returns the created mixer object. + */ +int AudioMixerCreate(const char *pszName, uint32_t fFlags, PAUDIOMIXER *ppMixer) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + size_t const cchName = strlen(pszName); + AssertReturn(cchName > 0 && cchName < 128, VERR_INVALID_NAME); + AssertReturn (!(fFlags & ~AUDMIXER_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); AssertPtrReturn(ppMixer, VERR_INVALID_POINTER); - int rc = VINF_SUCCESS; - - PAUDIOMIXER pMixer = (PAUDIOMIXER)RTMemAllocZ(sizeof(AUDIOMIXER)); + int rc; + PAUDIOMIXER pMixer = (PAUDIOMIXER)RTMemAllocZVar(sizeof(AUDIOMIXER) + cchName + 1); if (pMixer) { - pMixer->pszName = RTStrDup(pcszName); - if (!pMixer->pszName) - rc = VERR_NO_MEMORY; - - if (RT_SUCCESS(rc)) - rc = RTCritSectInit(&pMixer->CritSect); - + rc = RTCritSectInit(&pMixer->CritSect); if (RT_SUCCESS(rc)) { + pMixer->pszName = (const char *)memcpy(pMixer + 1, pszName, cchName + 1); + pMixer->cSinks = 0; RTListInit(&pMixer->lstSinks); + pMixer->fFlags = fFlags; + pMixer->uMagic = AUDIOMIXER_MAGIC; + + if (pMixer->fFlags & AUDMIXER_FLAGS_DEBUG) + LogRel(("Audio Mixer: Debug mode enabled\n")); + /* Set master volume to the max. */ - pMixer->VolMaster.fMuted = false; - pMixer->VolMaster.uLeft = PDMAUDIO_VOLUME_MAX; - pMixer->VolMaster.uRight = PDMAUDIO_VOLUME_MAX; + PDMAudioVolumeInitMax(&pMixer->VolMaster); LogFlowFunc(("Created mixer '%s'\n", pMixer->pszName)); - *ppMixer = pMixer; + return VINF_SUCCESS; } - else - RTMemFree(pMixer); + RTMemFree(pMixer); } else rc = VERR_NO_MEMORY; - LogFlowFuncLeaveRC(rc); return rc; } -/** - * Helper function for the internal debugger to print the mixer's current - * state, along with the attached sinks. - * - * @param pMixer Mixer to print debug output for. - * @param pHlp Debug info helper to use. - * @param pszArgs Optional arguments. Not being used at the moment. - */ -void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - RT_NOREF(pszArgs); - PAUDMIXSINK pSink; - unsigned iSink = 0; - - int rc2 = RTCritSectEnter(&pMixer->CritSect); - if (RT_FAILURE(rc2)) - return; - - pHlp->pfnPrintf(pHlp, "[Master] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", pMixer->pszName, - pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight, pMixer->VolMaster.fMuted); - - RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node) - { - pHlp->pfnPrintf(pHlp, "[Sink %u] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", iSink, pSink->pszName, - pSink->Volume.uLeft, pSink->Volume.uRight, pSink->Volume.fMuted); - ++iSink; - } - - rc2 = RTCritSectLeave(&pMixer->CritSect); - AssertRC(rc2); -} /** * Destroys an audio mixer. * - * @param pMixer Audio mixer to destroy. + * @param pMixer Audio mixer to destroy. NULL is ignored. + * @param pDevIns The device instance the statistics are associated with. */ -void AudioMixerDestroy(PAUDIOMIXER pMixer) +void AudioMixerDestroy(PAUDIOMIXER pMixer, PPDMDEVINS pDevIns) { if (!pMixer) return; + AssertPtrReturnVoid(pMixer); + AssertReturnVoid(pMixer->uMagic == AUDIOMIXER_MAGIC); int rc2 = RTCritSectEnter(&pMixer->CritSect); - AssertRC(rc2); + AssertRCReturnVoid(rc2); + Assert(pMixer->uMagic == AUDIOMIXER_MAGIC); LogFlowFunc(("Destroying %s ...\n", pMixer->pszName)); + pMixer->uMagic = AUDIOMIXER_MAGIC_DEAD; PAUDMIXSINK pSink, pSinkNext; RTListForEachSafe(&pMixer->lstSinks, pSink, pSinkNext, AUDMIXSINK, Node) { - /* Save a pointer to the sink to remove, as pSink - * will not be valid anymore after calling audioMixerRemoveSinkInternal(). */ - PAUDMIXSINK pSinkToRemove = pSink; - - audioMixerRemoveSinkInternal(pMixer, pSinkToRemove); - - audioMixerSinkDestroyInternal(pSinkToRemove); - - RTMemFree(pSinkToRemove); - } - - pMixer->cSinks = 0; - - if (pMixer->pszName) - { - RTStrFree(pMixer->pszName); - pMixer->pszName = NULL; + audioMixerRemoveSinkInternal(pMixer, pSink); + audioMixerSinkDestroyInternal(pSink, pDevIns); } + Assert(pMixer->cSinks == 0); rc2 = RTCritSectLeave(&pMixer->CritSect); AssertRC(rc2); RTCritSectDelete(&pMixer->CritSect); - RTMemFree(pMixer); - pMixer = NULL; } + /** - * Invalidates all internal data, internal version. + * Helper function for the internal debugger to print the mixer's current + * state, along with the attached sinks. * - * @returns IPRT status code. - * @param pMixer Mixer to invalidate data for. + * @param pMixer Mixer to print debug output for. + * @param pHlp Debug info helper to use. + * @param pszArgs Optional arguments. Not being used at the moment. */ -int audioMixerInvalidateInternal(PAUDIOMIXER pMixer) +void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs) { - AssertPtrReturn(pMixer, VERR_INVALID_POINTER); + RT_NOREF(pszArgs); + AssertReturnVoid(pMixer->uMagic == AUDIOMIXER_MAGIC); - LogFlowFunc(("[%s]\n", pMixer->pszName)); + int rc = RTCritSectEnter(&pMixer->CritSect); + AssertRCReturnVoid(rc); - /* Propagate new master volume to all connected sinks. */ + /* Determin max sink name length for pretty formatting: */ + size_t cchMaxName = strlen(pMixer->pszName); PAUDMIXSINK pSink; RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node) { - int rc2 = audioMixerSinkUpdateVolume(pSink, &pMixer->VolMaster); - AssertRC(rc2); + size_t const cchMixer = strlen(pSink->pszName); + cchMaxName = RT_MAX(cchMixer, cchMaxName); } - return VINF_SUCCESS; -} - -/** - * Invalidates all internal data. - * - * @returns IPRT status code. - * @param pMixer Mixer to invalidate data for. - */ -void AudioMixerInvalidate(PAUDIOMIXER pMixer) -{ - AssertPtrReturnVoid(pMixer); - - int rc2 = RTCritSectEnter(&pMixer->CritSect); - AssertRC(rc2); - - LogFlowFunc(("[%s]\n", pMixer->pszName)); - - rc2 = audioMixerInvalidateInternal(pMixer); - AssertRC(rc2); + /* Do the displaying. */ + pHlp->pfnPrintf(pHlp, "[Master] %*s: fMuted=%#RTbool auChannels=%.*Rhxs\n", cchMaxName, pMixer->pszName, + pMixer->VolMaster.fMuted, sizeof(pMixer->VolMaster.auChannels), pMixer->VolMaster.auChannels); + unsigned iSink = 0; + RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node) + { + pHlp->pfnPrintf(pHlp, "[Sink %u] %*s: fMuted=%#RTbool auChannels=%.*Rhxs\n", iSink, cchMaxName, pSink->pszName, + pSink->Volume.fMuted, sizeof(pSink->Volume.auChannels), pSink->Volume.auChannels); + ++iSink; + } - rc2 = RTCritSectLeave(&pMixer->CritSect); - AssertRC(rc2); + RTCritSectLeave(&pMixer->CritSect); } + /** - * Adds sink to an existing mixer. + * Sets the mixer's master volume. * * @returns VBox status code. - * @param pMixer Mixer to add sink to. - * @param pSink Sink to add. + * @param pMixer Mixer to set master volume for. + * @param pVol Volume to set. */ -static int audioMixerAddSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink) +int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PCPDMAUDIOVOLUME pVol) { AssertPtrReturn(pMixer, VERR_INVALID_POINTER); - AssertPtrReturn(pSink, VERR_INVALID_POINTER); + AssertReturn(pMixer->uMagic == AUDIOMIXER_MAGIC, VERR_INVALID_MAGIC); + AssertPtrReturn(pVol, VERR_INVALID_POINTER); - /** @todo Check upper sink limit? */ - /** @todo Check for double-inserted sinks? */ + int rc = RTCritSectEnter(&pMixer->CritSect); + AssertRCReturn(rc, rc); - RTListAppend(&pMixer->lstSinks, &pSink->Node); - pMixer->cSinks++; + /* + * Make a copy. + */ + LogFlowFunc(("[%s] fMuted=%RTbool auChannels=%.*Rhxs => fMuted=%RTbool auChannels=%.*Rhxs\n", pMixer->pszName, + pMixer->VolMaster.fMuted, sizeof(pMixer->VolMaster.auChannels), pMixer->VolMaster.auChannels, + pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels )); + memcpy(&pMixer->VolMaster, pVol, sizeof(PDMAUDIOVOLUME)); - LogFlowFunc(("pMixer=%p, pSink=%p, cSinks=%RU8\n", - pMixer, pSink, pMixer->cSinks)); + /* + * Propagate new master volume to all sinks. + */ + PAUDMIXSINK pSink; + RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node) + { + int rc2 = audioMixerSinkUpdateVolume(pSink, &pMixer->VolMaster); + AssertRC(rc2); + } - return VINF_SUCCESS; + RTCritSectLeave(&pMixer->CritSect); + return rc; } + /** - * Removes a formerly attached audio sink for an audio mixer, internal version. + * Removes an audio sink from the given audio mixer, internal version. + * + * Used by AudioMixerDestroy and AudioMixerSinkDestroy. * - * @returns IPRT status code. + * Caller must hold the mixer lock. + * + * @returns VBox status code. * @param pMixer Mixer to remove sink from. * @param pSink Sink to remove. */ static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink) { - AssertPtrReturn(pMixer, VERR_INVALID_POINTER); - if (!pSink) - return VERR_NOT_FOUND; - - AssertMsgReturn(pSink->pParent == pMixer, ("%s: Is not part of mixer '%s'\n", - pSink->pszName, pMixer->pszName), VERR_NOT_FOUND); - - LogFlowFunc(("[%s] pSink=%s, cSinks=%RU8\n", - pMixer->pszName, pSink->pszName, pMixer->cSinks)); + LogFlowFunc(("[%s] pSink=%s, cSinks=%RU8\n", pMixer->pszName, pSink->pszName, pMixer->cSinks)); + Assert(RTCritSectIsOwner(&pMixer->CritSect)); + AssertMsgReturn(pSink->pParent == pMixer, + ("%s: Is not part of mixer '%s'\n", pSink->pszName, pMixer->pszName), VERR_INTERNAL_ERROR_4); /* Remove sink from mixer. */ RTListNodeRemove(&pSink->Node); @@ -442,1668 +349,2141 @@ return VINF_SUCCESS; } -/** - * Removes a formerly attached audio sink for an audio mixer. - * - * @returns IPRT status code. - * @param pMixer Mixer to remove sink from. - * @param pSink Sink to remove. - */ -void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink) -{ - int rc2 = RTCritSectEnter(&pMixer->CritSect); - AssertRC(rc2); - - audioMixerSinkRemoveAllStreamsInternal(pSink); - audioMixerRemoveSinkInternal(pMixer, pSink); - rc2 = RTCritSectLeave(&pMixer->CritSect); -} +/********************************************************************************************************************************* +* Mixer Sink implementation. * +*********************************************************************************************************************************/ /** - * Sets the mixer's master volume. + * Creates an audio sink and attaches it to the given mixer. * - * @returns IPRT status code. - * @param pMixer Mixer to set master volume for. - * @param pVol Volume to set. + * @returns VBox status code. + * @param pMixer Mixer to attach created sink to. + * @param pszName Name of the sink to create. + * @param enmDir Direction of the sink to create. + * @param pDevIns The device instance to register statistics under. + * @param ppSink Pointer which returns the created sink on success. */ -int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol) +int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, PDMAUDIODIR enmDir, PPDMDEVINS pDevIns, PAUDMIXSINK *ppSink) { AssertPtrReturn(pMixer, VERR_INVALID_POINTER); - AssertPtrReturn(pVol, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + size_t const cchName = strlen(pszName); + AssertReturn(cchName > 0 && cchName < 64, VERR_INVALID_NAME); + AssertPtrNullReturn(ppSink, VERR_INVALID_POINTER); int rc = RTCritSectEnter(&pMixer->CritSect); - if (RT_FAILURE(rc)) - return rc; - - memcpy(&pMixer->VolMaster, pVol, sizeof(PDMAUDIOVOLUME)); + AssertRCReturn(rc, rc); - LogFlowFunc(("[%s] lVol=%RU32, rVol=%RU32 => fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n", - pMixer->pszName, pVol->uLeft, pVol->uRight, - pMixer->VolMaster.fMuted, pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight)); + /** @todo limit the number of sinks? */ - rc = audioMixerInvalidateInternal(pMixer); + /* + * Allocate the data and initialize the critsect. + */ + PAUDMIXSINK pSink = (PAUDMIXSINK)RTMemAllocZVar(sizeof(AUDMIXSINK) + cchName + 1); + if (pSink) + { + rc = RTCritSectInit(&pSink->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Initialize it. + */ + pSink->uMagic = AUDMIXSINK_MAGIC; + pSink->pParent = NULL; + pSink->enmDir = enmDir; + pSink->pszName = (const char *)memcpy(pSink + 1, pszName, cchName + 1); + RTListInit(&pSink->lstStreams); + + /* Set initial volume to max. */ + PDMAudioVolumeInitMax(&pSink->Volume); + + /* Ditto for the combined volume. */ + PDMAudioVolumeInitMax(&pSink->VolumeCombined); + + /* AIO */ + AssertPtr(pDevIns); + pSink->AIO.pDevIns = pDevIns; + pSink->AIO.hThread = NIL_RTTHREAD; + pSink->AIO.hEvent = NIL_RTSEMEVENT; + pSink->AIO.fStarted = false; + pSink->AIO.fShutdown = false; + pSink->AIO.cUpdateJobs = 0; + + /* + * Add it to the mixer. + */ + RTListAppend(&pMixer->lstSinks, &pSink->Node); + pMixer->cSinks++; + pSink->pParent = pMixer; + + RTCritSectLeave(&pMixer->CritSect); + + /* + * Register stats and return. + */ + char szPrefix[128]; + RTStrPrintf(szPrefix, sizeof(szPrefix), "MixerSink-%s/", pSink->pszName); + PDMDevHlpSTAMRegisterF(pDevIns, &pSink->MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Sink mixer buffer size in frames.", "%sMixBufSize", szPrefix); + PDMDevHlpSTAMRegisterF(pDevIns, &pSink->MixBuf.cUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Sink mixer buffer fill size in frames.", "%sMixBufUsed", szPrefix); + PDMDevHlpSTAMRegisterF(pDevIns, &pSink->cStreams, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Number of streams attached to the sink.", "%sStreams", szPrefix); + + if (ppSink) + *ppSink = pSink; + return VINF_SUCCESS; + } - int rc2 = RTCritSectLeave(&pMixer->CritSect); - AssertRC(rc2); + RTMemFree(pSink); + } + else + rc = VERR_NO_MEMORY; + RTCritSectLeave(&pMixer->CritSect); + if (ppSink) + *ppSink = NULL; return rc; } -/********************************************************************************************************************************* - * Mixer Sink implementation. - ********************************************************************************************************************************/ /** - * Adds an audio stream to a specific audio sink. + * Starts playback/capturing on the mixer sink. * - * @returns IPRT status code. - * @param pSink Sink to add audio stream to. - * @param pStream Stream to add. + * @returns VBox status code. Generally always VINF_SUCCESS unless the input + * is invalid. Individual driver errors are suppressed and ignored. + * @param pSink Mixer sink to control. */ -int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +int AudioMixerSinkStart(PAUDMIXSINK pSink) { - AssertPtrReturn(pSink, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; - - if (pSink->cStreams == UINT8_MAX) /* 255 streams per sink max. */ - { - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - - return VERR_NO_MORE_HANDLES; - } - - LogFlowFuncEnter(); + AssertRCReturn(rc, rc); + char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX]; + LogFunc(("Starting '%s'. Old status: %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus))); - /** @todo Check if stream already is assigned to (another) sink. */ + AssertReturnStmt(pSink->enmDir == PDMAUDIODIR_IN || pSink->enmDir == PDMAUDIODIR_OUT, + RTCritSectLeave(&pSink->CritSect), VERR_INTERNAL_ERROR_3); - /* If the sink is running and not in pending disable mode, - * make sure that the added stream also is enabled. */ - if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING) - && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)) + /* + * Make sure the sink and its streams are all stopped. + */ + if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING)) + Assert(pSink->fStatus == AUDMIXSINK_STS_NONE); + else { - rc = audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE, AUDMIXSTRMCTL_F_NONE); - if (rc == VERR_AUDIO_STREAM_NOT_READY) - rc = VINF_SUCCESS; /* Not fatal here, stream can become available at some later point in time. */ - } + LogFunc(("%s: This sink is still running!! Stop it before starting it again.\n", pSink->pszName)); - if (RT_SUCCESS(rc)) - { - /* Apply the sink's combined volume to the stream. */ - rc = pStream->pConn->pfnStreamSetVolume(pStream->pConn, pStream->pStream, &pSink->VolumeCombined); - AssertRC(rc); + PAUDMIXSTREAM pStream; + RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) + { + /** @todo PDMAUDIOSTREAMCMD_STOP_NOW */ + audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE); + } + audioMixerSinkResetInternal(pSink); } - if (RT_SUCCESS(rc)) + /* + * Send the command to the streams. + */ + PAUDMIXSTREAM pStream; + RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) { - /* Save pointer to sink the stream is attached to. */ - pStream->pSink = pSink; - - /* Append stream to sink's list. */ - RTListAppend(&pSink->lstStreams, &pStream->Node); - pSink->cStreams++; + audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE); } - LogFlowFunc(("[%s] cStreams=%RU8, rc=%Rrc\n", pSink->pszName, pSink->cStreams, rc)); + /* + * Update the sink status. + */ + pSink->fStatus = AUDMIXSINK_STS_RUNNING; - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + LogRel2(("Audio Mixer: Started sink '%s': %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus))); - return rc; + RTCritSectLeave(&pSink->CritSect); + return VINF_SUCCESS; } + /** - * Creates an audio mixer stream. + * Helper for AudioMixerSinkDrainAndStop that calculates the max length a drain + * operation should take. * - * @returns IPRT status code. - * @param pSink Sink to use for creating the stream. - * @param pConn Audio connector interface to use. - * @param pCfg Audio stream configuration to use. - * @param fFlags Stream flags. Currently unused, set to 0. - * @param ppStream Pointer which receives the newly created audio stream. + * @returns The drain deadline (relative to RTTimeNanoTS). + * @param pSink The sink. + * @param cbDmaLeftToDrain The number of bytes in the DMA buffer left to + * transfer into the mixbuf. */ -int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, - PPDMIAUDIOCONNECTOR pConn, PPDMAUDIOSTREAMCFG pCfg, AUDMIXSTREAMFLAGS fFlags, PAUDMIXSTREAM *ppStream) +static uint64_t audioMixerSinkDrainDeadline(PAUDMIXSINK pSink, uint32_t cbDmaLeftToDrain) { - AssertPtrReturn(pSink, VERR_INVALID_POINTER); - AssertPtrReturn(pConn, VERR_INVALID_POINTER); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - /** @todo Validate fFlags. */ - /* ppStream is optional. */ - - if (pConn->pfnGetStatus(pConn, PDMAUDIODIR_ANY) == PDMAUDIOBACKENDSTS_NOT_ATTACHED) - return VERR_AUDIO_BACKEND_NOT_ATTACHED; - - PAUDMIXSTREAM pMixStream = (PAUDMIXSTREAM)RTMemAllocZ(sizeof(AUDMIXSTREAM)); - if (!pMixStream) - return VERR_NO_MEMORY; - - PDMAUDIOBACKENDCFG BackendCfg; - int rc = pConn->pfnGetConfig(pConn, &BackendCfg); - if (RT_FAILURE(rc)) - { - RTMemFree(pMixStream); - return rc; - } - - /* Assign the backend's name to the mixer stream's name for easier identification in the (release) log. */ - pMixStream->pszName = RTStrAPrintf2("[%s] %s", pCfg->szName, BackendCfg.szName); - if (!pMixStream->pszName) - { - RTMemFree(pMixStream); - return VERR_NO_MEMORY; - } - - rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; - - LogFlowFunc(("[%s] fFlags=0x%x (enmDir=%ld, %u bits, %RU8 channels, %RU32Hz)\n", - pSink->pszName, fFlags, pCfg->enmDir, pCfg->Props.cbSample * 8, pCfg->Props.cChannels, pCfg->Props.uHz)); - /* - * Initialize the host-side configuration for the stream to be created. - * Always use the sink's PCM audio format as the host side when creating a stream for it. + * Calculate the max backend buffer size in mixbuf frames. + * (This is somewhat similar to audioMixerSinkUpdateOutputCalcFramesToRead.) */ - AssertMsg(DrvAudioHlpPCMPropsAreValid(&pSink->PCMProps), - ("%s: Does not (yet) have a format set when it must\n", pSink->pszName)); - - PDMAUDIOSTREAMCFG CfgHost; - rc = DrvAudioHlpPCMPropsToStreamCfg(&pSink->PCMProps, &CfgHost); - AssertRCReturn(rc, rc); - - /* Apply the sink's direction for the configuration to use to - * create the stream. */ - if (pSink->enmDir == AUDMIXSINKDIR_INPUT) - { - CfgHost.enmDir = PDMAUDIODIR_IN; - CfgHost.u.enmSrc = pCfg->u.enmSrc; - CfgHost.enmLayout = pCfg->enmLayout; - } - else - { - CfgHost.enmDir = PDMAUDIODIR_OUT; - CfgHost.u.enmDst = pCfg->u.enmDst; - CfgHost.enmLayout = pCfg->enmLayout; - } - - RTStrPrintf(CfgHost.szName, sizeof(CfgHost.szName), "%s", pCfg->szName); - - rc = RTCritSectInit(&pMixStream->CritSect); - if (RT_SUCCESS(rc)) - { - PPDMAUDIOSTREAM pStream; - rc = pConn->pfnStreamCreate(pConn, &CfgHost, pCfg, &pStream); - if (RT_SUCCESS(rc)) - { - /* Save the audio stream pointer to this mixing stream. */ - pMixStream->pStream = pStream; - - /* Increase the stream's reference count to let others know - * we're reyling on it to be around now. */ - pConn->pfnStreamRetain(pConn, pStream); - } - } - - if (RT_SUCCESS(rc)) - { - rc = RTCircBufCreate(&pMixStream->pCircBuf, DrvAudioHlpMilliToBytes(100 /* ms */, &pSink->PCMProps)); /** @todo Make this configurable. */ - AssertRC(rc); - } - - if (RT_SUCCESS(rc)) - { - pMixStream->fFlags = fFlags; - pMixStream->pConn = pConn; - - if (ppStream) - *ppStream = pMixStream; - } - else if (pMixStream) + uint32_t cFramesStreamMax = 0; + PAUDMIXSTREAM pMixStream; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) { - int rc2 = RTCritSectDelete(&pMixStream->CritSect); - AssertRC(rc2); - - if (pMixStream->pszName) + /*LogFunc(("Stream '%s': %#x (%u frames)\n", pMixStream->pszName, pMixStream->fStatus, pMixStream->cFramesBackendBuffer));*/ + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE) { - RTStrFree(pMixStream->pszName); - pMixStream->pszName = NULL; + uint32_t cFrames = pMixStream->cFramesBackendBuffer; + if (PDMAudioPropsHz(&pMixStream->pStream->Cfg.Props) == PDMAudioPropsHz(&pSink->MixBuf.Props)) + { /* likely */ } + else + cFrames = cFrames * PDMAudioPropsHz(&pSink->MixBuf.Props) / PDMAudioPropsHz(&pMixStream->pStream->Cfg.Props); + if (cFrames > cFramesStreamMax) + { + Log4Func(("%s: cFramesStreamMax %u -> %u; %s\n", pSink->pszName, cFramesStreamMax, cFrames, pMixStream->pszName)); + cFramesStreamMax = cFrames; + } } - - RTMemFree(pMixStream); - pMixStream = NULL; } - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - - return rc; + /* + * Combine that with the pending DMA and mixbuf content, then convert + * to nanoseconds and apply a fudge factor to get a generous deadline. + */ + uint32_t const cFramesDmaAndMixBuf = PDMAudioPropsBytesToFrames(&pSink->MixBuf.Props, cbDmaLeftToDrain) + + AudioMixBufUsed(&pSink->MixBuf); + uint64_t const cNsToDrainMax = PDMAudioPropsFramesToNano(&pSink->MixBuf.Props, cFramesDmaAndMixBuf + cFramesStreamMax); + uint64_t const nsDeadline = cNsToDrainMax * 2; + LogFlowFunc(("%s: cFramesStreamMax=%#x cFramesDmaAndMixBuf=%#x -> cNsToDrainMax=%RU64 -> %RU64\n", + pSink->pszName, cFramesStreamMax, cFramesDmaAndMixBuf, cNsToDrainMax, nsDeadline)); + return nsDeadline; } -/** - * Static helper function to translate a sink command - * to a PDM audio stream command. - * - * @returns PDM audio stream command, or PDMAUDIOSTREAMCMD_UNKNOWN if not found. - * @param enmCmd Mixer sink command to translate. - */ -static PDMAUDIOSTREAMCMD audioMixerSinkToStreamCmd(AUDMIXSINKCMD enmCmd) -{ - switch (enmCmd) - { - case AUDMIXSINKCMD_ENABLE: return PDMAUDIOSTREAMCMD_ENABLE; - case AUDMIXSINKCMD_DISABLE: return PDMAUDIOSTREAMCMD_DISABLE; - case AUDMIXSINKCMD_PAUSE: return PDMAUDIOSTREAMCMD_PAUSE; - case AUDMIXSINKCMD_RESUME: return PDMAUDIOSTREAMCMD_RESUME; - case AUDMIXSINKCMD_DROP: return PDMAUDIOSTREAMCMD_DROP; - default: break; - } - - AssertMsgFailed(("Unsupported sink command %d\n", enmCmd)); - return PDMAUDIOSTREAMCMD_UNKNOWN; -} /** - * Controls a mixer sink. + * Kicks off the draining and stopping playback/capture on the mixer sink. + * + * For input streams this causes an immediate stop, as draining only makes sense + * to output stream in the VBox device context. * - * @returns IPRT status code. - * @param pSink Mixer sink to control. - * @param enmSinkCmd Sink command to set. + * @returns VBox status code. Generally always VINF_SUCCESS unless the input + * is invalid. Individual driver errors are suppressed and ignored. + * @param pSink Mixer sink to control. + * @param cbComming The number of bytes still left in the device's DMA + * buffers that the update job has yet to transfer. This + * is ignored for input streams. */ -int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmSinkCmd) +int AudioMixerSinkDrainAndStop(PAUDMIXSINK pSink, uint32_t cbComming) { AssertPtrReturn(pSink, VERR_INVALID_POINTER); - - PDMAUDIOSTREAMCMD enmCmdStream = audioMixerSinkToStreamCmd(enmSinkCmd); - if (enmCmdStream == PDMAUDIOSTREAMCMD_UNKNOWN) - return VERR_NOT_SUPPORTED; + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; - - /* Input sink and no recording source set? Bail out early. */ - if ( pSink->enmDir == AUDMIXSINKDIR_INPUT - && pSink->In.pStreamRecSource == NULL) - { - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + AssertRCReturn(rc, rc); + char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX]; + LogFunc(("Draining '%s' with %#x bytes left. Old status: %s\n", + pSink->pszName, cbComming, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus) )); - return rc; - } + AssertReturnStmt(pSink->enmDir == PDMAUDIODIR_IN || pSink->enmDir == PDMAUDIODIR_OUT, + RTCritSectLeave(&pSink->CritSect), VERR_INTERNAL_ERROR_3); - PAUDMIXSTREAM pStream; - if ( pSink->enmDir == AUDMIXSINKDIR_INPUT - && pSink->In.pStreamRecSource) /* Any recording source set? */ + if (pSink->fStatus & AUDMIXSINK_STS_RUNNING) { - RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) + /* + * Output streams will be drained then stopped (all by the AIO thread). + * + * For streams we define that they shouldn't not be written to after we start draining, + * so we have to hold back sending the command to them till we've processed all the + * cbComming remaining bytes in the DMA buffer. + */ + if (pSink->enmDir == PDMAUDIODIR_OUT) { - if (pStream == pSink->In.pStreamRecSource) + if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING)) { - int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_F_NONE); - if (rc2 == VERR_NOT_SUPPORTED) - rc2 = VINF_SUCCESS; + Assert(!(pSink->fStatus & (AUDMIXSINK_STS_DRAINED_DMA | AUDMIXSINK_STS_DRAINED_MIXBUF))); - if (RT_SUCCESS(rc)) - rc = rc2; - /* Keep going. Flag? */ + /* Update the status and draining member. */ + pSink->cbDmaLeftToDrain = cbComming; + pSink->nsDrainDeadline = audioMixerSinkDrainDeadline(pSink, cbComming); + if (pSink->nsDrainDeadline > 0) + { + pSink->nsDrainStarted = RTTimeNanoTS(); + pSink->nsDrainDeadline += pSink->nsDrainStarted; + pSink->fStatus |= AUDMIXSINK_STS_DRAINING; + + /* Kick the AIO thread so it can keep pushing data till we're out of this + status. (The device's DMA timer won't kick it any more, so we must.) */ + AudioMixerSinkSignalUpdateJob(pSink); + } + else + { + LogFunc(("%s: No active streams, doing an immediate stop.\n", pSink->pszName)); + PAUDMIXSTREAM pStream; + RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) + { + audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE); + } + audioMixerSinkResetInternal(pSink); + } } + else + AssertMsgFailed(("Already draining '%s': %s\n", + pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus))); } - } - else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT) - { - RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) - { - int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_F_NONE); - if (rc2 == VERR_NOT_SUPPORTED) - rc2 = VINF_SUCCESS; - - if (RT_SUCCESS(rc)) - rc = rc2; - /* Keep going. Flag? */ - } - } - - switch (enmSinkCmd) - { - case AUDMIXSINKCMD_ENABLE: - { - /* Make sure to clear any other former flags again by assigning AUDMIXSINK_STS_RUNNING directly. */ - pSink->fStatus = AUDMIXSINK_STS_RUNNING; - break; - } - - case AUDMIXSINKCMD_DISABLE: + /* + * Input sinks are stopped immediately. + * + * It's the guest giving order here and we can't force it to accept data that's + * already in the buffer pipeline or anything. So, there can be no draining here. + */ + else { - if (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + PAUDMIXSTREAM pStream; + RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) { - /* Set the sink in a pending disable state first. - * The final status (disabled) will be set in the sink's iteration. */ - pSink->fStatus |= AUDMIXSINK_STS_PENDING_DISABLE; + audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE); } - break; + audioMixerSinkResetInternal(pSink); } - - case AUDMIXSINKCMD_DROP: - { - AudioMixBufReset(&pSink->MixBuf); - - /* Clear dirty bit, keep others. */ - pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY; - break; - } - - default: - AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); - break; } + else + LogFunc(("%s: Not running\n", pSink->pszName)); - char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus); - LogRel2(("Audio Mixer: Set new status of sink '%s' to %s\n", pSink->pszName, pszStatus)); - LogFlowFunc(("[%s] enmCmd=%RU32, fStatus=%s, rc=%Rrc\n", pSink->pszName, enmSinkCmd, pszStatus, rc)); - RTStrFree(pszStatus); - - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - - return rc; + LogRel2(("Audio Mixer: Started draining sink '%s': %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus))); + RTCritSectLeave(&pSink->CritSect); + return VINF_SUCCESS; } -/** - * Initializes a sink. - * - * @returns VBox status code. - * @param pSink Sink to initialize. - * @param pMixer Mixer the sink is assigned to. - * @param pcszName Name of the sink. - * @param enmDir Direction of the sink. - */ -static int audioMixerSinkInit(PAUDMIXSINK pSink, PAUDIOMIXER pMixer, const char *pcszName, AUDMIXSINKDIR enmDir) -{ - pSink->pszName = RTStrDup(pcszName); - if (!pSink->pszName) - return VERR_NO_MEMORY; - - int rc = RTCritSectInit(&pSink->CritSect); - if (RT_SUCCESS(rc)) - { - pSink->pParent = pMixer; - pSink->enmDir = enmDir; - - RTListInit(&pSink->lstStreams); - - /* Set initial volume to max. */ - pSink->Volume.fMuted = false; - pSink->Volume.uLeft = PDMAUDIO_VOLUME_MAX; - pSink->Volume.uRight = PDMAUDIO_VOLUME_MAX; - - /* Ditto for the combined volume. */ - pSink->VolumeCombined.fMuted = false; - pSink->VolumeCombined.uLeft = PDMAUDIO_VOLUME_MAX; - pSink->VolumeCombined.uRight = PDMAUDIO_VOLUME_MAX; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} /** - * Destroys a mixer sink and removes it from the attached mixer (if any). + * Destroys and frees a mixer sink. * - * @param pSink Mixer sink to destroy. - */ -void AudioMixerSinkDestroy(PAUDMIXSINK pSink) -{ - if (!pSink) - return; - - int rc2 = RTCritSectEnter(&pSink->CritSect); - AssertRC(rc2); - - if (pSink->pParent) - { - /* Save mixer pointer, as after audioMixerRemoveSinkInternal() the - * pointer will be gone from the stream. */ - PAUDIOMIXER pMixer = pSink->pParent; - AssertPtr(pMixer); - - audioMixerRemoveSinkInternal(pMixer, pSink); - } - - rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - - audioMixerSinkDestroyInternal(pSink); - - RTMemFree(pSink); - pSink = NULL; -} - -/** - * Destroys a mixer sink. + * Worker for AudioMixerSinkDestroy(), AudioMixerCreateSink() and + * AudioMixerDestroy(). * - * @param pSink Mixer sink to destroy. + * @param pSink Mixer sink to destroy. + * @param pDevIns The device instance statistics are registered with. */ -static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink) +static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink, PPDMDEVINS pDevIns) { AssertPtrReturnVoid(pSink); LogFunc(("%s\n", pSink->pszName)); + /* + * Invalidate the sink instance. + */ + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + pSink->uMagic = AUDMIXSINK_MAGIC_DEAD; + + /* + * Destroy all streams. + */ PAUDMIXSTREAM pStream, pStreamNext; RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node) { - /* Save a pointer to the stream to remove, as pStream - * will not be valid anymore after calling audioMixerSinkRemoveStreamInternal(). */ - PAUDMIXSTREAM pStreamToRemove = pStream; + audioMixerSinkRemoveStreamInternal(pSink, pStream); + audioMixerStreamDestroyInternal(pStream, pDevIns, true /*fImmediate*/); + } - audioMixerSinkRemoveStreamInternal(pSink, pStreamToRemove); - audioMixerStreamDestroyInternal(pStreamToRemove); + /* + * Destroy debug file and statistics. + */ + if (!pSink->Dbg.pFile) + { /* likely */ } + else + { + AudioHlpFileDestroy(pSink->Dbg.pFile); + pSink->Dbg.pFile = NULL; } -#ifdef VBOX_AUDIO_MIXER_DEBUG - DrvAudioHlpFileDestroy(pSink->Dbg.pFile); - pSink->Dbg.pFile = NULL; -#endif + char szPrefix[128]; + RTStrPrintf(szPrefix, sizeof(szPrefix), "MixerSink-%s/", pSink->pszName); + PDMDevHlpSTAMDeregisterByPrefix(pDevIns, szPrefix); - if (pSink->pszName) + /* + * Shutdown the AIO thread if started: + */ + ASMAtomicWriteBool(&pSink->AIO.fShutdown, true); + if (pSink->AIO.hEvent != NIL_RTSEMEVENT) + { + int rc2 = RTSemEventSignal(pSink->AIO.hEvent); + AssertRC(rc2); + } + if (pSink->AIO.hThread != NIL_RTTHREAD) { - RTStrFree(pSink->pszName); - pSink->pszName = NULL; + LogFlowFunc(("Waiting for AIO thread for %s...\n", pSink->pszName)); + int rc2 = RTThreadWait(pSink->AIO.hThread, RT_MS_30SEC, NULL); + AssertRC(rc2); + pSink->AIO.hThread = NIL_RTTHREAD; + } + if (pSink->AIO.hEvent != NIL_RTSEMEVENT) + { + int rc2 = RTSemEventDestroy(pSink->AIO.hEvent); + AssertRC(rc2); + pSink->AIO.hEvent = NIL_RTSEMEVENT; } - AudioMixBufDestroy(&pSink->MixBuf); + /* + * Mixing buffer, critsect and the structure itself. + */ + AudioMixBufTerm(&pSink->MixBuf); RTCritSectDelete(&pSink->CritSect); + RTMemFree(pSink); } + /** - * Returns the amount of bytes ready to be read from a sink since the last call - * to AudioMixerSinkUpdate(). + * Destroys a mixer sink and removes it from the attached mixer (if any). * - * @returns Amount of bytes ready to be read from the sink. - * @param pSink Sink to return number of available bytes for. + * @param pSink Mixer sink to destroy. NULL is ignored. + * @param pDevIns The device instance that statistics are registered with. */ -uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink) +void AudioMixerSinkDestroy(PAUDMIXSINK pSink, PPDMDEVINS pDevIns) { - AssertPtrReturn(pSink, 0); - - AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("%s: Can't read from a non-input sink\n", pSink->pszName)); + if (!pSink) + return; + AssertReturnVoid(pSink->uMagic == AUDMIXSINK_MAGIC); + /* + * Serializing paranoia. + */ int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return 0; + AssertRCReturnVoid(rc); + RTCritSectLeave(&pSink->CritSect); - uint32_t cbReadable = 0; - - if (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + /* + * Unlink from parent. + */ + PAUDIOMIXER pMixer = pSink->pParent; + if ( RT_VALID_PTR(pMixer) + && pMixer->uMagic == AUDIOMIXER_MAGIC) { -#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF_IN -# error "Implement me!" -#else - PAUDMIXSTREAM pStreamRecSource = pSink->In.pStreamRecSource; - if (!pStreamRecSource) - { - Log3Func(("[%s] No recording source specified, skipping ...\n", pSink->pszName)); - } - else - { - AssertPtr(pStreamRecSource->pConn); - cbReadable = pStreamRecSource->pConn->pfnStreamGetReadable(pStreamRecSource->pConn, pStreamRecSource->pStream); - } -#endif + RTCritSectEnter(&pMixer->CritSect); + audioMixerRemoveSinkInternal(pMixer, pSink); + RTCritSectLeave(&pMixer->CritSect); } + else if (pMixer) + AssertFailed(); - Log3Func(("[%s] cbReadable=%RU32\n", pSink->pszName, cbReadable)); - - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - - return cbReadable; + /* + * Actually destroy it. + */ + audioMixerSinkDestroyInternal(pSink, pDevIns); } + /** - * Returns the sink's current recording source. + * Get the number of bytes that can be read from the sink. * - * @return Mixer stream which currently is set as current recording source, NULL if none is set. - * @param pSink Audio mixer sink to return current recording source for. + * @returns Number of bytes. + * @param pSink The mixer sink. + * + * @note Only applicable to input sinks, will assert and return zero for + * other sink directions. */ -PAUDMIXSTREAM AudioMixerSinkGetRecordingSource(PAUDMIXSINK pSink) +uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink) { - int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return NULL; - - AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Specified sink is not an input sink\n")); + AssertPtrReturn(pSink, 0); + AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, 0); + AssertMsgReturn(pSink->enmDir == PDMAUDIODIR_IN, ("%s: Can't read from a non-input sink\n", pSink->pszName), 0); - PAUDMIXSTREAM pStream = pSink->In.pStreamRecSource; + int rc = RTCritSectEnter(&pSink->CritSect); + AssertRCReturn(rc, 0); - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + uint32_t cbReadable = 0; + if (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + cbReadable = AudioMixBufUsedBytes(&pSink->MixBuf); - return pStream; + RTCritSectLeave(&pSink->CritSect); + Log3Func(("[%s] cbReadable=%#x\n", pSink->pszName, cbReadable)); + return cbReadable; } + /** - * Returns the amount of bytes ready to be written to a sink since the last call - * to AudioMixerSinkUpdate(). + * Get the number of bytes that can be written to be sink. + * + * @returns Number of bytes. + * @param pSink The mixer sink. * - * @returns Amount of bytes ready to be written to the sink. - * @param pSink Sink to return number of available bytes for. + * @note Only applicable to output sinks, will assert and return zero for + * other sink directions. */ uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink) { AssertPtrReturn(pSink, 0); - - AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, ("%s: Can't write to a non-output sink\n", pSink->pszName)); + AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, 0); + AssertMsgReturn(pSink->enmDir == PDMAUDIODIR_OUT, ("%s: Can't write to a non-output sink\n", pSink->pszName), 0); int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return 0; + AssertRCReturn(rc, 0); uint32_t cbWritable = 0; - - if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING) - && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)) - { + if ((pSink->fStatus & (AUDMIXSINK_STS_RUNNING | AUDMIXSINK_STS_DRAINING)) == AUDMIXSINK_STS_RUNNING) cbWritable = AudioMixBufFreeBytes(&pSink->MixBuf); - } - - Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n", - pSink->pszName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pSink->PCMProps))); - - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + RTCritSectLeave(&pSink->CritSect); + Log3Func(("[%s] cbWritable=%#x (%RU64ms)\n", pSink->pszName, cbWritable, + PDMAudioPropsBytesToMilli(&pSink->PCMProps, cbWritable) )); return cbWritable; } + /** - * Returns the sink's mixing direction. + * Get the sink's mixing direction. * * @returns Mixing direction. - * @param pSink Sink to return direction for. + * @param pSink The mixer sink. */ -AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink) +PDMAUDIODIR AudioMixerSinkGetDir(PCAUDMIXSINK pSink) { - AssertPtrReturn(pSink, AUDMIXSINKDIR_UNKNOWN); - - int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return AUDMIXSINKDIR_UNKNOWN; - - const AUDMIXSINKDIR enmDir = pSink->enmDir; + AssertPtrReturn(pSink, PDMAUDIODIR_INVALID); + AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, PDMAUDIODIR_INVALID); - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - - return enmDir; + /* The sink direction cannot be changed after creation, so no need for locking here. */ + return pSink->enmDir; } -/** - * Returns the sink's (friendly) name. - * - * @returns The sink's (friendly) name. - */ -const char *AudioMixerSinkGetName(const PAUDMIXSINK pSink) -{ - AssertPtrReturn(pSink, ""); - - return pSink->pszName; -} /** - * Returns a specific mixer stream from a sink, based on its index. + * Get the sink status. * - * @returns Mixer stream if found, or NULL if not found. - * @param pSink Sink to retrieve mixer stream from. - * @param uIndex Index of the mixer stream to return. + * @returns AUDMIXSINK_STS_XXX + * @param pSink The mixer sink. */ -PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex) +uint32_t AudioMixerSinkGetStatus(PAUDMIXSINK pSink) { - AssertPtrReturn(pSink, NULL); + AssertPtrReturn(pSink, AUDMIXSINK_STS_NONE); + AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, AUDMIXSINK_STS_NONE); int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return NULL; + AssertRCReturn(rc, AUDMIXSINK_STS_NONE); - AssertMsgReturn(uIndex < pSink->cStreams, - ("Index %RU8 exceeds stream count (%RU8)", uIndex, pSink->cStreams), NULL); + uint32_t const fStsSink = pSink->fStatus; - /* Slow lookup, d'oh. */ - PAUDMIXSTREAM pStream = RTListGetFirst(&pSink->lstStreams, AUDMIXSTREAM, Node); - while (uIndex) - { - pStream = RTListGetNext(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node); - uIndex--; - } - - /** @todo Do we need to raise the stream's reference count here? */ - - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - - AssertPtr(pStream); - return pStream; + RTCritSectLeave(&pSink->CritSect); + return fStsSink; } + /** - * Returns the current status of a mixer sink. + * Checks if the sink is active not. * - * @returns The sink's current status. - * @param pSink Mixer sink to return status for. + * @note The pending disable state also counts as active. + * + * @retval true if active. + * @retval false if not active. + * @param pSink The mixer sink. NULL is okay (returns false). */ -AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink) +bool AudioMixerSinkIsActive(PAUDMIXSINK pSink) { if (!pSink) - return AUDMIXSINK_STS_NONE; - - int rc2 = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc2)) - return AUDMIXSINK_STS_NONE; + return false; + AssertPtr(pSink); + AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, false); - /* If the dirty flag is set, there is unprocessed data in the sink. */ - AUDMIXSINKSTS stsSink = pSink->fStatus; + int rc = RTCritSectEnter(&pSink->CritSect); + AssertRCReturn(rc, false); - rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + bool const fIsActive = RT_BOOL(pSink->fStatus & AUDMIXSINK_STS_RUNNING); - return stsSink; + RTCritSectLeave(&pSink->CritSect); + Log3Func(("[%s] returns %RTbool\n", pSink->pszName, fIsActive)); + return fIsActive; } + /** - * Returns the number of attached mixer streams to a mixer sink. + * Resets the sink's state. * - * @returns The number of attached mixer streams. - * @param pSink Mixer sink to return number for. + * @param pSink The sink to reset. + * @note Must own sink lock. */ -uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink) +static void audioMixerSinkResetInternal(PAUDMIXSINK pSink) { - if (!pSink) - return 0; - - int rc2 = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc2)) - return 0; - - const uint8_t cStreams = pSink->cStreams; + Assert(RTCritSectIsOwner(&pSink->CritSect)); + LogFunc(("[%s]\n", pSink->pszName)); - rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + /* Drop mixing buffer content. */ + AudioMixBufDrop(&pSink->MixBuf); - return cStreams; + /* Reset status. */ + pSink->fStatus = AUDMIXSINK_STS_NONE; + pSink->tsLastUpdatedMs = 0; } + /** - * Returns whether the sink is in an active state or not. - * Note: The pending disable state also counts as active. + * Resets a sink. This will immediately stop all processing. * - * @returns True if active, false if not. - * @param pSink Sink to return active state for. + * @param pSink Sink to reset. */ -bool AudioMixerSinkIsActive(PAUDMIXSINK pSink) +void AudioMixerSinkReset(PAUDMIXSINK pSink) { if (!pSink) - return false; + return; + AssertReturnVoid(pSink->uMagic == AUDMIXSINK_MAGIC); - int rc2 = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc2)) - return false; + int rc = RTCritSectEnter(&pSink->CritSect); + AssertRCReturnVoid(rc); - const bool fIsActive = pSink->fStatus & AUDMIXSINK_STS_RUNNING; - /* Note: AUDMIXSINK_STS_PENDING_DISABLE implies AUDMIXSINK_STS_RUNNING. */ + LogFlowFunc(("[%s]\n", pSink->pszName)); - Log3Func(("[%s] fActive=%RTbool\n", pSink->pszName, fIsActive)); + /* + * Stop any stream that's enabled before resetting the state. + */ + PAUDMIXSTREAM pStream; + RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) + { + if (pStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED) + audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE); + } - rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + /* + * Reset the state. + */ + audioMixerSinkResetInternal(pSink); - return fIsActive; + RTCritSectLeave(&pSink->CritSect); } + /** - * Reads audio data from a mixer sink. + * Sets the audio format of a mixer sink. * - * @returns IPRT status code. - * @param pSink Mixer sink to read data from. - * @param enmOp Mixer operation to use for reading the data. - * @param pvBuf Buffer where to store the read data. - * @param cbBuf Buffer size (in bytes) where to store the data. - * @param pcbRead Number of bytes read. Optional. + * @returns VBox status code. + * @param pSink The sink to set audio format for. + * @param pProps The properties of the new audio format (guest side). + * @param cMsSchedulingHint Scheduling hint for mixer buffer sizing. */ -int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PCPDMAUDIOPCMPROPS pProps, uint32_t cMsSchedulingHint) { AssertPtrReturn(pSink, VERR_INVALID_POINTER); - RT_NOREF(enmOp); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(cbBuf, VERR_INVALID_PARAMETER); - /* pcbRead is optional. */ + AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, VERR_INVALID_MAGIC); + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + AssertReturn(AudioHlpPcmPropsAreValid(pProps), VERR_INVALID_PARAMETER); - /** @todo Handle mixing operation enmOp! */ + /* + * Calculate the mixer buffer size so we can force a recreation if it changes. + * + * This used to be fixed at 100ms, however that's usually too generous and can + * in theory be too small. Generally, we size the buffer at 3 DMA periods as + * that seems reasonable. Now, since the we don't quite trust the scheduling + * hint we're getting, make sure we're got a minimum of 30ms buffer space, but + * no more than 500ms. + */ + if (cMsSchedulingHint <= 10) + cMsSchedulingHint = 30; + else + { + cMsSchedulingHint *= 3; + if (cMsSchedulingHint > 500) + cMsSchedulingHint = 500; + } + uint32_t const cBufferFrames = PDMAudioPropsMilliToFrames(pProps, cMsSchedulingHint); + /** @todo configuration override on the buffer size? */ int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; - - AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, - ("Can't read from a sink which is not an input sink\n")); - - uint32_t cbRead = 0; - - /* Flag indicating whether this sink is in a 'clean' state, - * e.g. there is no more data to read from. */ - bool fClean = true; + AssertRCReturn(rc, rc); - PAUDMIXSTREAM pStreamRecSource = pSink->In.pStreamRecSource; - if (!pStreamRecSource) - { - Log3Func(("[%s] No recording source specified, skipping ...\n", pSink->pszName)); - } - else if (!(pStreamRecSource->fStatus & AUDMIXSTREAM_STATUS_ENABLED)) - { - Log3Func(("[%s] Stream '%s' disabled, skipping ...\n", pSink->pszName, pStreamRecSource->pszName)); - } - else + /* + * Do nothing unless the format actually changed. + * The buffer size must not match exactly, within +/- 2% is okay. + */ + uint32_t cOldBufferFrames; + if ( !PDMAudioPropsAreEqual(&pSink->PCMProps, pProps) + || ( cBufferFrames != (cOldBufferFrames = AudioMixBufSize(&pSink->MixBuf)) + && (uint32_t)RT_ABS((int32_t)(cBufferFrames - cOldBufferFrames)) > cBufferFrames / 50) ) { - uint32_t cbToRead = cbBuf; - while (cbToRead) - { - uint32_t cbReadStrm; - AssertPtr(pStreamRecSource->pConn); -#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF_IN -# error "Implement me!" -#else - rc = pStreamRecSource->pConn->pfnStreamRead(pStreamRecSource->pConn, pStreamRecSource->pStream, - (uint8_t *)pvBuf + cbRead, cbToRead, &cbReadStrm); +#ifdef LOG_ENABLED + char szTmp[PDMAUDIOPROPSTOSTRING_MAX]; #endif - if (RT_FAILURE(rc)) - LogFunc(("[%s] Failed reading from stream '%s': %Rrc\n", pSink->pszName, pStreamRecSource->pszName, rc)); - - Log3Func(("[%s] Stream '%s': Read %RU32 bytes\n", pSink->pszName, pStreamRecSource->pszName, cbReadStrm)); - - if ( RT_FAILURE(rc) - || !cbReadStrm) - break; - - AssertBreakStmt(cbReadStrm <= cbToRead, rc = VERR_BUFFER_OVERFLOW); - cbToRead -= cbReadStrm; - cbRead += cbReadStrm; - Assert(cbRead <= cbBuf); - } - - uint32_t cbReadable = pStreamRecSource->pConn->pfnStreamGetReadable(pStreamRecSource->pConn, pStreamRecSource->pStream); - - /* Still some data available? Then sink is not clean (yet). */ - if (cbReadable) - fClean = false; + if (PDMAudioPropsHz(&pSink->PCMProps) != 0) + LogFlowFunc(("[%s] Old format: %s; buffer: %u frames\n", pSink->pszName, + PDMAudioPropsToString(&pSink->PCMProps, szTmp, sizeof(szTmp)), AudioMixBufSize(&pSink->MixBuf) )); + pSink->PCMProps = *pProps; + LogFlowFunc(("[%s] New format: %s; buffer: %u frames\n", pSink->pszName, + PDMAudioPropsToString(&pSink->PCMProps, szTmp, sizeof(szTmp)), cBufferFrames )); + + /* + * Also update the sink's mixing buffer format. + */ + AudioMixBufTerm(&pSink->MixBuf); + rc = AudioMixBufInit(&pSink->MixBuf, pSink->pszName, &pSink->PCMProps, cBufferFrames); if (RT_SUCCESS(rc)) { - if (fClean) - pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY; + /* + * Input sinks must init their (mostly dummy) peek state. + */ + if (pSink->enmDir == PDMAUDIODIR_IN) + rc = AudioMixBufInitPeekState(&pSink->MixBuf, &pSink->In.State, &pSink->PCMProps); + else + rc = AudioMixBufInitWriteState(&pSink->MixBuf, &pSink->Out.State, &pSink->PCMProps); + if (RT_SUCCESS(rc)) + { + /* + * Re-initialize the peek/write states as the frequency, channel count + * and other things may have changed now. + */ + PAUDMIXSTREAM pMixStream; + if (pSink->enmDir == PDMAUDIODIR_IN) + { + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + int rc2 = AudioMixBufInitWriteState(&pSink->MixBuf, &pMixStream->WriteState, &pMixStream->pStream->Cfg.Props); + /** @todo remember this. */ + AssertLogRelRC(rc2); + } + } + else + { + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + int rc2 = AudioMixBufInitPeekState(&pSink->MixBuf, &pMixStream->PeekState, &pMixStream->pStream->Cfg.Props); + /** @todo remember this. */ + AssertLogRelRC(rc2); + } + } - /* Update our last read time stamp. */ - pSink->tsLastReadWrittenNs = RTTimeNanoTS(); + /* + * Debug. + */ + if (!(pSink->pParent->fFlags & AUDMIXER_FLAGS_DEBUG)) + { /* likely */ } + else + { + AudioHlpFileClose(pSink->Dbg.pFile); -#ifdef VBOX_AUDIO_MIXER_DEBUG - int rc2 = DrvAudioHlpFileWrite(pSink->Dbg.pFile, pvBuf, cbRead, 0 /* fFlags */); - AssertRC(rc2); -#endif + char szName[64]; + RTStrPrintf(szName, sizeof(szName), "MixerSink-%s", pSink->pszName); + AudioHlpFileCreateAndOpen(&pSink->Dbg.pFile, NULL /*pszDir - use temp dir*/, szName, + 0 /*iInstance*/, &pSink->PCMProps); + } + } + else + LogFunc(("%s failed: %Rrc\n", + pSink->enmDir == PDMAUDIODIR_IN ? "AudioMixBufInitPeekState" : "AudioMixBufInitWriteState", rc)); } + else + LogFunc(("AudioMixBufInit failed: %Rrc\n", rc)); } -#ifdef LOG_ENABLED - char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus); - Log2Func(("[%s] cbRead=%RU32, fClean=%RTbool, fStatus=%s, rc=%Rrc\n", pSink->pszName, cbRead, fClean, pszStatus, rc)); - RTStrFree(pszStatus); -#endif + RTCritSectLeave(&pSink->CritSect); + LogFlowFuncLeaveRC(rc); + return rc; +} - if (pcbRead) - *pcbRead = cbRead; - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); +/** + * Updates the combined volume (sink + mixer) of a mixer sink. + * + * @returns VBox status code. + * @param pSink The mixer sink to update volume for (valid). + * @param pVolMaster The master (mixer) volume (valid). + */ +static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVolMaster) +{ + AssertPtr(pSink); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + AssertPtr(pVolMaster); + LogFlowFunc(("[%s] Master fMuted=%RTbool auChannels=%.*Rhxs\n", + pSink->pszName, pVolMaster->fMuted, sizeof(pVolMaster->auChannels), pVolMaster->auChannels)); - return rc; + PDMAudioVolumeCombine(&pSink->VolumeCombined, &pSink->Volume, pVolMaster); + + LogFlowFunc(("[%s] fMuted=%RTbool auChannels=%.*Rhxs -> fMuted=%RTbool auChannels=%.*Rhxs\n", pSink->pszName, + pSink->Volume.fMuted, sizeof(pSink->Volume.auChannels), pSink->Volume.auChannels, + pSink->VolumeCombined.fMuted, sizeof(pSink->VolumeCombined.auChannels), pSink->VolumeCombined.auChannels )); + + AudioMixBufSetVolume(&pSink->MixBuf, &pSink->VolumeCombined); + return VINF_SUCCESS; } + /** - * Removes a mixer stream from a mixer sink, internal version. + * Sets the volume a mixer sink. * - * @returns IPRT status code. - * @param pSink Sink to remove mixer stream from. - * @param pStream Stream to remove. + * @returns VBox status code. + * @param pSink The sink to set volume for. + * @param pVol New volume settings. */ -static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVol) { - AssertPtrReturn(pSink, VERR_INVALID_PARAMETER); - if ( !pStream - || !pStream->pSink) /* Not part of a sink anymore? */ - { - return VERR_NOT_FOUND; - } - - AssertMsgReturn(pStream->pSink == pSink, ("Stream '%s' is not part of sink '%s'\n", - pStream->pszName, pSink->pszName), VERR_NOT_FOUND); + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, VERR_INVALID_MAGIC); + AssertPtrReturn(pVol, VERR_INVALID_POINTER); - LogFlowFunc(("[%s] (Stream = %s), cStreams=%RU8\n", - pSink->pszName, pStream->pStream->szName, pSink->cStreams)); + int rc = RTCritSectEnter(&pSink->CritSect); + AssertRCReturn(rc, rc); - /* Remove stream from sink. */ - RTListNodeRemove(&pStream->Node); + memcpy(&pSink->Volume, pVol, sizeof(PDMAUDIOVOLUME)); - int rc = VINF_SUCCESS; + LogRel2(("Audio Mixer: Setting volume of sink '%s' to fMuted=%RTbool auChannels=%.*Rhxs\n", + pSink->pszName, pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels)); - if (pSink->enmDir == AUDMIXSINKDIR_INPUT) - { - /* Make sure to also un-set the recording source if this stream was set - * as the recording source before. */ - if (pStream == pSink->In.pStreamRecSource) - rc = audioMixerSinkSetRecSourceInternal(pSink, NULL); - } + Assert(pSink->pParent); + if (pSink->pParent) + rc = audioMixerSinkUpdateVolume(pSink, &pSink->pParent->VolMaster); - /* Set sink to NULL so that we know we're not part of any sink anymore. */ - pStream->pSink = NULL; + RTCritSectLeave(&pSink->CritSect); return rc; } + /** - * Removes a mixer stream from a mixer sink. + * Helper for audioMixerSinkUpdateInput that determins now many frames it can + * transfer from the drivers and into the sink's mixer buffer. + * + * This also updates the mixer stream status, which may involve stream re-inits. * - * @param pSink Sink to remove mixer stream from. - * @param pStream Stream to remove. + * @returns Number of frames. + * @param pSink The sink. + * @param pcReadableStreams Where to return the number of readable streams. */ -void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +static uint32_t audioMixerSinkUpdateInputCalcFramesToTransfer(PAUDMIXSINK pSink, uint32_t *pcReadableStreams) { - int rc2 = RTCritSectEnter(&pSink->CritSect); - AssertRC(rc2); - - rc2 = audioMixerSinkRemoveStreamInternal(pSink, pStream); - if (RT_SUCCESS(rc2)) + uint32_t cFramesToRead = AudioMixBufFree(&pSink->MixBuf); + uint32_t cReadableStreams = 0; + PAUDMIXSTREAM pMixStream; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) { - Assert(pSink->cStreams); - pSink->cStreams--; + int rc2 = audioMixerStreamUpdateStatus(pMixStream); + AssertRC(rc2); + + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_READ) + { + PPDMIAUDIOCONNECTOR const pIConnector = pMixStream->pConn; + PPDMAUDIOSTREAM const pStream = pMixStream->pStream; + pIConnector->pfnStreamIterate(pIConnector, pStream); + + uint32_t const cbReadable = pIConnector->pfnStreamGetReadable(pIConnector, pStream); + uint32_t cFrames = PDMAudioPropsBytesToFrames(&pStream->Cfg.Props, cbReadable); + pMixStream->cFramesLastAvail = cFrames; + if (PDMAudioPropsHz(&pStream->Cfg.Props) == PDMAudioPropsHz(&pSink->MixBuf.Props)) + { /* likely */ } + else + { + cFrames = cFrames * PDMAudioPropsHz(&pSink->MixBuf.Props) / PDMAudioPropsHz(&pStream->Cfg.Props); + cFrames = cFrames > 2 ? cFrames - 2 : 0; /* rounding safety fudge */ + } + if (cFramesToRead > cFrames && !pMixStream->fUnreliable) + { + Log4Func(("%s: cFramesToRead %u -> %u; %s (%u bytes readable)\n", + pSink->pszName, cFramesToRead, cFrames, pMixStream->pszName, cbReadable)); + cFramesToRead = cFrames; + } + cReadableStreams++; + } } - rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + *pcReadableStreams = cReadableStreams; + return cFramesToRead; } + /** - * Removes all attached streams from a given sink. + * Updates an input mixer sink. * - * @param pSink Sink to remove attached streams from. + * @returns VBox status code. + * @param pSink Mixer sink to update. + * @param cbDmaBuf The number of bytes in the DMA buffer. For detecting + * underruns. Zero if we don't know. + * @param cbDmaPeriod The minimum number of bytes required for reliable DMA + * operation. Zero if we don't know. */ -static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink) +static int audioMixerSinkUpdateInput(PAUDMIXSINK pSink, uint32_t cbDmaBuf, uint32_t cbDmaPeriod) { - if (!pSink) - return; + PAUDMIXSTREAM pMixStream; + Assert(!(pSink->fStatus & AUDMIXSINK_STS_DRAINED_MIXBUF)); /* (can't drain input sink) */ - LogFunc(("%s\n", pSink->pszName)); + /* + * Iterate, update status and check each mixing sink stream for how much + * we can transfer. + * + * We're currently using the minimum size of all streams, however this + * isn't a smart approach as it means one disfunctional stream can block + * working ones. So, if we end up with zero frames and a full mixer + * buffer we'll disregard the stream that accept the smallest amount and + * try again. + */ + uint32_t cReadableStreams = 0; + uint32_t cFramesToXfer = audioMixerSinkUpdateInputCalcFramesToTransfer(pSink, &cReadableStreams); + if ( cFramesToXfer != 0 + || cReadableStreams <= 1 + || cbDmaPeriod == 0 /* Insufficient info to decide. The update function will call us again, at least for HDA. */ + || cbDmaBuf + PDMAudioPropsFramesToBytes(&pSink->PCMProps, AudioMixBufUsed(&pSink->MixBuf)) >= cbDmaPeriod) + Log3Func(("%s: cFreeFrames=%#x cFramesToXfer=%#x cReadableStreams=%#x\n", pSink->pszName, + AudioMixBufFree(&pSink->MixBuf), cFramesToXfer, cReadableStreams)); + else + { + Log3Func(("%s: MixBuf is underrunning but one or more streams only provides zero frames. Try disregarding those...\n", pSink->pszName)); + uint32_t cReliableStreams = 0; + uint32_t cMarkedUnreliable = 0; + PAUDMIXSTREAM pMixStreamMin = NULL; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_READ) + { + if (!pMixStream->fUnreliable) + { + if (pMixStream->cFramesLastAvail == 0) + { + cMarkedUnreliable++; + pMixStream->fUnreliable = true; + Log3Func(("%s: Marked '%s' as unreliable.\n", pSink->pszName, pMixStream->pszName)); + pMixStreamMin = pMixStream; + } + else + { + if (!pMixStreamMin || pMixStream->cFramesLastAvail < pMixStreamMin->cFramesLastAvail) + pMixStreamMin = pMixStream; + cReliableStreams++; + } + } + } + } - PAUDMIXSTREAM pStream, pStreamNext; - RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node) - audioMixerSinkRemoveStreamInternal(pSink, pStream); -} + if (cMarkedUnreliable == 0 && cReliableStreams > 1 && pMixStreamMin != NULL) + { + cReliableStreams--; + cMarkedUnreliable++; + pMixStreamMin->fUnreliable = true; + Log3Func(("%s: Marked '%s' as unreliable (%u frames).\n", + pSink->pszName, pMixStreamMin->pszName, pMixStreamMin->cFramesLastAvail)); + } -/** - * Resets the sink's state. - * - * @param pSink Sink to reset. - */ -static void audioMixerSinkReset(PAUDMIXSINK pSink) -{ - if (!pSink) - return; + if (cMarkedUnreliable > 0) + { + cReadableStreams = 0; + cFramesToXfer = audioMixerSinkUpdateInputCalcFramesToTransfer(pSink, &cReadableStreams); + } - LogFunc(("[%s]\n", pSink->pszName)); + Log3Func(("%s: cFreeFrames=%#x cFramesToXfer=%#x cReadableStreams=%#x cMarkedUnreliable=%#x cReliableStreams=%#x\n", + pSink->pszName, AudioMixBufFree(&pSink->MixBuf), cFramesToXfer, + cReadableStreams, cMarkedUnreliable, cReliableStreams)); + } - AudioMixBufReset(&pSink->MixBuf); + if (cReadableStreams > 0) + { + if (cFramesToXfer > 0) + { +/*#define ELECTRIC_INPUT_BUFFER*/ /* if buffer code is misbehaving, enable this to catch overflows. */ +#ifndef ELECTRIC_INPUT_BUFFER + union + { + uint8_t ab[8192]; + uint64_t au64[8192 / sizeof(uint64_t)]; /* Use uint64_t to ensure good alignment. */ + } Buf; + void * const pvBuf = &Buf; + uint32_t const cbBuf = sizeof(Buf); +#else + uint32_t const cbBuf = 0x2000 - 16; + void * const pvBuf = RTMemEfAlloc(cbBuf, RTMEM_TAG, RT_SRC_POS); +#endif - /* Update last updated timestamp. */ - pSink->tsLastUpdatedMs = 0; + /* + * For each of the enabled streams, read cFramesToXfer frames worth + * of samples from them and merge that into the mixing buffer. + */ + bool fAssign = true; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_READ) + { + PPDMIAUDIOCONNECTOR const pIConnector = pMixStream->pConn; + PPDMAUDIOSTREAM const pStream = pMixStream->pStream; - /* Reset status. */ - pSink->fStatus = AUDMIXSINK_STS_NONE; -} + /* Calculate how many bytes we should read from this stream. */ + bool const fResampleSrc = PDMAudioPropsHz(&pStream->Cfg.Props) != PDMAudioPropsHz(&pSink->MixBuf.Props); + uint32_t const cbSrcToXfer = !fResampleSrc + ? PDMAudioPropsFramesToBytes(&pStream->Cfg.Props, cFramesToXfer) + : PDMAudioPropsFramesToBytes(&pStream->Cfg.Props, /** @todo check rounding errors here... */ + cFramesToXfer * PDMAudioPropsHz(&pSink->MixBuf.Props) + / PDMAudioPropsHz(&pStream->Cfg.Props)); + + /* Do the reading. */ + uint32_t offSrc = 0; + uint32_t offDstFrame = 0; + do + { + /* + * Read a chunk from the backend. + */ + uint32_t const cbSrcToRead = RT_MIN(cbBuf, cbSrcToXfer - offSrc); + uint32_t cbSrcRead = 0; + if (cbSrcToRead > 0) + { + int rc2 = pIConnector->pfnStreamCapture(pIConnector, pStream, pvBuf, cbSrcToRead, &cbSrcRead); + Log3Func(("%s: %#x L %#x => %#x bytes; rc2=%Rrc %s\n", + pSink->pszName, offSrc, cbSrcToRead, cbSrcRead, rc2, pMixStream->pszName)); + + if (RT_SUCCESS(rc2)) + AssertLogRelMsg(cbSrcRead == cbSrcToRead || pMixStream->fUnreliable, + ("cbSrcRead=%#x cbSrcToRead=%#x - (sink '%s')\n", + cbSrcRead, cbSrcToRead, pSink->pszName)); + else if (rc2 == VERR_AUDIO_STREAM_NOT_READY) + { + LogRel2(("Audio Mixer: '%s' (sink '%s'): Stream not ready - skipping.\n", + pMixStream->pszName, pSink->pszName)); /* must've changed status, stop processing */ + break; + } + else + { + Assert(rc2 != VERR_BUFFER_OVERFLOW); + LogRel2(("Audio Mixer: Reading from mixer stream '%s' (sink '%s') failed, rc=%Rrc\n", + pMixStream->pszName, pSink->pszName, rc2)); + break; + } + offSrc += cbSrcRead; + } + else + Assert(fResampleSrc); /** @todo test this case */ + + /* + * Assign or blend it into the mixer buffer. + */ + uint32_t cFramesDstTransferred = 0; + if (fAssign) + { + /** @todo could complicate this by detecting silence here too and stay in + * assign mode till we get a stream with non-silence... */ + AudioMixBufWrite(&pSink->MixBuf, &pMixStream->WriteState, pvBuf, cbSrcRead, + offDstFrame, cFramesToXfer - offDstFrame, &cFramesDstTransferred); + } + /* We don't need to blend silence buffers. For simplicity, always blend + when we're resampling (for rounding). */ + else if (fResampleSrc || !PDMAudioPropsIsBufferSilence(&pStream->Cfg.Props, pvBuf, cbSrcRead)) + { + AudioMixBufBlend(&pSink->MixBuf, &pMixStream->WriteState, pvBuf, cbSrcRead, + offDstFrame, cFramesToXfer - offDstFrame, &cFramesDstTransferred); + } + else + { + cFramesDstTransferred = PDMAudioPropsBytesToFrames(&pStream->Cfg.Props, cbSrcRead); + AudioMixBufBlendGap(&pSink->MixBuf, &pMixStream->WriteState, cFramesDstTransferred); + } + AssertBreak(cFramesDstTransferred > 0); + + /* Advance. */ + offDstFrame += cFramesDstTransferred; + } while (offDstFrame < cFramesToXfer); + + /* + * In case the first stream is misbehaving, make sure we written the entire area. + */ + if (offDstFrame >= cFramesToXfer) + { /* likely */ } + else if (fAssign) + AudioMixBufSilence(&pSink->MixBuf, &pMixStream->WriteState, offDstFrame, cFramesToXfer - offDstFrame); + else + AudioMixBufBlendGap(&pSink->MixBuf, &pMixStream->WriteState, cFramesToXfer - offDstFrame); + fAssign = false; + } + } -/** - * Removes all attached streams from a given sink. - * - * @param pSink Sink to remove attached streams from. - */ -void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink) -{ - if (!pSink) - return; + /* + * Commit the buffer area we've written and blended into. + */ + AudioMixBufCommit(&pSink->MixBuf, cFramesToXfer); - int rc2 = RTCritSectEnter(&pSink->CritSect); - AssertRC(rc2); +#ifdef ELECTRIC_INPUT_BUFFER + RTMemEfFree(pvBuf, RT_SRC_POS); +#endif + } - audioMixerSinkRemoveAllStreamsInternal(pSink); + /* + * Set the dirty flag for what it's worth. + */ + pSink->fStatus |= AUDMIXSINK_STS_DIRTY; + } + else + { + /* + * No readable stream. Clear the dirty flag if empty (pointless flag). + */ + if (!AudioMixBufUsed(&pSink->MixBuf)) + pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY; + } - pSink->cStreams = 0; + /* Update last updated timestamp. */ + pSink->tsLastUpdatedMs = RTTimeMilliTS(); - rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + return VINF_SUCCESS; } + /** - * Resets a sink. This will immediately stop all processing. + * Helper for audioMixerSinkUpdateOutput that determins now many frames it + * can transfer from the sink's mixer buffer and to the drivers. * - * @param pSink Sink to reset. + * This also updates the mixer stream status, which may involve stream re-inits. + * + * @returns Number of frames. + * @param pSink The sink. + * @param pcWritableStreams Where to return the number of writable streams. */ -void AudioMixerSinkReset(PAUDMIXSINK pSink) +static uint32_t audioMixerSinkUpdateOutputCalcFramesToRead(PAUDMIXSINK pSink, uint32_t *pcWritableStreams) { - if (!pSink) - return; - - int rc2 = RTCritSectEnter(&pSink->CritSect); - AssertRC(rc2); + uint32_t cFramesToRead = AudioMixBufUsed(&pSink->MixBuf); /* (to read from the mixing buffer) */ + uint32_t cWritableStreams = 0; + PAUDMIXSTREAM pMixStream; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { +#if 0 /** @todo this conceptually makes sense, but may mess up the pending-disable logic ... */ + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED) + pConn->pfnStreamIterate(pConn, pStream); +#endif - LogFlowFunc(("[%s]\n", pSink->pszName)); + int rc2 = audioMixerStreamUpdateStatus(pMixStream); + AssertRC(rc2); - audioMixerSinkReset(pSink); + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE) + { + uint32_t const cbWritable = pMixStream->pConn->pfnStreamGetWritable(pMixStream->pConn, pMixStream->pStream); + uint32_t cFrames = PDMAudioPropsBytesToFrames(&pMixStream->pStream->Cfg.Props, cbWritable); + pMixStream->cFramesLastAvail = cFrames; + if (PDMAudioPropsHz(&pMixStream->pStream->Cfg.Props) == PDMAudioPropsHz(&pSink->MixBuf.Props)) + { /* likely */ } + else + { + cFrames = cFrames * PDMAudioPropsHz(&pSink->MixBuf.Props) / PDMAudioPropsHz(&pMixStream->pStream->Cfg.Props); + cFrames = cFrames > 2 ? cFrames - 2 : 0; /* rounding safety fudge */ + } + if (cFramesToRead > cFrames && !pMixStream->fUnreliable) + { + Log4Func(("%s: cFramesToRead %u -> %u; %s (%u bytes writable)\n", + pSink->pszName, cFramesToRead, cFrames, pMixStream->pszName, cbWritable)); + cFramesToRead = cFrames; + } + cWritableStreams++; + } + } - rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + *pcWritableStreams = cWritableStreams; + return cFramesToRead; } + /** - * Returns the audio format of a mixer sink. + * Updates an output mixer sink. * - * @param pSink Sink to retrieve audio format for. - * @param pPCMProps Where to the returned audio format. + * @returns VBox status code. + * @param pSink Mixer sink to update. */ -void AudioMixerSinkGetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps) +static int audioMixerSinkUpdateOutput(PAUDMIXSINK pSink) { - AssertPtrReturnVoid(pSink); - AssertPtrReturnVoid(pPCMProps); - - int rc2 = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc2)) - return; - - memcpy(pPCMProps, &pSink->PCMProps, sizeof(PDMAUDIOPCMPROPS)); - - rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); -} - -/** - * Sets the audio format of a mixer sink. - * - * @returns IPRT status code. - * @param pSink Sink to set audio format for. - * @param pPCMProps Audio format (PCM properties) to set. - */ -int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps) -{ - AssertPtrReturn(pSink, VERR_INVALID_POINTER); - AssertPtrReturn(pPCMProps, VERR_INVALID_POINTER); - AssertReturn(DrvAudioHlpPCMPropsAreValid(pPCMProps), VERR_INVALID_PARAMETER); - - int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; + PAUDMIXSTREAM pMixStream; + Assert(!(pSink->fStatus & AUDMIXSINK_STS_DRAINED_MIXBUF) || AudioMixBufUsed(&pSink->MixBuf) == 0); - if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, pPCMProps)) /* Bail out early if PCM properties are equal. */ + /* + * Update each mixing sink stream's status and check how much we can + * write into them. + * + * We're currently using the minimum size of all streams, however this + * isn't a smart approach as it means one disfunctional stream can block + * working ones. So, if we end up with zero frames and a full mixer + * buffer we'll disregard the stream that accept the smallest amount and + * try again. + */ + uint32_t cWritableStreams = 0; + uint32_t cFramesToRead = audioMixerSinkUpdateOutputCalcFramesToRead(pSink, &cWritableStreams); + if ( cFramesToRead != 0 + || cWritableStreams <= 1 + || AudioMixBufFree(&pSink->MixBuf) > 2) + Log3Func(("%s: cLiveFrames=%#x cFramesToRead=%#x cWritableStreams=%#x\n", pSink->pszName, + AudioMixBufUsed(&pSink->MixBuf), cFramesToRead, cWritableStreams)); + else { - rc = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc); - - return rc; - } + Log3Func(("%s: MixBuf is full but one or more streams only want zero frames. Try disregarding those...\n", pSink->pszName)); + uint32_t cReliableStreams = 0; + uint32_t cMarkedUnreliable = 0; + PAUDMIXSTREAM pMixStreamMin = NULL; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE) + { + if (!pMixStream->fUnreliable) + { + if (pMixStream->cFramesLastAvail == 0) + { + cMarkedUnreliable++; + pMixStream->fUnreliable = true; + Log3Func(("%s: Marked '%s' as unreliable.\n", pSink->pszName, pMixStream->pszName)); + pMixStreamMin = pMixStream; + } + else + { + if (!pMixStreamMin || pMixStream->cFramesLastAvail < pMixStreamMin->cFramesLastAvail) + pMixStreamMin = pMixStream; + cReliableStreams++; + } + } + } + } - if (pSink->PCMProps.uHz) - LogFlowFunc(("[%s] Old format: %u bit, %RU8 channels, %RU32Hz\n", - pSink->pszName, pSink->PCMProps.cbSample * 8, pSink->PCMProps.cChannels, pSink->PCMProps.uHz)); + if (cMarkedUnreliable == 0 && cReliableStreams > 1 && pMixStreamMin != NULL) + { + cReliableStreams--; + cMarkedUnreliable++; + pMixStreamMin->fUnreliable = true; + Log3Func(("%s: Marked '%s' as unreliable (%u frames).\n", + pSink->pszName, pMixStreamMin->pszName, pMixStreamMin->cFramesLastAvail)); + } - memcpy(&pSink->PCMProps, pPCMProps, sizeof(PDMAUDIOPCMPROPS)); + if (cMarkedUnreliable > 0) + { + cWritableStreams = 0; + cFramesToRead = audioMixerSinkUpdateOutputCalcFramesToRead(pSink, &cWritableStreams); + } - LogFlowFunc(("[%s] New format %u bit, %RU8 channels, %RU32Hz\n", - pSink->pszName, pSink->PCMProps.cbSample * 8, pSink->PCMProps.cChannels, pSink->PCMProps.uHz)); + Log3Func(("%s: cLiveFrames=%#x cFramesToRead=%#x cWritableStreams=%#x cMarkedUnreliable=%#x cReliableStreams=%#x\n", + pSink->pszName, AudioMixBufUsed(&pSink->MixBuf), cFramesToRead, + cWritableStreams, cMarkedUnreliable, cReliableStreams)); + } - /* Also update the sink's mixing buffer format. */ - AudioMixBufDestroy(&pSink->MixBuf); - rc = AudioMixBufInit(&pSink->MixBuf, pSink->pszName, &pSink->PCMProps, - DrvAudioHlpMilliToFrames(100 /* ms */, &pSink->PCMProps)); /** @todo Make this configurable? */ - if (RT_SUCCESS(rc)) + if (cWritableStreams > 0) { - PAUDMIXSTREAM pStream; - RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) + if (cFramesToRead > 0) { - /** @todo Invalidate mix buffers! */ + /* + * For each of the enabled streams, convert cFramesToRead frames from + * the mixing buffer and write that to the downstream driver. + */ + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE) + { + uint32_t offSrcFrame = 0; + do + { + /* Convert a chunk from the mixer buffer. */ +/*#define ELECTRIC_PEEK_BUFFER*/ /* if buffer code is misbehaving, enable this to catch overflows. */ +#ifndef ELECTRIC_PEEK_BUFFER + union + { + uint8_t ab[8192]; + uint64_t au64[8192 / sizeof(uint64_t)]; /* Use uint64_t to ensure good alignment. */ + } Buf; + void * const pvBuf = &Buf; + uint32_t const cbBuf = sizeof(Buf); +#else + uint32_t const cbBuf = 0x2000 - 16; + void * const pvBuf = RTMemEfAlloc(cbBuf, RTMEM_TAG, RT_SRC_POS); +#endif + uint32_t cbDstPeeked = cbBuf; + uint32_t cSrcFramesPeeked = cFramesToRead - offSrcFrame; + AudioMixBufPeek(&pSink->MixBuf, offSrcFrame, cSrcFramesPeeked, &cSrcFramesPeeked, + &pMixStream->PeekState, pvBuf, cbBuf, &cbDstPeeked); + offSrcFrame += cSrcFramesPeeked; + + /* Write it to the backend. Since've checked that there is buffer + space available, this should always write the whole buffer unless + it's an unreliable stream. */ + uint32_t cbDstWritten = 0; + int rc2 = pMixStream->pConn->pfnStreamPlay(pMixStream->pConn, pMixStream->pStream, + pvBuf, cbDstPeeked, &cbDstWritten); + Log3Func(("%s: %#x L %#x => %#x bytes; wrote %#x rc2=%Rrc %s\n", pSink->pszName, offSrcFrame, + cSrcFramesPeeked - cSrcFramesPeeked, cbDstPeeked, cbDstWritten, rc2, pMixStream->pszName)); +#ifdef ELECTRIC_PEEK_BUFFER + RTMemEfFree(pvBuf, RT_SRC_POS); +#endif + if (RT_SUCCESS(rc2)) + AssertLogRelMsg(cbDstWritten == cbDstPeeked || pMixStream->fUnreliable, + ("cbDstWritten=%#x cbDstPeeked=%#x - (sink '%s')\n", + cbDstWritten, cbDstPeeked, pSink->pszName)); + else if (rc2 == VERR_AUDIO_STREAM_NOT_READY) + { + LogRel2(("Audio Mixer: '%s' (sink '%s'): Stream not ready - skipping.\n", + pMixStream->pszName, pSink->pszName)); + break; /* must've changed status, stop processing */ + } + else + { + Assert(rc2 != VERR_BUFFER_OVERFLOW); + LogRel2(("Audio Mixer: Writing to mixer stream '%s' (sink '%s') failed, rc=%Rrc\n", + pMixStream->pszName, pSink->pszName, rc2)); + break; + } + } while (offSrcFrame < cFramesToRead); + } + } + + AudioMixBufAdvance(&pSink->MixBuf, cFramesToRead); } + + /* + * Update the dirty flag for what it's worth. + */ + if (AudioMixBufUsed(&pSink->MixBuf) > 0) + pSink->fStatus |= AUDMIXSINK_STS_DIRTY; + else + pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY; + } + else + { + /* + * If no writable streams, just drop the mixer buffer content. + */ + AudioMixBufDrop(&pSink->MixBuf); + pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY; } -#ifdef VBOX_AUDIO_MIXER_DEBUG - if (RT_SUCCESS(rc)) + /* + * Iterate buffers. + */ + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) { - DrvAudioHlpFileClose(pSink->Dbg.pFile); + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED) + pMixStream->pConn->pfnStreamIterate(pMixStream->pConn, pMixStream->pStream); + } - char szTemp[RTPATH_MAX]; - int rc2 = RTPathTemp(szTemp, sizeof(szTemp)); - if (RT_SUCCESS(rc2)) - { - /** @todo Sanitize sink name. */ + /* Update last updated timestamp. */ + uint64_t const nsNow = RTTimeNanoTS(); + pSink->tsLastUpdatedMs = nsNow / RT_NS_1MS; - char szName[64]; - RTStrPrintf(szName, sizeof(szName), "MixerSink-%s", pSink->pszName); + /* + * Deal with pending disable. + * We reset the sink when all streams have been disabled. + */ + if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING)) + { /* likely, till we get to the end */ } + else if (nsNow <= pSink->nsDrainDeadline) + { + /* Have we drained the mixbuf now? If so, update status and send drain + command to streams. (As mentioned elsewhere we don't want to confuse + driver code by sending drain command while there is still data to write.) */ + Assert((pSink->fStatus & AUDMIXSINK_STS_DIRTY) == (AudioMixBufUsed(&pSink->MixBuf) > 0 ? AUDMIXSINK_STS_DIRTY : 0)); + if ((pSink->fStatus & (AUDMIXSINK_STS_DRAINED_MIXBUF | AUDMIXSINK_STS_DIRTY)) == 0) + { + LogFunc(("Sink '%s': Setting AUDMIXSINK_STS_DRAINED_MIXBUF and sending drain command to streams (after %RU64 ns).\n", + pSink->pszName, nsNow - pSink->nsDrainStarted)); + pSink->fStatus |= AUDMIXSINK_STS_DRAINED_MIXBUF; - char szFile[RTPATH_MAX]; - rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, szName, - 0 /* Instance */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - if (RT_SUCCESS(rc2)) + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) { - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAGS_NONE, - &pSink->Dbg.pFile); - if (RT_SUCCESS(rc2)) - rc2 = DrvAudioHlpFileOpen(pSink->Dbg.pFile, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, &pSink->PCMProps); + pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, PDMAUDIOSTREAMCMD_DRAIN); } } - } -#endif - - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Set the current recording source of an input mixer sink, internal version. - * - * @return IPRT status code. - * @param pSink Input mixer sink to set recording source for. - * @param pStream Mixer stream to set as current recording source. Must be an input stream. - * Specify NULL to un-set the current recording source. - */ -static int audioMixerSinkSetRecSourceInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) -{ - AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Specified sink is not an input sink\n")); - int rc; + /* Check if all streams has stopped, and if so we stop the sink. */ + uint32_t const cStreams = pSink->cStreams; + uint32_t cStreamsDisabled = pSink->cStreams; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED) + { + PDMAUDIOSTREAMSTATE const enmState = pMixStream->pConn->pfnStreamGetState(pMixStream->pConn, pMixStream->pStream); + if (enmState >= PDMAUDIOSTREAMSTATE_ENABLED) + cStreamsDisabled--; + } + } - if (pSink->In.pStreamRecSource) /* First, disable old recording source, if any set. */ - { - const PPDMIAUDIOCONNECTOR pConn = pSink->In.pStreamRecSource->pConn; - AssertPtr(pConn); - rc = pConn->pfnEnable(pConn, PDMAUDIODIR_IN, false /* Disable */); + if (cStreamsDisabled != cStreams) + Log3Func(("Sink '%s': %u out of %u streams disabled (after %RU64 ns).\n", + pSink->pszName, cStreamsDisabled, cStreams, nsNow - pSink->nsDrainStarted)); + else + { + LogFunc(("Sink '%s': All %u streams disabled. Drain done after %RU64 ns.\n", + pSink->pszName, cStreamsDisabled, nsNow - pSink->nsDrainStarted)); + audioMixerSinkResetInternal(pSink); /* clears the status */ + } } else - rc = VINF_SUCCESS; - - if (RT_SUCCESS(rc)) { - if (pStream) + /* Draining timed out. Just do an instant stop. */ + LogFunc(("Sink '%s': pending disable timed out after %RU64 ns!\n", pSink->pszName, nsNow - pSink->nsDrainStarted)); + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) { - AssertPtr(pStream->pStream); - AssertMsg(pStream->pStream->enmDir == PDMAUDIODIR_IN, ("Specified stream is not an input stream\n")); - AssertPtr(pStream->pConn); - rc = pStream->pConn->pfnEnable(pStream->pConn, PDMAUDIODIR_IN, true /* Enable */); - if (RT_SUCCESS(rc)) - pSink->In.pStreamRecSource = pStream; - else if (pSink->In.pStreamRecSource) /* Stay with the current recording source (if any) and re-enable it. */ - { - const PPDMIAUDIOCONNECTOR pConn = pSink->In.pStreamRecSource->pConn; - AssertPtr(pConn); - rc = pConn->pfnEnable(pConn, PDMAUDIODIR_IN, true /* Enable */); - } + pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, PDMAUDIOSTREAMCMD_DISABLE); } - else - pSink->In.pStreamRecSource = NULL; /* Unsetting, see audioMixerSinkRemoveStreamInternal. */ + audioMixerSinkResetInternal(pSink); /* clears the status */ } - LogFunc(("[%s] Recording source is now '%s', rc=%Rrc\n", - pSink->pszName, pSink->In.pStreamRecSource ? pSink->In.pStreamRecSource->pszName : "", rc)); - - if (RT_SUCCESS(rc)) - LogRel(("Audio Mixer: Setting recording source of sink '%s' to '%s'\n", - pSink->pszName, pSink->In.pStreamRecSource ? pSink->In.pStreamRecSource->pszName : "")); - else if (rc != VERR_AUDIO_STREAM_NOT_READY) - LogRel(("Audio Mixer: Setting recording source of sink '%s' to '%s' failed with %Rrc\n", - pSink->pszName, pSink->In.pStreamRecSource ? pSink->In.pStreamRecSource->pszName : "", rc)); - - return rc; + return VINF_SUCCESS; } /** - * Set the current recording source of an input mixer sink. + * Updates (invalidates) a mixer sink. * - * @return IPRT status code. - * @param pSink Input mixer sink to set recording source for. - * @param pStream Mixer stream to set as current recording source. Must be an input stream. - * Set to NULL to un-set the current recording source. + * @returns VBox status code. + * @param pSink Mixer sink to update. + * @param cbDmaUsed The DMA buffer fill for input stream, ignored for + * output sinks. + * @param cbDmaPeriod The DMA period in bytes for input stream, ignored + * for output sinks. */ -int AudioMixerSinkSetRecordingSource(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +int AudioMixerSinkUpdate(PAUDMIXSINK pSink, uint32_t cbDmaUsed, uint32_t cbDmaPeriod) { AssertPtrReturn(pSink, VERR_INVALID_POINTER); - + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; + AssertRCReturn(rc, rc); - rc = audioMixerSinkSetRecSourceInternal(pSink, pStream); +#ifdef LOG_ENABLED + char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX]; +#endif + Log3Func(("[%s] fStatus=%s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus))); - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + /* Only process running sinks. */ + if (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + { + /* Do separate processing for input and output sinks. */ + if (pSink->enmDir == PDMAUDIODIR_OUT) + rc = audioMixerSinkUpdateOutput(pSink); + else if (pSink->enmDir == PDMAUDIODIR_IN) + rc = audioMixerSinkUpdateInput(pSink, cbDmaUsed, cbDmaPeriod); + else + AssertFailedStmt(rc = VERR_INTERNAL_ERROR_3); + } + else + rc = VINF_SUCCESS; /* disabled */ + RTCritSectLeave(&pSink->CritSect); return rc; } + /** - * Sets the volume of an individual sink. - * - * @returns IPRT status code. - * @param pSink Sink to set volume for. - * @param pVol Volume to set. + * @callback_method_impl{FNRTTHREAD, Audio Mixer Sink asynchronous I/O thread} */ -int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol) +static DECLCALLBACK(int) audioMixerSinkAsyncIoThread(RTTHREAD hThreadSelf, void *pvUser) { - AssertPtrReturn(pSink, VERR_INVALID_POINTER); - AssertPtrReturn(pVol, VERR_INVALID_POINTER); + PAUDMIXSINK pSink = (PAUDMIXSINK)pvUser; + AssertPtr(pSink); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + RT_NOREF(hThreadSelf); - int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; - - memcpy(&pSink->Volume, pVol, sizeof(PDMAUDIOVOLUME)); - - LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", - pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight)); + /* + * The run loop. + */ + LogFlowFunc(("%s: Entering run loop...\n", pSink->pszName)); + while (!pSink->AIO.fShutdown) + { + RTMSINTERVAL cMsSleep = RT_INDEFINITE_WAIT; - LogRel2(("Audio Mixer: Setting volume of sink '%s' to %RU8/%RU8 (%s)\n", - pSink->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted ? "Muted" : "Unmuted")); + RTCritSectEnter(&pSink->CritSect); + if (pSink->fStatus & (AUDMIXSINK_STS_RUNNING | AUDMIXSINK_STS_DRAINING)) + { + /* + * Before doing jobs, always update input sinks. + */ + if (pSink->enmDir == PDMAUDIODIR_IN) + audioMixerSinkUpdateInput(pSink, 0 /*cbDmaUsed*/, 0 /*cbDmaPeriod*/); + + /* + * Do the device specific updating. + */ + uintptr_t const cUpdateJobs = RT_MIN(pSink->AIO.cUpdateJobs, RT_ELEMENTS(pSink->AIO.aUpdateJobs)); + for (uintptr_t iJob = 0; iJob < cUpdateJobs; iJob++) + pSink->AIO.aUpdateJobs[iJob].pfnUpdate(pSink->AIO.pDevIns, pSink, pSink->AIO.aUpdateJobs[iJob].pvUser); + + /* + * Update output sinks after the updating. + */ + if (pSink->enmDir == PDMAUDIODIR_OUT) + audioMixerSinkUpdateOutput(pSink); + + /* + * If we're in draining mode, we use the smallest typical interval of the + * jobs for the next wait as we're unlikly to be woken up again by any + * DMA timer as it has normally stopped running at this point. + */ + if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING)) + { /* likely */ } + else + { + /** @todo Also do some kind of timeout here and do a forced stream disable w/o + * any draining if we exceed it. */ + cMsSleep = pSink->AIO.cMsMinTypicalInterval; + } - AssertPtr(pSink->pParent); - rc = audioMixerSinkUpdateVolume(pSink, &pSink->pParent->VolMaster); + } + RTCritSectLeave(&pSink->CritSect); - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + /* + * Now block till we're signalled or + */ + if (!pSink->AIO.fShutdown) + { + int rc = RTSemEventWait(pSink->AIO.hEvent, cMsSleep); + AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%s: RTSemEventWait -> %Rrc\n", pSink->pszName, rc), rc); + } + } - return rc; + LogFlowFunc(("%s: returnining normally.\n", pSink->pszName)); + return VINF_SUCCESS; } + /** - * Updates a mixer sink, internal version. + * Adds an AIO update job to the sink. * - * @returns IPRT status code. - * @param pSink Mixer sink to update. + * @returns VBox status code. + * @retval VERR_ALREADY_EXISTS if already registered job with same @a pvUser + * and @a pfnUpdate. + * + * @param pSink The mixer sink to remove the AIO job from. + * @param pfnUpdate The update callback for the job. + * @param pvUser The user parameter to pass to @a pfnUpdate. This should + * identify the job unique together with @a pfnUpdate. + * @param cMsTypicalInterval A typical interval between jobs in milliseconds. + * This is used when draining. */ -static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink) +int AudioMixerSinkAddUpdateJob(PAUDMIXSINK pSink, PFNAUDMIXSINKUPDATE pfnUpdate, void *pvUser, uint32_t cMsTypicalInterval) { AssertPtrReturn(pSink, VERR_INVALID_POINTER); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + int rc = RTCritSectEnter(&pSink->CritSect); + AssertRCReturn(rc, rc); - int rc = VINF_SUCCESS; - -#ifdef LOG_ENABLED - char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus); - Log3Func(("[%s] fStatus=%s\n", pSink->pszName, pszStatus)); - RTStrFree(pszStatus); -#endif - - /* Sink disabled? Take a shortcut. */ - if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING)) - return rc; - - /* Input sink and no recording source set? Bail out early. */ - if ( pSink->enmDir == AUDMIXSINKDIR_INPUT - && pSink->In.pStreamRecSource == NULL) - return rc; - - /* Update each mixing sink stream's status. */ - PAUDMIXSTREAM pMixStream; - RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) - { - int rc2 = audioMixerStreamUpdateStatus(pMixStream); - AssertRC(rc2); - } - - /* Number of disabled streams of this sink. */ - uint8_t cStreamsDisabled = pSink->cStreams; - - /* Next, try to write (multiplex) as much audio data as possible to all connected mixer streams. */ - uint32_t cbToWriteToStreams = AudioMixBufUsedBytes(&pSink->MixBuf); + /* + * Check that the job hasn't already been added. + */ + uintptr_t const iEnd = pSink->AIO.cUpdateJobs; + for (uintptr_t i = 0; i < iEnd; i++) + AssertReturnStmt( pvUser != pSink->AIO.aUpdateJobs[i].pvUser + || pfnUpdate != pSink->AIO.aUpdateJobs[i].pfnUpdate, + RTCritSectLeave(&pSink->CritSect), + VERR_ALREADY_EXISTS); + + AssertReturnStmt(iEnd < RT_ELEMENTS(pSink->AIO.aUpdateJobs), + RTCritSectLeave(&pSink->CritSect), + VERR_ALREADY_EXISTS); - uint8_t arrChunkBuf[_1K]; /** @todo Hm ... some zero copy / shared buffers would be nice! */ - while (cbToWriteToStreams) + /* + * Create the thread if not already running or if it stopped. + */ +/** @todo move this to the sink "enable" code */ + if (pSink->AIO.hThread != NIL_RTTHREAD) { - uint32_t cfChunk; - rc = AudioMixBufAcquireReadBlock(&pSink->MixBuf, arrChunkBuf, RT_MIN(cbToWriteToStreams, sizeof(arrChunkBuf)), &cfChunk); - if (RT_FAILURE(rc)) - break; - - const uint32_t cbChunk = DrvAudioHlpFramesToBytes(cfChunk, &pSink->PCMProps); - Assert(cbChunk <= sizeof(arrChunkBuf)); - - /* Multiplex the current chunk in a synchronized fashion to all connected streams. */ - uint32_t cbChunkWrittenMin = 0; - rc = audioMixerSinkMultiplexSync(pSink, AUDMIXOP_COPY, arrChunkBuf, cbChunk, &cbChunkWrittenMin); - if (RT_SUCCESS(rc)) + int rcThread = VINF_SUCCESS; + rc = RTThreadWait(pSink->AIO.hThread, 0, &rcThread); + if (RT_FAILURE_NP(rc)) + { /* likely */ } + else { - RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) - { - int rc2 = audioMixerSinkWriteToStream(pSink, pMixStream); - AssertRC(rc2); - } + LogRel(("Audio: AIO thread for '%s' died? rcThread=%Rrc\n", pSink->pszName, rcThread)); + pSink->AIO.hThread = NIL_RTTHREAD; } - - Log3Func(("[%s] cbChunk=%RU32, cbChunkWrittenMin=%RU32\n", pSink->pszName, cbChunk, cbChunkWrittenMin)); - - AudioMixBufReleaseReadBlock(&pSink->MixBuf, AUDIOMIXBUF_B2F(&pSink->MixBuf, cbChunkWrittenMin)); - - if ( RT_FAILURE(rc) - || cbChunkWrittenMin == 0) - break; - - Assert(cbToWriteToStreams >= cbChunkWrittenMin); - cbToWriteToStreams -= cbChunkWrittenMin; } - - if ( !(pSink->fStatus & AUDMIXSINK_STS_DIRTY) - && AudioMixBufUsed(&pSink->MixBuf)) /* Still audio output data left? Consider the sink as being "dirty" then. */ + if (pSink->AIO.hThread == NIL_RTTHREAD) { - /* Set dirty bit. */ - pSink->fStatus |= AUDMIXSINK_STS_DIRTY; + LogFlowFunc(("%s: Starting AIO thread...\n", pSink->pszName)); + if (pSink->AIO.hEvent == NIL_RTSEMEVENT) + { + rc = RTSemEventCreate(&pSink->AIO.hEvent); + AssertRCReturnStmt(rc, RTCritSectLeave(&pSink->CritSect), rc); + } + static uint32_t volatile s_idxThread = 0; + uint32_t idxThread = ASMAtomicIncU32(&s_idxThread); + rc = RTThreadCreateF(&pSink->AIO.hThread, audioMixerSinkAsyncIoThread, pSink, 0 /*cbStack*/, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE | RTTHREADFLAGS_COM_MTA, "MixAIO-%u", idxThread); + AssertRCReturnStmt(rc, RTCritSectLeave(&pSink->CritSect), rc); } - RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) - { - /* Input sink and not the recording source? Skip. */ - if ( pSink->enmDir == AUDMIXSINKDIR_INPUT - && pSink->In.pStreamRecSource != pMixStream) - continue; - - PPDMAUDIOSTREAM pStream = pMixStream->pStream; - AssertPtr(pStream); + /* + * Finally, actually add the job. + */ + pSink->AIO.aUpdateJobs[iEnd].pfnUpdate = pfnUpdate; + pSink->AIO.aUpdateJobs[iEnd].pvUser = pvUser; + pSink->AIO.aUpdateJobs[iEnd].cMsTypicalInterval = cMsTypicalInterval; + pSink->AIO.cUpdateJobs = (uint8_t)(iEnd + 1); + if (cMsTypicalInterval < pSink->AIO.cMsMinTypicalInterval) + pSink->AIO.cMsMinTypicalInterval = cMsTypicalInterval; + LogFlowFunc(("%s: [#%zu]: Added pfnUpdate=%p pvUser=%p typically every %u ms (min %u ms)\n", + pSink->pszName, iEnd, pfnUpdate, pvUser, cMsTypicalInterval, pSink->AIO.cMsMinTypicalInterval)); - PPDMIAUDIOCONNECTOR pConn = pMixStream->pConn; - AssertPtr(pConn); + RTCritSectLeave(&pSink->CritSect); + return VINF_SUCCESS; - uint32_t cfProc = 0; +} - if (!(pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)) - continue; - int rc2 = pConn->pfnStreamIterate(pConn, pStream); - if (RT_SUCCESS(rc2)) - { - if (pSink->enmDir == AUDMIXSINKDIR_INPUT) - { - rc2 = pConn->pfnStreamCapture(pConn, pStream, &cfProc); - if (RT_FAILURE(rc2)) - { - LogFunc(("%s: Failed capturing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2)); - continue; - } +/** + * Removes an update job previously registered via AudioMixerSinkAddUpdateJob(). + * + * @returns VBox status code. + * @retval VERR_NOT_FOUND if not found. + * + * @param pSink The mixer sink to remove the AIO job from. + * @param pfnUpdate The update callback of the job. + * @param pvUser The user parameter identifying the job. + */ +int AudioMixerSinkRemoveUpdateJob(PAUDMIXSINK pSink, PFNAUDMIXSINKUPDATE pfnUpdate, void *pvUser) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + int rc = RTCritSectEnter(&pSink->CritSect); + AssertRCReturn(rc, rc); - if (cfProc) - pSink->fStatus |= AUDMIXSINK_STS_DIRTY; - } - else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT) - { - rc2 = pConn->pfnStreamPlay(pConn, pStream, &cfProc); - if (RT_FAILURE(rc2)) - { - LogFunc(("%s: Failed playing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2)); - continue; - } - } - else - { - AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); - continue; - } + rc = VERR_NOT_FOUND; + for (uintptr_t iJob = 0; iJob < pSink->AIO.cUpdateJobs; iJob++) + if ( pvUser == pSink->AIO.aUpdateJobs[iJob].pvUser + && pfnUpdate == pSink->AIO.aUpdateJobs[iJob].pfnUpdate) + { + pSink->AIO.cUpdateJobs--; + if (iJob != pSink->AIO.cUpdateJobs) + memmove(&pSink->AIO.aUpdateJobs[iJob], &pSink->AIO.aUpdateJobs[iJob + 1], + (pSink->AIO.cUpdateJobs - iJob) * sizeof(pSink->AIO.aUpdateJobs[0])); + LogFlowFunc(("%s: [#%zu]: Removed pfnUpdate=%p pvUser=%p => cUpdateJobs=%u\n", + pSink->pszName, iJob, pfnUpdate, pvUser, pSink->AIO.cUpdateJobs)); + rc = VINF_SUCCESS; + break; } + AssertRC(rc); - const PDMAUDIOSTREAMSTS fStreamStatusNew = pConn->pfnStreamGetStatus(pConn, pStream); + /* Recalc the minimum sleep interval (do it always). */ + pSink->AIO.cMsMinTypicalInterval = RT_MS_1SEC / 2; + for (uintptr_t iJob = 0; iJob < pSink->AIO.cUpdateJobs; iJob++) + if (pSink->AIO.aUpdateJobs[iJob].cMsTypicalInterval < pSink->AIO.cMsMinTypicalInterval) + pSink->AIO.cMsMinTypicalInterval = pSink->AIO.aUpdateJobs[iJob].cMsTypicalInterval; - /* Is the stream enabled or in pending disable state? - * Don't consider this stream as being disabled then. */ - if (fStreamStatusNew & (PDMAUDIOSTREAMSTS_FLAGS_ENABLED | PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE)) - cStreamsDisabled--; - /* Note: The mixer stream's internal status will be updated in the next iteration of this function. */ - Log3Func(("\t%s: cPlayed/cCaptured=%RU32, rc2=%Rrc\n", pStream->szName, cfProc, rc2)); - } + RTCritSectLeave(&pSink->CritSect); + return rc; +} - Log3Func(("[%s] fPendingDisable=%RTbool, %RU8/%RU8 streams disabled\n", - pSink->pszName, RT_BOOL(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE), cStreamsDisabled, pSink->cStreams)); - /* Update last updated timestamp. */ - pSink->tsLastUpdatedMs = RTTimeMilliTS(); +/** + * Writes data to a mixer output sink. + * + * @param pSink The sink to write data to. + * @param pvBuf Buffer containing the audio data to write. + * @param cbBuf How many bytes to write. + * @param pcbWritten Number of bytes written. + * + * @todo merge with caller. + */ +static void audioMixerSinkWrite(PAUDMIXSINK pSink, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + uint32_t cFrames = AudioMixBufFree(&pSink->MixBuf); + uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pSink->PCMProps, cFrames); + cbToWrite = RT_MIN(cbToWrite, cbBuf); + AudioMixBufWrite(&pSink->MixBuf, &pSink->Out.State, pvBuf, cbToWrite, 0 /*offDstFrame*/, cFrames, &cFrames); + Assert(cbToWrite == PDMAudioPropsFramesToBytes(&pSink->PCMProps, cFrames)); + AudioMixBufCommit(&pSink->MixBuf, cFrames); + *pcbWritten = cbToWrite; - /* All streams disabled and the sink is in pending disable mode? */ - if ( cStreamsDisabled == pSink->cStreams - && (pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)) - { - audioMixerSinkReset(pSink); - } + /* Update the sink's last written time stamp. */ + pSink->tsLastReadWrittenNs = RTTimeNanoTS(); - return rc; + Log3Func(("[%s] cbBuf=%#x -> cbWritten=%#x\n", pSink->pszName, cbBuf, cbToWrite)); } + /** - * Updates (invalidates) a mixer sink. + * Transfer data from the device's DMA buffer and into the sink. * - * @returns IPRT status code. - * @param pSink Mixer sink to update. + * The caller is already holding the mixer sink's critical section, either by + * way of being the AIO thread doing update jobs or by explicit locking calls. + * + * @returns The new stream offset. + * @param pSink The mixer sink to transfer samples to. + * @param pCircBuf The internal DMA buffer to move samples from. + * @param offStream The stream current offset (logging, dtrace, return). + * @param idStream Device specific audio stream identifier (logging, dtrace). + * @param pDbgFile Debug file, NULL if disabled. */ -int AudioMixerSinkUpdate(PAUDMIXSINK pSink) +uint64_t AudioMixerSinkTransferFromCircBuf(PAUDMIXSINK pSink, PRTCIRCBUF pCircBuf, uint64_t offStream, + uint32_t idStream, PAUDIOHLPFILE pDbgFile) { - AssertPtrReturn(pSink, VERR_INVALID_POINTER); + /* + * Sanity. + */ + AssertReturn(pSink, offStream); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + AssertReturn(pCircBuf, offStream); + Assert(RTCritSectIsOwner(&pSink->CritSect)); + Assert(pSink->enmDir == PDMAUDIODIR_OUT); + RT_NOREF(idStream); - int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; + /* + * Figure how much that we can push down. + */ + uint32_t const cbSinkWritable = AudioMixerSinkGetWritable(pSink); + uint32_t const cbCircBufReadable = (uint32_t)RTCircBufUsed(pCircBuf); + uint32_t cbToTransfer = RT_MIN(cbCircBufReadable, cbSinkWritable); + /* Make sure that we always align the number of bytes when reading to the stream's PCM properties. */ + uint32_t const cbToTransfer2 = cbToTransfer = PDMAudioPropsFloorBytesToFrame(&pSink->PCMProps, cbToTransfer); + + Log3Func(("idStream=%u: cbSinkWritable=%#RX32 cbCircBufReadable=%#RX32 -> cbToTransfer=%#RX32 @%#RX64\n", + idStream, cbSinkWritable, cbCircBufReadable, cbToTransfer, offStream)); + AssertMsg(!(pSink->fStatus & AUDMIXSINK_STS_DRAINING) || cbCircBufReadable == pSink->cbDmaLeftToDrain, + ("cbCircBufReadable=%#x cbDmaLeftToDrain=%#x\n", cbCircBufReadable, pSink->cbDmaLeftToDrain)); - rc = audioMixerSinkUpdateInternal(pSink); + /* + * Do the pushing. + */ + while (cbToTransfer > 0) + { + void /*const*/ *pvSrcBuf; + size_t cbSrcBuf; + RTCircBufAcquireReadBlock(pCircBuf, cbToTransfer, &pvSrcBuf, &cbSrcBuf); + + uint32_t cbWritten = 0; + audioMixerSinkWrite(pSink, pvSrcBuf, (uint32_t)cbSrcBuf, &cbWritten); + Assert(cbWritten <= cbSrcBuf); + + Log2Func(("idStream=%u: %#RX32/%#zx bytes read @%#RX64\n", idStream, cbWritten, cbSrcBuf, offStream)); +#ifdef VBOX_WITH_DTRACE + VBOXDD_AUDIO_MIXER_SINK_AIO_OUT(idStream, cbWritten, offStream); +#endif + offStream += cbWritten; - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); + if (!pDbgFile) + { /* likely */ } + else + AudioHlpFileWrite(pDbgFile, pvSrcBuf, cbSrcBuf); - return rc; + + RTCircBufReleaseReadBlock(pCircBuf, cbWritten); + + /* advance */ + cbToTransfer -= cbWritten; + } + + /* + * Advance drain status. + */ + if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING)) + { /* likely for most of the playback time ... */ } + else if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINED_DMA)) + { + if (cbToTransfer2 >= pSink->cbDmaLeftToDrain) + { + Assert(cbToTransfer2 == pSink->cbDmaLeftToDrain); + Log3Func(("idStream=%u/'%s': Setting AUDMIXSINK_STS_DRAINED_DMA.\n", idStream, pSink->pszName)); + pSink->cbDmaLeftToDrain = 0; + pSink->fStatus |= AUDMIXSINK_STS_DRAINED_DMA; + } + else + { + pSink->cbDmaLeftToDrain -= cbToTransfer2; + Log3Func(("idStream=%u/'%s': still %#x bytes left in the DMA buffer\n", + idStream, pSink->pszName, pSink->cbDmaLeftToDrain)); + } + } + else + Assert(cbToTransfer2 == 0); + + return offStream; } + /** - * Updates the (master) volume of a mixer sink. + * Transfer data to the device's DMA buffer from the sink. + * + * The caller is already holding the mixer sink's critical section, either by + * way of being the AIO thread doing update jobs or by explicit locking calls. * - * @returns IPRT status code. - * @param pSink Mixer sink to update volume for. - * @param pVolMaster Master volume to set. + * @returns The new stream offset. + * @param pSink The mixer sink to transfer samples from. + * @param pCircBuf The internal DMA buffer to move samples to. + * @param offStream The stream current offset (logging, dtrace, return). + * @param idStream Device specific audio stream identifier (logging, dtrace). + * @param pDbgFile Debug file, NULL if disabled. */ -static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster) +uint64_t AudioMixerSinkTransferToCircBuf(PAUDMIXSINK pSink, PRTCIRCBUF pCircBuf, uint64_t offStream, + uint32_t idStream, PAUDIOHLPFILE pDbgFile) { - AssertPtrReturn(pSink, VERR_INVALID_POINTER); - AssertPtrReturn(pVolMaster, VERR_INVALID_POINTER); + /* + * Sanity. + */ + AssertReturn(pSink, offStream); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + AssertReturn(pCircBuf, offStream); + Assert(RTCritSectIsOwner(&pSink->CritSect)); + + /* + * Figure out how much we can transfer. + */ + const uint32_t cbSinkReadable = AudioMixerSinkGetReadable(pSink); + const uint32_t cbCircBufWritable = (uint32_t)RTCircBufFree(pCircBuf); + uint32_t cbToTransfer = RT_MIN(cbCircBufWritable, cbSinkReadable); + uint32_t cFramesToTransfer = PDMAudioPropsBytesToFrames(&pSink->PCMProps, cbToTransfer); + cbToTransfer = PDMAudioPropsFramesToBytes(&pSink->PCMProps, cFramesToTransfer); + + Log3Func(("idStream=%u: cbSinkReadable=%#RX32 cbCircBufWritable=%#RX32 -> cbToTransfer=%#RX32 (%RU32 frames) @%#RX64\n", + idStream, cbSinkReadable, cbCircBufWritable, cbToTransfer, cFramesToTransfer, offStream)); + RT_NOREF(idStream); - LogFlowFunc(("[%s] Master fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n", - pSink->pszName, pVolMaster->fMuted, pVolMaster->uLeft, pVolMaster->uRight)); - LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU32, rVol=%RU32 ", - pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight)); + /** @todo should we throttle (read less) this if we're far ahead? */ - /** @todo Very crude implementation for now -- needs more work! */ + /* + * Copy loop. + */ + while (cbToTransfer > 0) + { +/** @todo We should be able to read straight into the circular buffer here + * as it should have a frame aligned size. */ - pSink->VolumeCombined.fMuted = pVolMaster->fMuted || pSink->Volume.fMuted; + /* Read a chunk of data. */ + uint8_t abBuf[4096]; + uint32_t cbRead = 0; + uint32_t cFramesRead = 0; + AudioMixBufPeek(&pSink->MixBuf, 0, cFramesToTransfer, &cFramesRead, + &pSink->In.State, abBuf, RT_MIN(cbToTransfer, sizeof(abBuf)), &cbRead); + AssertBreak(cFramesRead > 0); + Assert(cbRead > 0); + + cFramesToTransfer -= cFramesRead; + AudioMixBufAdvance(&pSink->MixBuf, cFramesRead); + + /* Write it to the internal DMA buffer. */ + uint32_t off = 0; + while (off < cbRead) + { + void *pvDstBuf; + size_t cbDstBuf; + RTCircBufAcquireWriteBlock(pCircBuf, cbRead - off, &pvDstBuf, &cbDstBuf); - pSink->VolumeCombined.uLeft = ( (pSink->Volume.uLeft ? pSink->Volume.uLeft : 1) - * (pVolMaster->uLeft ? pVolMaster->uLeft : 1)) / PDMAUDIO_VOLUME_MAX; + memcpy(pvDstBuf, &abBuf[off], cbDstBuf); - pSink->VolumeCombined.uRight = ( (pSink->Volume.uRight ? pSink->Volume.uRight : 1) - * (pVolMaster->uRight ? pVolMaster->uRight : 1)) / PDMAUDIO_VOLUME_MAX; +#ifdef VBOX_WITH_DTRACE + VBOXDD_AUDIO_MIXER_SINK_AIO_IN(idStream, (uint32_t)cbDstBuf, offStream); +#endif + offStream += cbDstBuf; - LogFlow(("-> fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n", - pSink->VolumeCombined.fMuted, pSink->VolumeCombined.uLeft, pSink->VolumeCombined.uRight)); + RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf); - /* Propagate new sink volume to all streams in the sink. */ - PAUDMIXSTREAM pMixStream; - RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) - { - int rc2 = pMixStream->pConn->pfnStreamSetVolume(pMixStream->pConn, pMixStream->pStream, &pSink->VolumeCombined); - AssertRC(rc2); + off += (uint32_t)cbDstBuf; + } + Assert(off == cbRead); + + /* Write to debug file? */ + if (RT_LIKELY(!pDbgFile)) + { /* likely */ } + else + AudioHlpFileWrite(pDbgFile, abBuf, cbRead); + + /* Advance. */ + Assert(cbRead <= cbToTransfer); + cbToTransfer -= cbRead; } - return VINF_SUCCESS; + return offStream; } + /** - * Writes (buffered) output data of a sink's stream to the bound audio connector stream. + * Signals the AIO thread to perform updates. * - * @returns IPRT status code. - * @param pSink Sink of stream that contains the mixer stream. - * @param pMixStream Mixer stream to write output data for. + * @returns VBox status code. + * @param pSink The mixer sink which AIO thread needs to do chores. */ -static int audioMixerSinkWriteToStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream) +int AudioMixerSinkSignalUpdateJob(PAUDMIXSINK pSink) { - if (!pMixStream->pCircBuf) - return VINF_SUCCESS; - - return audioMixerSinkWriteToStreamEx(pSink, pMixStream, (uint32_t)RTCircBufUsed(pMixStream->pCircBuf), NULL /* pcbWritten */); + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + return RTSemEventSignal(pSink->AIO.hEvent); } + /** - * Writes (buffered) output data of a sink's stream to the bound audio connector stream, extended version. + * Locks the mixer sink for purposes of serializing with the AIO thread. * - * @returns IPRT status code. - * @param pSink Sink of stream that contains the mixer stream. - * @param pMixStream Mixer stream to write output data for. - * @param cbToWrite Size (in bytes) to write. - * @param pcbWritten Size (in bytes) written on success. Optional. + * @returns VBox status code. + * @param pSink The mixer sink to lock. */ -static int audioMixerSinkWriteToStreamEx(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream, uint32_t cbToWrite, uint32_t *pcbWritten) +int AudioMixerSinkLock(PAUDMIXSINK pSink) { - /* pcbWritten is optional. */ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + return RTCritSectEnter(&pSink->CritSect); +} - if ( !cbToWrite - || !(pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)) - { - if (pcbWritten) - *pcbWritten = 0; - return VINF_SUCCESS; - } +/** + * Try to lock the mixer sink for purposes of serializing with the AIO thread. + * + * @returns VBox status code. + * @param pSink The mixer sink to lock. + */ +int AudioMixerSinkTryLock(PAUDMIXSINK pSink) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + return RTCritSectTryEnter(&pSink->CritSect); +} - PRTCIRCBUF pCircBuf = pMixStream->pCircBuf; - const uint32_t cbWritableStream = pMixStream->pConn->pfnStreamGetWritable(pMixStream->pConn, pMixStream->pStream); - cbToWrite = RT_MIN(cbToWrite, RT_MIN((uint32_t)RTCircBufUsed(pCircBuf), cbWritableStream)); +/** + * Unlocks the sink. + * + * @returns VBox status code. + * @param pSink The mixer sink to unlock. + */ +int AudioMixerSinkUnlock(PAUDMIXSINK pSink) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + return RTCritSectLeave(&pSink->CritSect); +} - Log3Func(("[%s] cbWritableStream=%RU32, cbToWrite=%RU32\n", - pMixStream->pszName, cbWritableStream, cbToWrite)); - uint32_t cbWritten = 0; +/** + * Creates an audio mixer stream. + * + * @returns VBox status code. + * @param pSink Sink to use for creating the stream. + * @param pConn Audio connector interface to use. + * @param pCfg Audio stream configuration to use. This may be modified + * in some unspecified way (see + * PDMIAUDIOCONNECTOR::pfnStreamCreate). + * @param pDevIns The device instance to register statistics with. + * @param ppStream Pointer which receives the newly created audio stream. + */ +int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, PPDMIAUDIOCONNECTOR pConn, PCPDMAUDIOSTREAMCFG pCfg, + PPDMDEVINS pDevIns, PAUDMIXSTREAM *ppStream) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + AssertPtrNullReturn(ppStream, VERR_INVALID_POINTER); + Assert(pSink->AIO.pDevIns == pDevIns); RT_NOREF(pDevIns); /* we'll probably be adding more statistics */ + AssertReturn(pCfg->enmDir == pSink->enmDir, VERR_MISMATCH); - int rc = VINF_SUCCESS; + /* + * Check status and get the host driver config. + */ + if (pConn->pfnGetStatus(pConn, PDMAUDIODIR_DUPLEX) == PDMAUDIOBACKENDSTS_NOT_ATTACHED) + return VERR_AUDIO_BACKEND_NOT_ATTACHED; - while (cbToWrite) - { - void *pvChunk; - size_t cbChunk; - RTCircBufAcquireReadBlock(pCircBuf, cbToWrite, &pvChunk, &cbChunk); + PDMAUDIOBACKENDCFG BackendCfg; + int rc = pConn->pfnGetConfig(pConn, &BackendCfg); + AssertRCReturn(rc, rc); - Log3Func(("[%s] cbChunk=%RU32\n", pMixStream->pszName, cbChunk)); + /* + * Allocate the instance. + */ + PAUDMIXSTREAM pMixStream = (PAUDMIXSTREAM)RTMemAllocZ(sizeof(AUDMIXSTREAM)); + AssertReturn(pMixStream, VERR_NO_MEMORY); - uint32_t cbChunkWritten = 0; - if (cbChunk) + /* Assign the backend's name to the mixer stream's name for easier identification in the (release) log. */ + pMixStream->pszName = RTStrAPrintf2("[%s] %s", pCfg->szName, BackendCfg.szName); + pMixStream->pszStatPrefix = RTStrAPrintf2("MixerSink-%s/%s/", pSink->pszName, BackendCfg.szName); + if (pMixStream->pszName && pMixStream->pszStatPrefix) + { + rc = RTCritSectInit(&pMixStream->CritSect); + if (RT_SUCCESS(rc)) { - rc = pMixStream->pConn->pfnStreamWrite(pMixStream->pConn, pMixStream->pStream, pvChunk, (uint32_t)cbChunk, - &cbChunkWritten); - if (RT_FAILURE(rc)) + /* + * Lock the sink so we can safely get it's properties and call + * down into the audio driver to create that end of the stream. + */ + rc = RTCritSectEnter(&pSink->CritSect); + AssertRC(rc); + if (RT_SUCCESS(rc)) { - if (rc == VERR_BUFFER_OVERFLOW) - { - LogRel2(("Audio Mixer: Buffer overrun for mixer stream '%s' (sink '%s')\n", pMixStream->pszName, pSink->pszName)); - break; - } - else if (rc == VERR_AUDIO_STREAM_NOT_READY) + LogFlowFunc(("[%s] (enmDir=%ld, %u bits, %RU8 channels, %RU32Hz)\n", pSink->pszName, pCfg->enmDir, + PDMAudioPropsSampleBits(&pCfg->Props), PDMAudioPropsChannels(&pCfg->Props), pCfg->Props.uHz)); + + /* + * Initialize the host-side configuration for the stream to be created, + * this is the sink format & direction with the src/dir, layout, name + * and device specific config copied from the guest side config (pCfg). + * We disregard any Backend settings here. + * + * (Note! pfnStreamCreate used to get both CfgHost and pCfg (aka pCfgGuest) + * passed in, but that became unnecessary with DrvAudio stoppping + * mixing. The mixing is done here and we bridge guest & host configs.) + */ + AssertMsg(AudioHlpPcmPropsAreValid(&pSink->PCMProps), + ("%s: Does not (yet) have a format set when it must\n", pSink->pszName)); + + PDMAUDIOSTREAMCFG CfgHost; + rc = PDMAudioStrmCfgInitWithProps(&CfgHost, &pSink->PCMProps); + AssertRC(rc); /* cannot fail */ + CfgHost.enmDir = pSink->enmDir; + CfgHost.enmPath = pCfg->enmPath; + CfgHost.Device = pCfg->Device; + RTStrCopy(CfgHost.szName, sizeof(CfgHost.szName), pCfg->szName); + + /* + * Create the stream. + * + * Output streams are not using any mixing buffers in DrvAudio. This will + * become the norm after we move the input mixing here and convert DevSB16 + * to use this mixer code too. + */ + PPDMAUDIOSTREAM pStream; + rc = pConn->pfnStreamCreate(pConn, 0 /*fFlags*/, &CfgHost, &pStream); + if (RT_SUCCESS(rc)) { - /* Stream is not enabled, just skip. */ - rc = VINF_SUCCESS; + pMixStream->cFramesBackendBuffer = pStream->Cfg.Backend.cFramesBufferSize; + + /* Set up the mixing buffer conversion state. */ + if (pSink->enmDir == PDMAUDIODIR_IN) + rc = AudioMixBufInitWriteState(&pSink->MixBuf, &pMixStream->WriteState, &pStream->Cfg.Props); + else + rc = AudioMixBufInitPeekState(&pSink->MixBuf, &pMixStream->PeekState, &pStream->Cfg.Props); + if (RT_SUCCESS(rc)) + { + /* Save the audio stream pointer to this mixing stream. */ + pMixStream->pStream = pStream; + + /* Increase the stream's reference count to let others know + * we're relying on it to be around now. */ + pConn->pfnStreamRetain(pConn, pStream); + pMixStream->pConn = pConn; + pMixStream->uMagic = AUDMIXSTREAM_MAGIC; + + RTCritSectLeave(&pSink->CritSect); + + if (ppStream) + *ppStream = pMixStream; + return VINF_SUCCESS; + } + + rc = pConn->pfnStreamDestroy(pConn, pStream, true /*fImmediate*/); } - else - LogRel2(("Audio Mixer: Writing to mixer stream '%s' (sink '%s') failed, rc=%Rrc\n", - pMixStream->pszName, pSink->pszName, rc)); - if (RT_FAILURE(rc)) - LogFunc(("[%s] Failed writing to stream '%s': %Rrc\n", pSink->pszName, pMixStream->pszName, rc)); + /* + * Failed. Tear down the stream. + */ + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); } + RTCritSectDelete(&pMixStream->CritSect); } - - RTCircBufReleaseReadBlock(pCircBuf, cbChunkWritten); - - if ( RT_FAILURE(rc) - || !cbChunkWritten) - break; - - Assert(cbToWrite >= cbChunkWritten); - cbToWrite -= (uint32_t)cbChunkWritten; - - cbWritten += (uint32_t)cbChunkWritten; } + else + rc = VERR_NO_STR_MEMORY; - Log3Func(("[%s] cbWritten=%RU32\n", pMixStream->pszName, cbWritten)); - - if (pcbWritten) - *pcbWritten = cbWritten; - -#ifdef DEBUG_andy - AssertRC(rc); -#endif - + RTStrFree(pMixStream->pszStatPrefix); + pMixStream->pszStatPrefix = NULL; + RTStrFree(pMixStream->pszName); + pMixStream->pszName = NULL; + RTMemFree(pMixStream); return rc; } + /** - * Multiplexes audio output data to all connected mixer streams in a synchronized fashion, e.g. - * only multiplex as much data as all streams can handle at this time. + * Adds an audio stream to a specific audio sink. * - * @returns IPRT status code. - * @param pSink Sink to write audio output to. - * @param enmOp What mixing operation to use. Currently not implemented. - * @param pvBuf Pointer to audio data to write. - * @param cbBuf Size (in bytes) of audio data to write. - * @param pcbWrittenMin Returns minimum size (in bytes) successfully written to all mixer streams. Optional. + * @returns VBox status code. + * @param pSink Sink to add audio stream to. + * @param pStream Stream to add. */ -static int audioMixerSinkMultiplexSync(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, - uint32_t *pcbWrittenMin) +int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) { - AssertReturn(cbBuf, VERR_INVALID_PARAMETER); - RT_NOREF(enmOp); - - AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, - ("%s: Can't multiplex to a sink which is not an output sink\n", pSink->pszName)); - - int rc = VINF_SUCCESS; + LogFlowFuncEnter(); + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + Assert(pSink->uMagic == AUDMIXSINK_MAGIC); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + Assert(pStream->uMagic == AUDMIXSTREAM_MAGIC); + AssertPtrReturn(pStream->pConn, VERR_AUDIO_STREAM_NOT_READY); + AssertReturn(pStream->pSink == NULL, VERR_ALREADY_EXISTS); - uint32_t cbToWriteMin = UINT32_MAX; + int rc = RTCritSectEnter(&pSink->CritSect); + AssertRCReturn(rc, rc); - Log3Func(("[%s] cbBuf=%RU32\n", pSink->pszName, cbBuf)); + AssertLogRelMsgReturnStmt(pSink->cStreams < UINT8_MAX, ("too many streams!\n"), RTCritSectLeave(&pSink->CritSect), + VERR_TOO_MANY_OPEN_FILES); - PAUDMIXSTREAM pMixStream; - RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + /* + * If the sink is running and not in pending disable mode, make sure that + * the added stream also is enabled. Ignore any failure to enable it. + */ + if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + && !(pSink->fStatus & AUDMIXSINK_STS_DRAINING)) { - if (!(pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)) /* Mixing stream not enabled? Skip handling. */ - { - Log3Func(("[%s] Stream '%s' disabled, skipping ...\n", pSink->pszName, pMixStream->pszName)); - continue; - } - - cbToWriteMin = RT_MIN(cbBuf, RT_MIN(cbToWriteMin, (uint32_t)RTCircBufFree(pMixStream->pCircBuf))); + audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE); } - if (cbToWriteMin == UINT32_MAX) /* No space at all? */ - cbToWriteMin = 0; + /* Save pointer to sink the stream is attached to. */ + pStream->pSink = pSink; - if (cbToWriteMin) - { - RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) - { - if (!(pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)) /* Mixing stream not enabled? Skip handling. */ - continue; + /* Append stream to sink's list. */ + RTListAppend(&pSink->lstStreams, &pStream->Node); + pSink->cStreams++; - PRTCIRCBUF pCircBuf = pMixStream->pCircBuf; - void *pvChunk; - size_t cbChunk; + LogFlowFunc(("[%s] cStreams=%RU8, rc=%Rrc\n", pSink->pszName, pSink->cStreams, rc)); + RTCritSectLeave(&pSink->CritSect); + return rc; +} - uint32_t cbWrittenBuf = 0; - uint32_t cbToWriteBuf = cbToWriteMin; - while (cbToWriteBuf) - { - RTCircBufAcquireWriteBlock(pCircBuf, cbToWriteBuf, &pvChunk, &cbChunk); +/** + * Removes a mixer stream from a mixer sink, internal version. + * + * @returns VBox status code. + * @param pSink The mixer sink (valid). + * @param pStream The stream to remove (valid). + * + * @note Caller must own the sink lock. + */ +static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +{ + AssertPtr(pSink); + AssertPtr(pStream); + AssertMsgReturn(pStream->pSink == pSink, ("Stream '%s' is not part of sink '%s'\n", + pStream->pszName, pSink->pszName), VERR_NOT_FOUND); + Assert(RTCritSectIsOwner(&pSink->CritSect)); + LogFlowFunc(("[%s] (Stream = %s), cStreams=%RU8\n", pSink->pszName, pStream->pStream->Cfg.szName, pSink->cStreams)); - if (cbChunk) - memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenBuf, cbChunk); + /* + * Remove stream from sink, update the count and set the pSink member to NULL. + */ + RTListNodeRemove(&pStream->Node); - RTCircBufReleaseWriteBlock(pCircBuf, cbChunk); + Assert(pSink->cStreams > 0); + pSink->cStreams--; - cbWrittenBuf += (uint32_t)cbChunk; - Assert(cbWrittenBuf <= cbBuf); + pStream->pSink = NULL; - Assert(cbToWriteBuf >= cbChunk); - cbToWriteBuf -= (uint32_t)cbChunk; - } + return VINF_SUCCESS; +} - if (cbWrittenBuf) /* Update the mixer stream's last written time stamp. */ - pMixStream->tsLastReadWrittenNs = RTTimeNanoTS(); - Log3Func(("[%s] Mixer stream '%s' -> cbWrittenBuf=%RU32\n", pSink->pszName, pMixStream->pszName, cbWrittenBuf)); - } - } +/** + * Removes a mixer stream from a mixer sink. + * + * @param pSink The mixer sink. + * @param pStream The stream to remove. + */ +void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +{ + AssertPtrReturnVoid(pSink); + AssertReturnVoid(pSink->uMagic == AUDMIXSINK_MAGIC); + AssertPtrReturnVoid(pStream); + AssertReturnVoid(pStream->uMagic == AUDMIXSTREAM_MAGIC); - Log3Func(("[%s] cbBuf=%RU32, cbToWriteMin=%RU32\n", pSink->pszName, cbBuf, cbToWriteMin)); + int rc = RTCritSectEnter(&pSink->CritSect); + AssertRCReturnVoid(rc); - if (pcbWrittenMin) - *pcbWrittenMin = cbToWriteMin; + audioMixerSinkRemoveStreamInternal(pSink, pStream); - return rc; + RTCritSectLeave(&pSink->CritSect); } + /** - * Writes data to a mixer sink. + * Removes all streams from a given sink. * - * @returns IPRT status code. - * @param pSink Sink to write data to. - * @param enmOp Mixer operation to use when writing data to the sink. - * @param pvBuf Buffer containing the audio data to write. - * @param cbBuf Size (in bytes) of the buffer containing the audio data. - * @param pcbWritten Number of bytes written. Optional. + * @param pSink The mixer sink. NULL is ignored. */ -int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink) { - AssertPtrReturn(pSink, VERR_INVALID_POINTER); - RT_NOREF(enmOp); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn (cbBuf, VERR_INVALID_PARAMETER); - /* pcbWritten is optional. */ + if (!pSink) + return; + AssertReturnVoid(pSink->uMagic == AUDMIXSINK_MAGIC); int rc = RTCritSectEnter(&pSink->CritSect); - if (RT_FAILURE(rc)) - return rc; - - AssertMsg(pSink->fStatus & AUDMIXSINK_STS_RUNNING, - ("%s: Can't write to a sink which is not running (anymore) (status 0x%x)\n", pSink->pszName, pSink->fStatus)); - AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, - ("%s: Can't write to a sink which is not an output sink\n", pSink->pszName)); - - uint32_t cbWritten = 0; - uint32_t cbToWrite = RT_MIN(AudioMixBufFreeBytes(&pSink->MixBuf), cbBuf); - while (cbToWrite) - { - /* First, write the data to the mixer sink's own mixing buffer. - * Here the audio data can be transformed into the mixer sink's format. */ - uint32_t cfWritten = 0; - rc = AudioMixBufWriteCirc(&pSink->MixBuf, (uint8_t *)pvBuf + cbWritten, cbToWrite, &cfWritten); - if (RT_FAILURE(rc)) - break; + AssertRCReturnVoid(rc); - const uint32_t cbWrittenChunk = DrvAudioHlpFramesToBytes(cfWritten, &pSink->PCMProps); + LogFunc(("%s\n", pSink->pszName)); - Assert(cbToWrite >= cbWrittenChunk); - cbToWrite -= cbWrittenChunk; - cbWritten += cbWrittenChunk; + PAUDMIXSTREAM pStream, pStreamNext; + RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node) + { + audioMixerSinkRemoveStreamInternal(pSink, pStream); } + AssertStmt(pSink->cStreams == 0, pSink->cStreams = 0); - Log3Func(("[%s] cbBuf=%RU32 -> cbWritten=%RU32\n", pSink->pszName, cbBuf, cbWritten)); - - /* Update the sink's last written time stamp. */ - pSink->tsLastReadWrittenNs = RTTimeNanoTS(); - - if (pcbWritten) - *pcbWritten = cbWritten; + RTCritSectLeave(&pSink->CritSect); +} - int rc2 = RTCritSectLeave(&pSink->CritSect); - AssertRC(rc2); - return rc; -} /********************************************************************************************************************************* * Mixer Stream implementation. @@ -2112,21 +2492,19 @@ /** * Controls a mixer stream, internal version. * - * @returns IPRT status code. + * @returns VBox status code (generally ignored). * @param pMixStream Mixer stream to control. * @param enmCmd Mixer stream command to use. - * @param fCtl Additional control flags. Pass 0. */ -static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl) +static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd) { - AssertPtr(pMixStream->pConn); - AssertPtr(pMixStream->pStream); - - RT_NOREF(fCtl); + Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC); + AssertPtrReturn(pMixStream->pConn, VERR_AUDIO_STREAM_NOT_READY); + AssertPtrReturn(pMixStream->pStream, VERR_AUDIO_STREAM_NOT_READY); int rc = pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, enmCmd); - LogFlowFunc(("[%s] enmCmd=%ld, rc=%Rrc\n", pMixStream->pszName, enmCmd, rc)); + LogFlowFunc(("[%s] enmCmd=%d, rc=%Rrc\n", pMixStream->pszName, enmCmd, rc)); return rc; } @@ -2134,35 +2512,82 @@ /** * Updates a mixer stream's internal status. * + * This may perform a stream re-init if the driver requests it, in which case + * this may take a little while longer than usual... + * * @returns VBox status code. * @param pMixStream Mixer stream to to update internal status for. */ static int audioMixerStreamUpdateStatus(PAUDMIXSTREAM pMixStream) { + Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC); + + /* + * Reset the mixer status to start with. + */ pMixStream->fStatus = AUDMIXSTREAM_STATUS_NONE; - if (pMixStream->pConn) /* Audio connector available? */ + PPDMIAUDIOCONNECTOR const pConn = pMixStream->pConn; + if (pConn) /* Audio connector available? */ { - const uint32_t fStreamStatus = pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream); + PPDMAUDIOSTREAM const pStream = pMixStream->pStream; - if (DrvAudioHlpStreamStatusIsReady(fStreamStatus)) - pMixStream->fStatus |= AUDMIXSTREAM_STATUS_ENABLED; - - AssertPtr(pMixStream->pSink); - switch (pMixStream->pSink->enmDir) + /* + * Get the stream status. + * Do re-init if needed and fetch the status again afterwards. + */ + PDMAUDIOSTREAMSTATE enmState = pConn->pfnStreamGetState(pConn, pStream); + if (enmState != PDMAUDIOSTREAMSTATE_NEED_REINIT) + { /* likely */ } + else { - case AUDMIXSINKDIR_INPUT: - if (DrvAudioHlpStreamStatusCanRead(fStreamStatus)) - pMixStream->fStatus |= AUDMIXSTREAM_STATUS_CAN_READ; - break; + LogFunc(("[%s] needs re-init...\n", pMixStream->pszName)); + int rc = pConn->pfnStreamReInit(pConn, pStream); + enmState = pConn->pfnStreamGetState(pConn, pStream); + LogFunc(("[%s] re-init returns %Rrc and %s.\n", pMixStream->pszName, rc, PDMAudioStreamStateGetName(enmState))); + + PAUDMIXSINK const pSink = pMixStream->pSink; + AssertPtr(pSink); + if (pSink->enmDir == PDMAUDIODIR_OUT) + { + rc = AudioMixBufInitPeekState(&pSink->MixBuf, &pMixStream->PeekState, &pStream->Cfg.Props); + /** @todo we need to remember this, don't we? */ + AssertLogRelRCReturn(rc, VINF_SUCCESS); + } + else + { + rc = AudioMixBufInitWriteState(&pSink->MixBuf, &pMixStream->WriteState, &pStream->Cfg.Props); + /** @todo we need to remember this, don't we? */ + AssertLogRelRCReturn(rc, VINF_SUCCESS); + } + } - case AUDMIXSINKDIR_OUTPUT: - if (DrvAudioHlpStreamStatusCanWrite(fStreamStatus)) - pMixStream->fStatus |= AUDMIXSTREAM_STATUS_CAN_WRITE; + /* + * Translate the status to mixer speak. + */ + AssertMsg(enmState > PDMAUDIOSTREAMSTATE_INVALID && enmState < PDMAUDIOSTREAMSTATE_END, ("%d\n", enmState)); + switch (enmState) + { + case PDMAUDIOSTREAMSTATE_NOT_WORKING: + case PDMAUDIOSTREAMSTATE_NEED_REINIT: + case PDMAUDIOSTREAMSTATE_INACTIVE: + pMixStream->fStatus = AUDMIXSTREAM_STATUS_NONE; break; - - default: - AssertFailedReturn(VERR_NOT_IMPLEMENTED); + case PDMAUDIOSTREAMSTATE_ENABLED: + pMixStream->fStatus = AUDMIXSTREAM_STATUS_ENABLED; + break; + case PDMAUDIOSTREAMSTATE_ENABLED_READABLE: + Assert(pMixStream->pSink->enmDir == PDMAUDIODIR_IN); + pMixStream->fStatus = AUDMIXSTREAM_STATUS_ENABLED | AUDMIXSTREAM_STATUS_CAN_READ; + break; + case PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE: + Assert(pMixStream->pSink->enmDir == PDMAUDIODIR_OUT); + pMixStream->fStatus = AUDMIXSTREAM_STATUS_ENABLED | AUDMIXSTREAM_STATUS_CAN_WRITE; + break; + /* no default */ + case PDMAUDIOSTREAMSTATE_INVALID: + case PDMAUDIOSTREAMSTATE_END: + case PDMAUDIOSTREAMSTATE_32BIT_HACK: break; } } @@ -2171,50 +2596,38 @@ return VINF_SUCCESS; } -/** - * Controls a mixer stream. - * - * @returns IPRT status code. - * @param pMixStream Mixer stream to control. - * @param enmCmd Mixer stream command to use. - * @param fCtl Additional control flags. Pass 0. - */ -int AudioMixerStreamCtl(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl) -{ - RT_NOREF(fCtl); - AssertPtrReturn(pMixStream, VERR_INVALID_POINTER); - /** @todo Validate fCtl. */ - - int rc = RTCritSectEnter(&pMixStream->CritSect); - if (RT_FAILURE(rc)) - return rc; - - rc = audioMixerStreamCtlInternal(pMixStream, enmCmd, fCtl); - - int rc2 = RTCritSectLeave(&pMixStream->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; - - return rc; -} /** - * Destroys a mixer stream, internal version. + * Destroys & frees a mixer stream, internal version. * - * @param pMixStream Mixer stream to destroy. + * Worker for audioMixerSinkDestroyInternal and AudioMixerStreamDestroy. + * + * @param pMixStream Mixer stream to destroy. + * @param pDevIns The device instance the statistics are registered with. + * @param fImmediate How to handle still draining streams, whether to let + * them complete (@c false) or destroy them immediately (@c + * true). */ -static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pMixStream) +static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pMixStream, PPDMDEVINS pDevIns, bool fImmediate) { - AssertPtrReturnVoid(pMixStream); - + AssertPtr(pMixStream); LogFunc(("%s\n", pMixStream->pszName)); + Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC); + + /* + * Invalidate it. + */ + pMixStream->uMagic = AUDMIXSTREAM_MAGIC_DEAD; + /* + * Destroy the driver stream (if any). + */ if (pMixStream->pConn) /* Stream has a connector interface present? */ { if (pMixStream->pStream) { pMixStream->pConn->pfnStreamRelease(pMixStream->pConn, pMixStream->pStream); - pMixStream->pConn->pfnStreamDestroy(pMixStream->pConn, pMixStream->pStream); + pMixStream->pConn->pfnStreamDestroy(pMixStream->pConn, pMixStream->pStream, fImmediate); pMixStream->pStream = NULL; } @@ -2222,128 +2635,70 @@ pMixStream->pConn = NULL; } - if (pMixStream->pszName) - { - RTStrFree(pMixStream->pszName); - pMixStream->pszName = NULL; - } - - if (pMixStream->pCircBuf) + /* + * Stats. Doing it by prefix is soo much faster than individually, btw. + */ + if (pMixStream->pszStatPrefix) { - RTCircBufDestroy(pMixStream->pCircBuf); - pMixStream->pCircBuf = NULL; + PDMDevHlpSTAMDeregisterByPrefix(pDevIns, pMixStream->pszStatPrefix); + RTStrFree(pMixStream->pszStatPrefix); + pMixStream->pszStatPrefix = NULL; } + /* + * Delete the critsect and free the memory. + */ int rc2 = RTCritSectDelete(&pMixStream->CritSect); AssertRC(rc2); + RTStrFree(pMixStream->pszName); + pMixStream->pszName = NULL; + RTMemFree(pMixStream); - pMixStream = NULL; } + /** * Destroys a mixer stream. * - * @param pMixStream Mixer stream to destroy. + * @param pMixStream Mixer stream to destroy. + * @param pDevIns The device instance statistics are registered with. + * @param fImmediate How to handle still draining streams, whether to let + * them complete (@c false) or destroy them immediately (@c + * true). */ -void AudioMixerStreamDestroy(PAUDMIXSTREAM pMixStream) +void AudioMixerStreamDestroy(PAUDMIXSTREAM pMixStream, PPDMDEVINS pDevIns, bool fImmediate) { if (!pMixStream) return; - - int rc2 = RTCritSectEnter(&pMixStream->CritSect); - AssertRC(rc2); - + AssertReturnVoid(pMixStream->uMagic == AUDMIXSTREAM_MAGIC); LogFunc(("%s\n", pMixStream->pszName)); - if (pMixStream->pSink) /* Is the stream part of a sink? */ - { - /* Save sink pointer, as after audioMixerSinkRemoveStreamInternal() the - * pointer will be gone from the stream. */ - PAUDMIXSINK pSink = pMixStream->pSink; - - rc2 = audioMixerSinkRemoveStreamInternal(pSink, pMixStream); - if (RT_SUCCESS(rc2)) - { - Assert(pSink->cStreams); - pSink->cStreams--; - } - } - else - rc2 = VINF_SUCCESS; - - int rc3 = RTCritSectLeave(&pMixStream->CritSect); - AssertRC(rc3); - - if (RT_SUCCESS(rc2)) - { - audioMixerStreamDestroyInternal(pMixStream); - pMixStream = NULL; - } - - LogFlowFunc(("Returning %Rrc\n", rc2)); -} - -/** - * Returns whether a mixer stream currently is active (playing/recording) or not. - * - * @returns @c true if playing/recording, @c false if not. - * @param pMixStream Mixer stream to return status for. - */ -bool AudioMixerStreamIsActive(PAUDMIXSTREAM pMixStream) -{ - int rc2 = RTCritSectEnter(&pMixStream->CritSect); - if (RT_FAILURE(rc2)) - return false; - - AssertPtr(pMixStream->pConn); - AssertPtr(pMixStream->pStream); - - bool fIsActive; + /* + * Serializing paranoia. + */ + int rc = RTCritSectEnter(&pMixStream->CritSect); + AssertRCReturnVoid(rc); + RTCritSectLeave(&pMixStream->CritSect); - if ( pMixStream->pConn - && pMixStream->pStream - && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTREAMSTS_FLAGS_ENABLED)) - { - fIsActive = true; + /* + * Unlink from sink if associated with one. + */ + PAUDMIXSINK pSink = pMixStream->pSink; + if ( RT_VALID_PTR(pSink) + && pSink->uMagic == AUDMIXSINK_MAGIC) + { + RTCritSectEnter(&pSink->CritSect); + audioMixerSinkRemoveStreamInternal(pMixStream->pSink, pMixStream); + RTCritSectLeave(&pSink->CritSect); } - else - fIsActive = false; - - rc2 = RTCritSectLeave(&pMixStream->CritSect); - AssertRC(rc2); + else if (pSink) + AssertFailed(); - return fIsActive; + /* + * Do the actual stream destruction. + */ + audioMixerStreamDestroyInternal(pMixStream, pDevIns, fImmediate); + LogFlowFunc(("returns\n")); } -/** - * Returns whether a mixer stream is valid (e.g. initialized and in a working state) or not. - * - * @returns @c true if valid, @c false if not. - * @param pMixStream Mixer stream to return status for. - */ -bool AudioMixerStreamIsValid(PAUDMIXSTREAM pMixStream) -{ - if (!pMixStream) - return false; - - int rc2 = RTCritSectEnter(&pMixStream->CritSect); - if (RT_FAILURE(rc2)) - return false; - - bool fIsValid; - - if ( pMixStream->pConn - && pMixStream->pStream - && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED)) - { - fIsValid = true; - } - else - fIsValid = false; - - rc2 = RTCritSectLeave(&pMixStream->CritSect); - AssertRC(rc2); - - return fIsValid; -} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixer.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixer.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/AudioMixer.h 2020-10-16 16:32:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/AudioMixer.h 2022-09-01 13:23:47.000000000 +0000 @@ -28,10 +28,23 @@ #include #include +#include "AudioMixBuffer.h" +#include "AudioHlp.h" +/** @defgroup grp_pdm_ifs_audio_mixing Audio Mixing + * @ingroup grp_pdm_ifs_audio + * + * @note This is currently placed under PDM Audio Interface as that seemed like + * the best place for it. + * + * @{ + */ + /** Pointer to an audio mixer sink. */ typedef struct AUDMIXSINK *PAUDMIXSINK; +/** Pointer to a const audio mixer sink. */ +typedef struct AUDMIXSINK const *PCAUDMIXSINK; /** @@ -39,39 +52,39 @@ */ typedef struct AUDIOMIXER { - /** The mixer's name. */ - char *pszName; - /** The mixer's critical section. */ - RTCRITSECT CritSect; + /** Magic value (AUDIOMIXER_MAGIC). */ + uintptr_t uMagic; + /** The mixer's name (allocated after this structure). */ + char const *pszName; /** The master volume of this mixer. */ PDMAUDIOVOLUME VolMaster; - /** List of audio mixer sinks. */ + /** List of audio mixer sinks (AUDMIXSINK). */ RTLISTANCHOR lstSinks; /** Number of used audio sinks. */ uint8_t cSinks; + /** Mixer flags. See AUDMIXER_FLAGS_XXX. */ + uint32_t fFlags; + /** The mixer's critical section. */ + RTCRITSECT CritSect; } AUDIOMIXER; /** Pointer to an audio mixer instance. */ typedef AUDIOMIXER *PAUDIOMIXER; -/** Defines an audio mixer stream's flags. */ -#define AUDMIXSTREAMFLAGS uint32_t - -/** No flags specified. */ -#define AUDMIXSTREAM_F_NONE 0 -/** The mixing stream is flagged as being enabled (active). */ -#define AUDMIXSTREAM_F_ENABLED RT_BIT(0) - -/** Defines an audio mixer stream's internal status. */ -#define AUDMIXSTREAMSTATUS uint32_t - -/** No status set. */ -#define AUDMIXSTREAM_STATUS_NONE 0 -/** The mixing stream is enabled (active). */ -#define AUDMIXSTREAM_STATUS_ENABLED RT_BIT(0) -/** The mixing stream can be read from. */ -#define AUDMIXSTREAM_STATUS_CAN_READ RT_BIT(1) -/** The mixing stream can be written to. */ -#define AUDMIXSTREAM_STATUS_CAN_WRITE RT_BIT(2) +/** Value for AUDIOMIXER::uMagic. (Attilio Joseph "Teo" Macero) */ +#define AUDIOMIXER_MAGIC UINT32_C(0x19251030) +/** Value for AUDIOMIXER::uMagic after destruction. */ +#define AUDIOMIXER_MAGIC_DEAD UINT32_C(0x20080219) + +/** @name AUDMIXER_FLAGS_XXX - For AudioMixerCreate(). + * @{ */ +/** No mixer flags specified. */ +#define AUDMIXER_FLAGS_NONE 0 +/** Debug mode enabled. + * This writes .WAV file to the host, usually to the temporary directory. */ +#define AUDMIXER_FLAGS_DEBUG RT_BIT(0) +/** Validation mask. */ +#define AUDMIXER_FLAGS_VALID_MASK UINT32_C(0x00000001) +/** @} */ /** @@ -79,136 +92,110 @@ */ typedef struct AUDMIXSTREAM { - /** List node. */ + /** List entry on AUDMIXSINK::lstStreams. */ RTLISTNODE Node; + /** Magic value (AUDMIXSTREAM_MAGIC). */ + uint32_t uMagic; + /** The backend buffer size in frames (for draining deadline calc). */ + uint32_t cFramesBackendBuffer; + /** Stream status of type AUDMIXSTREAM_STATUS_. */ + uint32_t fStatus; + /** Number of writable/readable frames the last time we checked. */ + uint32_t cFramesLastAvail; + /** Set if the stream has been found unreliable wrt. consuming/producing + * samples, and that we shouldn't consider it when deciding how much to move + * from the mixer buffer and to the drivers. */ + bool fUnreliable; /** Name of this stream. */ char *pszName; - /** The streams's critical section. */ - RTCRITSECT CritSect; + /** The statistics prefix. */ + char *pszStatPrefix; /** Sink this stream is attached to. */ PAUDMIXSINK pSink; - /** Stream flags of type AUDMIXSTREAM_F_. */ - uint32_t fFlags; - /** Stream status of type AUDMIXSTREAM_STATUS_. */ - uint32_t fStatus; /** Pointer to audio connector being used. */ PPDMIAUDIOCONNECTOR pConn; /** Pointer to PDM audio stream this mixer stream handles. */ PPDMAUDIOSTREAM pStream; + union + { + /** Output: Mixing buffer peeking state & config. */ + AUDIOMIXBUFPEEKSTATE PeekState; + /** Input: Mixing buffer writing state & config. */ + AUDIOMIXBUFWRITESTATE WriteState; + }; /** Last read (recording) / written (playback) timestamp (in ns). */ uint64_t tsLastReadWrittenNs; - /** The stream's circular buffer for temporarily - * holding (raw) device audio data. */ - PRTCIRCBUF pCircBuf; -} AUDMIXSTREAM, *PAUDMIXSTREAM; - -/** Defines an audio sink's current status. */ -#define AUDMIXSINKSTS uint32_t - -/** No status specified. */ -#define AUDMIXSINK_STS_NONE 0 -/** The sink is active and running. */ -#define AUDMIXSINK_STS_RUNNING RT_BIT(0) -/** The sink is in a pending disable state. */ -#define AUDMIXSINK_STS_PENDING_DISABLE RT_BIT(1) -/** Dirty flag. - * For output sinks this means that there is data in the - * sink which has not been played yet. - * For input sinks this means that there is data in the - * sink which has been recorded but not transferred to the - * destination yet. */ -#define AUDMIXSINK_STS_DIRTY RT_BIT(2) + /** The streams's critical section. */ + RTCRITSECT CritSect; +} AUDMIXSTREAM; +/** Pointer to an audio mixer stream. */ +typedef AUDMIXSTREAM *PAUDMIXSTREAM; + +/** Value for AUDMIXSTREAM::uMagic. (Jan Erik Kongshaug) */ +#define AUDMIXSTREAM_MAGIC UINT32_C(0x19440704) +/** Value for AUDMIXSTREAM::uMagic after destruction. */ +#define AUDMIXSTREAM_MAGIC_DEAD UINT32_C(0x20191105) -/** - * Audio mixer sink direction. - */ -typedef enum AUDMIXSINKDIR -{ - /** Unknown direction. */ - AUDMIXSINKDIR_UNKNOWN = 0, - /** Input (capturing from a device). */ - AUDMIXSINKDIR_INPUT, - /** Output (playing to a device). */ - AUDMIXSINKDIR_OUTPUT, - /** The usual 32-bit hack. */ - AUDMIXSINKDIR_32BIT_HACK = 0x7fffffff -} AUDMIXSINKDIR; -/** - * Audio mixer sink command. - */ -typedef enum AUDMIXSINKCMD -{ - /** Unknown command, do not use. */ - AUDMIXSINKCMD_UNKNOWN = 0, - /** Enables the sink. */ - AUDMIXSINKCMD_ENABLE, - /** Disables the sink. */ - AUDMIXSINKCMD_DISABLE, - /** Pauses the sink. */ - AUDMIXSINKCMD_PAUSE, - /** Resumes the sink. */ - AUDMIXSINKCMD_RESUME, - /** Tells the sink's streams to drop all (buffered) data immediately. */ - AUDMIXSINKCMD_DROP, - /** Hack to blow the type up to 32-bit. */ - AUDMIXSINKCMD_32BIT_HACK = 0x7fffffff -} AUDMIXSINKCMD; +/** @name AUDMIXSTREAM_STATUS_XXX - mixer stream status. + * (This is a destilled version of PDMAUDIOSTREAM_STS_XXX.) + * @{ */ +/** No status set. */ +#define AUDMIXSTREAM_STATUS_NONE UINT32_C(0) +/** The mixing stream is enabled (active). */ +#define AUDMIXSTREAM_STATUS_ENABLED RT_BIT_32(0) +/** The mixing stream can be read from. + * Always set together with AUDMIXSTREAM_STATUS_ENABLED. */ +#define AUDMIXSTREAM_STATUS_CAN_READ RT_BIT_32(1) +/** The mixing stream can be written to. + * Always set together with AUDMIXSTREAM_STATUS_ENABLED. */ +#define AUDMIXSTREAM_STATUS_CAN_WRITE RT_BIT_32(2) +/** @} */ -/** - * Audio input sink specifics. - * - * Do not use directly. Instead, use AUDMIXSINK. - */ -typedef struct AUDMIXSINKIN -{ - /** The current recording source. Can be NULL if not set. */ - PAUDMIXSTREAM pStreamRecSource; -} AUDMIXSINKIN; -/** - * Audio output sink specifics. - * - * Do not use directly. Instead, use AUDMIXSINK. - */ -typedef struct AUDMIXSINKOUT -{ -} AUDMIXSINKOUT; +/** Callback for an asynchronous I/O update job. */ +typedef DECLCALLBACKTYPE(void, FNAUDMIXSINKUPDATE,(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser)); +/** Pointer to a callback for an asynchronous I/O update job. */ +typedef FNAUDMIXSINKUPDATE *PFNAUDMIXSINKUPDATE; /** * Audio mixer sink. */ typedef struct AUDMIXSINK { + /** List entry on AUDIOMIXER::lstSinks. */ RTLISTNODE Node; + /** Magic value (AUDMIXSINK_MAGIC). */ + uint32_t uMagic; + /** The sink direction (either PDMAUDIODIR_IN or PDMAUDIODIR_OUT). */ + PDMAUDIODIR enmDir; /** Pointer to mixer object this sink is bound to. */ PAUDIOMIXER pParent; - /** Name of this sink. */ - char *pszName; - /** The sink direction, that is, - * if this sink handles input or output. */ - AUDMIXSINKDIR enmDir; - /** The sink's critical section. */ - RTCRITSECT CritSect; - /** This sink's mixing buffer, acting as - * a parent buffer for all streams this sink owns. */ - PDMAUDIOMIXBUF MixBuf; - /** Union for input/output specifics. */ - union - { - AUDMIXSINKIN In; - AUDMIXSINKOUT Out; - }; - /** Sink status of type AUDMIXSINK_STS_XXX. */ - AUDMIXSINKSTS fStatus; - /** The sink's PCM format. */ + /** Name of this sink (allocated after this structure). */ + char const *pszName; + /** The sink's PCM format (i.e. the guest device side). */ PDMAUDIOPCMPROPS PCMProps; + /** Sink status bits - AUDMIXSINK_STS_XXX. */ + uint32_t fStatus; + /** Number of bytes to be transferred from the device DMA buffer before the + * streams will be put into draining mode. */ + uint32_t cbDmaLeftToDrain; + /** The deadline for draining if it's pending. */ + uint64_t nsDrainDeadline; + /** When the draining startet (for logging). */ + uint64_t nsDrainStarted; /** Number of streams assigned. */ uint8_t cStreams; - /** List of assigned streams. - * Note: All streams have the same PCM properties, so the - * mixer does not do any conversion. */ - /** @todo Use something faster -- vector maybe? */ + /** List of assigned streams (AUDMIXSTREAM). + * @note All streams have the same PCM properties, so the mixer does not do + * any conversion. bird: That is *NOT* true any more, the mixer has + * encoders/decoder states for each stream (well, input is still a todo). + * + * @todo Use something faster -- vector maybe? bird: It won't be faster. You + * will have a vector of stream pointers (because you cannot have a vector + * of full AUDMIXSTREAM structures since they'll move when the vector is + * reallocated and we need pointers to them to give out to devices), which + * is the same cost as going via Node.pNext/pPrev. */ RTLISTANCHOR lstStreams; /** The volume of this sink. The volume always will * be combined with the mixer's master volume. */ @@ -219,68 +206,134 @@ uint64_t tsLastUpdatedMs; /** Last read (recording) / written (playback) timestamp (in ns). */ uint64_t tsLastReadWrittenNs; -#ifdef VBOX_AUDIO_MIXER_DEBUG + /** Union for input/output specifics. */ + union + { + struct + { + /** The sink's peek state. */ + AUDIOMIXBUFPEEKSTATE State; + } In; + struct + { + /** The sink's write state. */ + AUDIOMIXBUFWRITESTATE State; + } Out; + }; struct { - PPDMAUDIOFILE pFile; + PAUDIOHLPFILE pFile; } Dbg; -#endif + /** This sink's mixing buffer. */ + AUDIOMIXBUF MixBuf; + /** Asynchronous I/O thread related stuff. */ + struct + { + /** The thread handle, NIL_RTTHREAD if not active. */ + RTTHREAD hThread; + /** Event for letting the thread know there is some data to process. */ + RTSEMEVENT hEvent; + /** The device instance (same for all update jobs). */ + PPDMDEVINS pDevIns; + /** Started indicator. */ + volatile bool fStarted; + /** Shutdown indicator. */ + volatile bool fShutdown; + /** Number of update jobs this sink has (usually zero or one). */ + uint8_t cUpdateJobs; + /** The minimum typical interval for all jobs. */ + uint32_t cMsMinTypicalInterval; + /** Update jobs for this sink. */ + struct + { + /** User specific argument. */ + void *pvUser; + /** The callback. */ + PFNAUDMIXSINKUPDATE pfnUpdate; + /** Typical interval in milliseconds. */ + uint32_t cMsTypicalInterval; + } aUpdateJobs[8]; + } AIO; + /** The sink's critical section. */ + RTCRITSECT CritSect; } AUDMIXSINK; -/** - * Audio mixer operation. - */ -typedef enum AUDMIXOP -{ - /** Invalid operation, do not use. */ - AUDMIXOP_INVALID = 0, - /** Copy data from A to B, overwriting data in B. */ - AUDMIXOP_COPY, - /** Blend data from A with (existing) data in B. */ - AUDMIXOP_BLEND, - /** The usual 32-bit hack. */ - AUDMIXOP_32BIT_HACK = 0x7fffffff -} AUDMIXOP; - -/** No flags specified. */ -#define AUDMIXSTRMCTL_F_NONE 0 - -int AudioMixerCreate(const char *pszName, uint32_t uFlags, PAUDIOMIXER *ppMixer); -int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink); -void AudioMixerDestroy(PAUDIOMIXER pMixer); -void AudioMixerInvalidate(PAUDIOMIXER pMixer); -void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink); -int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol); -void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs); - -int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); -int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, PPDMIAUDIOCONNECTOR pConnector, PPDMAUDIOSTREAMCFG pCfg, AUDMIXSTREAMFLAGS fFlags, PAUDMIXSTREAM *ppStream); -int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmCmd); -void AudioMixerSinkDestroy(PAUDMIXSINK pSink); -uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink); -uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink); -AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink); -const char *AudioMixerSinkGetName(const PAUDMIXSINK pSink); -PAUDMIXSTREAM AudioMixerSinkGetRecordingSource(PAUDMIXSINK pSink); -PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex); -AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink); -uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink); -bool AudioMixerSinkIsActive(PAUDMIXSINK pSink); -int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead); -void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); -void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink); -void AudioMixerSinkReset(PAUDMIXSINK pSink); -void AudioMixerSinkGetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps); -int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps); -int AudioMixerSinkSetRecordingSource(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); -int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol); -int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten); -int AudioMixerSinkUpdate(PAUDMIXSINK pSink); - -int AudioMixerStreamCtl(PAUDMIXSTREAM pStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl); -void AudioMixerStreamDestroy(PAUDMIXSTREAM pStream); -bool AudioMixerStreamIsActive(PAUDMIXSTREAM pStream); -bool AudioMixerStreamIsValid(PAUDMIXSTREAM pStream); +/** Value for AUDMIXSINK::uMagic. (Sir George Martin) */ +#define AUDMIXSINK_MAGIC UINT32_C(0x19260103) +/** Value for AUDMIXSINK::uMagic after destruction. */ +#define AUDMIXSINK_MAGIC_DEAD UINT32_C(0x20160308) + + +/** @name AUDMIXSINK_STS_XXX - Sink status bits. + * @{ */ +/** No status specified. */ +#define AUDMIXSINK_STS_NONE 0 +/** The sink is active and running. */ +#define AUDMIXSINK_STS_RUNNING RT_BIT(0) +/** Draining the buffers and pending stop - output only. */ +#define AUDMIXSINK_STS_DRAINING RT_BIT(1) +/** Drained the DMA buffer. */ +#define AUDMIXSINK_STS_DRAINED_DMA RT_BIT(2) +/** Drained the mixer buffer, only waiting for streams (drivers) now. */ +#define AUDMIXSINK_STS_DRAINED_MIXBUF RT_BIT(3) +/** Dirty flag. + * - For output sinks this means that there is data in the sink which has not + * been played yet. + * - For input sinks this means that there is data in the sink which has been + * recorded but not transferred to the destination yet. + * @todo This isn't used for *anything* at the moment. Remove? */ +#define AUDMIXSINK_STS_DIRTY RT_BIT(4) +/** @} */ + + +/** @name Audio mixer methods + * @{ */ +int AudioMixerCreate(const char *pszName, uint32_t fFlags, PAUDIOMIXER *ppMixer); +void AudioMixerDestroy(PAUDIOMIXER pMixer, PPDMDEVINS pDevIns); +void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs); +int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PCPDMAUDIOVOLUME pVol); +int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, PDMAUDIODIR enmDir, PPDMDEVINS pDevIns, PAUDMIXSINK *ppSink); +/** @} */ + +/** @name Audio mixer sink methods + * @{ */ +int AudioMixerSinkStart(PAUDMIXSINK pSink); +int AudioMixerSinkDrainAndStop(PAUDMIXSINK pSink, uint32_t cbComming); +void AudioMixerSinkDestroy(PAUDMIXSINK pSink, PPDMDEVINS pDevIns); +uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink); +uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink); +PDMAUDIODIR AudioMixerSinkGetDir(PCAUDMIXSINK pSink); +uint32_t AudioMixerSinkGetStatus(PAUDMIXSINK pSink); +bool AudioMixerSinkIsActive(PAUDMIXSINK pSink); +void AudioMixerSinkReset(PAUDMIXSINK pSink); +int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PCPDMAUDIOPCMPROPS pPCMProps, uint32_t cMsSchedulingHint); +int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVol); +int AudioMixerSinkUpdate(PAUDMIXSINK pSink, uint32_t cbDmaUsed, uint32_t cbDmaPeriod); + +int AudioMixerSinkAddUpdateJob(PAUDMIXSINK pSink, PFNAUDMIXSINKUPDATE pfnUpdate, void *pvUser, uint32_t cMsTypicalInterval); +int AudioMixerSinkRemoveUpdateJob(PAUDMIXSINK pSink, PFNAUDMIXSINKUPDATE pfnUpdate, void *pvUser); +int AudioMixerSinkSignalUpdateJob(PAUDMIXSINK pSink); +uint64_t AudioMixerSinkTransferFromCircBuf(PAUDMIXSINK pSink, PRTCIRCBUF pCircBuf, uint64_t offStream, + uint32_t idStream, PAUDIOHLPFILE pDbgFile); +uint64_t AudioMixerSinkTransferToCircBuf(PAUDMIXSINK pSink, PRTCIRCBUF pCircBuf, uint64_t offStream, + uint32_t idStream, PAUDIOHLPFILE pDbgFile); +int AudioMixerSinkLock(PAUDMIXSINK pSink); +int AudioMixerSinkTryLock(PAUDMIXSINK pSink); +int AudioMixerSinkUnlock(PAUDMIXSINK pSink); + +int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, PPDMIAUDIOCONNECTOR pConnector, PCPDMAUDIOSTREAMCFG pCfg, + PPDMDEVINS pDevIns, PAUDMIXSTREAM *ppStream); +int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); +void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); +void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink); +/** @} */ + +/** @name Audio mixer stream methods + * @{ */ +void AudioMixerStreamDestroy(PAUDMIXSTREAM pStream, PPDMDEVINS pDevIns, bool fImmediate); +/** @} */ + +/** @} */ #endif /* !VBOX_INCLUDED_SRC_Audio_AudioMixer_h */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHdaCodec.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHdaCodec.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHdaCodec.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHdaCodec.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,2883 @@ +/* $Id: DevHdaCodec.cpp $ */ +/** @file + * Intel HD Audio Controller Emulation - Codec, Sigmatel/IDT STAC9220. + * + * Implemented based on the Intel HD Audio specification and the + * Sigmatel/IDT STAC9220 datasheet. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_HDA_CODEC +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "VBoxDD.h" +#include "AudioMixer.h" +#include "DevHda.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define AMPLIFIER_IN 0 +#define AMPLIFIER_OUT 1 +#define AMPLIFIER_LEFT 1 +#define AMPLIFIER_RIGHT 0 +#define AMPLIFIER_REGISTER(amp, inout, side, index) ((amp)[30*(inout) + 15*(side) + (index)]) + + +/** @name STAC9220 - Nodes IDs / Names. + * @{ */ +#define STAC9220_NID_ROOT 0x0 /* Root node */ +#define STAC9220_NID_AFG 0x1 /* Audio Configuration Group */ +#define STAC9220_NID_DAC0 0x2 /* Out */ +#define STAC9220_NID_DAC1 0x3 /* Out */ +#define STAC9220_NID_DAC2 0x4 /* Out */ +#define STAC9220_NID_DAC3 0x5 /* Out */ +#define STAC9220_NID_ADC0 0x6 /* In */ +#define STAC9220_NID_ADC1 0x7 /* In */ +#define STAC9220_NID_SPDIF_OUT 0x8 /* Out */ +#define STAC9220_NID_SPDIF_IN 0x9 /* In */ +/** Also known as PIN_A. */ +#define STAC9220_NID_PIN_HEADPHONE0 0xA /* In, Out */ +#define STAC9220_NID_PIN_B 0xB /* In, Out */ +#define STAC9220_NID_PIN_C 0xC /* In, Out */ +/** Also known as PIN D. */ +#define STAC9220_NID_PIN_HEADPHONE1 0xD /* In, Out */ +#define STAC9220_NID_PIN_E 0xE /* In */ +#define STAC9220_NID_PIN_F 0xF /* In, Out */ +/** Also known as DIGOUT0. */ +#define STAC9220_NID_PIN_SPDIF_OUT 0x10 /* Out */ +/** Also known as DIGIN. */ +#define STAC9220_NID_PIN_SPDIF_IN 0x11 /* In */ +#define STAC9220_NID_ADC0_MUX 0x12 /* In */ +#define STAC9220_NID_ADC1_MUX 0x13 /* In */ +#define STAC9220_NID_PCBEEP 0x14 /* Out */ +#define STAC9220_NID_PIN_CD 0x15 /* In */ +#define STAC9220_NID_VOL_KNOB 0x16 +#define STAC9220_NID_AMP_ADC0 0x17 /* In */ +#define STAC9220_NID_AMP_ADC1 0x18 /* In */ +/* Only for STAC9221. */ +#define STAC9221_NID_ADAT_OUT 0x19 /* Out */ +#define STAC9221_NID_I2S_OUT 0x1A /* Out */ +#define STAC9221_NID_PIN_I2S_OUT 0x1B /* Out */ + +/** Number of total nodes emulated. */ +#define STAC9221_NUM_NODES 0x1C +/** @} */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * A codec verb descriptor. + */ +typedef struct CODECVERB +{ + /** Verb. */ + uint32_t uVerb; + /** Verb mask. */ + uint32_t fMask; + /** + * Function pointer for implementation callback. + * + * This is always a valid pointer in ring-3, while elsewhere a NULL indicates + * that we must return to ring-3 to process it. + * + * @returns VBox status code (99.9% is VINF_SUCCESS, caller doesn't care much + * what you return at present). + * + * @param pThis The shared codec intance data. + * @param uCmd The command. + * @param puResp Where to return the response value. + * + * @thread EMT or task worker thread (see HDASTATE::hCorbDmaTask). + */ + DECLCALLBACKMEMBER(int, pfn)(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp); + /** Friendly name, for debugging. */ + const char *pszName; +} CODECVERB; +/** Pointer to a const codec verb descriptor. */ +typedef CODECVERB const *PCCODECVERB; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name STAC9220 Node Classifications. + * @note Referenced through STAC9220WIDGET in the constructor below. + * @{ */ +static uint8_t const g_abStac9220Ports[] = { STAC9220_NID_PIN_HEADPHONE0, STAC9220_NID_PIN_B, STAC9220_NID_PIN_C, STAC9220_NID_PIN_HEADPHONE1, STAC9220_NID_PIN_E, STAC9220_NID_PIN_F, 0 }; +static uint8_t const g_abStac9220Dacs[] = { STAC9220_NID_DAC0, STAC9220_NID_DAC1, STAC9220_NID_DAC2, STAC9220_NID_DAC3, 0 }; +static uint8_t const g_abStac9220Adcs[] = { STAC9220_NID_ADC0, STAC9220_NID_ADC1, 0 }; +static uint8_t const g_abStac9220SpdifOuts[] = { STAC9220_NID_SPDIF_OUT, 0 }; +static uint8_t const g_abStac9220SpdifIns[] = { STAC9220_NID_SPDIF_IN, 0 }; +static uint8_t const g_abStac9220DigOutPins[] = { STAC9220_NID_PIN_SPDIF_OUT, 0 }; +static uint8_t const g_abStac9220DigInPins[] = { STAC9220_NID_PIN_SPDIF_IN, 0 }; +static uint8_t const g_abStac9220AdcVols[] = { STAC9220_NID_AMP_ADC0, STAC9220_NID_AMP_ADC1, 0 }; +static uint8_t const g_abStac9220AdcMuxs[] = { STAC9220_NID_ADC0_MUX, STAC9220_NID_ADC1_MUX, 0 }; +static uint8_t const g_abStac9220Pcbeeps[] = { STAC9220_NID_PCBEEP, 0 }; +static uint8_t const g_abStac9220Cds[] = { STAC9220_NID_PIN_CD, 0 }; +static uint8_t const g_abStac9220VolKnobs[] = { STAC9220_NID_VOL_KNOB, 0 }; +/** @} */ + +/** @name STAC 9221 Values. + * @note Referenced through STAC9220WIDGET in the constructor below + * @{ */ +/** @todo Is STAC9220_NID_SPDIF_IN really correct for reserved nodes? */ +static uint8_t const g_abStac9220Reserveds[] = { STAC9220_NID_SPDIF_IN, STAC9221_NID_ADAT_OUT, STAC9221_NID_I2S_OUT, STAC9221_NID_PIN_I2S_OUT, 0 }; +/** @} */ + + +/** SSM description of CODECCOMMONNODE. */ +static SSMFIELD const g_aCodecNodeFields[] = +{ + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.uID), + SSMFIELD_ENTRY_PAD_HC_AUTO(3, 3), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F00_param), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F02_param), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, au32Params), + SSMFIELD_ENTRY_TERM() +}; + +/** Backward compatibility with v1 of CODECCOMMONNODE. */ +static SSMFIELD const g_aCodecNodeFieldsV1[] = +{ + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.uID), + SSMFIELD_ENTRY_PAD_HC_AUTO(3, 7), + SSMFIELD_ENTRY_OLD_HCPTR(Core.name), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F00_param), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F02_param), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, au32Params), + SSMFIELD_ENTRY_TERM() +}; + + + +/********************************************************************************************************************************* +* STAC9220 Constructor / Reset * +*********************************************************************************************************************************/ + +/** + * Resets a single node of the codec. + * + * @param pThis HDA codec of node to reset. + * @param uNID Node ID to set node to. + * @param pNode Node to reset. + * @param fInReset Set if we're called from hdaCodecReset via + * stac9220Reset, clear if called from stac9220Construct. + */ +static void stac9220NodeReset(PHDACODECR3 pThis, uint8_t uNID, PCODECNODE pNode, bool const fInReset) +{ + LogFlowFunc(("NID=0x%x (%RU8)\n", uNID, uNID)); + + if ( !fInReset + && ( uNID != STAC9220_NID_ROOT + && uNID != STAC9220_NID_AFG) + ) + { + RT_ZERO(pNode->node); + } + + /* Set common parameters across all nodes. */ + pNode->node.uID = uNID; + pNode->node.uSD = 0; + + switch (uNID) + { + /* Root node. */ + case STAC9220_NID_ROOT: + { + /* Set the revision ID. */ + pNode->root.node.au32F00_param[0x02] = CODEC_MAKE_F00_02(0x1, 0x0, 0x3, 0x4, 0x0, 0x1); + break; + } + + /* + * AFG (Audio Function Group). + */ + case STAC9220_NID_AFG: + { + pNode->afg.node.au32F00_param[0x08] = CODEC_MAKE_F00_08(1, 0xd, 0xd); + /* We set the AFG's PCM capabitilies fixed to 16kHz, 22.5kHz + 44.1kHz, 16-bit signed. */ + pNode->afg.node.au32F00_param[0x0A] = CODEC_F00_0A_44_1KHZ /* 44.1 kHz */ + | CODEC_F00_0A_44_1KHZ_1_2X /* Messed up way of saying 22.05 kHz */ + | CODEC_F00_0A_48KHZ_1_3X /* Messed up way of saying 16 kHz. */ + | CODEC_F00_0A_16_BIT; /* 16-bit signed */ + /* Note! We do not set CODEC_F00_0A_48KHZ here because we end up with + S/PDIF output showing up in windows and it trying to configure + streams other than 0 and 4 and stuff going sideways in the + stream setup/removal area. */ + pNode->afg.node.au32F00_param[0x0B] = CODEC_F00_0B_PCM; + pNode->afg.node.au32F00_param[0x0C] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_BALANCED_IO + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED + | CODEC_F00_0C_CAP_IMPENDANCE_SENSE; + + /* Default input amplifier capabilities. */ + pNode->node.au32F00_param[0x0D] = CODEC_MAKE_F00_0D(CODEC_AMP_CAP_MUTE, + CODEC_AMP_STEP_SIZE, + CODEC_AMP_NUM_STEPS, + CODEC_AMP_OFF_INITIAL); + /* Default output amplifier capabilities. */ + pNode->node.au32F00_param[0x12] = CODEC_MAKE_F00_12(CODEC_AMP_CAP_MUTE, + CODEC_AMP_STEP_SIZE, + CODEC_AMP_NUM_STEPS, + CODEC_AMP_OFF_INITIAL); + + pNode->afg.node.au32F00_param[0x11] = CODEC_MAKE_F00_11(1, 1, 0, 0, 4); + pNode->afg.node.au32F00_param[0x0F] = CODEC_F00_0F_D3 + | CODEC_F00_0F_D2 + | CODEC_F00_0F_D1 + | CODEC_F00_0F_D0; + + pNode->afg.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D2, CODEC_F05_D2); /* PS-Act: D2, PS->Set D2. */ + pNode->afg.u32F08_param = 0; + pNode->afg.u32F17_param = 0; + break; + } + + /* + * DACs. + */ + case STAC9220_NID_DAC0: /* DAC0: Headphones 0 + 1 */ + case STAC9220_NID_DAC1: /* DAC1: PIN C */ + case STAC9220_NID_DAC2: /* DAC2: PIN B */ + case STAC9220_NID_DAC3: /* DAC3: PIN F */ + { + pNode->dac.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, + HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_2X, HDA_SDFMT_16_BIT, + HDA_SDFMT_CHAN_STEREO); + + /* 7.3.4.6: Audio widget capabilities. */ + pNode->dac.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 13, 0) + | CODEC_F00_09_CAP_L_R_SWAP + | CODEC_F00_09_CAP_POWER_CTRL + | CODEC_F00_09_CAP_OUT_AMP_PRESENT + | CODEC_F00_09_CAP_STEREO; + + /* Connection list; must be 0 if the only connection for the widget is + * to the High Definition Audio Link. */ + pNode->dac.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 0 /* Entries */); + + pNode->dac.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); + + RT_ZERO(pNode->dac.B_params); + AMPLIFIER_REGISTER(pNode->dac.B_params, AMPLIFIER_OUT, AMPLIFIER_LEFT, 0) = 0x7F | RT_BIT(7); + AMPLIFIER_REGISTER(pNode->dac.B_params, AMPLIFIER_OUT, AMPLIFIER_RIGHT, 0) = 0x7F | RT_BIT(7); + break; + } + + /* + * ADCs. + */ + case STAC9220_NID_ADC0: /* Analog input. */ + { + pNode->node.au32F02_param[0] = STAC9220_NID_AMP_ADC0; + goto adc_init; + } + + case STAC9220_NID_ADC1: /* Analog input (CD). */ + { + pNode->node.au32F02_param[0] = STAC9220_NID_AMP_ADC1; + + /* Fall through is intentional. */ + adc_init: + + pNode->adc.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, + HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_2X, HDA_SDFMT_16_BIT, + HDA_SDFMT_CHAN_STEREO); + + pNode->adc.u32F03_param = RT_BIT(0); + pNode->adc.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); /* PS-Act: D3 Set: D3 */ + + pNode->adc.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_INPUT, 0xD, 0) + | CODEC_F00_09_CAP_POWER_CTRL + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_PROC_WIDGET + | CODEC_F00_09_CAP_STEREO; + /* Connection list entries. */ + pNode->adc.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + break; + } + + /* + * SP/DIF In/Out. + */ + case STAC9220_NID_SPDIF_OUT: + { + pNode->spdifout.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, + HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_2X, HDA_SDFMT_16_BIT, + HDA_SDFMT_CHAN_STEREO); + pNode->spdifout.u32F06_param = 0; + pNode->spdifout.u32F0d_param = 0; + + pNode->spdifout.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 4, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_FMT_OVERRIDE + | CODEC_F00_09_CAP_STEREO; + + /* Use a fixed format from AFG. */ + pNode->spdifout.node.au32F00_param[0xA] = pThis->aNodes[STAC9220_NID_AFG].node.au32F00_param[0xA]; + pNode->spdifout.node.au32F00_param[0xB] = CODEC_F00_0B_PCM; + break; + } + + case STAC9220_NID_SPDIF_IN: + { + pNode->spdifin.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, + HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_2X, HDA_SDFMT_16_BIT, + HDA_SDFMT_CHAN_STEREO); + + pNode->spdifin.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_INPUT, 4, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_FMT_OVERRIDE + | CODEC_F00_09_CAP_STEREO; + + /* Use a fixed format from AFG. */ + pNode->spdifin.node.au32F00_param[0xA] = pThis->aNodes[STAC9220_NID_AFG].node.au32F00_param[0xA]; + pNode->spdifin.node.au32F00_param[0xB] = CODEC_F00_0B_PCM; + + /* Connection list entries. */ + pNode->spdifin.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + pNode->spdifin.node.au32F02_param[0] = 0x11; + break; + } + + /* + * PINs / Ports. + */ + case STAC9220_NID_PIN_HEADPHONE0: /* Port A: Headphone in/out (front). */ + { + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(0 /*fPresent*/, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_HEADPHONE_AMP + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; + + /* Connection list entry 0: Goes to DAC0. */ + pNode->port.node.au32F02_param[0] = STAC9220_NID_DAC0; + + if (!fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_FRONT, + CODEC_F1C_DEVICE_HP, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_GREEN, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_1, 0x0 /* Seq */); + goto port_init; + } + + case STAC9220_NID_PIN_B: /* Port B: Rear CLFE (Center / Subwoofer). */ + { + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; + + /* Connection list entry 0: Goes to DAC2. */ + pNode->port.node.au32F02_param[0] = STAC9220_NID_DAC2; + + if (!fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_SPEAKER, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_BLACK, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_0, 0x1 /* Seq */); + goto port_init; + } + + case STAC9220_NID_PIN_C: /* Rear Speaker. */ + { + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; + + /* Connection list entry 0: Goes to DAC1. */ + pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC1; + + if (!fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_SPEAKER, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_GREEN, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_0, 0x0 /* Seq */); + goto port_init; + } + + case STAC9220_NID_PIN_HEADPHONE1: /* Also known as PIN_D. */ + { + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_HEADPHONE_AMP + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; + + /* Connection list entry 0: Goes to DAC1. */ + pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC0; + + if (!fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_FRONT, + CODEC_F1C_DEVICE_MIC, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_PINK, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_15, 0x0 /* Ignored */); + /* Fall through is intentional. */ + + port_init: + + pNode->port.u32F07_param = CODEC_F07_IN_ENABLE + | CODEC_F07_OUT_ENABLE; + pNode->port.u32F08_param = 0; + + pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_UNSOL + | CODEC_F00_09_CAP_STEREO; + /* Connection list entries. */ + pNode->port.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + break; + } + + case STAC9220_NID_PIN_E: + { + pNode->port.u32F07_param = CODEC_F07_IN_ENABLE; + pNode->port.u32F08_param = 0; + /* If Line in is reported as enabled, OS X sees no speakers! Windows does + * not care either way, although Linux does. + */ + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(0 /* fPresent */, 0); + + pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_UNSOL + | CODEC_F00_09_CAP_STEREO; + + pNode->port.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT; + + if (!fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_LINE_IN, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_BLUE, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_4, 0x1 /* Seq */); + break; + } + + case STAC9220_NID_PIN_F: + { + pNode->port.u32F07_param = CODEC_F07_IN_ENABLE | CODEC_F07_OUT_ENABLE; + pNode->port.u32F08_param = 0; + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /* fPresent */, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_UNSOL + | CODEC_F00_09_CAP_OUT_AMP_PRESENT + | CODEC_F00_09_CAP_STEREO; + + pNode->port.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT; + + /* Connection list entry 0: Goes to DAC3. */ + pNode->port.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC3; + + if (!fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_INTERNAL, + CODEC_F1C_DEVICE_SPEAKER, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_ORANGE, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_0, 0x2 /* Seq */); + break; + } + + case STAC9220_NID_PIN_SPDIF_OUT: /* Rear SPDIF Out. */ + { + pNode->digout.u32F07_param = CODEC_F07_OUT_ENABLE; + pNode->digout.u32F09_param = 0; + + pNode->digout.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_STEREO; + pNode->digout.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_OUTPUT; + + /* Connection list entries. */ + pNode->digout.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 3 /* Entries */); + pNode->digout.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_SPDIF_OUT, + STAC9220_NID_AMP_ADC0, STAC9221_NID_ADAT_OUT, 0); + if (!fInReset) + pNode->digout.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_SPDIF_OUT, + CODEC_F1C_CONNECTION_TYPE_DIN, + CODEC_F1C_COLOR_BLACK, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_2, 0x0 /* Seq */); + break; + } + + case STAC9220_NID_PIN_SPDIF_IN: + { + pNode->digin.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); /* PS-Act: D3 -> D3 */ + pNode->digin.u32F07_param = CODEC_F07_IN_ENABLE; + pNode->digin.u32F08_param = 0; + pNode->digin.u32F09_param = CODEC_MAKE_F09_DIGITAL(0, 0); + pNode->digin.u32F0c_param = 0; + + pNode->digin.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 3, 0) + | CODEC_F00_09_CAP_POWER_CTRL + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_UNSOL + | CODEC_F00_09_CAP_STEREO; + + pNode->digin.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_EAPD + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT; + if (!fInReset) + pNode->digin.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_SPDIF_IN, + CODEC_F1C_CONNECTION_TYPE_OTHER_DIGITAL, + CODEC_F1C_COLOR_BLACK, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_5, 0x0 /* Seq */); + break; + } + + case STAC9220_NID_ADC0_MUX: + { + pNode->adcmux.u32F01_param = 0; /* Connection select control index (STAC9220_NID_PIN_E). */ + goto adcmux_init; + } + + case STAC9220_NID_ADC1_MUX: + { + pNode->adcmux.u32F01_param = 1; /* Connection select control index (STAC9220_NID_PIN_CD). */ + /* Fall through is intentional. */ + + adcmux_init: + + pNode->adcmux.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_SELECTOR, 0, 0) + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_AMP_FMT_OVERRIDE + | CODEC_F00_09_CAP_OUT_AMP_PRESENT + | CODEC_F00_09_CAP_STEREO; + + pNode->adcmux.node.au32F00_param[0xD] = CODEC_MAKE_F00_0D(0, 27, 4, 0); + + /* Connection list entries. */ + pNode->adcmux.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 7 /* Entries */); + pNode->adcmux.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_PIN_E, + STAC9220_NID_PIN_CD, + STAC9220_NID_PIN_F, + STAC9220_NID_PIN_B); + pNode->adcmux.node.au32F02_param[0x4] = RT_MAKE_U32_FROM_U8(STAC9220_NID_PIN_C, + STAC9220_NID_PIN_HEADPHONE1, + STAC9220_NID_PIN_HEADPHONE0, + 0x0 /* Unused */); + + /* STAC 9220 v10 6.21-22.{4,5} both(left and right) out amplifiers initialized with 0. */ + RT_ZERO(pNode->adcmux.B_params); + break; + } + + case STAC9220_NID_PCBEEP: + { + pNode->pcbeep.u32F0a_param = 0; + + pNode->pcbeep.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_BEEP_GEN, 0, 0) + | CODEC_F00_09_CAP_AMP_FMT_OVERRIDE + | CODEC_F00_09_CAP_OUT_AMP_PRESENT; + pNode->pcbeep.node.au32F00_param[0xD] = CODEC_MAKE_F00_0D(0, 17, 3, 3); + + RT_ZERO(pNode->pcbeep.B_params); + break; + } + + case STAC9220_NID_PIN_CD: + { + pNode->cdnode.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_STEREO; + pNode->cdnode.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT; + + if (!fInReset) + pNode->cdnode.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_FIXED, + CODEC_F1C_LOCATION_INTERNAL, + CODEC_F1C_DEVICE_CD, + CODEC_F1C_CONNECTION_TYPE_ATAPI, + CODEC_F1C_COLOR_UNKNOWN, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_4, 0x2 /* Seq */); + break; + } + + case STAC9220_NID_VOL_KNOB: + { + pNode->volumeKnob.u32F08_param = 0; + pNode->volumeKnob.u32F0f_param = 0x7f; + + pNode->volumeKnob.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_VOLUME_KNOB, 0, 0); + pNode->volumeKnob.node.au32F00_param[0xD] = RT_BIT(7) | 0x7F; + + /* Connection list entries. */ + pNode->volumeKnob.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 4 /* Entries */); + pNode->volumeKnob.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_DAC0, + STAC9220_NID_DAC1, + STAC9220_NID_DAC2, + STAC9220_NID_DAC3); + break; + } + + case STAC9220_NID_AMP_ADC0: /* ADC0Vol */ + { + pNode->adcvol.node.au32F02_param[0] = STAC9220_NID_ADC0_MUX; + goto adcvol_init; + } + + case STAC9220_NID_AMP_ADC1: /* ADC1Vol */ + { + pNode->adcvol.node.au32F02_param[0] = STAC9220_NID_ADC1_MUX; + /* Fall through is intentional. */ + + adcvol_init: + + pNode->adcvol.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_SELECTOR, 0, 0) + | CODEC_F00_09_CAP_L_R_SWAP + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_IN_AMP_PRESENT + | CODEC_F00_09_CAP_STEREO; + + + pNode->adcvol.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + + RT_ZERO(pNode->adcvol.B_params); + AMPLIFIER_REGISTER(pNode->adcvol.B_params, AMPLIFIER_IN, AMPLIFIER_LEFT, 0) = RT_BIT(7); + AMPLIFIER_REGISTER(pNode->adcvol.B_params, AMPLIFIER_IN, AMPLIFIER_RIGHT, 0) = RT_BIT(7); + break; + } + + /* + * STAC9221 nodes. + */ + + case STAC9221_NID_ADAT_OUT: + { + pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_VENDOR_DEFINED, 3, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_STEREO; + break; + } + + case STAC9221_NID_I2S_OUT: + { + pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 3, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_STEREO; + break; + } + + case STAC9221_NID_PIN_I2S_OUT: + { + pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_STEREO; + + pNode->node.au32F00_param[0xC] = CODEC_F00_0C_CAP_OUTPUT; + + /* Connection list entries. */ + pNode->node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + pNode->node.au32F02_param[0] = STAC9221_NID_I2S_OUT; + + if (!fInReset) + pNode->reserved.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_NO_PHYS, + CODEC_F1C_LOCATION_NA, + CODEC_F1C_DEVICE_LINE_OUT, + CODEC_F1C_CONNECTION_TYPE_UNKNOWN, + CODEC_F1C_COLOR_UNKNOWN, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_15, 0x0 /* Ignored */); + break; + } + + default: + AssertMsgFailed(("Node %RU8 not implemented\n", uNID)); + break; + } +} + + +/** + * Resets the codec with all its connected nodes. + * + * @param pThis HDA codec to reset. + */ +static void stac9220Reset(PHDACODECR3 pThis) +{ + AssertPtrReturnVoid(pThis->aNodes); + + LogRel(("HDA: Codec reset\n")); + + uint8_t const cTotalNodes = (uint8_t)RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes)); + for (uint8_t i = 0; i < cTotalNodes; i++) + stac9220NodeReset(pThis, i, &pThis->aNodes[i], true /*fInReset*/); +} + + +static int stac9220Construct(PHDACODECR3 pThis, HDACODECCFG *pCfg) +{ + /* + * Note: The Linux kernel uses "patch_stac922x" for the fixups, + * which in turn uses "ref922x_pin_configs" for the configuration + * defaults tweaking in sound/pci/hda/patch_sigmatel.c. + */ + pCfg->idVendor = 0x8384; /* SigmaTel */ + pCfg->idDevice = 0x7680; /* STAC9221 A1 */ + pCfg->bBSKU = 0x76; + pCfg->idAssembly = 0x80; + + AssertCompile(STAC9221_NUM_NODES <= RT_ELEMENTS(pThis->aNodes)); + pCfg->cTotalNodes = STAC9221_NUM_NODES; + pCfg->idxAdcVolsLineIn = STAC9220_NID_AMP_ADC0; + pCfg->idxDacLineOut = STAC9220_NID_DAC1; + + /* Copy over the node class lists and popuplate afNodeClassifications. */ +#define STAC9220WIDGET(a_Type) do { \ + AssertCompile(RT_ELEMENTS(g_abStac9220##a_Type##s) <= RT_ELEMENTS(pCfg->ab##a_Type##s)); \ + uint8_t *pbDst = (uint8_t *)&pCfg->ab##a_Type##s[0]; \ + uintptr_t i; \ + for (i = 0; i < RT_ELEMENTS(g_abStac9220##a_Type##s); i++) \ + { \ + uint8_t const idNode = g_abStac9220##a_Type##s[i]; \ + if (idNode == 0) \ + break; \ + AssertReturn(idNode < RT_ELEMENTS(pThis->aNodes), VERR_INTERNAL_ERROR_3); \ + pCfg->afNodeClassifications[idNode] |= RT_CONCAT(CODEC_NODE_CLS_,a_Type); \ + pbDst[i] = idNode; \ + } \ + Assert(i + 1 == RT_ELEMENTS(g_abStac9220##a_Type##s)); \ + for (; i < RT_ELEMENTS(pCfg->ab##a_Type##s); i++) \ + pbDst[i] = 0; \ + } while (0) + STAC9220WIDGET(Port); + STAC9220WIDGET(Dac); + STAC9220WIDGET(Adc); + STAC9220WIDGET(AdcVol); + STAC9220WIDGET(AdcMux); + STAC9220WIDGET(Pcbeep); + STAC9220WIDGET(SpdifIn); + STAC9220WIDGET(SpdifOut); + STAC9220WIDGET(DigInPin); + STAC9220WIDGET(DigOutPin); + STAC9220WIDGET(Cd); + STAC9220WIDGET(VolKnob); + STAC9220WIDGET(Reserved); +#undef STAC9220WIDGET + + /* + * Initialize all codec nodes. + * This is specific to the codec, so do this here. + * + * Note: Do *not* call stac9220Reset() here, as this would not + * initialize the node default configuration values then! + */ + for (uint8_t i = 0; i < STAC9221_NUM_NODES; i++) + stac9220NodeReset(pThis, i, &pThis->aNodes[i], false /*fInReset*/); + + /* Common root node initializers. */ + pThis->aNodes[STAC9220_NID_ROOT].root.node.au32F00_param[0] = CODEC_MAKE_F00_00(pCfg->idVendor, pCfg->idDevice); + pThis->aNodes[STAC9220_NID_ROOT].root.node.au32F00_param[4] = CODEC_MAKE_F00_04(0x1, 0x1); + + /* Common AFG node initializers. */ + pThis->aNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x4] = CODEC_MAKE_F00_04(0x2, STAC9221_NUM_NODES - 2); + pThis->aNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x5] = CODEC_MAKE_F00_05(1, CODEC_F00_05_AFG); + pThis->aNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0xA] = CODEC_F00_0A_44_1KHZ | CODEC_F00_0A_16_BIT; + pThis->aNodes[STAC9220_NID_AFG].afg.u32F20_param = CODEC_MAKE_F20(pCfg->idVendor, pCfg->bBSKU, pCfg->idAssembly); + + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Common Helpers * +*********************************************************************************************************************************/ + +/* + * Some generic predicate functions. + */ +#define HDA_CODEC_IS_NODE_OF_TYPE_FUNC(a_Type) \ + DECLINLINE(bool) hdaCodecIs##a_Type##Node(PHDACODECR3 pThis, uint8_t idNode) \ + { \ + Assert(idNode < RT_ELEMENTS(pThis->Cfg.afNodeClassifications)); \ + Assert( (memchr(&pThis->Cfg.RT_CONCAT3(ab,a_Type,s)[0], idNode, sizeof(pThis->Cfg.RT_CONCAT3(ab,a_Type,s))) != NULL) \ + == RT_BOOL(pThis->Cfg.afNodeClassifications[idNode] & RT_CONCAT(CODEC_NODE_CLS_,a_Type))); \ + return RT_BOOL(pThis->Cfg.afNodeClassifications[idNode] & RT_CONCAT(CODEC_NODE_CLS_,a_Type)); \ + } +/* hdaCodecIsPortNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Port) +/* hdaCodecIsDacNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Dac) +/* hdaCodecIsAdcVolNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(AdcVol) +/* hdaCodecIsAdcNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Adc) +/* hdaCodecIsAdcMuxNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(AdcMux) +/* hdaCodecIsPcbeepNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Pcbeep) +/* hdaCodecIsSpdifOutNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(SpdifOut) +/* hdaCodecIsSpdifInNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(SpdifIn) +/* hdaCodecIsDigInPinNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(DigInPin) +/* hdaCodecIsDigOutPinNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(DigOutPin) +/* hdaCodecIsCdNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Cd) +/* hdaCodecIsVolKnobNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(VolKnob) +/* hdaCodecIsReservedNode */ +HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Reserved) + + +/* + * Misc helpers. + */ +static int hdaR3CodecToAudVolume(PHDACODECR3 pThis, PCODECNODE pNode, AMPLIFIER *pAmp, PDMAUDIOMIXERCTL enmMixerCtl) +{ + RT_NOREF(pNode); + + uint8_t iDir; + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_VOLUME_MASTER: + case PDMAUDIOMIXERCTL_FRONT: + iDir = AMPLIFIER_OUT; + break; + case PDMAUDIOMIXERCTL_LINE_IN: + case PDMAUDIOMIXERCTL_MIC_IN: + iDir = AMPLIFIER_IN; + break; + default: + AssertMsgFailedReturn(("Invalid mixer control %RU32\n", enmMixerCtl), VERR_INVALID_PARAMETER); + break; + } + + int iMute; + iMute = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & RT_BIT(7); + iMute |= AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_RIGHT, 0) & RT_BIT(7); + iMute >>=7; + iMute &= 0x1; + + uint8_t bLeft = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & 0x7f; + uint8_t bRight = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_RIGHT, 0) & 0x7f; + + /* + * The STAC9220 volume controls have 0 to -96dB attenuation range in 128 steps. + * We have 0 to -96dB range in 256 steps. HDA volume setting of 127 must map + * to 255 internally (0dB), while HDA volume setting of 0 (-96dB) should map + * to 1 (rather than zero) internally. + */ + bLeft = (bLeft + 1) * (2 * 255) / 256; + bRight = (bRight + 1) * (2 * 255) / 256; + + PDMAUDIOVOLUME Vol; + PDMAudioVolumeInitFromStereo(&Vol, RT_BOOL(iMute), bLeft, bRight); + + LogFunc(("[NID0x%02x] %RU8/%RU8%s\n", pNode->node.uID, bLeft, bRight, Vol.fMuted ? "- Muted!" : "")); + LogRel2(("HDA: Setting volume for mixer control '%s' to %RU8/%RU8%s\n", + PDMAudioMixerCtlGetName(enmMixerCtl), bLeft, bRight, Vol.fMuted ? "- Muted!" : "")); + + return hdaR3MixerSetVolume(pThis, enmMixerCtl, &Vol); +} + + +DECLINLINE(void) hdaCodecSetRegister(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset, uint32_t mask) +{ + Assert((pu32Reg && u8Offset < 32)); + *pu32Reg &= ~(mask << u8Offset); + *pu32Reg |= (u32Cmd & mask) << u8Offset; +} + +DECLINLINE(void) hdaCodecSetRegisterU8(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset) +{ + hdaCodecSetRegister(pu32Reg, u32Cmd, u8Offset, CODEC_VERB_8BIT_DATA); +} + +DECLINLINE(void) hdaCodecSetRegisterU16(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset) +{ + hdaCodecSetRegister(pu32Reg, u32Cmd, u8Offset, CODEC_VERB_16BIT_DATA); +} + + +/********************************************************************************************************************************* +* Verb Processor Functions. * +*********************************************************************************************************************************/ +#if 0 /* unused */ + +/** + * @interface_method_impl{CODECVERB,pfn, Unimplemented} + */ +static DECLCALLBACK(int) vrbProcUnimplemented(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + RT_NOREF(pThis, uCmd); + LogFlowFunc(("uCmd(raw:%x: cad:%x, d:%c, nid:%x, verb:%x)\n", uCmd, + CODEC_CAD(uCmd), CODEC_DIRECT(uCmd) ? 'N' : 'Y', CODEC_NID(uCmd), CODEC_VERBDATA(uCmd))); + *puResp = 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, ??? } + */ +static DECLCALLBACK(int) vrbProcBreak(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + int rc; + rc = vrbProcUnimplemented(pThis, uCmd, puResp); + *puResp |= CODEC_RESPONSE_UNSOLICITED; + return rc; +} + +#endif /* unused */ + +/** + * @interface_method_impl{CODECVERB,pfn, b-- } + */ +static DECLCALLBACK(int) vrbProcGetAmplifier(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + /* HDA spec 7.3.3.7 Note A */ + /** @todo If index out of range response should be 0. */ + uint8_t u8Index = CODEC_GET_AMP_DIRECTION(uCmd) == AMPLIFIER_OUT ? 0 : CODEC_GET_AMP_INDEX(uCmd); + + PCODECNODE pNode = &pThis->aNodes[CODEC_NID(uCmd)]; + if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + *puResp = AMPLIFIER_REGISTER(pNode->dac.B_params, + CODEC_GET_AMP_DIRECTION(uCmd), + CODEC_GET_AMP_SIDE(uCmd), + u8Index); + else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd))) + *puResp = AMPLIFIER_REGISTER(pNode->adcvol.B_params, + CODEC_GET_AMP_DIRECTION(uCmd), + CODEC_GET_AMP_SIDE(uCmd), + u8Index); + else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(uCmd))) + *puResp = AMPLIFIER_REGISTER(pNode->adcmux.B_params, + CODEC_GET_AMP_DIRECTION(uCmd), + CODEC_GET_AMP_SIDE(uCmd), + u8Index); + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd))) + *puResp = AMPLIFIER_REGISTER(pNode->pcbeep.B_params, + CODEC_GET_AMP_DIRECTION(uCmd), + CODEC_GET_AMP_SIDE(uCmd), + u8Index); + else if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + *puResp = AMPLIFIER_REGISTER(pNode->port.B_params, + CODEC_GET_AMP_DIRECTION(uCmd), + CODEC_GET_AMP_SIDE(uCmd), + u8Index); + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + *puResp = AMPLIFIER_REGISTER(pNode->adc.B_params, + CODEC_GET_AMP_DIRECTION(uCmd), + CODEC_GET_AMP_SIDE(uCmd), + u8Index); + else + LogRel2(("HDA: Warning: Unhandled get amplifier command: 0x%x (NID=0x%x [%RU8])\n", uCmd, CODEC_NID(uCmd), CODEC_NID(uCmd))); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, ??? } + */ +static DECLCALLBACK(int) vrbProcGetParameter(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + Assert((uCmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F00_PARAM_LENGTH); + if ((uCmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F00_PARAM_LENGTH) + { + *puResp = 0; + + LogFlowFunc(("invalid F00 parameter %d\n", (uCmd & CODEC_VERB_8BIT_DATA))); + return VINF_SUCCESS; + } + + *puResp = pThis->aNodes[CODEC_NID(uCmd)].node.au32F00_param[uCmd & CODEC_VERB_8BIT_DATA]; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f01 } + */ +static DECLCALLBACK(int) vrbProcGetConSelectCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].adcmux.u32F01_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F01_param; + else if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F01_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32F01_param; + else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].adcvol.u32F01_param; + else + LogRel2(("HDA: Warning: Unhandled get connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 701 } + */ +static DECLCALLBACK(int) vrbProcSetConSelectCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adcmux.u32F01_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F01_param; + else if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F01_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adc.u32F01_param; + else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adcvol.u32F01_param; + else + LogRel2(("HDA: Warning: Unhandled set connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, 0); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f07 } + */ +static DECLCALLBACK(int) vrbProcGetPinCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F07_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F07_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F07_param; + else if (hdaCodecIsCdNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].cdnode.u32F07_param; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].pcbeep.u32F07_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F07_param; + else + LogRel2(("HDA: Warning: Unhandled get pin control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 707 } + */ +static DECLCALLBACK(int) vrbProcSetPinCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F07_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F07_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F07_param; + else if (hdaCodecIsCdNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].cdnode.u32F07_param; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].pcbeep.u32F07_param; + else if ( hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd)) + && CODEC_NID(uCmd) == 0x1b) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F07_param; + else + LogRel2(("HDA: Warning: Unhandled set pin control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, 0); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f08 } + */ +static DECLCALLBACK(int) vrbProcGetUnsolicitedEnabled(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F08_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F08_param; + else if ((uCmd) == STAC9220_NID_AFG) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].afg.u32F08_param; + else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].volumeKnob.u32F08_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F08_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F08_param; + else + LogRel2(("HDA: Warning: Unhandled get unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 708 } + */ +static DECLCALLBACK(int) vrbProcSetUnsolicitedEnabled(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F08_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F08_param; + else if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].afg.u32F08_param; + else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].volumeKnob.u32F08_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F08_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F08_param; + else + LogRel2(("HDA: Warning: Unhandled set unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, 0); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f09 } + */ +static DECLCALLBACK(int) vrbProcGetPinSense(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F09_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F09_param; + else + { + AssertFailed(); + LogRel2(("HDA: Warning: Unhandled get pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 709 } + */ +static DECLCALLBACK(int) vrbProcSetPinSense(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F09_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F09_param; + else + LogRel2(("HDA: Warning: Unhandled set pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, 0); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, ??? } + */ +static DECLCALLBACK(int) vrbProcGetConnectionListEntry(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + Assert((uCmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F02_PARAM_LENGTH); + if ((uCmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F02_PARAM_LENGTH) + { + LogFlowFunc(("access to invalid F02 index %d\n", (uCmd & CODEC_VERB_8BIT_DATA))); + return VINF_SUCCESS; + } + *puResp = pThis->aNodes[CODEC_NID(uCmd)].node.au32F02_param[uCmd & CODEC_VERB_8BIT_DATA]; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f03 } + */ +static DECLCALLBACK(int) vrbProcGetProcessingState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32F03_param; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 703 } + */ +static DECLCALLBACK(int) vrbProcSetProcessingState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + hdaCodecSetRegisterU8(&pThis->aNodes[CODEC_NID(uCmd)].adc.u32F03_param, uCmd, 0); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f0d } + */ +static DECLCALLBACK(int) vrbProcGetDigitalConverter(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F0d_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F0d_param; + return VINF_SUCCESS; +} + + +static int codecSetDigitalConverter(PHDACODECR3 pThis, uint32_t uCmd, uint8_t u8Offset, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + hdaCodecSetRegisterU8(&pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F0d_param, uCmd, u8Offset); + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + hdaCodecSetRegisterU8(&pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F0d_param, uCmd, u8Offset); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 70d } + */ +static DECLCALLBACK(int) vrbProcSetDigitalConverter1(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + return codecSetDigitalConverter(pThis, uCmd, 0, puResp); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 70e } + */ +static DECLCALLBACK(int) vrbProcSetDigitalConverter2(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + return codecSetDigitalConverter(pThis, uCmd, 8, puResp); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f20 } + */ +static DECLCALLBACK(int) vrbProcGetSubId(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + Assert(CODEC_CAD(uCmd) == pThis->Cfg.id); + uint8_t const cTotalNodes = (uint8_t)RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes)); + Assert(CODEC_NID(uCmd) < cTotalNodes); + if (CODEC_NID(uCmd) >= cTotalNodes) + { + LogFlowFunc(("invalid node address %d\n", CODEC_NID(uCmd))); + *puResp = 0; + return VINF_SUCCESS; + } + if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].afg.u32F20_param; + else + *puResp = 0; + return VINF_SUCCESS; +} + + +static int codecSetSubIdX(PHDACODECR3 pThis, uint32_t uCmd, uint8_t u8Offset) +{ + Assert(CODEC_CAD(uCmd) == pThis->Cfg.id); + uint8_t const cTotalNodes = (uint8_t)RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes)); + Assert(CODEC_NID(uCmd) < cTotalNodes); + if (CODEC_NID(uCmd) >= cTotalNodes) + { + LogFlowFunc(("invalid node address %d\n", CODEC_NID(uCmd))); + return VINF_SUCCESS; + } + uint32_t *pu32Reg; + if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].afg.u32F20_param; + else + AssertFailedReturn(VINF_SUCCESS); + hdaCodecSetRegisterU8(pu32Reg, uCmd, u8Offset); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 720 } + */ +static DECLCALLBACK(int) vrbProcSetSubId0(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + return codecSetSubIdX(pThis, uCmd, 0); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 721 } + */ +static DECLCALLBACK(int) vrbProcSetSubId1(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + return codecSetSubIdX(pThis, uCmd, 8); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 722 } + */ +static DECLCALLBACK(int) vrbProcSetSubId2(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + return codecSetSubIdX(pThis, uCmd, 16); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 723 } + */ +static DECLCALLBACK(int) vrbProcSetSubId3(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + return codecSetSubIdX(pThis, uCmd, 24); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, ??? } + */ +static DECLCALLBACK(int) vrbProcReset(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + Assert(CODEC_CAD(uCmd) == pThis->Cfg.id); + + if (pThis->Cfg.enmType == CODECTYPE_STAC9220) + { + Assert(CODEC_NID(uCmd) == STAC9220_NID_AFG); + + if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + stac9220Reset(pThis); + } + else + AssertFailedReturn(VERR_NOT_IMPLEMENTED); + + *puResp = 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f05 } + */ +static DECLCALLBACK(int) vrbProcGetPowerState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].afg.u32F05_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32F05_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32F05_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F05_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F05_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F05_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F05_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F05_param; + else + LogRel2(("HDA: Warning: Unhandled get power state command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + LogFunc(("[NID0x%02x]: fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n", + CODEC_NID(uCmd), CODEC_F05_IS_RESET(*puResp), CODEC_F05_IS_STOPOK(*puResp), CODEC_F05_ACT(*puResp), CODEC_F05_SET(*puResp))); + return VINF_SUCCESS; +} + +#if 1 + +/** + * @interface_method_impl{CODECVERB,pfn, 705 } + */ +static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].afg.u32F05_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F05_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F05_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F05_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adc.u32F05_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F05_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F05_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F05_param; + else + { + LogRel2(("HDA: Warning: Unhandled set power state command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + } + + if (!pu32Reg) + return VINF_SUCCESS; + + uint8_t uPwrCmd = CODEC_F05_SET (uCmd); + bool fReset = CODEC_F05_IS_RESET (*pu32Reg); + bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg); +#ifdef LOG_ENABLED + bool fError = CODEC_F05_IS_ERROR (*pu32Reg); + uint8_t uPwrAct = CODEC_F05_ACT (*pu32Reg); + uint8_t uPwrSet = CODEC_F05_SET (*pu32Reg); + LogFunc(("[NID0x%02x] Cmd=D%RU8, fReset=%RTbool, fStopOk=%RTbool, fError=%RTbool, uPwrAct=D%RU8, uPwrSet=D%RU8\n", + CODEC_NID(uCmd), uPwrCmd, fReset, fStopOk, fError, uPwrAct, uPwrSet)); + LogFunc(("AFG: Act=D%RU8, Set=D%RU8\n", + CODEC_F05_ACT(pThis->aNodes[STAC9220_NID_AFG].afg.u32F05_param), + CODEC_F05_SET(pThis->aNodes[STAC9220_NID_AFG].afg.u32F05_param))); +#endif + + if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uPwrCmd /* PS-Act */, uPwrCmd /* PS-Set */); + + const uint8_t uAFGPwrAct = CODEC_F05_ACT(pThis->aNodes[STAC9220_NID_AFG].afg.u32F05_param); + if (uAFGPwrAct == CODEC_F05_D0) /* Only propagate power state if AFG is on (D0). */ + { + /* Propagate to all other nodes under this AFG. */ + LogFunc(("Propagating Act=D%RU8 (AFG), Set=D%RU8 to all AFG child nodes ...\n", uAFGPwrAct, uPwrCmd)); + +#define PROPAGATE_PWR_STATE(a_abList, a_Member) \ + do { \ + for (uintptr_t idxList = 0; idxList < RT_ELEMENTS(a_abList); idxList++) \ + { \ + uint8_t const idxNode = a_abList[idxList]; \ + if (idxNode) \ + { \ + pThis->aNodes[idxNode].a_Member.u32F05_param = CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd); \ + LogFunc(("\t[NID0x%02x]: Act=D%RU8, Set=D%RU8\n", idxNode, \ + CODEC_F05_ACT(pThis->aNodes[idxNode].a_Member.u32F05_param), \ + CODEC_F05_SET(pThis->aNodes[idxNode].a_Member.u32F05_param))); \ + } \ + else \ + break; \ + } \ + } while (0) + + PROPAGATE_PWR_STATE(pThis->Cfg.abDacs, dac); + PROPAGATE_PWR_STATE(pThis->Cfg.abAdcs, adc); + PROPAGATE_PWR_STATE(pThis->Cfg.abDigInPins, digin); + PROPAGATE_PWR_STATE(pThis->Cfg.abDigOutPins, digout); + PROPAGATE_PWR_STATE(pThis->Cfg.abSpdifIns, spdifin); + PROPAGATE_PWR_STATE(pThis->Cfg.abSpdifOuts, spdifout); + PROPAGATE_PWR_STATE(pThis->Cfg.abReserveds, reserved); + +#undef PROPAGATE_PWR_STATE + } + /* + * If this node is a reqular node (not the AFG one), adopt PS-Set of the AFG node + * as PS-Set of this node. PS-Act always is one level under PS-Set here. + */ + else + { + *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd); + } + + LogFunc(("[NID0x%02x] fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n", + CODEC_NID(uCmd), + CODEC_F05_IS_RESET(*pu32Reg), CODEC_F05_IS_STOPOK(*pu32Reg), CODEC_F05_ACT(*pu32Reg), CODEC_F05_SET(*pu32Reg))); + + return VINF_SUCCESS; +} + +#else + +DECLINLINE(void) codecPropogatePowerState(uint32_t *pu32F05_param) +{ + Assert(pu32F05_param); + if (!pu32F05_param) + return; + bool fReset = CODEC_F05_IS_RESET(*pu32F05_param); + bool fStopOk = CODEC_F05_IS_STOPOK(*pu32F05_param); + uint8_t u8SetPowerState = CODEC_F05_SET(*pu32F05_param); + *pu32F05_param = CODEC_MAKE_F05(fReset, fStopOk, 0, u8SetPowerState, u8SetPowerState); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 705 } + */ +static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + Assert(CODEC_CAD(uCmd) == pThis->Cfg.id); + uint8_t const cTotalNodes = (uint8_t)RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes)); + Assert(CODEC_NID(uCmd) < cTotalNodes); + if (CODEC_NID(uCmd) >= cTotalNodes) + { + *puResp = 0; + LogFlowFunc(("invalid node address %d\n", CODEC_NID(uCmd))); + return VINF_SUCCESS; + } + *puResp = 0; + uint32_t *pu32Reg; + if (CODEC_NID(uCmd) == 1 /* AFG */) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].afg.u32F05_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F05_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F05_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adc.u32F05_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F05_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F05_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F05_param; + else + AssertFailedReturn(VINF_SUCCESS); + + bool fReset = CODEC_F05_IS_RESET(*pu32Reg); + bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg); + + if (CODEC_NID(uCmd) != 1 /* AFG */) + { + /* + * We shouldn't propogate actual power state, which actual for AFG + */ + *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, + CODEC_F05_ACT(pThis->aNodes[1].afg.u32F05_param), + CODEC_F05_SET(uCmd)); + } + + /* Propagate next power state only if AFG is on or verb modifies AFG power state */ + if ( CODEC_NID(uCmd) == 1 /* AFG */ + || !CODEC_F05_ACT(pThis->aNodes[1].afg.u32F05_param)) + { + *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, CODEC_F05_SET(uCmd), CODEC_F05_SET(uCmd)); + if ( CODEC_NID(uCmd) == 1 /* AFG */ + && (CODEC_F05_SET(uCmd)) == CODEC_F05_D0) + { + /* now we're powered on AFG and may propogate power states on nodes */ + const uint8_t *pu8NodeIndex = &pThis->abDacs[0]; + while (*(++pu8NodeIndex)) + codecPropogatePowerState(&pThis->aNodes[*pu8NodeIndex].dac.u32F05_param); + + pu8NodeIndex = &pThis->abAdcs[0]; + while (*(++pu8NodeIndex)) + codecPropogatePowerState(&pThis->aNodes[*pu8NodeIndex].adc.u32F05_param); + + pu8NodeIndex = &pThis->abDigInPins[0]; + while (*(++pu8NodeIndex)) + codecPropogatePowerState(&pThis->aNodes[*pu8NodeIndex].digin.u32F05_param); + } + } + return VINF_SUCCESS; +} + +#endif + +/** + * @interface_method_impl{CODECVERB,pfn, f06 } + */ +static DECLCALLBACK(int) vrbProcGetStreamId(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32F06_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32F06_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F06_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F06_param; + else if (CODEC_NID(uCmd) == STAC9221_NID_I2S_OUT) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F06_param; + else + LogRel2(("HDA: Warning: Unhandled get stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + LogFlowFunc(("[NID0x%02x] Stream ID=%RU8, channel=%RU8\n", + CODEC_NID(uCmd), CODEC_F00_06_GET_STREAM_ID(uCmd), CODEC_F00_06_GET_CHANNEL_ID(uCmd))); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, a0 } + */ +static DECLCALLBACK(int) vrbProcGetConverterFormat(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32A_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32A_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32A_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32A_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32A_param; + else + LogRel2(("HDA: Warning: Unhandled get converter format command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, ??? - Also see section 3.7.1. } + */ +static DECLCALLBACK(int) vrbProcSetConverterFormat(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + hdaCodecSetRegisterU16(&pThis->aNodes[CODEC_NID(uCmd)].dac.u32A_param, uCmd, 0); + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + hdaCodecSetRegisterU16(&pThis->aNodes[CODEC_NID(uCmd)].adc.u32A_param, uCmd, 0); + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + hdaCodecSetRegisterU16(&pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32A_param, uCmd, 0); + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + hdaCodecSetRegisterU16(&pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32A_param, uCmd, 0); + else + LogRel2(("HDA: Warning: Unhandled set converter format command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f0c } + */ +static DECLCALLBACK(int) vrbProcGetEAPD_BTLEnabled(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].adcvol.u32F0c_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32F0c_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F0c_param; + else + LogRel2(("HDA: Warning: Unhandled get EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 70c } + */ +static DECLCALLBACK(int) vrbProcSetEAPD_BTLEnabled(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adcvol.u32F0c_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F0c_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F0c_param; + else + LogRel2(("HDA: Warning: Unhandled set EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, 0); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f0f } + */ +static DECLCALLBACK(int) vrbProcGetVolumeKnobCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].volumeKnob.u32F0f_param; + else + LogRel2(("HDA: Warning: Unhandled get volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 70f } + */ +static DECLCALLBACK(int) vrbProcSetVolumeKnobCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].volumeKnob.u32F0f_param; + else + LogRel2(("HDA: Warning: Unhandled set volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, 0); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f15 } + */ +static DECLCALLBACK(int) vrbProcGetGPIOData(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + RT_NOREF(pThis, uCmd); + *puResp = 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 715 } + */ +static DECLCALLBACK(int) vrbProcSetGPIOData(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + RT_NOREF(pThis, uCmd); + *puResp = 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f16 } + */ +static DECLCALLBACK(int) vrbProcGetGPIOEnableMask(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + RT_NOREF(pThis, uCmd); + *puResp = 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 716 } + */ +static DECLCALLBACK(int) vrbProcSetGPIOEnableMask(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + RT_NOREF(pThis, uCmd); + *puResp = 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f17 } + */ +static DECLCALLBACK(int) vrbProcGetGPIODirection(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + /* Note: this is true for ALC885. */ + if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + *puResp = pThis->aNodes[1].afg.u32F17_param; + else + LogRel2(("HDA: Warning: Unhandled get GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 717 } + */ +static DECLCALLBACK(int) vrbProcSetGPIODirection(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (CODEC_NID(uCmd) == STAC9220_NID_AFG) + pu32Reg = &pThis->aNodes[1].afg.u32F17_param; + else + LogRel2(("HDA: Warning: Unhandled set GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, 0); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f1c } + */ +static DECLCALLBACK(int) vrbProcGetConfig(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F1c_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F1c_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F1c_param; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].pcbeep.u32F1c_param; + else if (hdaCodecIsCdNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].cdnode.u32F1c_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F1c_param; + else + LogRel2(("HDA: Warning: Unhandled get config command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + +static int codecSetConfigX(PHDACODECR3 pThis, uint32_t uCmd, uint8_t u8Offset) +{ + uint32_t *pu32Reg = NULL; + if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F1c_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F1c_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F1c_param; + else if (hdaCodecIsCdNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].cdnode.u32F1c_param; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].pcbeep.u32F1c_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F1c_param; + else + LogRel2(("HDA: Warning: Unhandled set config command (%RU8) for NID0x%02x: 0x%x\n", u8Offset, CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, u8Offset); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 71c } + */ +static DECLCALLBACK(int) vrbProcSetConfig0(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + return codecSetConfigX(pThis, uCmd, 0); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 71d } + */ +static DECLCALLBACK(int) vrbProcSetConfig1(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + return codecSetConfigX(pThis, uCmd, 8); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 71e } + */ +static DECLCALLBACK(int) vrbProcSetConfig2(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + return codecSetConfigX(pThis, uCmd, 16); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 71e } + */ +static DECLCALLBACK(int) vrbProcSetConfig3(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + return codecSetConfigX(pThis, uCmd, 24); +} + + +/** + * @interface_method_impl{CODECVERB,pfn, f04 } + */ +static DECLCALLBACK(int) vrbProcGetSDISelect(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32F04_param; + else + LogRel2(("HDA: Warning: Unhandled get SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 704 } + */ +static DECLCALLBACK(int) vrbProcSetSDISelect(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F04_param; + else + LogRel2(("HDA: Warning: Unhandled set SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, uCmd, 0); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 3-- } + */ +static DECLCALLBACK(int) vrbProcR3SetAmplifier(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + PCODECNODE pNode = &pThis->aNodes[CODEC_NID(uCmd)]; + AMPLIFIER *pAmplifier = NULL; + if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + pAmplifier = &pNode->dac.B_params; + else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd))) + pAmplifier = &pNode->adcvol.B_params; + else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(uCmd))) + pAmplifier = &pNode->adcmux.B_params; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd))) + pAmplifier = &pNode->pcbeep.B_params; + else if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd))) + pAmplifier = &pNode->port.B_params; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + pAmplifier = &pNode->adc.B_params; + else + LogRel2(("HDA: Warning: Unhandled set amplifier command: 0x%x (Payload=%RU16, NID=0x%x [%RU8])\n", + uCmd, CODEC_VERB_PAYLOAD16(uCmd), CODEC_NID(uCmd), CODEC_NID(uCmd))); + + if (!pAmplifier) + return VINF_SUCCESS; + + bool fIsOut = CODEC_SET_AMP_IS_OUT_DIRECTION(uCmd); + bool fIsIn = CODEC_SET_AMP_IS_IN_DIRECTION(uCmd); + bool fIsLeft = CODEC_SET_AMP_IS_LEFT_SIDE(uCmd); + bool fIsRight = CODEC_SET_AMP_IS_RIGHT_SIDE(uCmd); + uint8_t u8Index = CODEC_SET_AMP_INDEX(uCmd); + + if ( (!fIsLeft && !fIsRight) + || (!fIsOut && !fIsIn)) + return VINF_SUCCESS; + + LogFunc(("[NID0x%02x] fIsOut=%RTbool, fIsIn=%RTbool, fIsLeft=%RTbool, fIsRight=%RTbool, Idx=%RU8\n", + CODEC_NID(uCmd), fIsOut, fIsIn, fIsLeft, fIsRight, u8Index)); + + if (fIsIn) + { + if (fIsLeft) + hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_LEFT, u8Index), uCmd, 0); + if (fIsRight) + hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_RIGHT, u8Index), uCmd, 0); + + /* + * Check if the node ID is the one we use for controlling the line-in volume; + * with STAC9220 this is connected to STAC9220_NID_AMP_ADC0 (ID 0x17). + * + * If we don't do this check here, some guests like newer Ubuntus mute mic-in + * afterwards (connected to STAC9220_NID_AMP_ADC1 (ID 0x18)). This then would + * also mute line-in, which breaks audio recording. + * + * See STAC9220 V1.0 01/08, p. 30 + oem2ticketref:53. + */ + if (CODEC_NID(uCmd) == pThis->Cfg.idxAdcVolsLineIn) + hdaR3CodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_LINE_IN); + +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN +# error "Implement mic-in volume / mute setting." + else if (CODEC_NID(uCmd) == pThis->Cfg.idxAdcVolsMicIn) + hdaR3CodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_MIC_IN); +#endif + + } + if (fIsOut) + { + if (fIsLeft) + hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_LEFT, u8Index), uCmd, 0); + if (fIsRight) + hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_RIGHT, u8Index), uCmd, 0); + + if (CODEC_NID(uCmd) == pThis->Cfg.idxDacLineOut) + hdaR3CodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_FRONT); + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{CODECVERB,pfn, 706 } + */ +static DECLCALLBACK(int) vrbProcR3SetStreamId(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + *puResp = 0; + + uint8_t uSD = CODEC_F00_06_GET_STREAM_ID(uCmd); + uint8_t uChannel = CODEC_F00_06_GET_CHANNEL_ID(uCmd); + + LogFlowFunc(("[NID0x%02x] Setting to stream ID=%RU8, channel=%RU8\n", + CODEC_NID(uCmd), uSD, uChannel)); + + ASSERT_GUEST_LOGREL_MSG_RETURN(uSD < HDA_MAX_STREAMS, + ("Setting stream ID #%RU8 is invalid\n", uSD), VERR_INVALID_PARAMETER); + + PDMAUDIODIR enmDir; + uint32_t *pu32Addr; + if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd))) + { + pu32Addr = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F06_param; + enmDir = PDMAUDIODIR_OUT; + } + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd))) + { + pu32Addr = &pThis->aNodes[CODEC_NID(uCmd)].adc.u32F06_param; + enmDir = PDMAUDIODIR_IN; + } + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd))) + { + pu32Addr = &pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F06_param; + enmDir = PDMAUDIODIR_OUT; + } + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd))) + { + pu32Addr = &pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F06_param; + enmDir = PDMAUDIODIR_IN; + } + else + { + enmDir = PDMAUDIODIR_UNKNOWN; + LogRel2(("HDA: Warning: Unhandled set stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd)); + return VINF_SUCCESS; + } + + /* Do we (re-)assign our input/output SDn (SDI/SDO) IDs? */ + pThis->aNodes[CODEC_NID(uCmd)].node.uSD = uSD; + pThis->aNodes[CODEC_NID(uCmd)].node.uChannel = uChannel; + + if (enmDir == PDMAUDIODIR_OUT) + { + /** @todo Check if non-interleaved streams need a different channel / SDn? */ + + /* Propagate to the controller. */ + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_FRONT, uSD, uChannel); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_CENTER_LFE, uSD, uChannel); + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_REAR, uSD, uChannel); +# endif + } + else if (enmDir == PDMAUDIODIR_IN) + { + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_LINE_IN, uSD, uChannel); +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_MIC_IN, uSD, uChannel); +# endif + } + + hdaCodecSetRegisterU8(pu32Addr, uCmd, 0); + + return VINF_SUCCESS; +} + + + +/** + * HDA codec verb descriptors. + * + * @note This must be ordered by uVerb so we can do a binary lookup. + */ +static const CODECVERB g_aCodecVerbs[] = +{ + /* Verb Verb mask Callback Name + ---------- --------------------- ------------------------------------------------------------------- */ + { 0x00020000, CODEC_VERB_16BIT_CMD, vrbProcSetConverterFormat , "SetConverterFormat " }, + { 0x00030000, CODEC_VERB_16BIT_CMD, vrbProcR3SetAmplifier , "SetAmplifier " }, + { 0x00070100, CODEC_VERB_8BIT_CMD , vrbProcSetConSelectCtrl , "SetConSelectCtrl " }, + { 0x00070300, CODEC_VERB_8BIT_CMD , vrbProcSetProcessingState , "SetProcessingState " }, + { 0x00070400, CODEC_VERB_8BIT_CMD , vrbProcSetSDISelect , "SetSDISelect " }, + { 0x00070500, CODEC_VERB_8BIT_CMD , vrbProcSetPowerState , "SetPowerState " }, + { 0x00070600, CODEC_VERB_8BIT_CMD , vrbProcR3SetStreamId , "SetStreamId " }, + { 0x00070700, CODEC_VERB_8BIT_CMD , vrbProcSetPinCtrl , "SetPinCtrl " }, + { 0x00070800, CODEC_VERB_8BIT_CMD , vrbProcSetUnsolicitedEnabled , "SetUnsolicitedEnabled " }, + { 0x00070900, CODEC_VERB_8BIT_CMD , vrbProcSetPinSense , "SetPinSense " }, + { 0x00070C00, CODEC_VERB_8BIT_CMD , vrbProcSetEAPD_BTLEnabled , "SetEAPD_BTLEnabled " }, + { 0x00070D00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter1 , "SetDigitalConverter1 " }, + { 0x00070E00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter2 , "SetDigitalConverter2 " }, + { 0x00070F00, CODEC_VERB_8BIT_CMD , vrbProcSetVolumeKnobCtrl , "SetVolumeKnobCtrl " }, + { 0x00071500, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOData , "SetGPIOData " }, + { 0x00071600, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOEnableMask , "SetGPIOEnableMask " }, + { 0x00071700, CODEC_VERB_8BIT_CMD , vrbProcSetGPIODirection , "SetGPIODirection " }, + { 0x00071C00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig0 , "SetConfig0 " }, + { 0x00071D00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig1 , "SetConfig1 " }, + { 0x00071E00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig2 , "SetConfig2 " }, + { 0x00071F00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig3 , "SetConfig3 " }, + { 0x00072000, CODEC_VERB_8BIT_CMD , vrbProcSetSubId0 , "SetSubId0 " }, + { 0x00072100, CODEC_VERB_8BIT_CMD , vrbProcSetSubId1 , "SetSubId1 " }, + { 0x00072200, CODEC_VERB_8BIT_CMD , vrbProcSetSubId2 , "SetSubId2 " }, + { 0x00072300, CODEC_VERB_8BIT_CMD , vrbProcSetSubId3 , "SetSubId3 " }, + { 0x0007FF00, CODEC_VERB_8BIT_CMD , vrbProcReset , "Reset " }, + { 0x000A0000, CODEC_VERB_16BIT_CMD, vrbProcGetConverterFormat , "GetConverterFormat " }, + { 0x000B0000, CODEC_VERB_16BIT_CMD, vrbProcGetAmplifier , "GetAmplifier " }, + { 0x000F0000, CODEC_VERB_8BIT_CMD , vrbProcGetParameter , "GetParameter " }, + { 0x000F0100, CODEC_VERB_8BIT_CMD , vrbProcGetConSelectCtrl , "GetConSelectCtrl " }, + { 0x000F0200, CODEC_VERB_8BIT_CMD , vrbProcGetConnectionListEntry , "GetConnectionListEntry" }, + { 0x000F0300, CODEC_VERB_8BIT_CMD , vrbProcGetProcessingState , "GetProcessingState " }, + { 0x000F0400, CODEC_VERB_8BIT_CMD , vrbProcGetSDISelect , "GetSDISelect " }, + { 0x000F0500, CODEC_VERB_8BIT_CMD , vrbProcGetPowerState , "GetPowerState " }, + { 0x000F0600, CODEC_VERB_8BIT_CMD , vrbProcGetStreamId , "GetStreamId " }, + { 0x000F0700, CODEC_VERB_8BIT_CMD , vrbProcGetPinCtrl , "GetPinCtrl " }, + { 0x000F0800, CODEC_VERB_8BIT_CMD , vrbProcGetUnsolicitedEnabled , "GetUnsolicitedEnabled " }, + { 0x000F0900, CODEC_VERB_8BIT_CMD , vrbProcGetPinSense , "GetPinSense " }, + { 0x000F0C00, CODEC_VERB_8BIT_CMD , vrbProcGetEAPD_BTLEnabled , "GetEAPD_BTLEnabled " }, + { 0x000F0D00, CODEC_VERB_8BIT_CMD , vrbProcGetDigitalConverter , "GetDigitalConverter " }, + { 0x000F0F00, CODEC_VERB_8BIT_CMD , vrbProcGetVolumeKnobCtrl , "GetVolumeKnobCtrl " }, + { 0x000F1500, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOData , "GetGPIOData " }, + { 0x000F1600, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOEnableMask , "GetGPIOEnableMask " }, + { 0x000F1700, CODEC_VERB_8BIT_CMD , vrbProcGetGPIODirection , "GetGPIODirection " }, + { 0x000F1C00, CODEC_VERB_8BIT_CMD , vrbProcGetConfig , "GetConfig " }, + { 0x000F2000, CODEC_VERB_8BIT_CMD , vrbProcGetSubId , "GetSubId " }, + /** @todo Implement 0x7e7: IDT Set GPIO (STAC922x only). */ +}; + + +/** + * Implements codec lookup and will call the handler on the verb it finds, + * returning the handler response. + * + * @returns VBox status code (not strict). + */ +DECLHIDDEN(int) hdaR3CodecLookup(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp) +{ + /* + * Clear the return value and assert some sanity. + */ + AssertPtr(puResp); + *puResp = 0; + AssertPtr(pThis); + AssertMsgReturn(CODEC_CAD(uCmd) == pThis->Cfg.id, + ("Unknown codec address 0x%x\n", CODEC_CAD(uCmd)), + VERR_INVALID_PARAMETER); + uint32_t const uCmdData = CODEC_VERBDATA(uCmd); + AssertMsgReturn( uCmdData != 0 + && CODEC_NID(uCmd) < RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes)), + ("[NID0x%02x] Unknown / invalid node or data (0x%x)\n", CODEC_NID(uCmd), uCmdData), + VERR_INVALID_PARAMETER); + STAM_COUNTER_INC(&pThis->CTX_SUFF(StatLookups)); + + /* + * Do a binary lookup of the verb. + * Note! if we want other verb tables, add a table selector before the loop. + */ + size_t iFirst = 0; + size_t iEnd = RT_ELEMENTS(g_aCodecVerbs); + for (;;) + { + size_t const iCur = iFirst + (iEnd - iFirst) / 2; + uint32_t const uVerb = g_aCodecVerbs[iCur].uVerb; + if (uCmdData < uVerb) + { + if (iCur > iFirst) + iEnd = iCur; + else + break; + } + else if ((uCmdData & g_aCodecVerbs[iCur].fMask) != uVerb) + { + if (iCur + 1 < iEnd) + iFirst = iCur + 1; + else + break; + } + else + { + /* + * Found it! Run the callback and return. + */ + AssertPtrReturn(g_aCodecVerbs[iCur].pfn, VERR_INTERNAL_ERROR_5); /* Paranoia^2. */ + + int rc = g_aCodecVerbs[iCur].pfn(pThis, uCmd, puResp); + AssertRC(rc); + Log3Func(("[NID0x%02x] (0x%x) %s: 0x%x -> 0x%x\n", + CODEC_NID(uCmd), g_aCodecVerbs[iCur].uVerb, g_aCodecVerbs[iCur].pszName, CODEC_VERB_PAYLOAD8(uCmd), *puResp)); + return rc; + } + } + +#ifdef VBOX_STRICT + for (size_t i = 0; i < RT_ELEMENTS(g_aCodecVerbs); i++) + { + AssertMsg(i == 0 || g_aCodecVerbs[i - 1].uVerb < g_aCodecVerbs[i].uVerb, + ("i=%#x uVerb[-1]=%#x uVerb=%#x - buggy table!\n", i, g_aCodecVerbs[i - 1].uVerb, g_aCodecVerbs[i].uVerb)); + AssertMsg((uCmdData & g_aCodecVerbs[i].fMask) != g_aCodecVerbs[i].uVerb, + ("i=%#x uVerb=%#x uCmd=%#x - buggy binary search or table!\n", i, g_aCodecVerbs[i].uVerb, uCmd)); + } +#endif + LogFunc(("[NID0x%02x] Callback for %x not found\n", CODEC_NID(uCmd), CODEC_VERBDATA(uCmd))); + return VERR_NOT_FOUND; +} + + +/********************************************************************************************************************************* +* Debug * +*********************************************************************************************************************************/ +/** + * CODEC debug info item printing state. + */ +typedef struct CODECDEBUG +{ + /** DBGF info helpers. */ + PCDBGFINFOHLP pHlp; + /** Current recursion level. */ + uint8_t uLevel; + /** Pointer to codec state. */ + PHDACODECR3 pThis; +} CODECDEBUG; +/** Pointer to the debug info item printing state for the codec. */ +typedef CODECDEBUG *PCODECDEBUG; + +#define CODECDBG_INDENT pInfo->uLevel++; +#define CODECDBG_UNINDENT if (pInfo->uLevel) pInfo->uLevel--; + +#define CODECDBG_PRINT(...) pInfo->pHlp->pfnPrintf(pInfo->pHlp, __VA_ARGS__) +#define CODECDBG_PRINTI(...) codecDbgPrintf(pInfo, __VA_ARGS__) + + +/** Wrapper around DBGFINFOHLP::pfnPrintf that adds identation. */ +static void codecDbgPrintf(PCODECDEBUG pInfo, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + pInfo->pHlp->pfnPrintf(pInfo->pHlp, "%*s%N", pInfo->uLevel * 4, "", pszFormat, &va); + va_end(va); +} + + +/** Power state */ +static void codecDbgPrintNodeRegF05(PCODECDEBUG pInfo, uint32_t u32Reg) +{ + codecDbgPrintf(pInfo, "Power (F05): fReset=%RTbool, fStopOk=%RTbool, Set=%RU8, Act=%RU8\n", + CODEC_F05_IS_RESET(u32Reg), CODEC_F05_IS_STOPOK(u32Reg), CODEC_F05_SET(u32Reg), CODEC_F05_ACT(u32Reg)); +} + + +static void codecDbgPrintNodeRegA(PCODECDEBUG pInfo, uint32_t u32Reg) +{ + codecDbgPrintf(pInfo, "RegA: %x\n", u32Reg); +} + + +static void codecDbgPrintNodeRegF00(PCODECDEBUG pInfo, uint32_t *paReg00) +{ + codecDbgPrintf(pInfo, "Parameters (F00):\n"); + + CODECDBG_INDENT + codecDbgPrintf(pInfo, "Connections: %RU8\n", CODEC_F00_0E_COUNT(paReg00[0xE])); + codecDbgPrintf(pInfo, "Amplifier Caps:\n"); + uint32_t uReg = paReg00[0xD]; + CODECDBG_INDENT + codecDbgPrintf(pInfo, "Input Steps=%02RU8, StepSize=%02RU8, StepOff=%02RU8, fCanMute=%RTbool\n", + CODEC_F00_0D_NUM_STEPS(uReg), + CODEC_F00_0D_STEP_SIZE(uReg), + CODEC_F00_0D_OFFSET(uReg), + RT_BOOL(CODEC_F00_0D_IS_CAP_MUTE(uReg))); + + uReg = paReg00[0x12]; + codecDbgPrintf(pInfo, "Output Steps=%02RU8, StepSize=%02RU8, StepOff=%02RU8, fCanMute=%RTbool\n", + CODEC_F00_12_NUM_STEPS(uReg), + CODEC_F00_12_STEP_SIZE(uReg), + CODEC_F00_12_OFFSET(uReg), + RT_BOOL(CODEC_F00_12_IS_CAP_MUTE(uReg))); + CODECDBG_UNINDENT + CODECDBG_UNINDENT +} + + +static void codecDbgPrintNodeAmp(PCODECDEBUG pInfo, uint32_t *paReg, uint8_t uIdx, uint8_t uDir) +{ +#define CODECDBG_AMP(reg, chan) \ + codecDbgPrintf(pInfo, "Amp %RU8 %s %s: In=%RTbool, Out=%RTbool, Left=%RTbool, Right=%RTbool, Idx=%RU8, fMute=%RTbool, uGain=%RU8\n", \ + uIdx, chan, uDir == AMPLIFIER_IN ? "In" : "Out", \ + RT_BOOL(CODEC_SET_AMP_IS_IN_DIRECTION(reg)), RT_BOOL(CODEC_SET_AMP_IS_OUT_DIRECTION(reg)), \ + RT_BOOL(CODEC_SET_AMP_IS_LEFT_SIDE(reg)), RT_BOOL(CODEC_SET_AMP_IS_RIGHT_SIDE(reg)), \ + CODEC_SET_AMP_INDEX(reg), RT_BOOL(CODEC_SET_AMP_MUTE(reg)), CODEC_SET_AMP_GAIN(reg)) + + uint32_t regAmp = AMPLIFIER_REGISTER(paReg, uDir, AMPLIFIER_LEFT, uIdx); + CODECDBG_AMP(regAmp, "Left"); + regAmp = AMPLIFIER_REGISTER(paReg, uDir, AMPLIFIER_RIGHT, uIdx); + CODECDBG_AMP(regAmp, "Right"); + +#undef CODECDBG_AMP +} + + +#if 0 /* unused */ +static void codecDbgPrintNodeConnections(PCODECDEBUG pInfo, PCODECNODE pNode) +{ + if (pNode->node.au32F00_param[0xE] == 0) /* Directly connected to HDA link. */ + { + codecDbgPrintf(pInfo, "[HDA LINK]\n"); + return; + } +} +#endif + + +static void codecDbgPrintNode(PCODECDEBUG pInfo, PCODECNODE pNode, bool fRecursive) +{ + codecDbgPrintf(pInfo, "Node 0x%02x (%02RU8): ", pNode->node.uID, pNode->node.uID); + + if (pNode->node.uID == STAC9220_NID_ROOT) + CODECDBG_PRINT("ROOT\n"); + else if (pNode->node.uID == STAC9220_NID_AFG) + { + CODECDBG_PRINT("AFG\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegF05(pInfo, pNode->afg.u32F05_param); + CODECDBG_UNINDENT + } + else if (hdaCodecIsPortNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("PORT\n"); + else if (hdaCodecIsDacNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("DAC\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegF05(pInfo, pNode->dac.u32F05_param); + codecDbgPrintNodeRegA (pInfo, pNode->dac.u32A_param); + codecDbgPrintNodeAmp (pInfo, pNode->dac.B_params, 0, AMPLIFIER_OUT); + CODECDBG_UNINDENT + } + else if (hdaCodecIsAdcVolNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("ADC VOLUME\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegA (pInfo, pNode->adcvol.u32A_params); + codecDbgPrintNodeAmp (pInfo, pNode->adcvol.B_params, 0, AMPLIFIER_IN); + CODECDBG_UNINDENT + } + else if (hdaCodecIsAdcNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("ADC\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegF05(pInfo, pNode->adc.u32F05_param); + codecDbgPrintNodeRegA (pInfo, pNode->adc.u32A_param); + codecDbgPrintNodeAmp (pInfo, pNode->adc.B_params, 0, AMPLIFIER_IN); + CODECDBG_UNINDENT + } + else if (hdaCodecIsAdcMuxNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("ADC MUX\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegA (pInfo, pNode->adcmux.u32A_param); + codecDbgPrintNodeAmp (pInfo, pNode->adcmux.B_params, 0, AMPLIFIER_IN); + CODECDBG_UNINDENT + } + else if (hdaCodecIsPcbeepNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("PC BEEP\n"); + else if (hdaCodecIsSpdifOutNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("SPDIF OUT\n"); + else if (hdaCodecIsSpdifInNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("SPDIF IN\n"); + else if (hdaCodecIsDigInPinNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("DIGITAL IN PIN\n"); + else if (hdaCodecIsDigOutPinNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("DIGITAL OUT PIN\n"); + else if (hdaCodecIsCdNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("CD\n"); + else if (hdaCodecIsVolKnobNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("VOLUME KNOB\n"); + else if (hdaCodecIsReservedNode(pInfo->pThis, pNode->node.uID)) + CODECDBG_PRINT("RESERVED\n"); + else + CODECDBG_PRINT("UNKNOWN TYPE 0x%x\n", pNode->node.uID); + + if (fRecursive) + { +#define CODECDBG_PRINT_CONLIST_ENTRY(_aNode, _aEntry) \ + if (cCnt >= _aEntry) \ + { \ + const uint8_t uID = RT_BYTE##_aEntry(_aNode->node.au32F02_param[0x0]); \ + if (pNode->node.uID == uID) \ + codecDbgPrintNode(pInfo, _aNode, false /* fRecursive */); \ + } + + /* Slow recursion, but this is debug stuff anyway. */ + for (uint8_t i = 0; i < pInfo->pThis->Cfg.cTotalNodes; i++) + { + const PCODECNODE pSubNode = &pInfo->pThis->aNodes[i]; + if (pSubNode->node.uID == pNode->node.uID) + continue; + + const uint8_t cCnt = CODEC_F00_0E_COUNT(pSubNode->node.au32F00_param[0xE]); + if (cCnt == 0) /* No connections present? Skip. */ + continue; + + CODECDBG_INDENT + CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 1) + CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 2) + CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 3) + CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 4) + CODECDBG_UNINDENT + } + +#undef CODECDBG_PRINT_CONLIST_ENTRY + } +} + + +/** + * Worker for hdaR3DbgInfoCodecNodes implementing the 'hdcnodes' info item. + */ +DECLHIDDEN(void) hdaR3CodecDbgListNodes(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + + pHlp->pfnPrintf(pHlp, "HDA LINK / INPUTS\n"); + + CODECDEBUG DbgInfo; + DbgInfo.pHlp = pHlp; + DbgInfo.pThis = pThis; + DbgInfo.uLevel = 0; + + PCODECDEBUG pInfo = &DbgInfo; + + CODECDBG_INDENT + for (uint8_t i = 0; i < pThis->Cfg.cTotalNodes; i++) + { + PCODECNODE pNode = &pThis->aNodes[i]; + + /* Start with all nodes which have connection entries set. */ + if (CODEC_F00_0E_COUNT(pNode->node.au32F00_param[0xE])) + codecDbgPrintNode(&DbgInfo, pNode, true /* fRecursive */); + } + CODECDBG_UNINDENT +} + + +/** + * Worker for hdaR3DbgInfoCodecSelector implementing the 'hdcselector' info item. + */ +DECLHIDDEN(void) hdaR3CodecDbgSelector(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pThis, pHlp, pszArgs); +} + + +#if 0 /* unused */ +static DECLCALLBACK(void) stac9220DbgNodes(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + uint8_t const cTotalNodes = RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes)); + for (uint8_t i = 1; i < cTotalNodes; i++) + { + PCODECNODE pNode = &pThis->aNodes[i]; + AMPLIFIER *pAmp = &pNode->dac.B_params; + + uint8_t lVol = AMPLIFIER_REGISTER(*pAmp, AMPLIFIER_OUT, AMPLIFIER_LEFT, 0) & 0x7f; + uint8_t rVol = AMPLIFIER_REGISTER(*pAmp, AMPLIFIER_OUT, AMPLIFIER_RIGHT, 0) & 0x7f; + + pHlp->pfnPrintf(pHlp, "0x%x: lVol=%RU8, rVol=%RU8\n", i, lVol, rVol); + } +} +#endif + + +/********************************************************************************************************************************* +* Stream and State Management * +*********************************************************************************************************************************/ + +int hdaR3CodecAddStream(PHDACODECR3 pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_VOLUME_MASTER: + case PDMAUDIOMIXERCTL_FRONT: +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOMIXERCTL_CENTER_LFE: + case PDMAUDIOMIXERCTL_REAR: +#endif + break; + + case PDMAUDIOMIXERCTL_LINE_IN: +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOMIXERCTL_MIC_IN: +#endif + break; + + default: + AssertMsgFailed(("Mixer control %#x not implemented\n", enmMixerCtl)); + rc = VERR_NOT_IMPLEMENTED; + break; + } + + if (RT_SUCCESS(rc)) + rc = hdaR3MixerAddStream(pThis, enmMixerCtl, pCfg); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +int hdaR3CodecRemoveStream(PHDACODECR3 pThis, PDMAUDIOMIXERCTL enmMixerCtl, bool fImmediate) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc = hdaR3MixerRemoveStream(pThis, enmMixerCtl, fImmediate); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Saved the codec state. + * + * @returns VBox status code. + * @param pDevIns The device instance of the HDA device. + * @param pThis The codec instance data. + * @param pSSM The saved state handle. + */ +int hdaCodecSaveState(PPDMDEVINS pDevIns, PHDACODECR3 pThis, PSSMHANDLE pSSM) +{ + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + AssertLogRelMsgReturn(pThis->Cfg.cTotalNodes == STAC9221_NUM_NODES, ("cTotalNodes=%#x, should be 0x1c", pThis->Cfg.cTotalNodes), + VERR_INTERNAL_ERROR); + pHlp->pfnSSMPutU32(pSSM, pThis->Cfg.cTotalNodes); + for (unsigned idxNode = 0; idxNode < pThis->Cfg.cTotalNodes; ++idxNode) + pHlp->pfnSSMPutStructEx(pSSM, &pThis->aNodes[idxNode].SavedState, sizeof(pThis->aNodes[idxNode].SavedState), + 0 /*fFlags*/, g_aCodecNodeFields, NULL /*pvUser*/); + return VINF_SUCCESS; +} + + +/** + * Loads the codec state. + * + * @returns VBox status code. + * @param pDevIns The device instance of the HDA device. + * @param pThis The codec instance data. + * @param pSSM The saved state handle. + * @param uVersion The state version. + */ +int hdaR3CodecLoadState(PPDMDEVINS pDevIns, PHDACODECR3 pThis, PSSMHANDLE pSSM, uint32_t uVersion) +{ + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + PCSSMFIELD pFields = NULL; + uint32_t fFlags = 0; + if (uVersion >= HDA_SAVED_STATE_VERSION_4) + { + /* Since version 4 a flexible node count is supported. */ + uint32_t cNodes; + int rc2 = pHlp->pfnSSMGetU32(pSSM, &cNodes); + AssertRCReturn(rc2, rc2); + AssertReturn(cNodes == 0x1c, VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + AssertReturn(pThis->Cfg.cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); + + pFields = g_aCodecNodeFields; + fFlags = 0; + } + else if (uVersion >= HDA_SAVED_STATE_VERSION_2) + { + AssertReturn(pThis->Cfg.cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); + pFields = g_aCodecNodeFields; + fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED; + } + else if (uVersion >= HDA_SAVED_STATE_VERSION_1) + { + AssertReturn(pThis->Cfg.cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); + pFields = g_aCodecNodeFieldsV1; + fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED; + } + else + AssertFailedReturn(VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); + + for (unsigned idxNode = 0; idxNode < pThis->Cfg.cTotalNodes; ++idxNode) + { + uint8_t idOld = pThis->aNodes[idxNode].SavedState.Core.uID; + int rc = pHlp->pfnSSMGetStructEx(pSSM, &pThis->aNodes[idxNode].SavedState, sizeof(pThis->aNodes[idxNode].SavedState), + fFlags, pFields, NULL); + AssertRCReturn(rc, rc); + AssertLogRelMsgReturn(idOld == pThis->aNodes[idxNode].SavedState.Core.uID, + ("loaded %#x, expected %#x\n", pThis->aNodes[idxNode].SavedState.Core.uID, idOld), + VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + } + + /* + * Update stuff after changing the state. + */ + PCODECNODE pNode; + if (hdaCodecIsDacNode(pThis, pThis->Cfg.idxDacLineOut)) + { + pNode = &pThis->aNodes[pThis->Cfg.idxDacLineOut]; + hdaR3CodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT); + } + else if (hdaCodecIsSpdifOutNode(pThis, pThis->Cfg.idxDacLineOut)) + { + pNode = &pThis->aNodes[pThis->Cfg.idxDacLineOut]; + hdaR3CodecToAudVolume(pThis, pNode, &pNode->spdifout.B_params, PDMAUDIOMIXERCTL_FRONT); + } + + pNode = &pThis->aNodes[pThis->Cfg.idxAdcVolsLineIn]; + hdaR3CodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN); + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +/** + * Powers off the codec (ring-3). + * + * @param pThis The codec data. + */ +void hdaR3CodecPowerOff(PHDACODECR3 pThis) +{ + LogFlowFuncEnter(); + LogRel2(("HDA: Powering off codec ...\n")); + + int rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_FRONT, true /*fImmediate*/); + AssertRC(rc2); +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_CENTER_LFE, true /*fImmediate*/); + AssertRC(rc2); + rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_REAR, true /*fImmediate*/); + AssertRC(rc2); +#endif + +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_MIC_IN, true /*fImmediate*/); + AssertRC(rc2); +#endif + rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_LINE_IN, true /*fImmediate*/); + AssertRC(rc2); +} + + +/** + * Constructs a codec (ring-3). + * + * @returns VBox status code. + * @param pDevIns The associated device instance. + * @param pThis The codec data. + * @param uLUN Device LUN to assign. + * @param pCfg CFGM node to use for configuration. + */ +int hdaR3CodecConstruct(PPDMDEVINS pDevIns, PHDACODECR3 pThis, uint16_t uLUN, PCFGMNODE pCfg) +{ + AssertPtrReturn(pDevIns, VERR_INVALID_POINTER); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + HDACODECCFG *pCodecCfg = (HDACODECCFG *)&pThis->Cfg; + + pCodecCfg->id = uLUN; + pCodecCfg->enmType = CODECTYPE_STAC9220; /** @todo Make this dynamic. */ + + int rc; + + switch (pCodecCfg->enmType) + { + case CODECTYPE_STAC9220: + { + rc = stac9220Construct(pThis, pCodecCfg); + AssertRCReturn(rc, rc); + break; + } + + default: + AssertFailedReturn(VERR_NOT_IMPLEMENTED); + break; + } + + /* + * Set initial volume. + */ + PCODECNODE pNode = &pThis->aNodes[pCodecCfg->idxDacLineOut]; + rc = hdaR3CodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT); + AssertRCReturn(rc, rc); + + pNode = &pThis->aNodes[pCodecCfg->idxAdcVolsLineIn]; + rc = hdaR3CodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN); + AssertRCReturn(rc, rc); + +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN +# error "Implement mic-in support!" +#endif + + /* + * Statistics + */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatLookupsR3, STAMTYPE_COUNTER, "Codec/LookupsR0", STAMUNIT_OCCURENCES, "Number of R0 codecLookup calls"); +#if 0 /* Codec is not yet kosher enough for ring-0. @bugref{9890c64} */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatLookupsR0, STAMTYPE_COUNTER, "Codec/LookupsR3", STAMUNIT_OCCURENCES, "Number of R3 codecLookup calls"); +#endif + + return rc; +} + + +/** + * Destructs a codec. + * + * @param pThis Codec to destruct. + */ +void hdaCodecDestruct(PHDACODECR3 pThis) +{ + LogFlowFuncEnter(); + + /* Nothing to do here atm. */ + RT_NOREF(pThis); +} + + +/** + * Resets a codec. + * + * @param pThis Codec to reset. + */ +void hdaCodecReset(PHDACODECR3 pThis) +{ + switch (pThis->Cfg.enmType) + { + case CODECTYPE_STAC9220: + stac9220Reset(pThis); + break; + + default: + AssertFailed(); + break; + } +} + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHdaCodec.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHdaCodec.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHdaCodec.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHdaCodec.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,899 @@ +/* $Id: DevHdaCodec.h $ */ +/** @file + * Intel HD Audio Controller Emulation - Codec, Sigmatel/IDT STAC9220. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DevHdaCodec_h +#define VBOX_INCLUDED_SRC_Audio_DevHdaCodec_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifndef VBOX_INCLUDED_SRC_Audio_DevHda_h +# error "Only include DevHda.h!" +#endif + +#include +#include "AudioMixer.h" + + +/** The ICH HDA (Intel) ring-3 codec state. */ +typedef struct HDACODECR3 *PHDACODECR3; + +/** + * Enumeration specifying the codec type to use. + */ +typedef enum CODECTYPE +{ + /** Invalid, do not use. */ + CODECTYPE_INVALID = 0, + /** SigmaTel 9220 (922x). */ + CODECTYPE_STAC9220, + /** Hack to blow the type up to 32-bit. */ + CODECTYPE_32BIT_HACK = 0x7fffffff +} CODECTYPE; + +/* PRM 5.3.1 */ +/** Codec address mask. */ +#define CODEC_CAD_MASK 0xF0000000 +/** Codec address shift. */ +#define CODEC_CAD_SHIFT 28 +#define CODEC_DIRECT_MASK RT_BIT(27) +/** Node ID mask. */ +#define CODEC_NID_MASK 0x07F00000 +/** Node ID shift. */ +#define CODEC_NID_SHIFT 20 +#define CODEC_VERBDATA_MASK 0x000FFFFF +#define CODEC_VERB_4BIT_CMD 0x000FFFF0 +#define CODEC_VERB_4BIT_DATA 0x0000000F +#define CODEC_VERB_8BIT_CMD 0x000FFF00 +#define CODEC_VERB_8BIT_DATA 0x000000FF +#define CODEC_VERB_16BIT_CMD 0x000F0000 +#define CODEC_VERB_16BIT_DATA 0x0000FFFF + +#define CODEC_CAD(cmd) (((cmd) & CODEC_CAD_MASK) >> CODEC_CAD_SHIFT) +#define CODEC_DIRECT(cmd) ((cmd) & CODEC_DIRECT_MASK) +#define CODEC_NID(cmd) ((((cmd) & CODEC_NID_MASK)) >> CODEC_NID_SHIFT) +#define CODEC_VERBDATA(cmd) ((cmd) & CODEC_VERBDATA_MASK) +#define CODEC_VERB_CMD(cmd, mask, x) (((cmd) & (mask)) >> (x)) +#define CODEC_VERB_CMD4(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_4BIT_CMD, 4)) +#define CODEC_VERB_CMD8(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_8BIT_CMD, 8)) +#define CODEC_VERB_CMD16(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_16BIT_CMD, 16)) +#define CODEC_VERB_PAYLOAD4(cmd) ((cmd) & CODEC_VERB_4BIT_DATA) +#define CODEC_VERB_PAYLOAD8(cmd) ((cmd) & CODEC_VERB_8BIT_DATA) +#define CODEC_VERB_PAYLOAD16(cmd) ((cmd) & CODEC_VERB_16BIT_DATA) + +#define CODEC_VERB_GET_AMP_DIRECTION RT_BIT(15) +#define CODEC_VERB_GET_AMP_SIDE RT_BIT(13) +#define CODEC_VERB_GET_AMP_INDEX 0x7 + +/* HDA spec 7.3.3.7 NoteA */ +#define CODEC_GET_AMP_DIRECTION(cmd) (((cmd) & CODEC_VERB_GET_AMP_DIRECTION) >> 15) +#define CODEC_GET_AMP_SIDE(cmd) (((cmd) & CODEC_VERB_GET_AMP_SIDE) >> 13) +#define CODEC_GET_AMP_INDEX(cmd) (CODEC_GET_AMP_DIRECTION(cmd) ? 0 : ((cmd) & CODEC_VERB_GET_AMP_INDEX)) + +/* HDA spec 7.3.3.7 NoteC */ +#define CODEC_VERB_SET_AMP_OUT_DIRECTION RT_BIT(15) +#define CODEC_VERB_SET_AMP_IN_DIRECTION RT_BIT(14) +#define CODEC_VERB_SET_AMP_LEFT_SIDE RT_BIT(13) +#define CODEC_VERB_SET_AMP_RIGHT_SIDE RT_BIT(12) +#define CODEC_VERB_SET_AMP_INDEX (0x7 << 8) +#define CODEC_VERB_SET_AMP_MUTE RT_BIT(7) +/** Note: 7-bit value [6:0]. */ +#define CODEC_VERB_SET_AMP_GAIN 0x7F + +#define CODEC_SET_AMP_IS_OUT_DIRECTION(cmd) (((cmd) & CODEC_VERB_SET_AMP_OUT_DIRECTION) != 0) +#define CODEC_SET_AMP_IS_IN_DIRECTION(cmd) (((cmd) & CODEC_VERB_SET_AMP_IN_DIRECTION) != 0) +#define CODEC_SET_AMP_IS_LEFT_SIDE(cmd) (((cmd) & CODEC_VERB_SET_AMP_LEFT_SIDE) != 0) +#define CODEC_SET_AMP_IS_RIGHT_SIDE(cmd) (((cmd) & CODEC_VERB_SET_AMP_RIGHT_SIDE) != 0) +#define CODEC_SET_AMP_INDEX(cmd) (((cmd) & CODEC_VERB_SET_AMP_INDEX) >> 7) +#define CODEC_SET_AMP_MUTE(cmd) ((cmd) & CODEC_VERB_SET_AMP_MUTE) +#define CODEC_SET_AMP_GAIN(cmd) ((cmd) & CODEC_VERB_SET_AMP_GAIN) + +/* HDA spec 7.3.3.1 defines layout of configuration registers/verbs (0xF00) */ +/* VendorID (7.3.4.1) */ +#define CODEC_MAKE_F00_00(vendorID, deviceID) (((vendorID) << 16) | (deviceID)) +#define CODEC_F00_00_VENDORID(f00_00) (((f00_00) >> 16) & 0xFFFF) +#define CODEC_F00_00_DEVICEID(f00_00) ((f00_00) & 0xFFFF) + +/** RevisionID (7.3.4.2). */ +#define CODEC_MAKE_F00_02(majRev, minRev, venFix, venProg, stepFix, stepProg) \ + ( (((majRev) & 0xF) << 20) \ + | (((minRev) & 0xF) << 16) \ + | (((venFix) & 0xF) << 12) \ + | (((venProg) & 0xF) << 8) \ + | (((stepFix) & 0xF) << 4) \ + | ((stepProg) & 0xF)) + +/** Subordinate node count (7.3.4.3). */ +#define CODEC_MAKE_F00_04(startNodeNumber, totalNodeNumber) ((((startNodeNumber) & 0xFF) << 16)|((totalNodeNumber) & 0xFF)) +#define CODEC_F00_04_TO_START_NODE_NUMBER(f00_04) (((f00_04) >> 16) & 0xFF) +#define CODEC_F00_04_TO_NODE_COUNT(f00_04) ((f00_04) & 0xFF) +/* + * Function Group Type (7.3.4.4) + * 0 & [0x3-0x7f] are reserved types + * [0x80 - 0xff] are vendor defined function groups + */ +#define CODEC_MAKE_F00_05(UnSol, NodeType) (((UnSol) << 8)|(NodeType)) +#define CODEC_F00_05_UNSOL RT_BIT(8) +#define CODEC_F00_05_AFG (0x1) +#define CODEC_F00_05_MFG (0x2) +#define CODEC_F00_05_IS_UNSOL(f00_05) RT_BOOL((f00_05) & RT_BIT(8)) +#define CODEC_F00_05_GROUP(f00_05) ((f00_05) & 0xff) +/* Audio Function Group capabilities (7.3.4.5). */ +#define CODEC_MAKE_F00_08(BeepGen, InputDelay, OutputDelay) ((((BeepGen) & 0x1) << 16)| (((InputDelay) & 0xF) << 8) | ((OutputDelay) & 0xF)) +#define CODEC_F00_08_BEEP_GEN(f00_08) ((f00_08) & RT_BIT(16) + +/* Converter Stream, Channel (7.3.3.11). */ +#define CODEC_F00_06_GET_STREAM_ID(cmd) (((cmd) >> 4) & 0x0F) +#define CODEC_F00_06_GET_CHANNEL_ID(cmd) (((cmd) & 0x0F)) + +/* Widget Capabilities (7.3.4.6). */ +#define CODEC_MAKE_F00_09(type, delay, chan_ext) \ + ( (((type) & 0xF) << 20) \ + | (((delay) & 0xF) << 16) \ + | (((chan_ext) & 0xF) << 13)) +/* note: types 0x8-0xe are reserved */ +#define CODEC_F00_09_TYPE_AUDIO_OUTPUT (0x0) +#define CODEC_F00_09_TYPE_AUDIO_INPUT (0x1) +#define CODEC_F00_09_TYPE_AUDIO_MIXER (0x2) +#define CODEC_F00_09_TYPE_AUDIO_SELECTOR (0x3) +#define CODEC_F00_09_TYPE_PIN_COMPLEX (0x4) +#define CODEC_F00_09_TYPE_POWER_WIDGET (0x5) +#define CODEC_F00_09_TYPE_VOLUME_KNOB (0x6) +#define CODEC_F00_09_TYPE_BEEP_GEN (0x7) +#define CODEC_F00_09_TYPE_VENDOR_DEFINED (0xF) + +#define CODEC_F00_09_CAP_CP RT_BIT(12) +#define CODEC_F00_09_CAP_L_R_SWAP RT_BIT(11) +#define CODEC_F00_09_CAP_POWER_CTRL RT_BIT(10) +#define CODEC_F00_09_CAP_DIGITAL RT_BIT(9) +#define CODEC_F00_09_CAP_CONNECTION_LIST RT_BIT(8) +#define CODEC_F00_09_CAP_UNSOL RT_BIT(7) +#define CODEC_F00_09_CAP_PROC_WIDGET RT_BIT(6) +#define CODEC_F00_09_CAP_STRIPE RT_BIT(5) +#define CODEC_F00_09_CAP_FMT_OVERRIDE RT_BIT(4) +#define CODEC_F00_09_CAP_AMP_FMT_OVERRIDE RT_BIT(3) +#define CODEC_F00_09_CAP_OUT_AMP_PRESENT RT_BIT(2) +#define CODEC_F00_09_CAP_IN_AMP_PRESENT RT_BIT(1) +#define CODEC_F00_09_CAP_STEREO RT_BIT(0) + +#define CODEC_F00_09_TYPE(f00_09) (((f00_09) >> 20) & 0xF) + +#define CODEC_F00_09_IS_CAP_CP(f00_09) RT_BOOL((f00_09) & RT_BIT(12)) +#define CODEC_F00_09_IS_CAP_L_R_SWAP(f00_09) RT_BOOL((f00_09) & RT_BIT(11)) +#define CODEC_F00_09_IS_CAP_POWER_CTRL(f00_09) RT_BOOL((f00_09) & RT_BIT(10)) +#define CODEC_F00_09_IS_CAP_DIGITAL(f00_09) RT_BOOL((f00_09) & RT_BIT(9)) +#define CODEC_F00_09_IS_CAP_CONNECTION_LIST(f00_09) RT_BOOL((f00_09) & RT_BIT(8)) +#define CODEC_F00_09_IS_CAP_UNSOL(f00_09) RT_BOOL((f00_09) & RT_BIT(7)) +#define CODEC_F00_09_IS_CAP_PROC_WIDGET(f00_09) RT_BOOL((f00_09) & RT_BIT(6)) +#define CODEC_F00_09_IS_CAP_STRIPE(f00_09) RT_BOOL((f00_09) & RT_BIT(5)) +#define CODEC_F00_09_IS_CAP_FMT_OVERRIDE(f00_09) RT_BOOL((f00_09) & RT_BIT(4)) +#define CODEC_F00_09_IS_CAP_AMP_OVERRIDE(f00_09) RT_BOOL((f00_09) & RT_BIT(3)) +#define CODEC_F00_09_IS_CAP_OUT_AMP_PRESENT(f00_09) RT_BOOL((f00_09) & RT_BIT(2)) +#define CODEC_F00_09_IS_CAP_IN_AMP_PRESENT(f00_09) RT_BOOL((f00_09) & RT_BIT(1)) +#define CODEC_F00_09_IS_CAP_LSB(f00_09) RT_BOOL((f00_09) & RT_BIT(0)) + +/* Supported PCM size, rates (7.3.4.7) */ +#define CODEC_F00_0A_32_BIT RT_BIT(19) +#define CODEC_F00_0A_24_BIT RT_BIT(18) +#define CODEC_F00_0A_16_BIT RT_BIT(17) +#define CODEC_F00_0A_8_BIT RT_BIT(16) + +#define CODEC_F00_0A_48KHZ_MULT_8X RT_BIT(11) +#define CODEC_F00_0A_48KHZ_MULT_4X RT_BIT(10) +#define CODEC_F00_0A_44_1KHZ_MULT_4X RT_BIT(9) +#define CODEC_F00_0A_48KHZ_MULT_2X RT_BIT(8) +#define CODEC_F00_0A_44_1KHZ_MULT_2X RT_BIT(7) +#define CODEC_F00_0A_48KHZ RT_BIT(6) +#define CODEC_F00_0A_44_1KHZ RT_BIT(5) +/* 2/3 * 48kHz */ +#define CODEC_F00_0A_48KHZ_2_3X RT_BIT(4) +/* 1/2 * 44.1kHz */ +#define CODEC_F00_0A_44_1KHZ_1_2X RT_BIT(3) +/* 1/3 * 48kHz */ +#define CODEC_F00_0A_48KHZ_1_3X RT_BIT(2) +/* 1/4 * 44.1kHz */ +#define CODEC_F00_0A_44_1KHZ_1_4X RT_BIT(1) +/* 1/6 * 48kHz */ +#define CODEC_F00_0A_48KHZ_1_6X RT_BIT(0) + +/* Supported streams formats (7.3.4.8) */ +#define CODEC_F00_0B_AC3 RT_BIT(2) +#define CODEC_F00_0B_FLOAT32 RT_BIT(1) +#define CODEC_F00_0B_PCM RT_BIT(0) + +/* Pin Capabilities (7.3.4.9)*/ +#define CODEC_MAKE_F00_0C(vref_ctrl) (((vref_ctrl) & 0xFF) << 8) +#define CODEC_F00_0C_CAP_HBR RT_BIT(27) +#define CODEC_F00_0C_CAP_DP RT_BIT(24) +#define CODEC_F00_0C_CAP_EAPD RT_BIT(16) +#define CODEC_F00_0C_CAP_HDMI RT_BIT(7) +#define CODEC_F00_0C_CAP_BALANCED_IO RT_BIT(6) +#define CODEC_F00_0C_CAP_INPUT RT_BIT(5) +#define CODEC_F00_0C_CAP_OUTPUT RT_BIT(4) +#define CODEC_F00_0C_CAP_HEADPHONE_AMP RT_BIT(3) +#define CODEC_F00_0C_CAP_PRESENCE_DETECT RT_BIT(2) +#define CODEC_F00_0C_CAP_TRIGGER_REQUIRED RT_BIT(1) +#define CODEC_F00_0C_CAP_IMPENDANCE_SENSE RT_BIT(0) + +#define CODEC_F00_0C_IS_CAP_HBR(f00_0c) ((f00_0c) & RT_BIT(27)) +#define CODEC_F00_0C_IS_CAP_DP(f00_0c) ((f00_0c) & RT_BIT(24)) +#define CODEC_F00_0C_IS_CAP_EAPD(f00_0c) ((f00_0c) & RT_BIT(16)) +#define CODEC_F00_0C_IS_CAP_HDMI(f00_0c) ((f00_0c) & RT_BIT(7)) +#define CODEC_F00_0C_IS_CAP_BALANCED_IO(f00_0c) ((f00_0c) & RT_BIT(6)) +#define CODEC_F00_0C_IS_CAP_INPUT(f00_0c) ((f00_0c) & RT_BIT(5)) +#define CODEC_F00_0C_IS_CAP_OUTPUT(f00_0c) ((f00_0c) & RT_BIT(4)) +#define CODEC_F00_0C_IS_CAP_HP(f00_0c) ((f00_0c) & RT_BIT(3)) +#define CODEC_F00_0C_IS_CAP_PRESENCE_DETECT(f00_0c) ((f00_0c) & RT_BIT(2)) +#define CODEC_F00_0C_IS_CAP_TRIGGER_REQUIRED(f00_0c) ((f00_0c) & RT_BIT(1)) +#define CODEC_F00_0C_IS_CAP_IMPENDANCE_SENSE(f00_0c) ((f00_0c) & RT_BIT(0)) + +/* Input Amplifier capabilities (7.3.4.10). */ +#define CODEC_MAKE_F00_0D(mute_cap, step_size, num_steps, offset) \ + ( (((mute_cap) & UINT32_C(0x1)) << 31) \ + | (((step_size) & UINT32_C(0xFF)) << 16) \ + | (((num_steps) & UINT32_C(0xFF)) << 8) \ + | ((offset) & UINT32_C(0xFF))) + +#define CODEC_F00_0D_CAP_MUTE RT_BIT(7) + +#define CODEC_F00_0D_IS_CAP_MUTE(f00_0d) ( ( f00_0d) & RT_BIT(31)) +#define CODEC_F00_0D_STEP_SIZE(f00_0d) ((( f00_0d) & (0x7F << 16)) >> 16) +#define CODEC_F00_0D_NUM_STEPS(f00_0d) ((((f00_0d) & (0x7F << 8)) >> 8) + 1) +#define CODEC_F00_0D_OFFSET(f00_0d) ( (f00_0d) & 0x7F) + +/** Indicates that the amplifier can be muted. */ +#define CODEC_AMP_CAP_MUTE 0x1 +/** The amplifier's maximum number of steps. We want + * a ~90dB dynamic range, so 64 steps with 1.25dB each + * should do the trick. + * + * As we want to map our range to [0..128] values we can avoid + * multiplication and simply doing a shift later. + * + * Produces -96dB to +0dB. + * "0" indicates a step of 0.25dB, "127" indicates a step of 32dB. + */ +#define CODEC_AMP_NUM_STEPS 0x7F +/** The initial gain offset (and when doing a node reset). */ +#define CODEC_AMP_OFF_INITIAL 0x7F +/** The amplifier's gain step size. */ +#define CODEC_AMP_STEP_SIZE 0x2 + +/* Output Amplifier capabilities (7.3.4.10) */ +#define CODEC_MAKE_F00_12 CODEC_MAKE_F00_0D + +#define CODEC_F00_12_IS_CAP_MUTE(f00_12) CODEC_F00_0D_IS_CAP_MUTE(f00_12) +#define CODEC_F00_12_STEP_SIZE(f00_12) CODEC_F00_0D_STEP_SIZE(f00_12) +#define CODEC_F00_12_NUM_STEPS(f00_12) CODEC_F00_0D_NUM_STEPS(f00_12) +#define CODEC_F00_12_OFFSET(f00_12) CODEC_F00_0D_OFFSET(f00_12) + +/* Connection list lenght (7.3.4.11). */ +#define CODEC_MAKE_F00_0E(long_form, length) \ + ( (((long_form) & 0x1) << 7) \ + | ((length) & 0x7F)) +/* Indicates short-form NIDs. */ +#define CODEC_F00_0E_LIST_NID_SHORT 0 +/* Indicates long-form NIDs. */ +#define CODEC_F00_0E_LIST_NID_LONG 1 +#define CODEC_F00_0E_IS_LONG(f00_0e) RT_BOOL((f00_0e) & RT_BIT(7)) +#define CODEC_F00_0E_COUNT(f00_0e) ((f00_0e) & 0x7F) +/* Supported Power States (7.3.4.12) */ +#define CODEC_F00_0F_EPSS RT_BIT(31) +#define CODEC_F00_0F_CLKSTOP RT_BIT(30) +#define CODEC_F00_0F_S3D3 RT_BIT(29) +#define CODEC_F00_0F_D3COLD RT_BIT(4) +#define CODEC_F00_0F_D3 RT_BIT(3) +#define CODEC_F00_0F_D2 RT_BIT(2) +#define CODEC_F00_0F_D1 RT_BIT(1) +#define CODEC_F00_0F_D0 RT_BIT(0) + +/* Processing capabilities 7.3.4.13 */ +#define CODEC_MAKE_F00_10(num, benign) ((((num) & 0xFF) << 8) | ((benign) & 0x1)) +#define CODEC_F00_10_NUM(f00_10) (((f00_10) & (0xFF << 8)) >> 8) +#define CODEC_F00_10_BENING(f00_10) ((f00_10) & 0x1) + +/* GPIO count (7.3.4.14). */ +#define CODEC_MAKE_F00_11(wake, unsol, numgpi, numgpo, numgpio) \ + ( (((wake) & UINT32_C(0x1)) << 31) \ + | (((unsol) & UINT32_C(0x1)) << 30) \ + | (((numgpi) & UINT32_C(0xFF)) << 16) \ + | (((numgpo) & UINT32_C(0xFF)) << 8) \ + | ((numgpio) & UINT32_C(0xFF))) + +/* Processing States (7.3.3.4). */ +#define CODEC_F03_OFF (0) +#define CODEC_F03_ON RT_BIT(0) +#define CODEC_F03_BENING RT_BIT(1) +/* Power States (7.3.3.10). */ +#define CODEC_MAKE_F05(reset, stopok, error, act, set) \ + ( (((reset) & 0x1) << 10) \ + | (((stopok) & 0x1) << 9) \ + | (((error) & 0x1) << 8) \ + | (((act) & 0xF) << 4) \ + | ((set) & 0xF)) +#define CODEC_F05_D3COLD (4) +#define CODEC_F05_D3 (3) +#define CODEC_F05_D2 (2) +#define CODEC_F05_D1 (1) +#define CODEC_F05_D0 (0) + +#define CODEC_F05_IS_RESET(value) (((value) & RT_BIT(10)) != 0) +#define CODEC_F05_IS_STOPOK(value) (((value) & RT_BIT(9)) != 0) +#define CODEC_F05_IS_ERROR(value) (((value) & RT_BIT(8)) != 0) +#define CODEC_F05_ACT(value) (((value) & 0xF0) >> 4) +#define CODEC_F05_SET(value) (((value) & 0xF)) + +#define CODEC_F05_GE(p0, p1) ((p0) <= (p1)) +#define CODEC_F05_LE(p0, p1) ((p0) >= (p1)) + +/* Converter Stream, Channel (7.3.3.11). */ +#define CODEC_MAKE_F06(stream, channel) \ + ( (((stream) & 0xF) << 4) \ + | ((channel) & 0xF)) +#define CODEC_F06_STREAM(value) ((value) & 0xF0) +#define CODEC_F06_CHANNEL(value) ((value) & 0xF) + +/* Pin Widged Control (7.3.3.13). */ +#define CODEC_F07_VREF_HIZ (0) +#define CODEC_F07_VREF_50 (0x1) +#define CODEC_F07_VREF_GROUND (0x2) +#define CODEC_F07_VREF_80 (0x4) +#define CODEC_F07_VREF_100 (0x5) +#define CODEC_F07_IN_ENABLE RT_BIT(5) +#define CODEC_F07_OUT_ENABLE RT_BIT(6) +#define CODEC_F07_OUT_H_ENABLE RT_BIT(7) + +/* Volume Knob Control (7.3.3.29). */ +#define CODEC_F0F_IS_DIRECT RT_BIT(7) +#define CODEC_F0F_VOLUME (0x7F) + +/* Unsolicited enabled (7.3.3.14). */ +#define CODEC_MAKE_F08(enable, tag) ((((enable) & 1) << 7) | ((tag) & 0x3F)) + +/* Converter formats (7.3.3.8) and (3.7.1). */ +/* This is the same format as SDnFMT. */ +#define CODEC_MAKE_A HDA_SDFMT_MAKE + +#define CODEC_A_TYPE HDA_SDFMT_TYPE +#define CODEC_A_TYPE_PCM HDA_SDFMT_TYPE_PCM +#define CODEC_A_TYPE_NON_PCM HDA_SDFMT_TYPE_NON_PCM + +#define CODEC_A_BASE HDA_SDFMT_BASE +#define CODEC_A_BASE_48KHZ HDA_SDFMT_BASE_48KHZ +#define CODEC_A_BASE_44KHZ HDA_SDFMT_BASE_44KHZ + +/* Pin Sense (7.3.3.15). */ +#define CODEC_MAKE_F09_ANALOG(fPresent, impedance) \ +( (((fPresent) & 0x1) << 31) \ + | (((impedance) & UINT32_C(0x7FFFFFFF)))) +#define CODEC_F09_ANALOG_NA UINT32_C(0x7FFFFFFF) +#define CODEC_MAKE_F09_DIGITAL(fPresent, fELDValid) \ +( (((fPresent) & UINT32_C(0x1)) << 31) \ + | (((fELDValid) & UINT32_C(0x1)) << 30)) + +#define CODEC_MAKE_F0C(lrswap, eapd, btl) ((((lrswap) & 1) << 2) | (((eapd) & 1) << 1) | ((btl) & 1)) +#define CODEC_FOC_IS_LRSWAP(f0c) RT_BOOL((f0c) & RT_BIT(2)) +#define CODEC_FOC_IS_EAPD(f0c) RT_BOOL((f0c) & RT_BIT(1)) +#define CODEC_FOC_IS_BTL(f0c) RT_BOOL((f0c) & RT_BIT(0)) +/* HDA spec 7.3.3.31 defines layout of configuration registers/verbs (0xF1C) */ +/* Configuration's port connection */ +#define CODEC_F1C_PORT_MASK (0x3) +#define CODEC_F1C_PORT_SHIFT (30) + +#define CODEC_F1C_PORT_COMPLEX (0x0) +#define CODEC_F1C_PORT_NO_PHYS (0x1) +#define CODEC_F1C_PORT_FIXED (0x2) +#define CODEC_F1C_BOTH (0x3) + +/* Configuration default: connection */ +#define CODEC_F1C_PORT_MASK (0x3) +#define CODEC_F1C_PORT_SHIFT (30) + +/* Connected to a jack (1/8", ATAPI, ...). */ +#define CODEC_F1C_PORT_COMPLEX (0x0) +/* No physical connection. */ +#define CODEC_F1C_PORT_NO_PHYS (0x1) +/* Fixed function device (integrated speaker, integrated mic, ...). */ +#define CODEC_F1C_PORT_FIXED (0x2) +/* Both, a jack and an internal device are attached. */ +#define CODEC_F1C_BOTH (0x3) + +/* Configuration default: Location */ +#define CODEC_F1C_LOCATION_MASK (0x3F) +#define CODEC_F1C_LOCATION_SHIFT (24) + +/* [4:5] bits of location region means chassis attachment */ +#define CODEC_F1C_LOCATION_PRIMARY_CHASSIS (0) +#define CODEC_F1C_LOCATION_INTERNAL RT_BIT(4) +#define CODEC_F1C_LOCATION_SECONDRARY_CHASSIS RT_BIT(5) +#define CODEC_F1C_LOCATION_OTHER RT_BIT(5) + +/* [0:3] bits of location region means geometry location attachment */ +#define CODEC_F1C_LOCATION_NA (0) +#define CODEC_F1C_LOCATION_REAR (0x1) +#define CODEC_F1C_LOCATION_FRONT (0x2) +#define CODEC_F1C_LOCATION_LEFT (0x3) +#define CODEC_F1C_LOCATION_RIGTH (0x4) +#define CODEC_F1C_LOCATION_TOP (0x5) +#define CODEC_F1C_LOCATION_BOTTOM (0x6) +#define CODEC_F1C_LOCATION_SPECIAL_0 (0x7) +#define CODEC_F1C_LOCATION_SPECIAL_1 (0x8) +#define CODEC_F1C_LOCATION_SPECIAL_2 (0x9) + +/* Configuration default: Device type */ +#define CODEC_F1C_DEVICE_MASK (0xF) +#define CODEC_F1C_DEVICE_SHIFT (20) +#define CODEC_F1C_DEVICE_LINE_OUT (0) +#define CODEC_F1C_DEVICE_SPEAKER (0x1) +#define CODEC_F1C_DEVICE_HP (0x2) +#define CODEC_F1C_DEVICE_CD (0x3) +#define CODEC_F1C_DEVICE_SPDIF_OUT (0x4) +#define CODEC_F1C_DEVICE_DIGITAL_OTHER_OUT (0x5) +#define CODEC_F1C_DEVICE_MODEM_LINE_SIDE (0x6) +#define CODEC_F1C_DEVICE_MODEM_HANDSET_SIDE (0x7) +#define CODEC_F1C_DEVICE_LINE_IN (0x8) +#define CODEC_F1C_DEVICE_AUX (0x9) +#define CODEC_F1C_DEVICE_MIC (0xA) +#define CODEC_F1C_DEVICE_PHONE (0xB) +#define CODEC_F1C_DEVICE_SPDIF_IN (0xC) +#define CODEC_F1C_DEVICE_RESERVED (0xE) +#define CODEC_F1C_DEVICE_OTHER (0xF) + +/* Configuration default: Connection type */ +#define CODEC_F1C_CONNECTION_TYPE_MASK (0xF) +#define CODEC_F1C_CONNECTION_TYPE_SHIFT (16) + +#define CODEC_F1C_CONNECTION_TYPE_UNKNOWN (0) +#define CODEC_F1C_CONNECTION_TYPE_1_8INCHES (0x1) +#define CODEC_F1C_CONNECTION_TYPE_1_4INCHES (0x2) +#define CODEC_F1C_CONNECTION_TYPE_ATAPI (0x3) +#define CODEC_F1C_CONNECTION_TYPE_RCA (0x4) +#define CODEC_F1C_CONNECTION_TYPE_OPTICAL (0x5) +#define CODEC_F1C_CONNECTION_TYPE_OTHER_DIGITAL (0x6) +#define CODEC_F1C_CONNECTION_TYPE_ANALOG (0x7) +#define CODEC_F1C_CONNECTION_TYPE_DIN (0x8) +#define CODEC_F1C_CONNECTION_TYPE_XLR (0x9) +#define CODEC_F1C_CONNECTION_TYPE_RJ_11 (0xA) +#define CODEC_F1C_CONNECTION_TYPE_COMBO (0xB) +#define CODEC_F1C_CONNECTION_TYPE_OTHER (0xF) + +/* Configuration's color */ +#define CODEC_F1C_COLOR_MASK (0xF) +#define CODEC_F1C_COLOR_SHIFT (12) +#define CODEC_F1C_COLOR_UNKNOWN (0) +#define CODEC_F1C_COLOR_BLACK (0x1) +#define CODEC_F1C_COLOR_GREY (0x2) +#define CODEC_F1C_COLOR_BLUE (0x3) +#define CODEC_F1C_COLOR_GREEN (0x4) +#define CODEC_F1C_COLOR_RED (0x5) +#define CODEC_F1C_COLOR_ORANGE (0x6) +#define CODEC_F1C_COLOR_YELLOW (0x7) +#define CODEC_F1C_COLOR_PURPLE (0x8) +#define CODEC_F1C_COLOR_PINK (0x9) +#define CODEC_F1C_COLOR_RESERVED_0 (0xA) +#define CODEC_F1C_COLOR_RESERVED_1 (0xB) +#define CODEC_F1C_COLOR_RESERVED_2 (0xC) +#define CODEC_F1C_COLOR_RESERVED_3 (0xD) +#define CODEC_F1C_COLOR_WHITE (0xE) +#define CODEC_F1C_COLOR_OTHER (0xF) + +/* Configuration's misc */ +#define CODEC_F1C_MISC_MASK (0xF) +#define CODEC_F1C_MISC_SHIFT (8) +#define CODEC_F1C_MISC_NONE 0 +#define CODEC_F1C_MISC_JACK_NO_PRESENCE_DETECT RT_BIT(0) +#define CODEC_F1C_MISC_RESERVED_0 RT_BIT(1) +#define CODEC_F1C_MISC_RESERVED_1 RT_BIT(2) +#define CODEC_F1C_MISC_RESERVED_2 RT_BIT(3) + +/* Configuration default: Association */ +#define CODEC_F1C_ASSOCIATION_MASK (0xF) +#define CODEC_F1C_ASSOCIATION_SHIFT (4) + +/** Reserved; don't use. */ +#define CODEC_F1C_ASSOCIATION_INVALID 0x0 +#define CODEC_F1C_ASSOCIATION_GROUP_0 0x1 +#define CODEC_F1C_ASSOCIATION_GROUP_1 0x2 +#define CODEC_F1C_ASSOCIATION_GROUP_2 0x3 +#define CODEC_F1C_ASSOCIATION_GROUP_3 0x4 +#define CODEC_F1C_ASSOCIATION_GROUP_4 0x5 +#define CODEC_F1C_ASSOCIATION_GROUP_5 0x6 +#define CODEC_F1C_ASSOCIATION_GROUP_6 0x7 +#define CODEC_F1C_ASSOCIATION_GROUP_7 0x8 +/* Note: Windows OSes will treat group 15 (0xF) as single PIN devices. + * The sequence number associated with that group then will be ignored. */ +#define CODEC_F1C_ASSOCIATION_GROUP_15 0xF + +/* Configuration default: Association Sequence. */ +#define CODEC_F1C_SEQ_MASK (0xF) +#define CODEC_F1C_SEQ_SHIFT (0) + +/* Implementation identification (7.3.3.30). */ +#define CODEC_MAKE_F20(bmid, bsku, aid) \ + ( (((bmid) & 0xFFFF) << 16) \ + | (((bsku) & 0xFF) << 8) \ + | (((aid) & 0xFF)) \ + ) + +/* Macro definition helping in filling the configuration registers. */ +#define CODEC_MAKE_F1C(port_connectivity, location, device, connection_type, color, misc, association, sequence) \ + ( (((port_connectivity) & 0xF) << CODEC_F1C_PORT_SHIFT) \ + | (((location) & 0xF) << CODEC_F1C_LOCATION_SHIFT) \ + | (((device) & 0xF) << CODEC_F1C_DEVICE_SHIFT) \ + | (((connection_type) & 0xF) << CODEC_F1C_CONNECTION_TYPE_SHIFT) \ + | (((color) & 0xF) << CODEC_F1C_COLOR_SHIFT) \ + | (((misc) & 0xF) << CODEC_F1C_MISC_SHIFT) \ + | (((association) & 0xF) << CODEC_F1C_ASSOCIATION_SHIFT) \ + | (((sequence) & 0xF))) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The F00 parameter length (in dwords). */ +#define CODECNODE_F00_PARAM_LENGTH 20 +/** The F02 parameter length (in dwords). */ +#define CODECNODE_F02_PARAM_LENGTH 16 + +/* PRM 5.3.1 */ +#define CODEC_RESPONSE_UNSOLICITED RT_BIT_64(34) + +#define AMPLIFIER_SIZE 60 + +typedef uint32_t AMPLIFIER[AMPLIFIER_SIZE]; + +/** + * Common (or core) codec node structure. + */ +typedef struct CODECCOMMONNODE +{ + /** The node's ID. */ + uint8_t uID; + /** The node's name. */ + /** The SDn ID this node is assigned to. + * 0 means not assigned, 1 is SDn0. */ + uint8_t uSD; + /** The SDn's channel to use. + * Only valid if a valid SDn ID is set. */ + uint8_t uChannel; + /* PRM 5.3.6 */ + uint32_t au32F00_param[CODECNODE_F00_PARAM_LENGTH]; + uint32_t au32F02_param[CODECNODE_F02_PARAM_LENGTH]; +} CODECCOMMONNODE; +AssertCompile(CODECNODE_F00_PARAM_LENGTH == 20); /* saved state */ +AssertCompile(CODECNODE_F02_PARAM_LENGTH == 16); /* saved state */ +AssertCompileSize(CODECCOMMONNODE, (1 + 20 + 16) * sizeof(uint32_t)); +typedef CODECCOMMONNODE *PCODECCOMMONNODE; + +/** + * Compile time assertion on the expected node size. + */ +#define AssertNodeSize(a_Node, a_cParams) \ + AssertCompile((a_cParams) <= (60 + 6)); /* the max size - saved state */ \ + AssertCompile( sizeof(a_Node) - sizeof(CODECCOMMONNODE) \ + == ((a_cParams) * sizeof(uint32_t)) ) + +typedef struct ROOTCODECNODE +{ + CODECCOMMONNODE node; +} ROOTCODECNODE, *PROOTCODECNODE; +AssertNodeSize(ROOTCODECNODE, 0); + +typedef struct DACNODE +{ + CODECCOMMONNODE node; + uint32_t u32F0d_param; + uint32_t u32F04_param; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F0c_param; + + uint32_t u32A_param; + AMPLIFIER B_params; + +} DACNODE, *PDACNODE; +AssertNodeSize(DACNODE, 6 + 60); + +typedef struct ADCNODE +{ + CODECCOMMONNODE node; + uint32_t u32F01_param; + uint32_t u32F03_param; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F09_param; + + uint32_t u32A_param; + AMPLIFIER B_params; +} ADCNODE, *PADCNODE; +AssertNodeSize(DACNODE, 6 + 60); + +typedef struct SPDIFOUTNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F09_param; + uint32_t u32F0d_param; + + uint32_t u32A_param; + AMPLIFIER B_params; +} SPDIFOUTNODE, *PSPDIFOUTNODE; +AssertNodeSize(SPDIFOUTNODE, 5 + 60); + +typedef struct SPDIFINNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F09_param; + uint32_t u32F0d_param; + + uint32_t u32A_param; + AMPLIFIER B_params; +} SPDIFINNODE, *PSPDIFINNODE; +AssertNodeSize(SPDIFINNODE, 5 + 60); + +typedef struct AFGCODECNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F08_param; + uint32_t u32F17_param; + uint32_t u32F20_param; +} AFGCODECNODE, *PAFGCODECNODE; +AssertNodeSize(AFGCODECNODE, 4); + +typedef struct PORTNODE +{ + CODECCOMMONNODE node; + uint32_t u32F01_param; + uint32_t u32F07_param; + uint32_t u32F08_param; + uint32_t u32F09_param; + uint32_t u32F1c_param; + AMPLIFIER B_params; +} PORTNODE, *PPORTNODE; +AssertNodeSize(PORTNODE, 5 + 60); + +typedef struct DIGOUTNODE +{ + CODECCOMMONNODE node; + uint32_t u32F01_param; + uint32_t u32F05_param; + uint32_t u32F07_param; + uint32_t u32F08_param; + uint32_t u32F09_param; + uint32_t u32F1c_param; +} DIGOUTNODE, *PDIGOUTNODE; +AssertNodeSize(DIGOUTNODE, 6); + +typedef struct DIGINNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F07_param; + uint32_t u32F08_param; + uint32_t u32F09_param; + uint32_t u32F0c_param; + uint32_t u32F1c_param; + uint32_t u32F1e_param; +} DIGINNODE, *PDIGINNODE; +AssertNodeSize(DIGINNODE, 7); + +typedef struct ADCMUXNODE +{ + CODECCOMMONNODE node; + uint32_t u32F01_param; + + uint32_t u32A_param; + AMPLIFIER B_params; +} ADCMUXNODE, *PADCMUXNODE; +AssertNodeSize(ADCMUXNODE, 2 + 60); + +typedef struct PCBEEPNODE +{ + CODECCOMMONNODE node; + uint32_t u32F07_param; + uint32_t u32F0a_param; + + uint32_t u32A_param; + AMPLIFIER B_params; + uint32_t u32F1c_param; +} PCBEEPNODE, *PPCBEEPNODE; +AssertNodeSize(PCBEEPNODE, 3 + 60 + 1); + +typedef struct CDNODE +{ + CODECCOMMONNODE node; + uint32_t u32F07_param; + uint32_t u32F1c_param; +} CDNODE, *PCDNODE; +AssertNodeSize(CDNODE, 2); + +typedef struct VOLUMEKNOBNODE +{ + CODECCOMMONNODE node; + uint32_t u32F08_param; + uint32_t u32F0f_param; +} VOLUMEKNOBNODE, *PVOLUMEKNOBNODE; +AssertNodeSize(VOLUMEKNOBNODE, 2); + +typedef struct ADCVOLNODE +{ + CODECCOMMONNODE node; + uint32_t u32F0c_param; + uint32_t u32F01_param; + uint32_t u32A_params; + AMPLIFIER B_params; +} ADCVOLNODE, *PADCVOLNODE; +AssertNodeSize(ADCVOLNODE, 3 + 60); + +typedef struct RESNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F07_param; + uint32_t u32F1c_param; + + uint32_t u32A_param; +} RESNODE, *PRESNODE; +AssertNodeSize(RESNODE, 5); + +/** + * Used for the saved state. + */ +typedef struct CODECSAVEDSTATENODE +{ + CODECCOMMONNODE Core; + uint32_t au32Params[60 + 6]; +} CODECSAVEDSTATENODE; +AssertNodeSize(CODECSAVEDSTATENODE, 60 + 6); + +typedef union CODECNODE +{ + CODECCOMMONNODE node; + ROOTCODECNODE root; + AFGCODECNODE afg; + DACNODE dac; + ADCNODE adc; + SPDIFOUTNODE spdifout; + SPDIFINNODE spdifin; + PORTNODE port; + DIGOUTNODE digout; + DIGINNODE digin; + ADCMUXNODE adcmux; + PCBEEPNODE pcbeep; + CDNODE cdnode; + VOLUMEKNOBNODE volumeKnob; + ADCVOLNODE adcvol; + RESNODE reserved; + CODECSAVEDSTATENODE SavedState; +} CODECNODE, *PCODECNODE; +AssertNodeSize(CODECNODE, 60 + 6); + +#define CODEC_NODES_MAX 32 + +/** @name CODEC_NODE_CLS_XXX - node classification flags. + * @{ */ +#define CODEC_NODE_CLS_Port UINT16_C(0x0001) +#define CODEC_NODE_CLS_Dac UINT16_C(0x0002) +#define CODEC_NODE_CLS_AdcVol UINT16_C(0x0004) +#define CODEC_NODE_CLS_Adc UINT16_C(0x0008) +#define CODEC_NODE_CLS_AdcMux UINT16_C(0x0010) +#define CODEC_NODE_CLS_Pcbeep UINT16_C(0x0020) +#define CODEC_NODE_CLS_SpdifIn UINT16_C(0x0040) +#define CODEC_NODE_CLS_SpdifOut UINT16_C(0x0080) +#define CODEC_NODE_CLS_DigInPin UINT16_C(0x0100) +#define CODEC_NODE_CLS_DigOutPin UINT16_C(0x0200) +#define CODEC_NODE_CLS_Cd UINT16_C(0x0400) +#define CODEC_NODE_CLS_VolKnob UINT16_C(0x0800) +#define CODEC_NODE_CLS_Reserved UINT16_C(0x1000) +/** @} */ + +/** + * Codec configuration. + * + * This will not change after construction and is therefore kept in a const + * member of HDACODECR3 to encourage compiler optimizations and avoid accidental + * modification. + */ +typedef struct HDACODECCFG +{ + /** Codec implementation type. */ + CODECTYPE enmType; + /** Codec ID. */ + uint16_t id; + uint16_t idVendor; + uint16_t idDevice; + uint8_t bBSKU; + uint8_t idAssembly; + + uint8_t cTotalNodes; + uint8_t idxAdcVolsLineIn; + uint8_t idxDacLineOut; + + /** Align the lists below so they don't cross cache lines (assumes + * CODEC_NODES_MAX is 32). */ + uint8_t const abPadding1[CODEC_NODES_MAX - 15]; + + /** @name Node classifications. + * @note These are copies of the g_abStac9220Xxxx arrays in DevHdaCodec.cpp. + * They are used both for classifying a node and for processing a class of + * nodes. + * @{ */ + uint8_t abPorts[CODEC_NODES_MAX]; + uint8_t abDacs[CODEC_NODES_MAX]; + uint8_t abAdcVols[CODEC_NODES_MAX]; + uint8_t abAdcs[CODEC_NODES_MAX]; + uint8_t abAdcMuxs[CODEC_NODES_MAX]; + uint8_t abPcbeeps[CODEC_NODES_MAX]; + uint8_t abSpdifIns[CODEC_NODES_MAX]; + uint8_t abSpdifOuts[CODEC_NODES_MAX]; + uint8_t abDigInPins[CODEC_NODES_MAX]; + uint8_t abDigOutPins[CODEC_NODES_MAX]; + uint8_t abCds[CODEC_NODES_MAX]; + uint8_t abVolKnobs[CODEC_NODES_MAX]; + uint8_t abReserveds[CODEC_NODES_MAX]; + /** @} */ + + /** The CODEC_NODE_CLS_XXX flags for each node. */ + uint16_t afNodeClassifications[CODEC_NODES_MAX]; +} HDACODECCFG; +AssertCompileMemberAlignment(HDACODECCFG, abPorts, CODEC_NODES_MAX); +AssertCompileSizeAlignment(HDACODECCFG, 64); + + +/** + * HDA codec state (ring-3, no shared state). + */ +typedef struct HDACODECR3 +{ + /** The codec configuration - initialized at construction time. */ + HDACODECCFG const Cfg; + /** The state data for each node. */ + CODECNODE aNodes[CODEC_NODES_MAX]; + /** Statistics. */ + STAMCOUNTER StatLookupsR3; + /** Size alignment padding. */ + uint64_t const au64Padding1[7]; +} HDACODECR3; +AssertCompile(RT_IS_POWER_OF_TWO(CODEC_NODES_MAX)); +AssertCompileMemberAlignment(HDACODECR3, aNodes, 64); +AssertCompileSizeAlignment(HDACODECR3, 64); + + +/** @name HDA Codec API used by the device emulation. + * @{ */ +int hdaR3CodecConstruct(PPDMDEVINS pDevIns, PHDACODECR3 pThis, uint16_t uLUN, PCFGMNODE pCfg); +void hdaR3CodecPowerOff(PHDACODECR3 pThis); +int hdaR3CodecLoadState(PPDMDEVINS pDevIns, PHDACODECR3 pThis, PSSMHANDLE pSSM, uint32_t uVersion); +int hdaR3CodecAddStream(PHDACODECR3 pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg); +int hdaR3CodecRemoveStream(PHDACODECR3 pThis, PDMAUDIOMIXERCTL enmMixerCtl, bool fImmediate); + +int hdaCodecSaveState(PPDMDEVINS pDevIns, PHDACODECR3 pThis, PSSMHANDLE pSSM); +void hdaCodecDestruct(PHDACODECR3 pThis); +void hdaCodecReset(PHDACODECR3 pThis); + +DECLHIDDEN(int) hdaR3CodecLookup(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp); +DECLHIDDEN(void) hdaR3CodecDbgListNodes(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs); +DECLHIDDEN(void) hdaR3CodecDbgSelector(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs); +/** @} */ + +#endif /* !VBOX_INCLUDED_SRC_Audio_DevHdaCodec_h */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHDACommon.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHDACommon.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHDACommon.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHDACommon.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,731 +0,0 @@ -/* $Id: DevHDACommon.cpp $ */ -/** @file - * DevHDACommon.cpp - Shared HDA device functions. - * - * @todo r=bird: Shared with whom exactly? - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#include -#include - -#include - -#define LOG_GROUP LOG_GROUP_DEV_HDA -#include - -#include "DrvAudio.h" - -#include "DevHDA.h" -#include "DevHDACommon.h" - -#include "HDAStream.h" - - -/** - * Processes (de/asserts) the interrupt according to the HDA's current state. - * - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param pszSource Caller information. - */ -#if defined(LOG_ENABLED) || defined(DOXYGEN_RUNNING) -void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis, const char *pszSource) -#else -void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis) -#endif -{ - uint32_t uIntSts = hdaGetINTSTS(pThis); - - HDA_REG(pThis, INTSTS) = uIntSts; - - /* NB: It is possible to have GIS set even when CIE/SIEn are all zero; the GIS bit does - * not control the interrupt signal. See Figure 4 on page 54 of the HDA 1.0a spec. - */ - /* Global Interrupt Enable (GIE) set? */ - if ( (HDA_REG(pThis, INTCTL) & HDA_INTCTL_GIE) - && (HDA_REG(pThis, INTSTS) & HDA_REG(pThis, INTCTL) & (HDA_INTCTL_CIE | HDA_STRMINT_MASK))) - { - Log3Func(("Asserted (%s)\n", pszSource)); - - PDMDevHlpPCISetIrq(pDevIns, 0, 1 /* Assert */); - pThis->u8IRQL = 1; - -#ifdef DEBUG - pThis->Dbg.IRQ.tsAssertedNs = RTTimeNanoTS(); - pThis->Dbg.IRQ.tsProcessedLastNs = pThis->Dbg.IRQ.tsAssertedNs; -#endif - } - else - { - Log3Func(("Deasserted (%s)\n", pszSource)); - - PDMDevHlpPCISetIrq(pDevIns, 0, 0 /* Deassert */); - pThis->u8IRQL = 0; - } -} - -/** - * Retrieves the currently set value for the wall clock. - * - * @return IPRT status code. - * @return Currently set wall clock value. - * @param pThis The shared HDA device state. - * - * @remark Operation is atomic. - */ -uint64_t hdaWalClkGetCurrent(PHDASTATE pThis) -{ - return ASMAtomicReadU64(&pThis->u64WalClk); -} - -#ifdef IN_RING3 - -/** - * Helper for hdaR3WalClkSet. - */ -DECLINLINE(PHDASTREAMPERIOD) hdaR3SinkToStreamPeriod(PHDAMIXERSINK pSink) -{ - PHDASTREAM pStream = hdaR3GetSharedStreamFromSink(pSink); - if (pStream) - return &pStream->State.Period; - return NULL; -} - -/** - * Sets the actual WALCLK register to the specified wall clock value. - * The specified wall clock value only will be set (unless fForce is set to true) if all - * handled HDA streams have passed (in time) that value. This guarantees that the WALCLK - * register stays in sync with all handled HDA streams. - * - * @return true if the WALCLK register has been updated, false if not. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - * @param u64WalClk Wall clock value to set WALCLK register to. - * @param fForce Whether to force setting the wall clock value or not. - */ -bool hdaR3WalClkSet(PHDASTATE pThis, PHDASTATER3 pThisCC, uint64_t u64WalClk, bool fForce) -{ - const bool fFrontPassed = hdaR3StreamPeriodHasPassedAbsWalClk( hdaR3SinkToStreamPeriod(&pThisCC->SinkFront), u64WalClk); - const uint64_t u64FrontAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(hdaR3SinkToStreamPeriod(&pThisCC->SinkFront)); -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND -# error "Implement me!" -# endif - - const bool fLineInPassed = hdaR3StreamPeriodHasPassedAbsWalClk (hdaR3SinkToStreamPeriod(&pThisCC->SinkLineIn), u64WalClk); - const uint64_t u64LineInAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(hdaR3SinkToStreamPeriod(&pThisCC->SinkLineIn)); -# ifdef VBOX_WITH_HDA_MIC_IN - const bool fMicInPassed = hdaR3StreamPeriodHasPassedAbsWalClk (hdaR3SinkToStreamPeriod(&pThisCC->SinkMicIn), u64WalClk); - const uint64_t u64MicInAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(hdaR3SinkToStreamPeriod(&pThisCC->SinkMicIn)); -# endif - -# ifdef VBOX_STRICT - const uint64_t u64WalClkCur = ASMAtomicReadU64(&pThis->u64WalClk); -# endif - - /* Only drive the WALCLK register forward if all (active) stream periods have passed - * the specified point in time given by u64WalClk. */ - if ( ( fFrontPassed -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND -# error "Implement me!" -# endif - && fLineInPassed -# ifdef VBOX_WITH_HDA_MIC_IN - && fMicInPassed -# endif - ) - || fForce) - { - if (!fForce) - { - /* Get the maximum value of all periods we need to handle. - * Not the most elegant solution, but works for now ... */ - u64WalClk = RT_MAX(u64WalClk, u64FrontAbsWalClk); -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND -# error "Implement me!" -# endif - u64WalClk = RT_MAX(u64WalClk, u64LineInAbsWalClk); -# ifdef VBOX_WITH_HDA_MIC_IN - u64WalClk = RT_MAX(u64WalClk, u64MicInAbsWalClk); -# endif - -# ifdef VBOX_STRICT - AssertMsg(u64WalClk >= u64WalClkCur, - ("Setting WALCLK to a value going backwards does not make any sense (old %RU64 vs. new %RU64)\n", - u64WalClkCur, u64WalClk)); - if (u64WalClk == u64WalClkCur) /* Setting a stale value? */ - { - if (pThis->u8WalClkStaleCnt++ > 3) - AssertMsgFailed(("Setting WALCLK to a stale value (%RU64) too often isn't a good idea really. " - "Good luck with stuck audio stuff.\n", u64WalClk)); - } - else - pThis->u8WalClkStaleCnt = 0; -# endif - } - - /* Set the new WALCLK value. */ - ASMAtomicWriteU64(&pThis->u64WalClk, u64WalClk); - } - - const uint64_t u64WalClkNew = hdaWalClkGetCurrent(pThis); - - Log3Func(("Cur: %RU64, New: %RU64 (force %RTbool) -> %RU64 %s\n", - u64WalClkCur, u64WalClk, fForce, - u64WalClkNew, u64WalClkNew == u64WalClk ? "[OK]" : "[DELAYED]")); - - return (u64WalClkNew == u64WalClk); -} - -/** - * Returns the default (mixer) sink from a given SD#. - * Returns NULL if no sink is found. - * - * @return PHDAMIXERSINK - * @param pThisCC The ring-3 HDA device state. - * @param uSD SD# to return mixer sink for. - * NULL if not found / handled. - */ -PHDAMIXERSINK hdaR3GetDefaultSink(PHDASTATER3 pThisCC, uint8_t uSD) -{ - if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN) - { - const uint8_t uFirstSDI = 0; - - if (uSD == uFirstSDI) /* First SDI. */ - return &pThisCC->SinkLineIn; -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - if (uSD == uFirstSDI + 1) - return &pThisCC->SinkMicIn; -# else - /* If we don't have a dedicated Mic-In sink, use the always present Line-In sink. */ - return &pThisCC->SinkLineIn; -# endif - } - else - { - const uint8_t uFirstSDO = HDA_MAX_SDI; - - if (uSD == uFirstSDO) - return &pThisCC->SinkFront; -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - if (uSD == uFirstSDO + 1) - return &pThisCC->SinkCenterLFE; - if (uSD == uFirstSDO + 2) - return &pThisCC->SinkRear; -# endif - } - - return NULL; -} - -#endif /* IN_RING3 */ - -/** - * Returns the audio direction of a specified stream descriptor. - * - * The register layout specifies that input streams (SDI) come first, - * followed by the output streams (SDO). So every stream ID below HDA_MAX_SDI - * is an input stream, whereas everything >= HDA_MAX_SDI is an output stream. - * - * Note: SDnFMT register does not provide that information, so we have to judge - * for ourselves. - * - * @return Audio direction. - */ -PDMAUDIODIR hdaGetDirFromSD(uint8_t uSD) -{ - AssertReturn(uSD < HDA_MAX_STREAMS, PDMAUDIODIR_UNKNOWN); - - if (uSD < HDA_MAX_SDI) - return PDMAUDIODIR_IN; - - return PDMAUDIODIR_OUT; -} - -/** - * Returns the HDA stream of specified stream descriptor number. - * - * @return Pointer to HDA stream, or NULL if none found. - */ -PHDASTREAM hdaGetStreamFromSD(PHDASTATE pThis, uint8_t uSD) -{ - AssertPtr(pThis); - ASSERT_GUEST_MSG_RETURN(uSD < HDA_MAX_STREAMS, ("uSD=%u (%#x)\n", uSD, uSD), NULL); - return &pThis->aStreams[uSD]; -} - -#ifdef IN_RING3 - -/** - * Returns the HDA stream of specified HDA sink. - * - * @return Pointer to HDA stream, or NULL if none found. - */ -PHDASTREAMR3 hdaR3GetR3StreamFromSink(PHDAMIXERSINK pSink) -{ - AssertPtrReturn(pSink, NULL); - - /** @todo Do something with the channel mapping here? */ - return pSink->pStreamR3; -} - - -/** - * Returns the HDA stream of specified HDA sink. - * - * @return Pointer to HDA stream, or NULL if none found. - */ -PHDASTREAM hdaR3GetSharedStreamFromSink(PHDAMIXERSINK pSink) -{ - AssertPtrReturn(pSink, NULL); - - /** @todo Do something with the channel mapping here? */ - return pSink->pStreamShared; -} - -/* - * Reads DMA data from a given HDA output stream. - * - * @return IPRT status code. - * @param pDevIns The device instance. - * @param pThis The shared HDA device state (for stats). - * @param pStreamShared HDA output stream to read DMA data from - shared bits. - * @param pStreamR3 HDA output stream to read DMA data from - shared ring-3. - * @param pvBuf Where to store the read data. - * @param cbBuf How much to read in bytes. - * @param pcbRead Returns read bytes from DMA. Optional. - */ -int hdaR3DMARead(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, - void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) -{ - RT_NOREF(pThis); - PHDABDLE pBDLE = &pStreamShared->State.BDLE; - - int rc = VINF_SUCCESS; - - uint32_t cbReadTotal = 0; - uint32_t cbLeft = RT_MIN(cbBuf, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff); - -# ifdef HDA_DEBUG_SILENCE - uint64_t csSilence = 0; - - pStreamCC->Dbg.cSilenceThreshold = 100; - pStreamCC->Dbg.cbSilenceReadMin = _1M; -# endif - - RTGCPHYS GCPhysChunk = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff; - - while (cbLeft) - { - uint32_t cbChunk = RT_MIN(cbLeft, pStreamShared->u16FIFOS); - - rc = PDMDevHlpPhysRead(pDevIns, GCPhysChunk, (uint8_t *)pvBuf + cbReadTotal, cbChunk); - AssertRCBreak(rc); - -# ifdef HDA_DEBUG_SILENCE - uint16_t *pu16Buf = (uint16_t *)pvBuf; - for (size_t i = 0; i < cbChunk / sizeof(uint16_t); i++) - { - if (*pu16Buf == 0) - csSilence++; - else - break; - pu16Buf++; - } -# endif - if (RT_LIKELY(!pStreamR3->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, (uint8_t *)pvBuf + cbReadTotal, cbChunk, 0 /* fFlags */); - - STAM_COUNTER_ADD(&pThis->StatBytesRead, cbChunk); - - /* advance */ - Assert(cbLeft >= cbChunk); - GCPhysChunk = (GCPhysChunk + cbChunk) % pBDLE->Desc.u32BufSize; - cbReadTotal += cbChunk; - cbLeft -= cbChunk; - } - -# ifdef HDA_DEBUG_SILENCE - if (csSilence) - pStreamR3->Dbg.csSilence += csSilence; - - if ( csSilence == 0 - && pStreamR3->Dbg.csSilence > pStreamR3->Dbg.cSilenceThreshold - && pStreamR3->Dbg.cbReadTotal >= pStreamR3->Dbg.cbSilenceReadMin) - { - LogFunc(("Silent block detected: %RU64 audio samples\n", pStreamR3->Dbg.csSilence)); - pStreamR3->Dbg.csSilence = 0; - } -# endif - - if (RT_SUCCESS(rc)) - { - if (pcbRead) - *pcbRead = cbReadTotal; - } - - return rc; -} - -/** - * Writes audio data from an HDA input stream's FIFO to its associated DMA area. - * - * @return IPRT status code. - * @param pDevIns The device instance. - * @param pThis The shared HDA device state (for stats). - * @param pStreamShared HDA input stream to write audio data to - shared. - * @param pStreamR3 HDA input stream to write audio data to - ring-3. - * @param pvBuf Data to write. - * @param cbBuf How much (in bytes) to write. - * @param pcbWritten Returns written bytes on success. Optional. - */ -int hdaR3DMAWrite(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, - const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) -{ - RT_NOREF(pThis); - PHDABDLE pBDLE = &pStreamShared->State.BDLE; - int rc = VINF_SUCCESS; - uint32_t cbWrittenTotal = 0; - uint32_t cbLeft = RT_MIN(cbBuf, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff); - RTGCPHYS GCPhysChunk = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff; - while (cbLeft) - { - uint32_t cbChunk = RT_MIN(cbLeft, pStreamShared->u16FIFOS); - - /* Sanity checks. */ - Assert(cbChunk <= pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff); - - if (RT_LIKELY(!pStreamR3->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, 0 /* fFlags */); - - rc = PDMDevHlpPCIPhysWrite(pDevIns, GCPhysChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk); - AssertRCReturn(rc, rc); - - STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbChunk); - - /* advance */ - Assert(cbLeft >= cbChunk); - cbWrittenTotal += (uint32_t)cbChunk; - GCPhysChunk = (GCPhysChunk + cbChunk) % pBDLE->Desc.u32BufSize; - cbLeft -= (uint32_t)cbChunk; - } - - if (RT_SUCCESS(rc)) - { - if (pcbWritten) - *pcbWritten = cbWrittenTotal; - } - else - LogFunc(("Failed with %Rrc\n", rc)); - - return rc; -} - -#endif /* IN_RING3 */ - -/** - * Returns a new INTSTS value based on the current device state. - * - * @returns Determined INTSTS register value. - * @param pThis The shared HDA device state. - * - * @remark This function does *not* set INTSTS! - */ -uint32_t hdaGetINTSTS(PHDASTATE pThis) -{ - uint32_t intSts = 0; - - /* Check controller interrupts (RIRB, STATEST). */ - if (HDA_REG(pThis, RIRBSTS) & HDA_REG(pThis, RIRBCTL) & (HDA_RIRBCTL_ROIC | HDA_RIRBCTL_RINTCTL)) - { - intSts |= HDA_INTSTS_CIS; /* Set the Controller Interrupt Status (CIS). */ - } - - /* Check SDIN State Change Status Flags. */ - if (HDA_REG(pThis, STATESTS) & HDA_REG(pThis, WAKEEN)) - { - intSts |= HDA_INTSTS_CIS; /* Touch Controller Interrupt Status (CIS). */ - } - - /* For each stream, check if any interrupt status bit is set and enabled. */ - for (uint8_t iStrm = 0; iStrm < HDA_MAX_STREAMS; ++iStrm) - { - if (HDA_STREAM_REG(pThis, STS, iStrm) & HDA_STREAM_REG(pThis, CTL, iStrm) & (HDA_SDCTL_DEIE | HDA_SDCTL_FEIE | HDA_SDCTL_IOCE)) - { - Log3Func(("[SD%d] interrupt status set\n", iStrm)); - intSts |= RT_BIT(iStrm); - } - } - - if (intSts) - intSts |= HDA_INTSTS_GIS; /* Set the Global Interrupt Status (GIS). */ - - Log3Func(("-> 0x%x\n", intSts)); - - return intSts; -} - -#ifdef IN_RING3 - -/** - * Converts an HDA stream's SDFMT register into a given PCM properties structure. - * - * @return IPRT status code. - * @param u16SDFMT The HDA stream's SDFMT value to convert. - * @param pProps PCM properties structure to hold converted result on success. - */ -int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, VERR_INVALID_POINTER); - -# define EXTRACT_VALUE(v, mask, shift) ((v & ((mask) << (shift))) >> (shift)) - - int rc = VINF_SUCCESS; - - uint32_t u32Hz = EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BASE_RATE_MASK, HDA_SDFMT_BASE_RATE_SHIFT) - ? 44100 : 48000; - uint32_t u32HzMult = 1; - uint32_t u32HzDiv = 1; - - switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_MULT_MASK, HDA_SDFMT_MULT_SHIFT)) - { - case 0: u32HzMult = 1; break; - case 1: u32HzMult = 2; break; - case 2: u32HzMult = 3; break; - case 3: u32HzMult = 4; break; - default: - LogFunc(("Unsupported multiplier %x\n", - EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_MULT_MASK, HDA_SDFMT_MULT_SHIFT))); - rc = VERR_NOT_SUPPORTED; - break; - } - switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_DIV_MASK, HDA_SDFMT_DIV_SHIFT)) - { - case 0: u32HzDiv = 1; break; - case 1: u32HzDiv = 2; break; - case 2: u32HzDiv = 3; break; - case 3: u32HzDiv = 4; break; - case 4: u32HzDiv = 5; break; - case 5: u32HzDiv = 6; break; - case 6: u32HzDiv = 7; break; - case 7: u32HzDiv = 8; break; - default: - LogFunc(("Unsupported divisor %x\n", - EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_DIV_MASK, HDA_SDFMT_DIV_SHIFT))); - rc = VERR_NOT_SUPPORTED; - break; - } - - uint8_t cBytes = 0; - switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT)) - { - case 0: - cBytes = 1; - break; - case 1: - cBytes = 2; - break; - case 4: - cBytes = 4; - break; - default: - AssertMsgFailed(("Unsupported bits per sample %x\n", - EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT))); - rc = VERR_NOT_SUPPORTED; - break; - } - - if (RT_SUCCESS(rc)) - { - RT_BZERO(pProps, sizeof(PDMAUDIOPCMPROPS)); - - pProps->cbSample = cBytes; - pProps->fSigned = true; - pProps->cChannels = (u16SDFMT & 0xf) + 1; - pProps->uHz = u32Hz * u32HzMult / u32HzDiv; - pProps->cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cbSample, pProps->cChannels); - } - -# undef EXTRACT_VALUE - return rc; -} - -# ifdef LOG_ENABLED -void hdaR3BDLEDumpAll(PPDMDEVINS pDevIns, PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE) -{ - LogFlowFunc(("BDLEs @ 0x%x (%RU16):\n", u64BDLBase, cBDLE)); - if (!u64BDLBase) - return; - - uint32_t cbBDLE = 0; - for (uint16_t i = 0; i < cBDLE; i++) - { - HDABDLEDESC bd; - PDMDevHlpPhysRead(pDevIns, u64BDLBase + i * sizeof(HDABDLEDESC), &bd, sizeof(bd)); - - LogFunc(("\t#%03d BDLE(adr:0x%llx, size:%RU32, ioc:%RTbool)\n", - i, bd.u64BufAddr, bd.u32BufSize, bd.fFlags & HDA_BDLE_F_IOC)); - - cbBDLE += bd.u32BufSize; - } - - LogFlowFunc(("Total: %RU32 bytes\n", cbBDLE)); - - if (!pThis->u64DPBase) /* No DMA base given? Bail out. */ - return; - - LogFlowFunc(("DMA counters:\n")); - - for (int i = 0; i < cBDLE; i++) - { - uint32_t uDMACnt; - PDMDevHlpPhysRead(pDevIns, (pThis->u64DPBase & DPBASE_ADDR_MASK) + (i * 2 * sizeof(uint32_t)), - &uDMACnt, sizeof(uDMACnt)); - - LogFlowFunc(("\t#%03d DMA @ 0x%x\n", i , uDMACnt)); - } -} -# endif /* LOG_ENABLED */ - -/** - * Fetches a Bundle Descriptor List Entry (BDLE) from the DMA engine. - * - * @param pDevIns The device instance. - * @param pBDLE Where to store the fetched result. - * @param u64BaseDMA Address base of DMA engine to use. - * @param u16Entry BDLE entry to fetch. - */ -int hdaR3BDLEFetch(PPDMDEVINS pDevIns, PHDABDLE pBDLE, uint64_t u64BaseDMA, uint16_t u16Entry) -{ - AssertPtrReturn(pBDLE, VERR_INVALID_POINTER); - AssertReturn(u64BaseDMA, VERR_INVALID_PARAMETER); - - if (!u64BaseDMA) - { - LogRel2(("HDA: Unable to fetch BDLE #%RU16 - no base DMA address set (yet)\n", u16Entry)); - return VERR_NOT_FOUND; - } - /** @todo Compare u16Entry with LVI. */ - - int rc = PDMDevHlpPhysRead(pDevIns, u64BaseDMA + (u16Entry * sizeof(HDABDLEDESC)), - &pBDLE->Desc, sizeof(pBDLE->Desc)); - - if (RT_SUCCESS(rc)) - { - /* Reset internal state. */ - RT_ZERO(pBDLE->State); - pBDLE->State.u32BDLIndex = u16Entry; - } - - Log3Func(("Entry #%d @ 0x%x: %R[bdle], rc=%Rrc\n", u16Entry, u64BaseDMA + (u16Entry * sizeof(HDABDLEDESC)), pBDLE, rc)); - - - return VINF_SUCCESS; -} - -/** - * Tells whether a given BDLE is complete or not. - * - * @return true if BDLE is complete, false if not. - * @param pBDLE BDLE to retrieve status for. - */ -bool hdaR3BDLEIsComplete(PHDABDLE pBDLE) -{ - bool fIsComplete = false; - - if ( !pBDLE->Desc.u32BufSize /* There can be BDLEs with 0 size. */ - || (pBDLE->State.u32BufOff >= pBDLE->Desc.u32BufSize)) - { - Assert(pBDLE->State.u32BufOff == pBDLE->Desc.u32BufSize); - fIsComplete = true; - } - - Log3Func(("%R[bdle] => %s\n", pBDLE, fIsComplete ? "COMPLETE" : "INCOMPLETE")); - - return fIsComplete; -} - -/** - * Tells whether a given BDLE needs an interrupt or not. - * - * @return true if BDLE needs an interrupt, false if not. - * @param pBDLE BDLE to retrieve status for. - */ -bool hdaR3BDLENeedsInterrupt(PHDABDLE pBDLE) -{ - return (pBDLE->Desc.fFlags & HDA_BDLE_F_IOC); -} - -/** - * Sets the virtual device timer to a new expiration time. - * - * @returns Whether the new expiration time was set or not. - * @param pDevIns The device instance. - * @param pStreamShared HDA stream to set timer for (shared). - * @param tsExpire New (virtual) expiration time to set. - * @param fForce Whether to force setting the expiration time or not. - * @param tsNow The current clock timestamp if available, 0 if not. - * - * @remark This function takes all active HDA streams and their - * current timing into account. This is needed to make sure - * that all streams can match their needed timing. - * - * To achieve this, the earliest (lowest) timestamp of all - * active streams found will be used for the next scheduling slot. - * - * Forcing a new expiration time will override the above mechanism. - */ -bool hdaR3TimerSet(PPDMDEVINS pDevIns, PHDASTREAM pStreamShared, uint64_t tsExpire, bool fForce, uint64_t tsNow) -{ - AssertPtr(pStreamShared); - - if (!tsNow) - tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); - - if (!fForce) - { - /** @todo r=bird: hdaR3StreamTransferIsScheduled() also does a - * PDMDevHlpTimerGet(), so, some callers does one, this does, and then we do - * right afterwards == very inefficient! */ - if (hdaR3StreamTransferIsScheduled(pStreamShared, tsNow)) - { - uint64_t const tsNext = hdaR3StreamTransferGetNext(pStreamShared); - if (tsExpire > tsNext) - tsExpire = tsNext; - } - } - - /* - * Make sure to not go backwards in time, as this will assert in TMTimerSet(). - * This in theory could happen in hdaR3StreamTransferGetNext() from above. - */ - if (tsExpire < tsNow) - tsExpire = tsNow; - - int rc = PDMDevHlpTimerSet(pDevIns, pStreamShared->hTimer, tsExpire); - AssertRCReturn(rc, false); - - return true; -} - -#endif /* IN_RING3 */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHDACommon.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHDACommon.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHDACommon.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHDACommon.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,678 +0,0 @@ -/* $Id: DevHDACommon.h $ */ -/** @file - * DevHDACommon.h - Shared HDA device defines / functions. - */ - -/* - * Copyright (C) 2016-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_DevHDACommon_h -#define VBOX_INCLUDED_SRC_Audio_DevHDACommon_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include "AudioMixer.h" -#include /* LOG_ENABLED */ - -/** Pointer to an HDA stream (SDI / SDO). */ -typedef struct HDASTREAMR3 *PHDASTREAMR3; - - - -/** Read callback. */ -typedef VBOXSTRICTRC FNHDAREGREAD(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); -/** Write callback. */ -typedef VBOXSTRICTRC FNHDAREGWRITE(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); - -/** - * HDA register descriptor. - * - * See 302349 p 6.2. - */ -typedef struct HDAREGDESC -{ - /** Register offset in the register space. */ - uint32_t offset; - /** Size in bytes. Registers of size > 4 are in fact tables. */ - uint32_t size; - /** Readable bits. */ - uint32_t readable; - /** Writable bits. */ - uint32_t writable; - /** Register descriptor (RD) flags of type HDA_RD_F_XXX. These are used to - * specify the handling (read/write) policy of the register. */ - uint32_t fFlags; - /** Read callback. */ - FNHDAREGREAD *pfnRead; - /** Write callback. */ - FNHDAREGWRITE *pfnWrite; - /** Index into the register storage array. */ - uint32_t mem_idx; - /** Abbreviated name. */ - const char *abbrev; - /** Descripton. */ - const char *desc; -} HDAREGDESC; -/** Pointer to a a const HDA register descriptor. */ -typedef HDAREGDESC const *PCHDAREGDESC; - -/** - * HDA register aliases (HDA spec 3.3.45). - * @remarks Sorted by offReg. - */ -typedef struct HDAREGALIAS -{ - /** The alias register offset. */ - uint32_t offReg; - /** The register index. */ - int idxAlias; -} HDAREGALIAS; - -/** - * At the moment we support 4 input + 4 output streams max, which is 8 in total. - * Bidirectional streams are currently *not* supported. - * - * Note: When changing any of those values, be prepared for some saved state - * fixups / trouble! - */ -#define HDA_MAX_SDI 4 -#define HDA_MAX_SDO 4 -#define HDA_MAX_STREAMS (HDA_MAX_SDI + HDA_MAX_SDO) -AssertCompile(HDA_MAX_SDI <= HDA_MAX_SDO); - -/** Number of general registers. */ -#define HDA_NUM_GENERAL_REGS 34 -/** Number of total registers in the HDA's register map. */ -#define HDA_NUM_REGS (HDA_NUM_GENERAL_REGS + (HDA_MAX_STREAMS * 10 /* Each stream descriptor has 10 registers */)) -/** Total number of stream tags (channels). Index 0 is reserved / invalid. */ -#define HDA_MAX_TAGS 16 - -/** - * ICH6 datasheet defines limits for FIFOS registers (18.2.39). - * Formula: size - 1 - * Other values not listed are not supported. - */ -/** Maximum FIFO size (in bytes). */ -#define HDA_FIFO_MAX 256 - -/** Default timer frequency (in Hz). - * - * Lowering this value can ask for trouble, as backends then can run - * into data underruns. - * - * Note: For handling surround setups (e.g. 5.1 speaker setups) we need - * a higher Hz rate, as the device emulation otherwise will come into - * timing trouble, making the output (DMA reads) crackling. */ -#define HDA_TIMER_HZ_DEFAULT 100 - -/** Default position adjustment (in audio samples). - * - * For snd_hda_intel (Linux guests), the first BDL entry always is being used as - * so-called BDL adjustment, which can vary, and is being used for chipsets which - * misbehave and/or are incorrectly implemented. - * - * The BDL adjustment entry *always* has the IOC (Interrupt on Completion) bit set. - * - * For Intel Baytrail / Braswell implementations the BDL default adjustment is 32 frames, whereas - * for ICH / PCH it's only one (1) frame. - * - * See default_bdl_pos_adj() and snd_hdac_stream_setup_periods() for more information. - * - * By default we apply some simple heuristics in hdaStreamInit(). - */ -#define HDA_POS_ADJUST_DEFAULT 0 - -/** HDA's (fixed) audio frame size in bytes. - * We only support 16-bit stereo frames at the moment. */ -#define HDA_FRAME_SIZE_DEFAULT 4 - -/** Offset of the SD0 register map. */ -#define HDA_REG_DESC_SD0_BASE 0x80 - -/** Turn a short global register name into an memory index and a stringized name. */ -#define HDA_REG_IDX(abbrev) HDA_MEM_IND_NAME(abbrev), #abbrev - -/** Turns a short stream register name into an memory index and a stringized name. */ -#define HDA_REG_IDX_STRM(reg, suff) HDA_MEM_IND_NAME(reg ## suff), #reg #suff - -/** Same as above for a register *not* stored in memory. */ -#define HDA_REG_IDX_NOMEM(abbrev) 0, #abbrev - -extern const HDAREGDESC g_aHdaRegMap[HDA_NUM_REGS]; - -/** - * NB: Register values stored in memory (au32Regs[]) are indexed through - * the HDA_RMX_xxx macros (also HDA_MEM_IND_NAME()). On the other hand, the - * register descriptors in g_aHdaRegMap[] are indexed through the - * HDA_REG_xxx macros (also HDA_REG_IND_NAME()). - * - * The au32Regs[] layout is kept unchanged for saved state - * compatibility. - */ - -/* Registers */ -#define HDA_REG_IND_NAME(x) HDA_REG_##x -#define HDA_MEM_IND_NAME(x) HDA_RMX_##x -#define HDA_REG_IND(pThis, x) ((pThis)->au32Regs[g_aHdaRegMap[x].mem_idx]) -#define HDA_REG(pThis, x) (HDA_REG_IND((pThis), HDA_REG_IND_NAME(x))) - - -#define HDA_REG_GCAP 0 /* Range 0x00 - 0x01 */ -#define HDA_RMX_GCAP 0 -/** - * GCAP HDASpec 3.3.2 This macro encodes the following information about HDA in a compact manner: - * - * oss (15:12) - Number of output streams supported. - * iss (11:8) - Number of input streams supported. - * bss (7:3) - Number of bidirectional streams supported. - * bds (2:1) - Number of serial data out (SDO) signals supported. - * b64sup (0) - 64 bit addressing supported. - */ -#define HDA_MAKE_GCAP(oss, iss, bss, bds, b64sup) \ - ( (((oss) & 0xF) << 12) \ - | (((iss) & 0xF) << 8) \ - | (((bss) & 0x1F) << 3) \ - | (((bds) & 0x3) << 2) \ - | ((b64sup) & 1)) - -#define HDA_REG_VMIN 1 /* 0x02 */ -#define HDA_RMX_VMIN 1 - -#define HDA_REG_VMAJ 2 /* 0x03 */ -#define HDA_RMX_VMAJ 2 - -#define HDA_REG_OUTPAY 3 /* 0x04-0x05 */ -#define HDA_RMX_OUTPAY 3 - -#define HDA_REG_INPAY 4 /* 0x06-0x07 */ -#define HDA_RMX_INPAY 4 - -#define HDA_REG_GCTL 5 /* 0x08-0x0B */ -#define HDA_RMX_GCTL 5 -#define HDA_GCTL_UNSOL RT_BIT(8) /* Accept Unsolicited Response Enable */ -#define HDA_GCTL_FCNTRL RT_BIT(1) /* Flush Control */ -#define HDA_GCTL_CRST RT_BIT(0) /* Controller Reset */ - -#define HDA_REG_WAKEEN 6 /* 0x0C */ -#define HDA_RMX_WAKEEN 6 - -#define HDA_REG_STATESTS 7 /* 0x0E */ -#define HDA_RMX_STATESTS 7 -#define HDA_STATESTS_SCSF_MASK 0x7 /* State Change Status Flags (6.2.8). */ - -#define HDA_REG_GSTS 8 /* 0x10-0x11*/ -#define HDA_RMX_GSTS 8 -#define HDA_GSTS_FSTS RT_BIT(1) /* Flush Status */ - -#define HDA_REG_OUTSTRMPAY 9 /* 0x18 */ -#define HDA_RMX_OUTSTRMPAY 112 - -#define HDA_REG_INSTRMPAY 10 /* 0x1a */ -#define HDA_RMX_INSTRMPAY 113 - -#define HDA_REG_INTCTL 11 /* 0x20 */ -#define HDA_RMX_INTCTL 9 -#define HDA_INTCTL_GIE RT_BIT(31) /* Global Interrupt Enable */ -#define HDA_INTCTL_CIE RT_BIT(30) /* Controller Interrupt Enable */ -/** Bits 0-29 correspond to streams 0-29. */ -#define HDA_STRMINT_MASK 0xFF /* Streams 0-7 implemented. Applies to INTCTL and INTSTS. */ - -#define HDA_REG_INTSTS 12 /* 0x24 */ -#define HDA_RMX_INTSTS 10 -#define HDA_INTSTS_GIS RT_BIT(31) /* Global Interrupt Status */ -#define HDA_INTSTS_CIS RT_BIT(30) /* Controller Interrupt Status */ - -#define HDA_REG_WALCLK 13 /* 0x30 */ -/**NB: HDA_RMX_WALCLK is not defined because the register is not stored in memory. */ - -/** - * Note: The HDA specification defines a SSYNC register at offset 0x38. The - * ICH6/ICH9 datahseet defines SSYNC at offset 0x34. The Linux HDA driver matches - * the datasheet. - */ -#define HDA_REG_SSYNC 14 /* 0x34 */ -#define HDA_RMX_SSYNC 12 - -#define HDA_REG_CORBLBASE 15 /* 0x40 */ -#define HDA_RMX_CORBLBASE 13 - -#define HDA_REG_CORBUBASE 16 /* 0x44 */ -#define HDA_RMX_CORBUBASE 14 - -#define HDA_REG_CORBWP 17 /* 0x48 */ -#define HDA_RMX_CORBWP 15 - -#define HDA_REG_CORBRP 18 /* 0x4A */ -#define HDA_RMX_CORBRP 16 -#define HDA_CORBRP_RST RT_BIT(15) /* CORB Read Pointer Reset */ - -#define HDA_REG_CORBCTL 19 /* 0x4C */ -#define HDA_RMX_CORBCTL 17 -#define HDA_CORBCTL_DMA RT_BIT(1) /* Enable CORB DMA Engine */ -#define HDA_CORBCTL_CMEIE RT_BIT(0) /* CORB Memory Error Interrupt Enable */ - -#define HDA_REG_CORBSTS 20 /* 0x4D */ -#define HDA_RMX_CORBSTS 18 - -#define HDA_REG_CORBSIZE 21 /* 0x4E */ -#define HDA_RMX_CORBSIZE 19 -#define HDA_CORBSIZE_SZ_CAP 0xF0 -#define HDA_CORBSIZE_SZ 0x3 - -/** Number of CORB buffer entries. */ -#define HDA_CORB_SIZE 256 -/** CORB element size (in bytes). */ -#define HDA_CORB_ELEMENT_SIZE 4 -/** Number of RIRB buffer entries. */ -#define HDA_RIRB_SIZE 256 -/** RIRB element size (in bytes). */ -#define HDA_RIRB_ELEMENT_SIZE 8 - -#define HDA_REG_RIRBLBASE 22 /* 0x50 */ -#define HDA_RMX_RIRBLBASE 20 - -#define HDA_REG_RIRBUBASE 23 /* 0x54 */ -#define HDA_RMX_RIRBUBASE 21 - -#define HDA_REG_RIRBWP 24 /* 0x58 */ -#define HDA_RMX_RIRBWP 22 -#define HDA_RIRBWP_RST RT_BIT(15) /* RIRB Write Pointer Reset */ - -#define HDA_REG_RINTCNT 25 /* 0x5A */ -#define HDA_RMX_RINTCNT 23 - -/** Maximum number of Response Interrupts. */ -#define HDA_MAX_RINTCNT 256 - -#define HDA_REG_RIRBCTL 26 /* 0x5C */ -#define HDA_RMX_RIRBCTL 24 -#define HDA_RIRBCTL_ROIC RT_BIT(2) /* Response Overrun Interrupt Control */ -#define HDA_RIRBCTL_RDMAEN RT_BIT(1) /* RIRB DMA Enable */ -#define HDA_RIRBCTL_RINTCTL RT_BIT(0) /* Response Interrupt Control */ - -#define HDA_REG_RIRBSTS 27 /* 0x5D */ -#define HDA_RMX_RIRBSTS 25 -#define HDA_RIRBSTS_RIRBOIS RT_BIT(2) /* Response Overrun Interrupt Status */ -#define HDA_RIRBSTS_RINTFL RT_BIT(0) /* Response Interrupt Flag */ - -#define HDA_REG_RIRBSIZE 28 /* 0x5E */ -#define HDA_RMX_RIRBSIZE 26 - -#define HDA_REG_IC 29 /* 0x60 */ -#define HDA_RMX_IC 27 - -#define HDA_REG_IR 30 /* 0x64 */ -#define HDA_RMX_IR 28 - -#define HDA_REG_IRS 31 /* 0x68 */ -#define HDA_RMX_IRS 29 -#define HDA_IRS_IRV RT_BIT(1) /* Immediate Result Valid */ -#define HDA_IRS_ICB RT_BIT(0) /* Immediate Command Busy */ - -#define HDA_REG_DPLBASE 32 /* 0x70 */ -#define HDA_RMX_DPLBASE 30 - -#define HDA_REG_DPUBASE 33 /* 0x74 */ -#define HDA_RMX_DPUBASE 31 - -#define DPBASE_ADDR_MASK (~(uint64_t)0x7f) - -#define HDA_STREAM_REG_DEF(name, num) (HDA_REG_SD##num##name) -#define HDA_STREAM_RMX_DEF(name, num) (HDA_RMX_SD##num##name) -/** Note: sdnum here _MUST_ be stream reg number [0,7]. */ -#define HDA_STREAM_REG(pThis, name, sdnum) (HDA_REG_IND((pThis), HDA_REG_SD0##name + (sdnum) * 10)) - -#define HDA_SD_NUM_FROM_REG(pThis, func, reg) ((reg - HDA_STREAM_REG_DEF(func, 0)) / 10) - -/** @todo Condense marcos! */ - -#define HDA_REG_SD0CTL HDA_NUM_GENERAL_REGS /* 0x80; other streams offset by 0x20 */ -#define HDA_RMX_SD0CTL 32 -#define HDA_RMX_SD1CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 10) -#define HDA_RMX_SD2CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 20) -#define HDA_RMX_SD3CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 30) -#define HDA_RMX_SD4CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 40) -#define HDA_RMX_SD5CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 50) -#define HDA_RMX_SD6CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 60) -#define HDA_RMX_SD7CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 70) - -#define HDA_SDCTL_NUM_MASK 0xF -#define HDA_SDCTL_NUM_SHIFT 20 -#define HDA_SDCTL_DIR RT_BIT(19) /* Direction (Bidirectional streams only!) */ -#define HDA_SDCTL_TP RT_BIT(18) /* Traffic Priority (PCI Express) */ -#define HDA_SDCTL_STRIPE_MASK 0x3 -#define HDA_SDCTL_STRIPE_SHIFT 16 -#define HDA_SDCTL_DEIE RT_BIT(4) /* Descriptor Error Interrupt Enable */ -#define HDA_SDCTL_FEIE RT_BIT(3) /* FIFO Error Interrupt Enable */ -#define HDA_SDCTL_IOCE RT_BIT(2) /* Interrupt On Completion Enable */ -#define HDA_SDCTL_RUN RT_BIT(1) /* Stream Run */ -#define HDA_SDCTL_SRST RT_BIT(0) /* Stream Reset */ - -#define HDA_REG_SD0STS 35 /* 0x83; other streams offset by 0x20 */ -#define HDA_RMX_SD0STS 33 -#define HDA_RMX_SD1STS (HDA_STREAM_RMX_DEF(STS, 0) + 10) -#define HDA_RMX_SD2STS (HDA_STREAM_RMX_DEF(STS, 0) + 20) -#define HDA_RMX_SD3STS (HDA_STREAM_RMX_DEF(STS, 0) + 30) -#define HDA_RMX_SD4STS (HDA_STREAM_RMX_DEF(STS, 0) + 40) -#define HDA_RMX_SD5STS (HDA_STREAM_RMX_DEF(STS, 0) + 50) -#define HDA_RMX_SD6STS (HDA_STREAM_RMX_DEF(STS, 0) + 60) -#define HDA_RMX_SD7STS (HDA_STREAM_RMX_DEF(STS, 0) + 70) - -#define HDA_SDSTS_FIFORDY RT_BIT(5) /* FIFO Ready */ -#define HDA_SDSTS_DESE RT_BIT(4) /* Descriptor Error */ -#define HDA_SDSTS_FIFOE RT_BIT(3) /* FIFO Error */ -#define HDA_SDSTS_BCIS RT_BIT(2) /* Buffer Completion Interrupt Status */ - -#define HDA_REG_SD0LPIB 36 /* 0x84; other streams offset by 0x20 */ -#define HDA_REG_SD1LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 10) /* 0xA4 */ -#define HDA_REG_SD2LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 20) /* 0xC4 */ -#define HDA_REG_SD3LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 30) /* 0xE4 */ -#define HDA_REG_SD4LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 40) /* 0x104 */ -#define HDA_REG_SD5LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 50) /* 0x124 */ -#define HDA_REG_SD6LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 60) /* 0x144 */ -#define HDA_REG_SD7LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 70) /* 0x164 */ -#define HDA_RMX_SD0LPIB 34 -#define HDA_RMX_SD1LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 10) -#define HDA_RMX_SD2LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 20) -#define HDA_RMX_SD3LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 30) -#define HDA_RMX_SD4LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 40) -#define HDA_RMX_SD5LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 50) -#define HDA_RMX_SD6LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 60) -#define HDA_RMX_SD7LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 70) - -#define HDA_REG_SD0CBL 37 /* 0x88; other streams offset by 0x20 */ -#define HDA_RMX_SD0CBL 35 -#define HDA_RMX_SD1CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 10) -#define HDA_RMX_SD2CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 20) -#define HDA_RMX_SD3CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 30) -#define HDA_RMX_SD4CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 40) -#define HDA_RMX_SD5CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 50) -#define HDA_RMX_SD6CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 60) -#define HDA_RMX_SD7CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 70) - -#define HDA_REG_SD0LVI 38 /* 0x8C; other streams offset by 0x20 */ -#define HDA_RMX_SD0LVI 36 -#define HDA_RMX_SD1LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 10) -#define HDA_RMX_SD2LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 20) -#define HDA_RMX_SD3LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 30) -#define HDA_RMX_SD4LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 40) -#define HDA_RMX_SD5LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 50) -#define HDA_RMX_SD6LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 60) -#define HDA_RMX_SD7LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 70) - -#define HDA_REG_SD0FIFOW 39 /* 0x8E; other streams offset by 0x20 */ -#define HDA_RMX_SD0FIFOW 37 -#define HDA_RMX_SD1FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 10) -#define HDA_RMX_SD2FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 20) -#define HDA_RMX_SD3FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 30) -#define HDA_RMX_SD4FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 40) -#define HDA_RMX_SD5FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 50) -#define HDA_RMX_SD6FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 60) -#define HDA_RMX_SD7FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 70) - -/* - * ICH6 datasheet defined limits for FIFOW values (18.2.38). - */ -#define HDA_SDFIFOW_8B 0x2 -#define HDA_SDFIFOW_16B 0x3 -#define HDA_SDFIFOW_32B 0x4 - -#define HDA_REG_SD0FIFOS 40 /* 0x90; other streams offset by 0x20 */ -#define HDA_RMX_SD0FIFOS 38 -#define HDA_RMX_SD1FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 10) -#define HDA_RMX_SD2FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 20) -#define HDA_RMX_SD3FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 30) -#define HDA_RMX_SD4FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 40) -#define HDA_RMX_SD5FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 50) -#define HDA_RMX_SD6FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 60) -#define HDA_RMX_SD7FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 70) - -#define HDA_SDIFIFO_120B 0x77 /* 8-, 16-, 20-, 24-, 32-bit Input Streams */ -#define HDA_SDIFIFO_160B 0x9F /* 20-, 24-bit Input Streams Streams */ - -#define HDA_SDOFIFO_16B 0x0F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ -#define HDA_SDOFIFO_32B 0x1F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ -#define HDA_SDOFIFO_64B 0x3F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ -#define HDA_SDOFIFO_128B 0x7F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ -#define HDA_SDOFIFO_192B 0xBF /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ -#define HDA_SDOFIFO_256B 0xFF /* 20-, 24-bit Output Streams */ - -#define HDA_REG_SD0FMT 41 /* 0x92; other streams offset by 0x20 */ -#define HDA_RMX_SD0FMT 39 -#define HDA_RMX_SD1FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 10) -#define HDA_RMX_SD2FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 20) -#define HDA_RMX_SD3FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 30) -#define HDA_RMX_SD4FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 40) -#define HDA_RMX_SD5FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 50) -#define HDA_RMX_SD6FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 60) -#define HDA_RMX_SD7FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 70) - -#define HDA_REG_SD0BDPL 42 /* 0x98; other streams offset by 0x20 */ -#define HDA_RMX_SD0BDPL 40 -#define HDA_RMX_SD1BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 10) -#define HDA_RMX_SD2BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 20) -#define HDA_RMX_SD3BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 30) -#define HDA_RMX_SD4BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 40) -#define HDA_RMX_SD5BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 50) -#define HDA_RMX_SD6BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 60) -#define HDA_RMX_SD7BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 70) - -#define HDA_REG_SD0BDPU 43 /* 0x9C; other streams offset by 0x20 */ -#define HDA_RMX_SD0BDPU 41 -#define HDA_RMX_SD1BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 10) -#define HDA_RMX_SD2BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 20) -#define HDA_RMX_SD3BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 30) -#define HDA_RMX_SD4BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 40) -#define HDA_RMX_SD5BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 50) -#define HDA_RMX_SD6BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 60) -#define HDA_RMX_SD7BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 70) - -#define HDA_CODEC_CAD_SHIFT 28 -/** Encodes the (required) LUN into a codec command. */ -#define HDA_CODEC_CMD(cmd, lun) ((cmd) | (lun << HDA_CODEC_CAD_SHIFT)) - -#define HDA_SDFMT_NON_PCM_SHIFT 15 -#define HDA_SDFMT_NON_PCM_MASK 0x1 -#define HDA_SDFMT_BASE_RATE_SHIFT 14 -#define HDA_SDFMT_BASE_RATE_MASK 0x1 -#define HDA_SDFMT_MULT_SHIFT 11 -#define HDA_SDFMT_MULT_MASK 0x7 -#define HDA_SDFMT_DIV_SHIFT 8 -#define HDA_SDFMT_DIV_MASK 0x7 -#define HDA_SDFMT_BITS_SHIFT 4 -#define HDA_SDFMT_BITS_MASK 0x7 -#define HDA_SDFMT_CHANNELS_MASK 0xF - -#define HDA_SDFMT_TYPE RT_BIT(15) -#define HDA_SDFMT_TYPE_PCM (0) -#define HDA_SDFMT_TYPE_NON_PCM (1) - -#define HDA_SDFMT_BASE RT_BIT(14) -#define HDA_SDFMT_BASE_48KHZ (0) -#define HDA_SDFMT_BASE_44KHZ (1) - -#define HDA_SDFMT_MULT_1X (0) -#define HDA_SDFMT_MULT_2X (1) -#define HDA_SDFMT_MULT_3X (2) -#define HDA_SDFMT_MULT_4X (3) - -#define HDA_SDFMT_DIV_1X (0) -#define HDA_SDFMT_DIV_2X (1) -#define HDA_SDFMT_DIV_3X (2) -#define HDA_SDFMT_DIV_4X (3) -#define HDA_SDFMT_DIV_5X (4) -#define HDA_SDFMT_DIV_6X (5) -#define HDA_SDFMT_DIV_7X (6) -#define HDA_SDFMT_DIV_8X (7) - -#define HDA_SDFMT_8_BIT (0) -#define HDA_SDFMT_16_BIT (1) -#define HDA_SDFMT_20_BIT (2) -#define HDA_SDFMT_24_BIT (3) -#define HDA_SDFMT_32_BIT (4) - -#define HDA_SDFMT_CHAN_MONO (0) -#define HDA_SDFMT_CHAN_STEREO (1) - -/** Emits a SDnFMT register format. - * Also being used in the codec's converter format. */ -#define HDA_SDFMT_MAKE(_afNonPCM, _aBaseRate, _aMult, _aDiv, _aBits, _aChan) \ - ( (((_afNonPCM) & HDA_SDFMT_NON_PCM_MASK) << HDA_SDFMT_NON_PCM_SHIFT) \ - | (((_aBaseRate) & HDA_SDFMT_BASE_RATE_MASK) << HDA_SDFMT_BASE_RATE_SHIFT) \ - | (((_aMult) & HDA_SDFMT_MULT_MASK) << HDA_SDFMT_MULT_SHIFT) \ - | (((_aDiv) & HDA_SDFMT_DIV_MASK) << HDA_SDFMT_DIV_SHIFT) \ - | (((_aBits) & HDA_SDFMT_BITS_MASK) << HDA_SDFMT_BITS_SHIFT) \ - | ( (_aChan) & HDA_SDFMT_CHANNELS_MASK)) - -/** Interrupt on completion (IOC) flag. */ -#define HDA_BDLE_F_IOC RT_BIT(0) - - - -/** Pointer to a shared HDA state. */ -typedef struct HDASTATE *PHDASTATE; -/** Pointer to a HDA stream state. */ -typedef struct HDASTREAM *PHDASTREAM; -/** Pointer to a mixer sink. */ -typedef struct HDAMIXERSINK *PHDAMIXERSINK; - - -/** - * Internal state of a Buffer Descriptor List Entry (BDLE), - * needed to keep track of the data needed for the actual device - * emulation. - */ -typedef struct HDABDLESTATE -{ - /** Own index within the BDL (Buffer Descriptor List). */ - uint32_t u32BDLIndex; - /** Number of bytes below the stream's FIFO watermark (SDFIFOW). - * Used to check if we need fill up the FIFO again. */ - uint32_t cbBelowFIFOW; - /** Current offset in DMA buffer (in bytes).*/ - uint32_t u32BufOff; - uint32_t Padding; -} HDABDLESTATE, *PHDABDLESTATE; - -/** - * BDL description structure. - * Do not touch this, as this must match to the HDA specs. - */ -typedef struct HDABDLEDESC -{ - /** Starting address of the actual buffer. Must be 128-bit aligned. */ - uint64_t u64BufAddr; - /** Size of the actual buffer (in bytes). */ - uint32_t u32BufSize; - /** Bit 0: Interrupt on completion; the controller will generate - * an interrupt when the last byte of the buffer has been - * fetched by the DMA engine. - * - * Rest is reserved for further use and must be 0. */ - uint32_t fFlags; -} HDABDLEDESC, *PHDABDLEDESC; -AssertCompileSize(HDABDLEDESC, 16); /* Always 16 byte. Also must be aligned on 128-byte boundary. */ - -/** - * Buffer Descriptor List Entry (BDLE) (3.6.3). - */ -typedef struct HDABDLE -{ - /** The actual BDL description. */ - HDABDLEDESC Desc; - /** Internal state of this BDLE. - * Not part of the actual BDLE registers. */ - HDABDLESTATE State; -} HDABDLE; -AssertCompileSizeAlignment(HDABDLE, 8); -/** Pointer to a buffer descriptor list entry (BDLE). */ -typedef HDABDLE *PHDABDLE; - -/** @name Object lookup functions. - * @{ - */ -#ifdef IN_RING3 -PHDAMIXERSINK hdaR3GetDefaultSink(PHDASTATER3 pThisCC, uint8_t uSD); -#endif -PDMAUDIODIR hdaGetDirFromSD(uint8_t uSD); -//PHDASTREAM hdaGetStreamFromSD(PHDASTATER3 pThisCC, uint8_t uSD); -#ifdef IN_RING3 -PHDASTREAMR3 hdaR3GetR3StreamFromSink(PHDAMIXERSINK pSink); -PHDASTREAM hdaR3GetSharedStreamFromSink(PHDAMIXERSINK pSink); -#endif -/** @} */ - -/** @name Interrupt functions. - * @{ - */ -#if defined(LOG_ENABLED) || defined(DOXYGEN_RUNNING) -void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis, const char *pszSource); -# define HDA_PROCESS_INTERRUPT(a_pDevIns, a_pThis) hdaProcessInterrupt((a_pDevIns), (a_pThis), __FUNCTION__) -#else -void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis); -# define HDA_PROCESS_INTERRUPT(a_pDevIns, a_pThis) hdaProcessInterrupt((a_pDevIns), (a_pThis)) -#endif -/** @} */ - -/** @name Wall clock (WALCLK) functions. - * @{ - */ -uint64_t hdaWalClkGetCurrent(PHDASTATE pThis); -#ifdef IN_RING3 -bool hdaR3WalClkSet(PHDASTATE pThis, PHDASTATER3 pThisCC, uint64_t u64WalClk, bool fForce); -#endif -/** @} */ - -/** @name DMA utility functions. - * @{ - */ -#ifdef IN_RING3 -int hdaR3DMARead(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, - void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead); -int hdaR3DMAWrite(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, - const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten); -#endif -/** @} */ - -/** @name Register functions. - * @{ - */ -uint32_t hdaGetINTSTS(PHDASTATE pThis); -#ifdef IN_RING3 -int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps); -#endif /* IN_RING3 */ -/** @} */ - -/** @name BDLE (Buffer Descriptor List Entry) functions. - * @{ - */ -#ifdef IN_RING3 -# ifdef LOG_ENABLED -void hdaR3BDLEDumpAll(PPDMDEVINS pDevIns, PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE); -# endif -int hdaR3BDLEFetch(PPDMDEVINS pDevIns, PHDABDLE pBDLE, uint64_t u64BaseDMA, uint16_t u16Entry); -bool hdaR3BDLEIsComplete(PHDABDLE pBDLE); -bool hdaR3BDLENeedsInterrupt(PHDABDLE pBDLE); -#endif /* IN_RING3 */ -/** @} */ - -/** @name Device timer functions. - * @{ - */ -#ifdef IN_RING3 -bool hdaR3TimerSet(PPDMDEVINS pDevIns, PHDASTREAM pStreamShared, uint64_t u64Expire, bool fForce, uint64_t tsNow); -#endif -/** @} */ - -#endif /* !VBOX_INCLUDED_SRC_Audio_DevHDACommon_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHda.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHda.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHda.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHda.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,5411 @@ +/* $Id: DevHda.cpp $ */ +/** @file + * Intel HD Audio Controller Emulation. + * + * Implemented against the specifications found in "High Definition Audio + * Specification", Revision 1.0a June 17, 2010, and "Intel I/O Controller + * HUB 6 (ICH6) Family, Datasheet", document number 301473-002. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_HDA +#include + +#include +#include +#include +#ifdef HDA_DEBUG_GUEST_RIP +# include +#endif +#include +#include + +#include +#include +#include +#include +#include +# include +#ifdef IN_RING3 +# include +# include +# include +#endif + +#include "VBoxDD.h" + +#include "AudioMixBuffer.h" +#include "AudioMixer.h" + +#define VBOX_HDA_CAN_ACCESS_REG_MAP /* g_aHdaRegMap is accessible */ +#include "DevHda.h" + +#include "AudioHlp.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#if defined(VBOX_WITH_HP_HDA) +/* HP Pavilion dv4t-1300 */ +# define HDA_PCI_VENDOR_ID 0x103c +# define HDA_PCI_DEVICE_ID 0x30f7 +#elif defined(VBOX_WITH_INTEL_HDA) +/* Intel HDA controller */ +# define HDA_PCI_VENDOR_ID 0x8086 +# define HDA_PCI_DEVICE_ID 0x2668 +#elif defined(VBOX_WITH_NVIDIA_HDA) +/* nVidia HDA controller */ +# define HDA_PCI_VENDOR_ID 0x10de +# define HDA_PCI_DEVICE_ID 0x0ac0 +#else +# error "Please specify your HDA device vendor/device IDs" +#endif + +/** + * Acquires the HDA lock. + */ +#define DEVHDA_LOCK(a_pDevIns, a_pThis) \ + do { \ + int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \ + AssertRC(rcLock); \ + } while (0) + +/** + * Acquires the HDA lock or returns. + */ +#define DEVHDA_LOCK_RETURN(a_pDevIns, a_pThis, a_rcBusy) \ + do { \ + int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, a_rcBusy); \ + if (rcLock == VINF_SUCCESS) \ + { /* likely */ } \ + else \ + { \ + AssertRC(rcLock); \ + return rcLock; \ + } \ + } while (0) + +/** + * Acquires the HDA lock or returns. + */ +# define DEVHDA_LOCK_RETURN_VOID(a_pDevIns, a_pThis) \ + do { \ + int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \ + if (rcLock == VINF_SUCCESS) \ + { /* likely */ } \ + else \ + { \ + AssertRC(rcLock); \ + return; \ + } \ + } while (0) + +/** + * Releases the HDA lock. + */ +#define DEVHDA_UNLOCK(a_pDevIns, a_pThis) \ + do { PDMDevHlpCritSectLeave((a_pDevIns), &(a_pThis)->CritSect); } while (0) + +/** + * Acquires the TM lock and HDA lock, returns on failure. + */ +#define DEVHDA_LOCK_BOTH_RETURN(a_pDevIns, a_pThis, a_pStream, a_rcBusy) \ + do { \ + VBOXSTRICTRC rcLock = PDMDevHlpTimerLockClock2(pDevIns, (a_pStream)->hTimer, &(a_pThis)->CritSect, (a_rcBusy)); \ + if (RT_LIKELY(rcLock == VINF_SUCCESS)) \ + { /* likely */ } \ + else \ + return VBOXSTRICTRC_TODO(rcLock); \ + } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Structure defining a (host backend) driver stream. + * Each driver has its own instances of audio mixer streams, which then + * can go into the same (or even different) audio mixer sinks. + */ +typedef struct HDADRIVERSTREAM +{ + /** Associated mixer handle. */ + R3PTRTYPE(PAUDMIXSTREAM) pMixStrm; +} HDADRIVERSTREAM, *PHDADRIVERSTREAM; + +/** + * Struct for maintaining a host backend driver. + * This driver must be associated to one, and only one, + * HDA codec. The HDA controller does the actual multiplexing + * of HDA codec data to various host backend drivers then. + * + * This HDA device uses a timer in order to synchronize all + * read/write accesses across all attached LUNs / backends. + */ +typedef struct HDADRIVER +{ + /** Node for storing this driver in our device driver list of HDASTATE. */ + RTLISTNODER3 Node; + /** Pointer to shared HDA device state. */ + R3PTRTYPE(PHDASTATE) pHDAStateShared; + /** Pointer to the ring-3 HDA device state. */ + R3PTRTYPE(PHDASTATER3) pHDAStateR3; + /** LUN to which this driver has been assigned. */ + uint8_t uLUN; + /** Whether this driver is in an attached state or not. */ + bool fAttached; + uint8_t u32Padding0[6]; + /** Pointer to attached driver base interface. */ + R3PTRTYPE(PPDMIBASE) pDrvBase; + /** Audio connector interface to the underlying host backend. */ + R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector; + /** Mixer stream for line input. */ + HDADRIVERSTREAM LineIn; +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + /** Mixer stream for mic input. */ + HDADRIVERSTREAM MicIn; +#endif + /** Mixer stream for front output. */ + HDADRIVERSTREAM Front; +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + /** Mixer stream for center/LFE output. */ + HDADRIVERSTREAM CenterLFE; + /** Mixer stream for rear output. */ + HDADRIVERSTREAM Rear; +#endif + /** The LUN description. */ + char szDesc[48 - 2]; +} HDADRIVER; +/** The HDA host driver backend. */ +typedef struct HDADRIVER *PHDADRIVER; + + +/** Internal state of this BDLE. + * Not part of the actual BDLE registers. + * @note Only for saved state. */ +typedef struct HDABDLESTATELEGACY +{ + /** Own index within the BDL (Buffer Descriptor List). */ + uint32_t u32BDLIndex; + /** Number of bytes below the stream's FIFO watermark (SDFIFOW). + * Used to check if we need fill up the FIFO again. */ + uint32_t cbBelowFIFOW; + /** Current offset in DMA buffer (in bytes).*/ + uint32_t u32BufOff; + uint32_t Padding; +} HDABDLESTATELEGACY; + +/** + * BDLE and state. + * @note Only for saved state. + */ +typedef struct HDABDLELEGACY +{ + /** The actual BDL description. */ + HDABDLEDESC Desc; + HDABDLESTATELEGACY State; +} HDABDLELEGACY; +AssertCompileSize(HDABDLELEGACY, 32); + + +/** Read callback. */ +typedef VBOXSTRICTRC FNHDAREGREAD(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +/** Write callback. */ +typedef VBOXSTRICTRC FNHDAREGWRITE(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); + +/** + * HDA register descriptor. + */ +typedef struct HDAREGDESC +{ + /** Register offset in the register space. */ + uint32_t off; + /** Size in bytes. Registers of size > 4 are in fact tables. */ + uint8_t cb; + /** Register descriptor (RD) flags of type HDA_RD_F_XXX. These are used to + * specify the read/write handling policy of the register. */ + uint8_t fFlags; + /** Index into the register storage array (HDASTATE::au32Regs). */ + uint8_t idxReg; + uint8_t bUnused; + /** Readable bits. */ + uint32_t fReadableMask; + /** Writable bits. */ + uint32_t fWritableMask; + /** Read callback. */ + FNHDAREGREAD *pfnRead; + /** Write callback. */ + FNHDAREGWRITE *pfnWrite; +#if defined(IN_RING3) || defined(LOG_ENABLED) /* Saves 0x2f23 - 0x1888 = 0x169B (5787) bytes in VBoxDDR0. */ + /** Abbreviated name. */ + const char *pszName; +# ifdef IN_RING3 + /** Description (for stats). */ + const char *pszDesc; +# endif +#endif +} HDAREGDESC; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +#ifdef IN_RING3 +static void hdaR3GCTLReset(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC); +#endif + +/** @name Register read/write stubs. + * @{ + */ +static FNHDAREGREAD hdaRegReadUnimpl; +static FNHDAREGWRITE hdaRegWriteUnimpl; +/** @} */ + +/** @name Global register set read/write functions. + * @{ + */ +static FNHDAREGWRITE hdaRegWriteGCTL; +static FNHDAREGREAD hdaRegReadLPIB; +static FNHDAREGREAD hdaRegReadWALCLK; +static FNHDAREGWRITE hdaRegWriteSSYNC; +static FNHDAREGWRITE hdaRegWriteNewSSYNC; +static FNHDAREGWRITE hdaRegWriteCORBWP; +static FNHDAREGWRITE hdaRegWriteCORBRP; +static FNHDAREGWRITE hdaRegWriteCORBCTL; +static FNHDAREGWRITE hdaRegWriteCORBSIZE; +static FNHDAREGWRITE hdaRegWriteCORBSTS; +static FNHDAREGWRITE hdaRegWriteRINTCNT; +static FNHDAREGWRITE hdaRegWriteRIRBWP; +static FNHDAREGWRITE hdaRegWriteRIRBSTS; +static FNHDAREGWRITE hdaRegWriteSTATESTS; +static FNHDAREGWRITE hdaRegWriteIRS; +static FNHDAREGREAD hdaRegReadIRS; +static FNHDAREGWRITE hdaRegWriteBase; +/** @} */ + +/** @name {IOB}SDn read/write functions. + * @{ + */ +static FNHDAREGWRITE hdaRegWriteSDCBL; +static FNHDAREGWRITE hdaRegWriteSDCTL; +static FNHDAREGWRITE hdaRegWriteSDSTS; +static FNHDAREGWRITE hdaRegWriteSDLVI; +static FNHDAREGWRITE hdaRegWriteSDFIFOW; +static FNHDAREGWRITE hdaRegWriteSDFIFOS; +static FNHDAREGWRITE hdaRegWriteSDFMT; +static FNHDAREGWRITE hdaRegWriteSDBDPL; +static FNHDAREGWRITE hdaRegWriteSDBDPU; +static FNHDAREGREAD hdaRegReadSDnPIB; +static FNHDAREGREAD hdaRegReadSDnEFIFOS; +/** @} */ + +/** @name Generic register read/write functions. + * @{ + */ +static FNHDAREGREAD hdaRegReadU32; +static FNHDAREGWRITE hdaRegWriteU32; +static FNHDAREGREAD hdaRegReadU24; +#ifdef IN_RING3 +static FNHDAREGWRITE hdaRegWriteU24; +#endif +static FNHDAREGREAD hdaRegReadU16; +static FNHDAREGWRITE hdaRegWriteU16; +static FNHDAREGREAD hdaRegReadU8; +static FNHDAREGWRITE hdaRegWriteU8; +/** @} */ + +/** @name HDA device functions. + * @{ + */ +#ifdef IN_RING3 +static int hdaR3AddStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg); +static int hdaR3RemoveStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg); +#endif /* IN_RING3 */ +/** @} */ + +/** @name HDA mixer functions. + * @{ + */ +#ifdef IN_RING3 +static int hdaR3MixerAddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv); +#endif +/** @} */ + +#ifdef IN_RING3 +static FNSSMFIELDGETPUT hdaR3GetPutTrans_HDABDLEDESC_fFlags_6; +static FNSSMFIELDGETPUT hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4; +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** No register description (RD) flags defined. */ +#define HDA_RD_F_NONE 0 +/** Writes to SD are allowed while RUN bit is set. */ +#define HDA_RD_F_SD_WRITE_RUN RT_BIT(0) + +/** @def HDA_REG_ENTRY_EX + * Maps the entry values to the actual HDAREGDESC layout, which is differs + * depending on context and build type. */ +#if defined(IN_RING3) || defined(LOG_ENABLED) +# ifdef IN_RING3 +# define HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_idxMap, a_szName, a_szDesc) \ + { a_offBar, a_cbReg, a_fFlags, a_idxMap, 0, a_fReadMask, a_fWriteMask, a_pfnRead, a_pfnWrite, a_szName, a_szDesc } +# else +# define HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_idxMap, a_szName, a_szDesc) \ + { a_offBar, a_cbReg, a_fFlags, a_idxMap, 0, a_fReadMask, a_fWriteMask, a_pfnRead, a_pfnWrite, a_szName } +# endif +#else +# define HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_idxMap, a_szName, a_szDesc) \ + { a_offBar, a_cbReg, a_fFlags, a_idxMap, 0, a_fReadMask, a_fWriteMask, a_pfnRead, a_pfnWrite } +#endif + +#define HDA_REG_ENTRY(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_ShortRegNm, a_szDesc) \ + HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, HDA_MEM_IND_NAME(a_ShortRegNm), #a_ShortRegNm, a_szDesc) +#define HDA_REG_ENTRY_STR(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_StrPrefix, a_ShortRegNm, a_szDesc) \ + HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, HDA_MEM_IND_NAME(a_StrPrefix ## a_ShortRegNm), #a_StrPrefix #a_ShortRegNm, #a_StrPrefix ": " a_szDesc) + +/** Emits a single audio stream register set (e.g. OSD0) at a specified offset. */ +#define HDA_REG_MAP_STRM(offset, name) \ + /* offset size read mask write mask flags read callback write callback index, abbrev, description */ \ + /* ------- ------- ---------- ---------- ---------------------- -------------- ----------------- ----------------------------- ----------- */ \ + /* Offset 0x80 (SD0) */ \ + HDA_REG_ENTRY_STR(offset, 0x00003, 0x00FF001F, 0x00F0001F, HDA_RD_F_SD_WRITE_RUN, hdaRegReadU24 , hdaRegWriteSDCTL , name, CTL , "Stream Descriptor Control"), \ + /* Offset 0x83 (SD0) */ \ + HDA_REG_ENTRY_STR(offset + 0x3, 0x00001, 0x0000003C, 0x0000001C, HDA_RD_F_SD_WRITE_RUN, hdaRegReadU8 , hdaRegWriteSDSTS , name, STS , "Status" ), \ + /* Offset 0x84 (SD0) */ \ + HDA_REG_ENTRY_STR(offset + 0x4, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadLPIB, hdaRegWriteU32 , name, LPIB , "Link Position In Buffer" ), \ + /* Offset 0x88 (SD0) */ \ + HDA_REG_ENTRY_STR(offset + 0x8, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDCBL , name, CBL , "Cyclic Buffer Length" ), \ + /* Offset 0x8C (SD0) -- upper 8 bits are reserved */ \ + HDA_REG_ENTRY_STR(offset + 0xC, 0x00002, 0x0000FFFF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDLVI , name, LVI , "Last Valid Index" ), \ + /* Reserved: FIFO Watermark. ** @todo Document this! */ \ + HDA_REG_ENTRY_STR(offset + 0xE, 0x00002, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOW, name, FIFOW, "FIFO Watermark" ), \ + /* Offset 0x90 (SD0) */ \ + HDA_REG_ENTRY_STR(offset + 0x10, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOS, name, FIFOS, "FIFO Size" ), \ + /* Offset 0x92 (SD0) */ \ + HDA_REG_ENTRY_STR(offset + 0x12, 0x00002, 0x00007F7F, 0x00007F7F, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFMT , name, FMT , "Stream Format" ), \ + /* Reserved: 0x94 - 0x98. */ \ + /* Offset 0x98 (SD0) */ \ + HDA_REG_ENTRY_STR(offset + 0x18, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDBDPL , name, BDPL , "Buffer Descriptor List Pointer-Lower Base Address" ), \ + /* Offset 0x9C (SD0) */ \ + HDA_REG_ENTRY_STR(offset + 0x1C, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDBDPU , name, BDPU , "Buffer Descriptor List Pointer-Upper Base Address" ) + +/** Defines a single audio stream register set (e.g. OSD0). */ +#define HDA_REG_MAP_DEF_STREAM(index, name) \ + HDA_REG_MAP_STRM(HDA_REG_DESC_SD0_BASE + (index * 32 /* 0x20 */), name) + +/** Skylake stream registers. */ +#define HDA_REG_MAP_SKYLAKE_STRM(a_off, a_StrPrefix) \ + /* offset size read mask write mask flags read callback write callback index, abbrev, description */ \ + /* ------- ------- ---------- ---------- -------------- -------------- ----------------- ----------------------------- ----------- */ \ + /* 0x1084 */ \ + HDA_REG_ENTRY_STR(a_off + 0x04, 0x00004, 0xffffffff, 0x00000000, HDA_RD_F_NONE, hdaRegReadSDnPIB, hdaRegWriteUnimpl, a_StrPrefix, DPIB, "DMA Position In Buffer" ), \ + /* 0x1094 */ \ + HDA_REG_ENTRY_STR(a_off + 0x14, 0x00004, 0xffffffff, 0x00000000, HDA_RD_F_NONE, hdaRegReadSDnEFIFOS, hdaRegWriteUnimpl, a_StrPrefix, EFIFOS, "Extended FIFO Size" ) + + +/** See 302349 p 6.2. */ +static const HDAREGDESC g_aHdaRegMap[HDA_NUM_REGS] = +{ + /* offset size read mask write mask flags read callback write callback index + abbrev */ + /*------- ------- ---------- ---------- -------------- ---------------- ------------------- ------------------------ */ + HDA_REG_ENTRY(0x00000, 0x00002, 0x0000FFFB, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , GCAP, "Global Capabilities" ), + HDA_REG_ENTRY(0x00002, 0x00001, 0x000000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , VMIN, "Minor Version" ), + HDA_REG_ENTRY(0x00003, 0x00001, 0x000000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , VMAJ, "Major Version" ), + HDA_REG_ENTRY(0x00004, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , OUTPAY, "Output Payload Capabilities" ), + HDA_REG_ENTRY(0x00006, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , INPAY, "Input Payload Capabilities" ), + HDA_REG_ENTRY(0x00008, 0x00004, 0x00000103, 0x00000103, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteGCTL , GCTL, "Global Control" ), + HDA_REG_ENTRY(0x0000c, 0x00002, 0x00007FFF, 0x00007FFF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , WAKEEN, "Wake Enable" ), + HDA_REG_ENTRY(0x0000e, 0x00002, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteSTATESTS, STATESTS, "State Change Status" ), + HDA_REG_ENTRY(0x00010, 0x00002, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadUnimpl, hdaRegWriteUnimpl , GSTS, "Global Status" ), + HDA_REG_ENTRY(0x00014, 0x00002, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , LLCH, "Linked List Capabilities Header" ), + HDA_REG_ENTRY(0x00018, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , OUTSTRMPAY, "Output Stream Payload Capability" ), + HDA_REG_ENTRY(0x0001A, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , INSTRMPAY, "Input Stream Payload Capability" ), + HDA_REG_ENTRY(0x00020, 0x00004, 0xC00000FF, 0xC00000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , INTCTL, "Interrupt Control" ), + HDA_REG_ENTRY(0x00024, 0x00004, 0xC00000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , INTSTS, "Interrupt Status" ), + HDA_REG_ENTRY_EX(0x00030, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadWALCLK, hdaRegWriteUnimpl , 0, "WALCLK", "Wall Clock Counter" ), + HDA_REG_ENTRY(0x00034, 0x00004, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSSYNC , SSYNC, "Stream Synchronization (old)" ), + HDA_REG_ENTRY(0x00038, 0x00004, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteNewSSYNC, SSYNC, "Stream Synchronization (new)" ), + HDA_REG_ENTRY(0x00040, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , CORBLBASE, "CORB Lower Base Address" ), + HDA_REG_ENTRY(0x00044, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , CORBUBASE, "CORB Upper Base Address" ), + HDA_REG_ENTRY(0x00048, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteCORBWP , CORBWP, "CORB Write Pointer" ), + HDA_REG_ENTRY(0x0004A, 0x00002, 0x000080FF, 0x00008000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteCORBRP , CORBRP, "CORB Read Pointer" ), + HDA_REG_ENTRY(0x0004C, 0x00001, 0x00000003, 0x00000003, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBCTL , CORBCTL, "CORB Control" ), + HDA_REG_ENTRY(0x0004D, 0x00001, 0x00000001, 0x00000001, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBSTS , CORBSTS, "CORB Status" ), + HDA_REG_ENTRY(0x0004E, 0x00001, 0x000000F3, 0x00000003, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBSIZE, CORBSIZE, "CORB Size" ), + HDA_REG_ENTRY(0x00050, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , RIRBLBASE, "RIRB Lower Base Address" ), + HDA_REG_ENTRY(0x00054, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , RIRBUBASE, "RIRB Upper Base Address" ), + HDA_REG_ENTRY(0x00058, 0x00002, 0x000000FF, 0x00008000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteRIRBWP , RIRBWP, "RIRB Write Pointer" ), + HDA_REG_ENTRY(0x0005A, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteRINTCNT , RINTCNT, "Response Interrupt Count" ), + HDA_REG_ENTRY(0x0005C, 0x00001, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteU8 , RIRBCTL, "RIRB Control" ), + HDA_REG_ENTRY(0x0005D, 0x00001, 0x00000005, 0x00000005, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteRIRBSTS , RIRBSTS, "RIRB Status" ), + HDA_REG_ENTRY(0x0005E, 0x00001, 0x000000F3, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , RIRBSIZE, "RIRB Size" ), + HDA_REG_ENTRY(0x00060, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , IC, "Immediate Command" ), + HDA_REG_ENTRY(0x00064, 0x00004, 0x00000000, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , IR, "Immediate Response" ), + HDA_REG_ENTRY(0x00068, 0x00002, 0x00000002, 0x00000002, HDA_RD_F_NONE, hdaRegReadIRS , hdaRegWriteIRS , IRS, "Immediate Command Status" ), + HDA_REG_ENTRY(0x00070, 0x00004, 0xFFFFFFFF, 0xFFFFFF81, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , DPLBASE, "DMA Position Lower Base" ), + HDA_REG_ENTRY(0x00074, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , DPUBASE, "DMA Position Upper Base" ), + /* 4 Serial Data In (SDI). */ + HDA_REG_MAP_DEF_STREAM(0, SD0), + HDA_REG_MAP_DEF_STREAM(1, SD1), + HDA_REG_MAP_DEF_STREAM(2, SD2), + HDA_REG_MAP_DEF_STREAM(3, SD3), + /* 4 Serial Data Out (SDO). */ + HDA_REG_MAP_DEF_STREAM(4, SD4), + HDA_REG_MAP_DEF_STREAM(5, SD5), + HDA_REG_MAP_DEF_STREAM(6, SD6), + HDA_REG_MAP_DEF_STREAM(7, SD7), + HDA_REG_ENTRY(0x00c00, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , MLCH, "Multiple Links Capability Header" ), + HDA_REG_ENTRY(0x00c04, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , MLCD, "Multiple Links Capability Declaration" ), + HDA_REG_MAP_SKYLAKE_STRM(0x01080, SD0), + HDA_REG_MAP_SKYLAKE_STRM(0x010a0, SD1), + HDA_REG_MAP_SKYLAKE_STRM(0x010c0, SD2), + HDA_REG_MAP_SKYLAKE_STRM(0x010e0, SD3), + HDA_REG_MAP_SKYLAKE_STRM(0x01100, SD4), + HDA_REG_MAP_SKYLAKE_STRM(0x01120, SD5), + HDA_REG_MAP_SKYLAKE_STRM(0x01140, SD6), + HDA_REG_MAP_SKYLAKE_STRM(0x01160, SD7), +}; + +#undef HDA_REG_ENTRY_EX +#undef HDA_REG_ENTRY +#undef HDA_REG_ENTRY_STR +#undef HDA_REG_MAP_STRM +#undef HDA_REG_MAP_DEF_STREAM + +/** + * HDA register aliases (HDA spec 3.3.45). + * @remarks Sorted by offReg. + * @remarks Lookup code ASSUMES this starts somewhere after g_aHdaRegMap ends. + */ +static struct HDAREGALIAS +{ + /** The alias register offset. */ + uint32_t offReg; + /** The register index. */ + int idxAlias; +} const g_aHdaRegAliases[] = +{ + { 0x2030, HDA_REG_WALCLK }, + { 0x2084, HDA_REG_SD0LPIB }, + { 0x20a4, HDA_REG_SD1LPIB }, + { 0x20c4, HDA_REG_SD2LPIB }, + { 0x20e4, HDA_REG_SD3LPIB }, + { 0x2104, HDA_REG_SD4LPIB }, + { 0x2124, HDA_REG_SD5LPIB }, + { 0x2144, HDA_REG_SD6LPIB }, + { 0x2164, HDA_REG_SD7LPIB } +}; + +#ifdef IN_RING3 + +/** HDABDLEDESC field descriptors for the v7+ saved state. */ +static SSMFIELD const g_aSSMBDLEDescFields7[] = +{ + SSMFIELD_ENTRY(HDABDLEDESC, u64BufAddr), + SSMFIELD_ENTRY(HDABDLEDESC, u32BufSize), + SSMFIELD_ENTRY(HDABDLEDESC, fFlags), + SSMFIELD_ENTRY_TERM() +}; + +/** HDABDLEDESC field descriptors for the v6 saved states. */ +static SSMFIELD const g_aSSMBDLEDescFields6[] = +{ + SSMFIELD_ENTRY(HDABDLEDESC, u64BufAddr), + SSMFIELD_ENTRY(HDABDLEDESC, u32BufSize), + SSMFIELD_ENTRY_CALLBACK(HDABDLEDESC, fFlags, hdaR3GetPutTrans_HDABDLEDESC_fFlags_6), + SSMFIELD_ENTRY_TERM() +}; + +/** HDABDLESTATE field descriptors for the v6 saved state. */ +static SSMFIELD const g_aSSMBDLEStateFields6[] = +{ + SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BDLIndex), + SSMFIELD_ENTRY(HDABDLESTATELEGACY, cbBelowFIFOW), + SSMFIELD_ENTRY_OLD(FIFO, 256), /* Deprecated; now is handled in the stream's circular buffer. */ + SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BufOff), + SSMFIELD_ENTRY_TERM() +}; + +/** HDABDLESTATE field descriptors for the v7+ saved state. */ +static SSMFIELD const g_aSSMBDLEStateFields7[] = +{ + SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BDLIndex), + SSMFIELD_ENTRY(HDABDLESTATELEGACY, cbBelowFIFOW), + SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BufOff), + SSMFIELD_ENTRY_TERM() +}; + +/** HDASTREAMSTATE field descriptors for the v6 saved state. */ +static SSMFIELD const g_aSSMStreamStateFields6[] = +{ + SSMFIELD_ENTRY_OLD(cBDLE, sizeof(uint16_t)), /* Deprecated. */ + SSMFIELD_ENTRY_OLD(uCurBDLE, sizeof(uint16_t)), /* We figure it out from LPID */ + SSMFIELD_ENTRY_OLD(fStop, 1), /* Deprecated; see SSMR3PutBool(). */ + SSMFIELD_ENTRY_OLD(fRunning, 1), /* Deprecated; using the HDA_SDCTL_RUN bit is sufficient. */ + SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset), + SSMFIELD_ENTRY_TERM() +}; + +/** HDASTREAMSTATE field descriptors for the v7+ saved state. */ +static SSMFIELD const g_aSSMStreamStateFields7[] = +{ + SSMFIELD_ENTRY(HDASTREAMSTATE, idxCurBdle), /* For backward compatibility we save this. We use LPIB on restore. */ + SSMFIELD_ENTRY_OLD(uCurBDLEHi, sizeof(uint8_t)), /* uCurBDLE was 16-bit for some reason, so store/ignore the zero top byte. */ + SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset), + SSMFIELD_ENTRY(HDASTREAMSTATE, tsTransferNext), + SSMFIELD_ENTRY_TERM() +}; + +/** HDABDLE field descriptors for the v1 thru v4 saved states. */ +static SSMFIELD const g_aSSMStreamBdleFields1234[] = +{ + SSMFIELD_ENTRY(HDABDLELEGACY, Desc.u64BufAddr), /* u64BdleCviAddr */ + SSMFIELD_ENTRY_OLD(u32BdleMaxCvi, sizeof(uint32_t)), /* u32BdleMaxCvi */ + SSMFIELD_ENTRY(HDABDLELEGACY, State.u32BDLIndex), /* u32BdleCvi */ + SSMFIELD_ENTRY(HDABDLELEGACY, Desc.u32BufSize), /* u32BdleCviLen */ + SSMFIELD_ENTRY(HDABDLELEGACY, State.u32BufOff), /* u32BdleCviPos */ + SSMFIELD_ENTRY_CALLBACK(HDABDLELEGACY, Desc.fFlags, hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4), /* fBdleCviIoc */ + SSMFIELD_ENTRY(HDABDLELEGACY, State.cbBelowFIFOW), /* cbUnderFifoW */ + SSMFIELD_ENTRY_OLD(au8FIFO, 256), /* au8FIFO */ + SSMFIELD_ENTRY_TERM() +}; + +#endif /* IN_RING3 */ + +/** + * 32-bit size indexed masks, i.e. g_afMasks[2 bytes] = 0xffff. + */ +static uint32_t const g_afMasks[5] = +{ + UINT32_C(0), UINT32_C(0x000000ff), UINT32_C(0x0000ffff), UINT32_C(0x00ffffff), UINT32_C(0xffffffff) +}; + + +#ifdef VBOX_STRICT + +/** + * Strict register accessor verifing defines and mapping table. + * @see HDA_REG + */ +DECLINLINE(uint32_t *) hdaStrictRegAccessor(PHDASTATE pThis, uint32_t idxMap, uint32_t idxReg) +{ + Assert(idxMap < RT_ELEMENTS(g_aHdaRegMap)); + AssertMsg(idxReg == g_aHdaRegMap[idxMap].idxReg, ("idxReg=%d\n", idxReg)); + return &pThis->au32Regs[idxReg]; +} + +/** + * Strict stream register accessor verifing defines and mapping table. + * @see HDA_STREAM_REG + */ +DECLINLINE(uint32_t *) hdaStrictStreamRegAccessor(PHDASTATE pThis, uint32_t idxMap0, uint32_t idxReg0, size_t idxStream) +{ + Assert(idxMap0 < RT_ELEMENTS(g_aHdaRegMap)); + AssertMsg(idxStream < RT_ELEMENTS(pThis->aStreams), ("%#zx\n", idxStream)); + AssertMsg(idxReg0 + idxStream * 10 == g_aHdaRegMap[idxMap0 + idxStream * 10].idxReg, + ("idxReg0=%d idxStream=%zx\n", idxReg0, idxStream)); + return &pThis->au32Regs[idxReg0 + idxStream * 10]; +} + +#endif /* VBOX_STRICT */ + + +/** + * Returns a new INTSTS value based on the current device state. + * + * @returns Determined INTSTS register value. + * @param pThis The shared HDA device state. + * + * @remarks This function does *not* set INTSTS! + */ +static uint32_t hdaGetINTSTS(PHDASTATE pThis) +{ + uint32_t intSts = 0; + + /* Check controller interrupts (RIRB, STATEST). */ + if (HDA_REG(pThis, RIRBSTS) & HDA_REG(pThis, RIRBCTL) & (HDA_RIRBCTL_ROIC | HDA_RIRBCTL_RINTCTL)) + { + intSts |= HDA_INTSTS_CIS; /* Set the Controller Interrupt Status (CIS). */ + } + + /* Check SDIN State Change Status Flags. */ + if (HDA_REG(pThis, STATESTS) & HDA_REG(pThis, WAKEEN)) + { + intSts |= HDA_INTSTS_CIS; /* Touch Controller Interrupt Status (CIS). */ + } + + /* For each stream, check if any interrupt status bit is set and enabled. */ + for (uint8_t iStrm = 0; iStrm < HDA_MAX_STREAMS; ++iStrm) + { + if (HDA_STREAM_REG(pThis, STS, iStrm) & HDA_STREAM_REG(pThis, CTL, iStrm) & (HDA_SDCTL_DEIE | HDA_SDCTL_FEIE | HDA_SDCTL_IOCE)) + { + Log3Func(("[SD%d] interrupt status set\n", iStrm)); + intSts |= RT_BIT(iStrm); + } + } + + if (intSts) + intSts |= HDA_INTSTS_GIS; /* Set the Global Interrupt Status (GIS). */ + + Log3Func(("-> 0x%x\n", intSts)); + + return intSts; +} + + +/** + * Processes (asserts/deasserts) the HDA interrupt according to the current state. + * + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pszSource Caller information. + */ +#if defined(LOG_ENABLED) || defined(DOXYGEN_RUNNING) +void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis, const char *pszSource) +#else +void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis) +#endif +{ + uint32_t uIntSts = hdaGetINTSTS(pThis); + + HDA_REG(pThis, INTSTS) = uIntSts; + + /* NB: It is possible to have GIS set even when CIE/SIEn are all zero; the GIS bit does + * not control the interrupt signal. See Figure 4 on page 54 of the HDA 1.0a spec. + */ + /* Global Interrupt Enable (GIE) set? */ + if ( (HDA_REG(pThis, INTCTL) & HDA_INTCTL_GIE) + && (HDA_REG(pThis, INTSTS) & HDA_REG(pThis, INTCTL) & (HDA_INTCTL_CIE | HDA_STRMINT_MASK))) + { + Log3Func(("Asserted (%s)\n", pszSource)); + + PDMDevHlpPCISetIrq(pDevIns, 0, 1 /* Assert */); + pThis->u8IRQL = 1; + +#ifdef DEBUG + pThis->Dbg.IRQ.tsAssertedNs = RTTimeNanoTS(); + pThis->Dbg.IRQ.tsProcessedLastNs = pThis->Dbg.IRQ.tsAssertedNs; +#endif + } + else + { + Log3Func(("Deasserted (%s)\n", pszSource)); + + PDMDevHlpPCISetIrq(pDevIns, 0, 0 /* Deassert */); + pThis->u8IRQL = 0; + } +} + + +/** + * Looks up a register at the exact offset given by @a offReg. + * + * @returns Register index on success, -1 if not found. + * @param offReg The register offset. + */ +static int hdaRegLookup(uint32_t offReg) +{ + /* + * Aliases. + */ + if (offReg >= g_aHdaRegAliases[0].offReg) + { + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++) + if (offReg == g_aHdaRegAliases[i].offReg) + return g_aHdaRegAliases[i].idxAlias; + Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].off < offReg); + return -1; + } + + /* + * Binary search the + */ + int idxEnd = RT_ELEMENTS(g_aHdaRegMap); + int idxLow = 0; + for (;;) + { + int idxMiddle = idxLow + (idxEnd - idxLow) / 2; + if (offReg < g_aHdaRegMap[idxMiddle].off) + { + if (idxLow != idxMiddle) + idxEnd = idxMiddle; + else + break; + } + else if (offReg > g_aHdaRegMap[idxMiddle].off) + { + idxLow = idxMiddle + 1; + if (idxLow < idxEnd) + { /* likely */ } + else + break; + } + else + return idxMiddle; + } + +#ifdef RT_STRICT + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) + Assert(g_aHdaRegMap[i].off != offReg); +#endif + return -1; +} + +#ifdef IN_RING3 + +/** + * Looks up a register covering the offset given by @a offReg. + * + * @returns Register index on success, -1 if not found. + * @param offReg The register offset. + */ +static int hdaR3RegLookupWithin(uint32_t offReg) +{ + /* + * Aliases. + */ + if (offReg >= g_aHdaRegAliases[0].offReg) + { + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++) + { + uint32_t off = offReg - g_aHdaRegAliases[i].offReg; + if (off < 4 && off < g_aHdaRegMap[g_aHdaRegAliases[i].idxAlias].cb) + return g_aHdaRegAliases[i].idxAlias; + } + Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].off < offReg); + return -1; + } + + /* + * Binary search the register map. + */ + int idxEnd = RT_ELEMENTS(g_aHdaRegMap); + int idxLow = 0; + for (;;) + { + int idxMiddle = idxLow + (idxEnd - idxLow) / 2; + if (offReg < g_aHdaRegMap[idxMiddle].off) + { + if (idxLow == idxMiddle) + break; + idxEnd = idxMiddle; + } + else if (offReg >= g_aHdaRegMap[idxMiddle].off + g_aHdaRegMap[idxMiddle].cb) + { + idxLow = idxMiddle + 1; + if (idxLow >= idxEnd) + break; + } + else + return idxMiddle; + } + +# ifdef RT_STRICT + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) + Assert(offReg - g_aHdaRegMap[i].off >= g_aHdaRegMap[i].cb); +# endif + return -1; +} + +#endif /* IN_RING3 */ + +#ifdef IN_RING3 /* Codec is not yet kosher enough for ring-0. @bugref{9890c64} */ + +/** + * Synchronizes the CORB / RIRB buffers between internal <-> device state. + * + * @returns VBox status code. + * + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param fLocal Specify true to synchronize HDA state's CORB buffer with the device state, + * or false to synchronize the device state's RIRB buffer with the HDA state. + * + * @todo r=andy Break this up into two functions? + */ +static int hdaR3CmdSync(PPDMDEVINS pDevIns, PHDASTATE pThis, bool fLocal) +{ + int rc = VINF_SUCCESS; + if (fLocal) + { + if (pThis->u64CORBBase) + { + Assert(pThis->cbCorbBuf); + rc = PDMDevHlpPCIPhysRead(pDevIns, pThis->u64CORBBase, pThis->au32CorbBuf, + RT_MIN(pThis->cbCorbBuf, sizeof(pThis->au32CorbBuf))); + Log3Func(("CORB: read %RGp LB %#x (%Rrc)\n", pThis->u64CORBBase, pThis->cbCorbBuf, rc)); + AssertRCReturn(rc, rc); + } + } + else + { + if (pThis->u64RIRBBase) + { + Assert(pThis->cbRirbBuf); + + rc = PDMDevHlpPCIPhysWrite(pDevIns, pThis->u64RIRBBase, pThis->au64RirbBuf, + RT_MIN(pThis->cbRirbBuf, sizeof(pThis->au64RirbBuf))); + Log3Func(("RIRB: phys read %RGp LB %#x (%Rrc)\n", pThis->u64RIRBBase, pThis->cbRirbBuf, rc)); + AssertRCReturn(rc, rc); + } + } + +# ifdef DEBUG_CMD_BUFFER + LogFunc(("fLocal=%RTbool\n", fLocal)); + + uint8_t i = 0; + do + { + LogFunc(("CORB%02x: ", i)); + uint8_t j = 0; + do + { + const char *pszPrefix; + if ((i + j) == HDA_REG(pThis, CORBRP)) + pszPrefix = "[R]"; + else if ((i + j) == HDA_REG(pThis, CORBWP)) + pszPrefix = "[W]"; + else + pszPrefix = " "; /* three spaces */ + Log((" %s%08x", pszPrefix, pThis->pu32CorbBuf[i + j])); + j++; + } while (j < 8); + Log(("\n")); + i += 8; + } while (i != 0); + + do + { + LogFunc(("RIRB%02x: ", i)); + uint8_t j = 0; + do + { + const char *prefix; + if ((i + j) == HDA_REG(pThis, RIRBWP)) + prefix = "[W]"; + else + prefix = " "; + Log((" %s%016lx", prefix, pThis->pu64RirbBuf[i + j])); + } while (++j < 8); + Log(("\n")); + i += 8; + } while (i != 0); +# endif + return rc; +} + + +/** + * Processes the next CORB buffer command in the queue. + * + * This will invoke the HDA codec ring-3 verb dispatcher. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pThisCC The ring-0 HDA device state. + */ +static int hdaR3CORBCmdProcess(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATECC pThisCC) +{ + Log3Func(("ENTER CORB(RP:%x, WP:%x) RIRBWP:%x\n", HDA_REG(pThis, CORBRP), HDA_REG(pThis, CORBWP), HDA_REG(pThis, RIRBWP))); + + if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) + { + LogFunc(("CORB DMA not active, skipping\n")); + return VINF_SUCCESS; + } + + Assert(pThis->cbCorbBuf); + + int rc = hdaR3CmdSync(pDevIns, pThis, true /* Sync from guest */); + AssertRCReturn(rc, rc); + + /* + * Prepare local copies of relevant registers. + */ + uint16_t cIntCnt = HDA_REG(pThis, RINTCNT) & 0xff; + if (!cIntCnt) /* 0 means 256 interrupts. */ + cIntCnt = HDA_MAX_RINTCNT; + + uint32_t const cCorbEntries = RT_MIN(RT_MAX(pThis->cbCorbBuf, 1), sizeof(pThis->au32CorbBuf)) / HDA_CORB_ELEMENT_SIZE; + uint8_t const corbWp = HDA_REG(pThis, CORBWP) % cCorbEntries; + uint8_t corbRp = HDA_REG(pThis, CORBRP); + uint8_t rirbWp = HDA_REG(pThis, RIRBWP); + + /* + * The loop. + */ + Log3Func(("START CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt)); + while (corbRp != corbWp) + { + /* Fetch the command from the CORB. */ + corbRp = (corbRp + 1) /* Advance +1 as the first command(s) are at CORBWP + 1. */ % cCorbEntries; + uint32_t const uCmd = pThis->au32CorbBuf[corbRp]; + + /* + * Execute the command. + */ + uint64_t uResp = 0; + rc = hdaR3CodecLookup(&pThisCC->Codec, HDA_CODEC_CMD(uCmd, 0 /* Codec index */), &uResp); + if (RT_SUCCESS(rc)) + AssertRCSuccess(rc); /* no informational statuses */ + else + Log3Func(("Lookup for codec verb %08x failed: %Rrc\n", uCmd, rc)); + Log3Func(("Codec verb %08x -> response %016RX64\n", uCmd, uResp)); + + if ( (uResp & CODEC_RESPONSE_UNSOLICITED) + && !(HDA_REG(pThis, GCTL) & HDA_GCTL_UNSOL)) + { + LogFunc(("Unexpected unsolicited response.\n")); + HDA_REG(pThis, CORBRP) = corbRp; + /** @todo r=andy No RIRB syncing to guest required in that case? */ + /** @todo r=bird: Why isn't RIRBWP updated here. The response might come + * after already processing several commands, can't it? (When you think + * about it, it is bascially the same question as Andy is asking.) */ + return VINF_SUCCESS; + } + + /* + * Store the response in the RIRB. + */ + AssertCompile(HDA_RIRB_SIZE == RT_ELEMENTS(pThis->au64RirbBuf)); + rirbWp = (rirbWp + 1) % HDA_RIRB_SIZE; + pThis->au64RirbBuf[rirbWp] = uResp; + + /* + * Send interrupt if needed. + */ + bool fSendInterrupt = false; + pThis->u16RespIntCnt++; + if (pThis->u16RespIntCnt >= cIntCnt) /* Response interrupt count reached? */ + { + pThis->u16RespIntCnt = 0; /* Reset internal interrupt response counter. */ + + Log3Func(("Response interrupt count reached (%RU16)\n", pThis->u16RespIntCnt)); + fSendInterrupt = true; + } + else if (corbRp == corbWp) /* Did we reach the end of the current command buffer? */ + { + Log3Func(("Command buffer empty\n")); + fSendInterrupt = true; + } + if (fSendInterrupt) + { + if (HDA_REG(pThis, RIRBCTL) & HDA_RIRBCTL_RINTCTL) /* Response Interrupt Control (RINTCTL) enabled? */ + { + HDA_REG(pThis, RIRBSTS) |= HDA_RIRBSTS_RINTFL; + HDA_PROCESS_INTERRUPT(pDevIns, pThis); + } + } + } + + /* + * Put register locals back. + */ + Log3Func(("END CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt)); + HDA_REG(pThis, CORBRP) = corbRp; + HDA_REG(pThis, RIRBWP) = rirbWp; + + /* + * Write out the response. + */ + rc = hdaR3CmdSync(pDevIns, pThis, false /* Sync to guest */); + AssertRC(rc); + + return rc; +} + +#endif /* IN_RING3 - @bugref{9890c64} */ + +#ifdef IN_RING3 +/** + * @callback_method_impl{FNPDMTASKDEV, Continue CORB DMA in ring-3} + */ +static DECLCALLBACK(void) hdaR3CorbDmaTaskWorker(PPDMDEVINS pDevIns, void *pvUser) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + RT_NOREF(pvUser); + LogFlowFunc(("\n")); + + DEVHDA_LOCK(pDevIns, pThis); + hdaR3CORBCmdProcess(pDevIns, pThis, pThisCC); + DEVHDA_UNLOCK(pDevIns, pThis); + +} +#endif /* IN_RING3 */ + +/* Register access handlers. */ + +static VBOXSTRICTRC hdaRegReadUnimpl(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + *pu32Value = 0; + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteUnimpl(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg, u32Value); + return VINF_SUCCESS; +} + +/* U8 */ +static VBOXSTRICTRC hdaRegReadU8(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].idxReg] & g_aHdaRegMap[iReg].fReadableMask) & UINT32_C(0xffffff00)) == 0); + return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); +} + +static VBOXSTRICTRC hdaRegWriteU8(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + Assert((u32Value & 0xffffff00) == 0); + return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); +} + +/* U16 */ +static VBOXSTRICTRC hdaRegReadU16(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].idxReg] & g_aHdaRegMap[iReg].fReadableMask) & UINT32_C(0xffff0000)) == 0); + return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); +} + +static VBOXSTRICTRC hdaRegWriteU16(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + Assert((u32Value & 0xffff0000) == 0); + return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); +} + +/* U24 */ +static VBOXSTRICTRC hdaRegReadU24(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].idxReg] & g_aHdaRegMap[iReg].fReadableMask) & UINT32_C(0xff000000)) == 0); + return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); +} + +#ifdef IN_RING3 +static VBOXSTRICTRC hdaRegWriteU24(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + Assert((u32Value & 0xff000000) == 0); + return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); +} +#endif + +/* U32 */ +static VBOXSTRICTRC hdaRegReadU32(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns); + + uint32_t const iRegMem = g_aHdaRegMap[iReg].idxReg; + *pu32Value = pThis->au32Regs[iRegMem] & g_aHdaRegMap[iReg].fReadableMask; + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteU32(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns); + + uint32_t const iRegMem = g_aHdaRegMap[iReg].idxReg; + pThis->au32Regs[iRegMem] = (u32Value & g_aHdaRegMap[iReg].fWritableMask) + | (pThis->au32Regs[iRegMem] & ~g_aHdaRegMap[iReg].fWritableMask); + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteGCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, iReg); + + if (u32Value & HDA_GCTL_CRST) + { + /* Set the CRST bit to indicate that we're leaving reset mode. */ + HDA_REG(pThis, GCTL) |= HDA_GCTL_CRST; + LogFunc(("Guest leaving HDA reset\n")); + } + else + { +#ifdef IN_RING3 + /* Enter reset state. */ + LogFunc(("Guest entering HDA reset with DMA(RIRB:%s, CORB:%s)\n", + HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA ? "on" : "off", + HDA_REG(pThis, RIRBCTL) & HDA_RIRBCTL_RDMAEN ? "on" : "off")); + + /* Clear the CRST bit to indicate that we're in reset state. */ + HDA_REG(pThis, GCTL) &= ~HDA_GCTL_CRST; + + hdaR3GCTLReset(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)); +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + + if (u32Value & HDA_GCTL_FCNTRL) + { + /* Flush: GSTS:1 set, see 6.2.6. */ + HDA_REG(pThis, GSTS) |= HDA_GSTS_FSTS; /* Set the flush status. */ + /* DPLBASE and DPUBASE should be initialized with initial value (see 6.2.6). */ + } + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteSTATESTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns); + + uint32_t v = HDA_REG_IND(pThis, iReg); + uint32_t nv = u32Value & HDA_STATESTS_SCSF_MASK; + + HDA_REG(pThis, STATESTS) &= ~(v & nv); /* Write of 1 clears corresponding bit. */ + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegReadLPIB(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns); + uint8_t const uSD = HDA_SD_NUM_FROM_REG(pThis, LPIB, iReg); + uint32_t const uLPIB = HDA_STREAM_REG(pThis, LPIB, uSD); + +#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA + /* + * Should we consider doing DMA work while we're here? That would require + * the stream to have the DMA engine enabled and be an output stream. + */ + if ( (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_RUN) + && hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT + && uSD < RT_ELEMENTS(pThis->aStreams) /* paranoia */) + { + PHDASTREAM const pStreamShared = &pThis->aStreams[uSD]; + Assert(pStreamShared->u8SD == uSD); + if (pStreamShared->State.fRunning /* should be same as HDA_SDCTL_RUN, but doesn't hurt to check twice */) + { + /* + * Calculate where the DMA engine should be according to the clock, if we can. + */ + uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamShared->State.Cfg.Props); + uint32_t const cbPeriod = pStreamShared->State.cbCurDmaPeriod; + if (cbPeriod > cbFrame) + { + AssertMsg(pStreamShared->State.cbDmaTotal < cbPeriod, ("%#x vs %#x\n", pStreamShared->State.cbDmaTotal, cbPeriod)); + uint64_t const tsTransferNext = pStreamShared->State.tsTransferNext; + uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer); /* only #0 works in r0 */ + uint32_t cbFuture; + if (tsNow < tsTransferNext) + { + /** @todo ASSUMES nanosecond clock ticks, need to make this + * resolution independent. */ + cbFuture = PDMAudioPropsNanoToBytes(&pStreamShared->State.Cfg.Props, tsTransferNext - tsNow); + cbFuture = RT_MIN(cbFuture, cbPeriod - cbFrame); + } + else + { + /* We've hit/overshot the timer deadline. Return to ring-3 if we're + not already there to increase the chance that we'll help expidite + the timer. If we're already in ring-3, do all but the last frame. */ +# ifndef IN_RING3 + LogFunc(("[SD%RU8] DMA period expired: tsNow=%RU64 >= tsTransferNext=%RU64 -> VINF_IOM_R3_MMIO_READ\n", + tsNow, tsTransferNext)); + return VINF_IOM_R3_MMIO_READ; +# else + cbFuture = cbPeriod - cbFrame; + LogFunc(("[SD%RU8] DMA period expired: tsNow=%RU64 >= tsTransferNext=%RU64 -> cbFuture=%#x (cbPeriod=%#x - cbFrame=%#x)\n", + tsNow, tsTransferNext, cbFuture, cbPeriod, cbFrame)); +# endif + } + uint32_t const offNow = PDMAudioPropsFloorBytesToFrame(&pStreamShared->State.Cfg.Props, cbPeriod - cbFuture); + + /* + * Should we transfer a little? Minimum is 64 bytes (semi-random, + * suspect real hardware might be doing some cache aligned stuff, + * which might soon get complicated if you take unaligned buffers + * into consideration and which cache line size (128 bytes is just + * as likely as 64 or 32 bytes)). + */ + uint32_t cbDmaTotal = pStreamShared->State.cbDmaTotal; + if (cbDmaTotal + 64 <= offNow) + { + VBOXSTRICTRC rcStrict = hdaStreamDoOnAccessDmaOutput(pDevIns, pThis, pStreamShared, + tsNow, offNow - cbDmaTotal); + + /* LPIB is updated by hdaStreamDoOnAccessDmaOutput, so get the new value. */ + uint32_t const uNewLpib = HDA_STREAM_REG(pThis, LPIB, uSD); + *pu32Value = uNewLpib; + + LogFlowFunc(("[SD%RU8] LPIB=%#RX32 (CBL=%#RX32 PrevLPIB=%#x offNow=%#x) rcStrict=%Rrc\n", uSD, + uNewLpib, HDA_STREAM_REG(pThis, CBL, uSD), uLPIB, offNow, VBOXSTRICTRC_VAL(rcStrict) )); + return rcStrict; + } + + /* + * Do nothing, just return LPIB as it is. + */ + LogFlowFunc(("[SD%RU8] Skipping DMA transfer: cbDmaTotal=%#x offNow=%#x\n", uSD, cbDmaTotal, offNow)); + } + else + LogFunc(("[SD%RU8] cbPeriod=%#x <= cbFrame=%#x!!\n", uSD, cbPeriod, cbFrame)); + } + else + LogFunc(("[SD%RU8] fRunning=0 SDnCTL=%#x!!\n", uSD, HDA_STREAM_REG(pThis, CTL, uSD) )); + } +#endif /* VBOX_HDA_WITH_ON_REG_ACCESS_DMA */ + + LogFlowFunc(("[SD%RU8] LPIB=%#RX32 (CBL=%#RX32 CTL=%#RX32)\n", + uSD, uLPIB, HDA_STREAM_REG(pThis, CBL, uSD), HDA_STREAM_REG(pThis, CTL, uSD) )); + *pu32Value = uLPIB; + return VINF_SUCCESS; +} + +/** + * Gets the wall clock. + * + * Used by hdaRegReadWALCLK() and 'info hda'. + * + * @returns Strict VBox status code if @a fDoDma is @c true, otherwise + * VINF_SUCCESS. + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param fDoDma Whether to consider doing DMA work or not. + * @param puWallNow Where to return the current wall clock time. + */ +static VBOXSTRICTRC hdaQueryWallClock(PPDMDEVINS pDevIns, PHDASTATE pThis, bool fDoDma, uint64_t *puWallNow) +{ + /* + * The wall clock is calculated from the virtual sync clock. Since + * the clock is supposed to reset to zero on controller reset, a + * start offset is subtracted. + * + * In addition, we hold the clock back when there are active DMA engines + * so that the guest won't conclude we've gotten further in the buffer + * processing than what we really have. (We generally read a whole buffer + * at once when the IOC is due, so we're a lot later than what real + * hardware would be in reading/writing the buffers.) + * + * Here are some old notes from the DMA engine that might be useful even + * if a little dated: + * + * Note 1) Only certain guests (like Linux' snd_hda_intel) rely on the WALCLK register + * in order to determine the correct timing of the sound device. Other guests + * like Windows 7 + 10 (or even more exotic ones like Haiku) will completely + * ignore this. + * + * Note 2) When updating the WALCLK register too often / early (or even in a non-monotonic + * fashion) this *will* upset guest device drivers and will completely fuck up the + * sound output. Running VLC on the guest will tell! + */ + uint64_t const uFreq = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[0].hTimer); + Assert(uFreq <= UINT32_MAX); + uint64_t const tsStart = 0; /** @todo pThis->tsWallClkStart (as it is reset on controller reset) */ + uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer); + + /* Find the oldest DMA transfer timestamp from the active streams. */ + int iDmaNow = -1; + uint64_t tsDmaNow = tsNow; + for (size_t i = 0; i < RT_ELEMENTS(pThis->aStreams); i++) + if (pThis->aStreams[i].State.fRunning) + { +#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA + /* Linux is reading WALCLK before one of the DMA position reads and + we've already got the current time from TM, so check if we should + do a little bit of DMA'ing here to help WALCLK ahead. */ + if (fDoDma) + { + if (hdaGetDirFromSD((uint8_t)i) == PDMAUDIODIR_OUT) + { + VBOXSTRICTRC rcStrict = hdaStreamMaybeDoOnAccessDmaOutput(pDevIns, pThis, &pThis->aStreams[i], tsNow); + if (rcStrict == VINF_SUCCESS) + { /* likely */ } + else + return rcStrict; + } + } +#endif + + if ( pThis->aStreams[i].State.tsTransferLast < tsDmaNow + && pThis->aStreams[i].State.tsTransferLast > tsStart) + { + tsDmaNow = pThis->aStreams[i].State.tsTransferLast; + iDmaNow = (int)i; + } + } + + /* Convert it to wall clock ticks. */ + uint64_t const uWallClkNow = ASMMultU64ByU32DivByU32(tsDmaNow - tsStart, + 24000000 /*Wall clock frequency */, + uFreq); + Log3Func(("Returning %#RX64 - tsNow=%#RX64 tsDmaNow=%#RX64 (%d) -> %#RX64\n", + uWallClkNow, tsNow, tsDmaNow, iDmaNow, tsNow - tsDmaNow)); + RT_NOREF(iDmaNow, fDoDma); + *puWallNow = uWallClkNow; + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegReadWALCLK(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint64_t uWallNow = 0; + VBOXSTRICTRC rcStrict = hdaQueryWallClock(pDevIns, pThis, true /*fDoDma*/, &uWallNow); + if (rcStrict == VINF_SUCCESS) + { + *pu32Value = (uint32_t)uWallNow; + return VINF_SUCCESS; + } + RT_NOREF(iReg); + return rcStrict; +} + +static VBOXSTRICTRC hdaRegWriteSSYNCWorker(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value, const char *pszCaller) +{ + RT_NOREF(pszCaller); + + /* + * The SSYNC register is a DMA pause mask where each bit represents a stream. + * There should be no DMA transfers going down the driver chains when the a + * stream has its bit set here. There are two scenarios described in the + * specification, starting and stopping, though it can probably be used for + * other purposes if the guest gets creative... + * + * Anyway, if we ever want to implement this, we'd be manipulating the DMA + * timers of the affected streams here, I think. At least in the start + * scenario, we would run the first DMA transfers from here. + */ + uint32_t const fOld = HDA_REG(pThis, SSYNC); + uint32_t const fNew = (u32Value & g_aHdaRegMap[iReg].fWritableMask) + | (fOld & ~g_aHdaRegMap[iReg].fWritableMask); + uint32_t const fChanged = (fNew ^ fOld) & (RT_BIT_32(HDA_MAX_STREAMS) - 1); + if (fChanged) + { +#if 0 /** @todo implement SSYNC: ndef IN_RING3 */ + Log3(("%s: Going to ring-3 to handle SSYNC change: %#x\n", pszCaller, fChanged)); + return VINF_IOM_R3_MMIO_WRITE; +#else + for (uint32_t fMask = 1, i = 0; fMask < RT_BIT_32(HDA_MAX_STREAMS); i++, fMask <<= 1) + if (!(fChanged & fMask)) + { /* nothing */ } + else if (fNew & fMask) + { + Log3(("%Rfn: SSYNC bit %u set\n", pszCaller, i)); + /* See code in SDCTL around hdaR3StreamTimerMain call. */ + } + else + { + Log3(("%Rfn: SSYNC bit %u cleared\n", pszCaller, i)); + /* The next DMA timer callout will not do anything. */ + } +#endif + } + + HDA_REG(pThis, SSYNC) = fNew; + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteSSYNC(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns); + return hdaRegWriteSSYNCWorker(pThis, iReg, u32Value, __FUNCTION__); +} + +static VBOXSTRICTRC hdaRegWriteNewSSYNC(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns); + return hdaRegWriteSSYNCWorker(pThis, iReg, u32Value, __FUNCTION__); +} + +static VBOXSTRICTRC hdaRegWriteCORBRP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, iReg); + if (u32Value & HDA_CORBRP_RST) + { + /* Do a CORB reset. */ + if (pThis->cbCorbBuf) + RT_ZERO(pThis->au32CorbBuf); + + LogRel2(("HDA: CORB reset\n")); + HDA_REG(pThis, CORBRP) = HDA_CORBRP_RST; /* Clears the pointer. */ + } + else + HDA_REG(pThis, CORBRP) &= ~HDA_CORBRP_RST; /* Only CORBRP_RST bit is writable. */ + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteCORBCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + VBOXSTRICTRC rc = hdaRegWriteU8(pDevIns, pThis, iReg, u32Value); + AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); + + if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* DMA engine started? */ + { +#ifdef IN_RING3 /** @todo do PDMDevHlpTaskTrigger everywhere? */ + rc = hdaR3CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATECC)); +#else + rc = PDMDevHlpTaskTrigger(pDevIns, pThis->hCorbDmaTask); + if (rc != VINF_SUCCESS && RT_SUCCESS(rc)) + rc = VINF_SUCCESS; +#endif + } + else + LogFunc(("CORB DMA not running, skipping\n")); + + return rc; +} + +static VBOXSTRICTRC hdaRegWriteCORBSIZE(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, iReg); + + if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) /* Ignore request if CORB DMA engine is (still) running. */ + { + u32Value = (u32Value & HDA_CORBSIZE_SZ); + + uint16_t cEntries; + switch (u32Value) + { + case 0: /* 8 byte; 2 entries. */ + cEntries = 2; + break; + case 1: /* 64 byte; 16 entries. */ + cEntries = 16; + break; + case 2: /* 1 KB; 256 entries. */ + cEntries = HDA_CORB_SIZE; /* default. */ + break; + default: + LogRel(("HDA: Guest tried to set an invalid CORB size (0x%x), keeping default\n", u32Value)); + u32Value = 2; + cEntries = HDA_CORB_SIZE; /* Use default size. */ + break; + } + + uint32_t cbCorbBuf = cEntries * HDA_CORB_ELEMENT_SIZE; + Assert(cbCorbBuf <= sizeof(pThis->au32CorbBuf)); /* paranoia */ + + if (cbCorbBuf != pThis->cbCorbBuf) + { + RT_ZERO(pThis->au32CorbBuf); /* Clear CORB when setting a new size. */ + pThis->cbCorbBuf = cbCorbBuf; + } + + LogFunc(("CORB buffer size is now %RU32 bytes (%u entries)\n", pThis->cbCorbBuf, pThis->cbCorbBuf / HDA_CORB_ELEMENT_SIZE)); + + HDA_REG(pThis, CORBSIZE) = u32Value; + } + else + LogFunc(("CORB DMA is (still) running, skipping\n")); + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteCORBSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, iReg); + + uint32_t v = HDA_REG(pThis, CORBSTS); + HDA_REG(pThis, CORBSTS) &= ~(v & u32Value); + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteCORBWP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + VBOXSTRICTRC rc = hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); + AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); + +#ifdef IN_RING3 /** @todo do PDMDevHlpTaskTrigger everywhere? */ + return hdaR3CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATECC)); +#else + rc = PDMDevHlpTaskTrigger(pDevIns, pThis->hCorbDmaTask); + return RT_SUCCESS(rc) ? VINF_SUCCESS : rc; +#endif +} + +static VBOXSTRICTRC hdaRegWriteSDCBL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); +} + +static VBOXSTRICTRC hdaRegWriteSDCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ +#ifdef IN_RING3 + /* Get the stream descriptor number. */ + const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, CTL, iReg); + AssertReturn(uSD < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ + + /* + * Extract the stream tag the guest wants to use for this specific + * stream descriptor (SDn). This only can happen if the stream is in a non-running + * state, so we're doing the lookup and assignment here. + * + * So depending on the guest OS, SD3 can use stream tag 4, for example. + */ + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + uint8_t uTag = (u32Value >> HDA_SDCTL_NUM_SHIFT) & HDA_SDCTL_NUM_MASK; + ASSERT_GUEST_MSG_RETURN(uTag < RT_ELEMENTS(pThisCC->aTags), + ("SD%RU8: Invalid stream tag %RU8 (u32Value=%#x)!\n", uSD, uTag, u32Value), + VINF_SUCCESS /* Always return success to the MMIO handler. */); + + PHDASTREAM const pStreamShared = &pThis->aStreams[uSD]; + PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[uSD]; + + const bool fRun = RT_BOOL(u32Value & HDA_SDCTL_RUN); + const bool fReset = RT_BOOL(u32Value & HDA_SDCTL_SRST); + + /* If the run bit is set, we take the virtual-sync clock lock as well so we + can safely update timers via hdaR3TimerSet if necessary. We need to be + very careful with the fInReset and fInRun indicators here, as they may + change during the relocking if we need to acquire the clock lock. */ + const bool fNeedVirtualSyncClockLock = (u32Value & (HDA_SDCTL_RUN | HDA_SDCTL_SRST)) == HDA_SDCTL_RUN + && (HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN) == 0; + if (fNeedVirtualSyncClockLock) + { + DEVHDA_UNLOCK(pDevIns, pThis); + DEVHDA_LOCK_BOTH_RETURN(pDevIns, pThis, pStreamShared, VINF_IOM_R3_MMIO_WRITE); + } + + const bool fInRun = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN); + const bool fInReset = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_SRST); + + /*LogFunc(("[SD%RU8] fRun=%RTbool, fInRun=%RTbool, fReset=%RTbool, fInReset=%RTbool, %R[sdctl]\n", + uSD, fRun, fInRun, fReset, fInReset, u32Value));*/ + if (fInReset) + { + ASSERT_GUEST(!fReset); + ASSERT_GUEST(!fInRun && !fRun); + + /* Exit reset state. */ + ASMAtomicXchgBool(&pStreamShared->State.fInReset, false); + + /* Report that we're done resetting this stream by clearing SRST. */ + HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_SRST; + + LogFunc(("[SD%RU8] Reset exit\n", uSD)); + } + else if (fReset) + { + /* ICH6 datasheet 18.2.33 says that RUN bit should be cleared before initiation of reset. */ + ASSERT_GUEST(!fInRun && !fRun); + + LogFunc(("[SD%RU8] Reset enter\n", uSD)); + + STAM_REL_PROFILE_START_NS(&pStreamR3->State.StatReset, a); + Assert(PDMCritSectIsOwner(&pThis->CritSect)); + PAUDMIXSINK const pMixSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL; + if (pMixSink) + AudioMixerSinkLock(pMixSink); + + /* Deal with reset while running. */ + if (pStreamShared->State.fRunning) + { + int rc2 = hdaR3StreamEnable(pThis, pStreamShared, pStreamR3, false /* fEnable */); + AssertRC(rc2); Assert(!pStreamShared->State.fRunning); + pStreamShared->State.fRunning = false; + } + + hdaR3StreamReset(pThis, pThisCC, pStreamShared, pStreamR3, uSD); + + if (pMixSink) /* (FYI. pMixSink might not be what pStreamR3->pMixSink->pMixSink points at any longer) */ + AudioMixerSinkUnlock(pMixSink); + STAM_REL_PROFILE_STOP_NS(&pStreamR3->State.StatReset, a); + } + else + { + /* + * We enter here to change DMA states only. + */ + if (fInRun != fRun) + { + STAM_REL_PROFILE_START_NS((fRun ? &pStreamR3->State.StatStart : &pStreamR3->State.StatStop), r); + Assert(!fReset && !fInReset); /* (code change paranoia, currently impossible ) */ + LogFunc(("[SD%RU8] State changed (fRun=%RTbool)\n", uSD, fRun)); + + Assert(PDMCritSectIsOwner(&pThis->CritSect)); + /** @todo bird: It's not clear to me when the pMixSink is actually + * assigned to the stream, so being paranoid till I find out... */ + PAUDMIXSINK const pMixSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL; + if (pMixSink) + AudioMixerSinkLock(pMixSink); + + int rc2 = VINF_SUCCESS; + if (fRun) + { + if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) + { + const uint8_t uStripeCtl = ((u32Value >> HDA_SDCTL_STRIPE_SHIFT) & HDA_SDCTL_STRIPE_MASK) + 1; + LogFunc(("[SD%RU8] Using %RU8 SDOs (stripe control)\n", uSD, uStripeCtl)); + if (uStripeCtl > 1) + LogRel2(("HDA: Warning: Striping output over more than one SDO for stream #%RU8 currently is not implemented " \ + "(%RU8 SDOs requested)\n", uSD, uStripeCtl)); + } + + /* Assign new values. */ + LogFunc(("[SD%RU8] Using stream tag=%RU8\n", uSD, uTag)); + PHDATAG pTag = &pThisCC->aTags[uTag]; + pTag->uTag = uTag; + pTag->pStreamR3 = &pThisCC->aStreams[uSD]; + +# ifdef LOG_ENABLED + if (LogIsEnabled()) + { + PDMAUDIOPCMPROPS Props = { 0 }; + rc2 = hdaR3SDFMTToPCMProps(HDA_STREAM_REG(pThis, FMT, uSD), &Props); AssertRC(rc2); + LogFunc(("[SD%RU8] %RU32Hz, %RU8bit, %RU8 channel(s)\n", + uSD, Props.uHz, PDMAudioPropsSampleBits(&Props), PDMAudioPropsChannels(&Props))); + } +# endif + /* (Re-)initialize the stream with current values. */ + rc2 = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, uSD); + if ( RT_SUCCESS(rc2) + /* Any vital stream change occurred so that we need to (re-)add the stream to our setup? + * Otherwise just skip this, as this costs a lot of performance. */ + /** @todo r=bird: hdaR3StreamSetUp does not return VINF_NO_CHANGE since r142810. */ + && rc2 != VINF_NO_CHANGE) + { + /* Remove the old stream from the device setup. */ + rc2 = hdaR3RemoveStream(pThisCC, &pStreamShared->State.Cfg); + AssertRC(rc2); + + /* Add the stream to the device setup. */ + rc2 = hdaR3AddStream(pThisCC, &pStreamShared->State.Cfg); + AssertRC(rc2); + } + } + + if (RT_SUCCESS(rc2)) + { + /* Enable/disable the stream. */ + rc2 = hdaR3StreamEnable(pThis, pStreamShared, pStreamR3, fRun /* fEnable */); + AssertRC(rc2); + + if (fRun) + { + /** @todo move this into a HDAStream.cpp function. */ + uint64_t tsNow; + if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) + { + /* Output streams: Avoid going through the timer here by calling the stream's timer + function directly. Should speed up starting the stream transfers. */ + tsNow = hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3); + } + else + { + /* Input streams: Arm the timer and kick the AIO thread. */ + tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); + pStreamShared->State.tsTransferLast = tsNow; /* for WALCLK */ + + uint64_t tsTransferNext = tsNow + pStreamShared->State.aSchedule[0].cPeriodTicks; + pStreamShared->State.tsTransferNext = tsTransferNext; /* legacy */ + pStreamShared->State.cbCurDmaPeriod = pStreamShared->State.aSchedule[0].cbPeriod; + Log3Func(("[SD%RU8] tsTransferNext=%RU64 (in %RU64)\n", + pStreamShared->u8SD, tsTransferNext, tsTransferNext - tsNow)); + + int rc = PDMDevHlpTimerSet(pDevIns, pStreamShared->hTimer, tsTransferNext); + AssertRC(rc); + + /** @todo we should have a delayed AIO thread kick off, really... */ + if (pStreamR3->pMixSink && pStreamR3->pMixSink->pMixSink) + AudioMixerSinkSignalUpdateJob(pStreamR3->pMixSink->pMixSink); + else + AssertFailed(); + } + hdaR3StreamMarkStarted(pDevIns, pThis, pStreamShared, tsNow); + } + else + hdaR3StreamMarkStopped(pStreamShared); + } + + /* Make sure to leave the lock before (eventually) starting the timer. */ + if (pMixSink) + AudioMixerSinkUnlock(pMixSink); + STAM_REL_PROFILE_STOP_NS((fRun ? &pStreamR3->State.StatStart : &pStreamR3->State.StatStop), r); + } + } + + if (fNeedVirtualSyncClockLock) + PDMDevHlpTimerUnlockClock(pDevIns, pStreamShared->hTimer); /* Caller will unlock pThis->CritSect. */ + + return hdaRegWriteU24(pDevIns, pThis, iReg, u32Value); +#else /* !IN_RING3 */ + RT_NOREF(pDevIns, pThis, iReg, u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +static VBOXSTRICTRC hdaRegWriteSDSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + uint32_t v = HDA_REG_IND(pThis, iReg); + + /* Clear (zero) FIFOE, DESE and BCIS bits when writing 1 to it (6.2.33). */ + HDA_REG_IND(pThis, iReg) &= ~(u32Value & v); + + HDA_PROCESS_INTERRUPT(pDevIns, pThis); + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteSDLVI(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + const size_t idxStream = HDA_SD_NUM_FROM_REG(pThis, LVI, iReg); + AssertReturn(idxStream < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ + + ASSERT_GUEST_LOGREL_MSG(u32Value <= UINT8_MAX, /* Should be covered by the register write mask, but just to make sure. */ + ("LVI for stream #%zu must not be bigger than %RU8\n", idxStream, UINT8_MAX - 1)); + return hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); +} + +/** + * Calculates the number of bytes of a FIFOW register. + * + * @return Number of bytes of a given FIFOW register. + * @param u16RegFIFOW FIFOW register to convert. + */ +uint8_t hdaSDFIFOWToBytes(uint16_t u16RegFIFOW) +{ + uint32_t cb; + switch (u16RegFIFOW) + { + case HDA_SDFIFOW_8B: cb = 8; break; + case HDA_SDFIFOW_16B: cb = 16; break; + case HDA_SDFIFOW_32B: cb = 32; break; + default: + AssertFailedStmt(cb = 32); /* Paranoia. */ + break; + } + + Assert(RT_IS_POWER_OF_TWO(cb)); + return cb; +} + +static VBOXSTRICTRC hdaRegWriteSDFIFOW(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + size_t const idxStream = HDA_SD_NUM_FROM_REG(pThis, FIFOW, iReg); + AssertReturn(idxStream < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ + + if (RT_LIKELY(hdaGetDirFromSD((uint8_t)idxStream) == PDMAUDIODIR_IN)) /* FIFOW for input streams only. */ + { /* likely */ } + else + { +#ifndef IN_RING0 + LogRel(("HDA: Warning: Guest tried to write read-only FIFOW to output stream #%RU8, ignoring\n", idxStream)); + return VINF_SUCCESS; +#else + return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */ +#endif + } + + uint16_t u16FIFOW = 0; + switch (u32Value) + { + case HDA_SDFIFOW_8B: + case HDA_SDFIFOW_16B: + case HDA_SDFIFOW_32B: + u16FIFOW = RT_LO_U16(u32Value); /* Only bits 2:0 are used; see ICH-6, 18.2.38. */ + break; + default: + ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried writing unsupported FIFOW (0x%zx) to stream #%RU8, defaulting to 32 bytes\n", + u32Value, idxStream)); + u16FIFOW = HDA_SDFIFOW_32B; + break; + } + + pThis->aStreams[idxStream].u8FIFOW = hdaSDFIFOWToBytes(u16FIFOW); + LogFunc(("[SD%zu] Updating FIFOW to %RU8 bytes\n", idxStream, pThis->aStreams[idxStream].u8FIFOW)); + return hdaRegWriteU16(pDevIns, pThis, iReg, u16FIFOW); +} + +/** + * @note This method could be called for changing value on Output Streams only (ICH6 datasheet 18.2.39). + */ +static VBOXSTRICTRC hdaRegWriteSDFIFOS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, FIFOS, iReg); + + ASSERT_GUEST_LOGREL_MSG_RETURN(hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT, /* FIFOS for output streams only. */ + ("Guest tried writing read-only FIFOS to input stream #%RU8, ignoring\n", uSD), + VINF_SUCCESS); + + uint32_t u32FIFOS; + switch (u32Value) + { + case HDA_SDOFIFO_16B: + case HDA_SDOFIFO_32B: + case HDA_SDOFIFO_64B: + case HDA_SDOFIFO_128B: + case HDA_SDOFIFO_192B: + case HDA_SDOFIFO_256B: + u32FIFOS = u32Value; + break; + + default: + ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried writing unsupported FIFOS (0x%x) to stream #%RU8, defaulting to 192 bytes\n", + u32Value, uSD)); + u32FIFOS = HDA_SDOFIFO_192B; + break; + } + + return hdaRegWriteU16(pDevIns, pThis, iReg, u32FIFOS); +} + +#ifdef IN_RING3 + +/** + * Adds an audio output stream to the device setup using the given configuration. + * + * @returns VBox status code. + * @param pThisCC The ring-3 HDA device state. + * @param pCfg Stream configuration to use for adding a stream. + */ +static int hdaR3AddStreamOut(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + + LogFlowFunc(("Stream=%s\n", pCfg->szName)); + + int rc = VINF_SUCCESS; + + bool fUseFront = true; /* Always use front out by default. */ +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + bool fUseRear; + bool fUseCenter; + bool fUseLFE; + + fUseRear = fUseCenter = fUseLFE = false; + + /* + * Use commonly used setups for speaker configurations. + */ + + /** @todo Make the following configurable through mixer API and/or CFGM? */ + switch (PDMAudioPropsChannels(&pCfg->Props)) + { + case 3: /* 2.1: Front (Stereo) + LFE. */ + { + fUseLFE = true; + break; + } + + case 4: /* Quadrophonic: Front (Stereo) + Rear (Stereo). */ + { + fUseRear = true; + break; + } + + case 5: /* 4.1: Front (Stereo) + Rear (Stereo) + LFE. */ + { + fUseRear = true; + fUseLFE = true; + break; + } + + case 6: /* 5.1: Front (Stereo) + Rear (Stereo) + Center/LFE. */ + { + fUseRear = true; + fUseCenter = true; + fUseLFE = true; + break; + } + + default: /* Unknown; fall back to 2 front channels (stereo). */ + { + rc = VERR_NOT_SUPPORTED; + break; + } + } +# endif /* !VBOX_WITH_AUDIO_HDA_51_SURROUND */ + + if (rc == VERR_NOT_SUPPORTED) + { + LogRel2(("HDA: Warning: Unsupported channel count (%RU8), falling back to stereo channels (2)\n", + PDMAudioPropsChannels(&pCfg->Props) )); + + /* Fall back to 2 channels (see below in fUseFront block). */ + rc = VINF_SUCCESS; + } + + do + { + if (RT_FAILURE(rc)) + break; + + if (fUseFront) + { + RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Front"); + + pCfg->enmPath = PDMAUDIOPATH_OUT_FRONT; + /// @todo PDMAudioPropsSetChannels(&pCfg->Props, 2); ? + + rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_FRONT, pCfg); + } + +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + if ( RT_SUCCESS(rc) + && (fUseCenter || fUseLFE)) + { + RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Center/LFE"); + + pCfg->enmPath = PDMAUDIOPATH_OUT_CENTER_LFE; + PDMAudioPropsSetChannels(&pCfg->Props, fUseCenter && fUseLFE ? 2 : 1); + + rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_CENTER_LFE, pCfg); + } + + if ( RT_SUCCESS(rc) + && fUseRear) + { + RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Rear"); + + pCfg->enmPath = PDMAUDIOPATH_OUT_REAR; + PDMAudioPropsSetChannels(&pCfg->Props, 2); + + rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_REAR, pCfg); + } +# endif /* VBOX_WITH_AUDIO_HDA_51_SURROUND */ + + } while (0); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds an audio input stream to the device setup using the given configuration. + * + * @returns VBox status code. + * @param pThisCC The ring-3 HDA device state. + * @param pCfg Stream configuration to use for adding a stream. + */ +static int hdaR3AddStreamIn(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + AssertReturn(pCfg->enmDir == PDMAUDIODIR_IN, VERR_INVALID_PARAMETER); + + LogFlowFunc(("Stream=%s enmPath=%ld\n", pCfg->szName, pCfg->enmPath)); + + int rc; + switch (pCfg->enmPath) + { + case PDMAUDIOPATH_IN_LINE: + rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_LINE_IN, pCfg); + break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOPATH_IN_MIC: + rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_MIC_IN, pCfg); + break; +# endif + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds an audio stream to the device setup using the given configuration. + * + * @returns VBox status code. + * @param pThisCC The ring-3 HDA device state. + * @param pCfg Stream configuration to use for adding a stream. + */ +static int hdaR3AddStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + int rc; + switch (pCfg->enmDir) + { + case PDMAUDIODIR_OUT: + rc = hdaR3AddStreamOut(pThisCC, pCfg); + break; + + case PDMAUDIODIR_IN: + rc = hdaR3AddStreamIn(pThisCC, pCfg); + break; + + default: + rc = VERR_NOT_SUPPORTED; + AssertFailed(); + break; + } + + LogFlowFunc(("Returning %Rrc\n", rc)); + + return rc; +} + +/** + * Removes an audio stream from the device setup using the given configuration. + * + * Used by hdaRegWriteSDCTL(). + * + * @returns VBox status code. + * @param pThisCC The ring-3 HDA device state. + * @param pCfg Stream configuration to use for removing a stream. + */ +static int hdaR3RemoveStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + PDMAUDIOMIXERCTL enmMixerCtl = PDMAUDIOMIXERCTL_UNKNOWN; + switch (pCfg->enmDir) + { + case PDMAUDIODIR_IN: + { + LogFlowFunc(("Stream=%s enmPath=%d (src)\n", pCfg->szName, pCfg->enmPath)); + + switch (pCfg->enmPath) + { + case PDMAUDIOPATH_UNKNOWN: break; + case PDMAUDIOPATH_IN_LINE: enmMixerCtl = PDMAUDIOMIXERCTL_LINE_IN; break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOPATH_IN_MIC: enmMixerCtl = PDMAUDIOMIXERCTL_MIC_IN; break; +# endif + default: + rc = VERR_NOT_SUPPORTED; + break; + } + break; + } + + case PDMAUDIODIR_OUT: + { + LogFlowFunc(("Stream=%s, enmPath=%d (dst)\n", pCfg->szName, pCfg->enmPath)); + + switch (pCfg->enmPath) + { + case PDMAUDIOPATH_UNKNOWN: break; + case PDMAUDIOPATH_OUT_FRONT: enmMixerCtl = PDMAUDIOMIXERCTL_FRONT; break; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOPATH_OUT_CENTER_LFE: enmMixerCtl = PDMAUDIOMIXERCTL_CENTER_LFE; break; + case PDMAUDIOPATH_OUT_REAR: enmMixerCtl = PDMAUDIOMIXERCTL_REAR; break; +# endif + default: + rc = VERR_NOT_SUPPORTED; + break; + } + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if ( RT_SUCCESS(rc) + && enmMixerCtl != PDMAUDIOMIXERCTL_UNKNOWN) + { + rc = hdaR3CodecRemoveStream(&pThisCC->Codec, enmMixerCtl, false /*fImmediate*/); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#endif /* IN_RING3 */ + +static VBOXSTRICTRC hdaRegWriteSDFMT(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ +#ifdef IN_RING3 + PDMAUDIOPCMPROPS Props; + int rc2 = hdaR3SDFMTToPCMProps(RT_LO_U16(u32Value), &Props); + AssertRC(rc2); + LogFunc(("[SD%RU8] Set to %#x (%RU32Hz, %RU8bit, %RU8 channel(s))\n", HDA_SD_NUM_FROM_REG(pThis, FMT, iReg), u32Value, + PDMAudioPropsHz(&Props), PDMAudioPropsSampleBits(&Props), PDMAudioPropsChannels(&Props))); + + /* + * Write the wanted stream format into the register in any case. + * + * This is important for e.g. MacOS guests, as those try to initialize streams which are not reported + * by the device emulation (wants 4 channels, only have 2 channels at the moment). + * + * When ignoring those (invalid) formats, this leads to MacOS thinking that the device is malfunctioning + * and therefore disabling the device completely. + */ + return hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); +#else + RT_NOREF(pDevIns, pThis, iReg, u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#endif +} + +/** + * Worker for writes to the BDPL and BDPU registers. + */ +DECLINLINE(VBOXSTRICTRC) hdaRegWriteSDBDPX(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value, uint8_t uSD) +{ + RT_NOREF(uSD); + return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); +} + +static VBOXSTRICTRC hdaRegWriteSDBDPL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + return hdaRegWriteSDBDPX(pDevIns, pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPL, iReg)); +} + +static VBOXSTRICTRC hdaRegWriteSDBDPU(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + return hdaRegWriteSDBDPX(pDevIns, pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPU, iReg)); +} + +/** Skylake specific. */ +static VBOXSTRICTRC hdaRegReadSDnPIB(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint8_t const uSD = HDA_SD_NUM_FROM_SKYLAKE_REG(DPIB, iReg); + LogFlowFunc(("uSD=%u -> SDnLPIB\n", uSD)); + return hdaRegReadLPIB(pDevIns, pThis, HDA_SD_TO_REG(LPIB, uSD), pu32Value); +} + +/** Skylake specific. */ +static VBOXSTRICTRC hdaRegReadSDnEFIFOS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + /** @todo This is not implemented as I have found no specs yet. */ + RT_NOREF(pDevIns, pThis, iReg); + LogFunc(("TODO - need register spec: uSD=%u\n", HDA_SD_NUM_FROM_SKYLAKE_REG(DPIB, iReg))); + *pu32Value = 256; + return VINF_SUCCESS; +} + + +static VBOXSTRICTRC hdaRegReadIRS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + /* regarding 3.4.3 we should mark IRS as busy in case CORB is active */ + if ( HDA_REG(pThis, CORBWP) != HDA_REG(pThis, CORBRP) + || (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) + HDA_REG(pThis, IRS) = HDA_IRS_ICB; /* busy */ + + return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); +} + +static VBOXSTRICTRC hdaRegWriteIRS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, iReg); + + /* + * If the guest set the ICB bit of IRS register, HDA should process the verb in IC register, + * write the response to IR register, and set the IRV (valid in case of success) bit of IRS register. + */ + if ( (u32Value & HDA_IRS_ICB) + && !(HDA_REG(pThis, IRS) & HDA_IRS_ICB)) + { +#ifdef IN_RING3 + uint32_t uCmd = HDA_REG(pThis, IC); + + if (HDA_REG(pThis, CORBWP) != HDA_REG(pThis, CORBRP)) + { + /* + * 3.4.3: Defines behavior of immediate Command status register. + */ + LogRel(("HDA: Guest attempted process immediate verb (%x) with active CORB\n", uCmd)); + return VINF_SUCCESS; + } + + HDA_REG(pThis, IRS) = HDA_IRS_ICB; /* busy */ + + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + uint64_t uResp = 0; + int rc2 = hdaR3CodecLookup(&pThisCC->Codec, HDA_CODEC_CMD(uCmd, 0 /* LUN */), &uResp); + if (RT_FAILURE(rc2)) + LogFunc(("Codec lookup failed with rc2=%Rrc\n", rc2)); + + HDA_REG(pThis, IR) = (uint32_t)uResp; /** @todo r=andy Do we need a 64-bit response? */ + HDA_REG(pThis, IRS) = HDA_IRS_IRV; /* result is ready */ + /** @todo r=michaln We just set the IRS value, why are we clearing unset bits? */ + HDA_REG(pThis, IRS) &= ~HDA_IRS_ICB; /* busy is clear */ + + return VINF_SUCCESS; +#else /* !IN_RING3 */ + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ + } + + /* + * Once the guest read the response, it should clear the IRV bit of the IRS register. + */ + HDA_REG(pThis, IRS) &= ~(u32Value & HDA_IRS_IRV); + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteRIRBWP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, iReg); + + if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */ + LogFunc(("CORB DMA (still) running, skipping\n")); + else + { + if (u32Value & HDA_RIRBWP_RST) + { + /* Do a RIRB reset. */ + if (pThis->cbRirbBuf) + RT_ZERO(pThis->au64RirbBuf); + + LogRel2(("HDA: RIRB reset\n")); + + HDA_REG(pThis, RIRBWP) = 0; + } + /* The remaining bits are O, see 6.2.22. */ + } + return VINF_SUCCESS; +} + +static VBOXSTRICTRC hdaRegWriteRINTCNT(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns); + if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */ + { + LogFunc(("CORB DMA is (still) running, skipping\n")); + return VINF_SUCCESS; + } + + VBOXSTRICTRC rc = hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); + AssertRC(VBOXSTRICTRC_VAL(rc)); + + /** @todo r=bird: Shouldn't we make sure the HDASTATE::u16RespIntCnt is below + * the new RINTCNT value? Or alterantively, make the DMA look take + * this into account instead... I'll do the later for now. */ + + LogFunc(("Response interrupt count is now %RU8\n", HDA_REG(pThis, RINTCNT) & 0xFF)); + return rc; +} + +static VBOXSTRICTRC hdaRegWriteBase(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns); + + VBOXSTRICTRC rc = hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); + AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); + + uint32_t const iRegMem = g_aHdaRegMap[iReg].idxReg; + switch (iReg) + { + case HDA_REG_CORBLBASE: + pThis->u64CORBBase &= UINT64_C(0xFFFFFFFF00000000); + pThis->u64CORBBase |= pThis->au32Regs[iRegMem]; + break; + case HDA_REG_CORBUBASE: + pThis->u64CORBBase &= UINT64_C(0x00000000FFFFFFFF); + pThis->u64CORBBase |= (uint64_t)pThis->au32Regs[iRegMem] << 32; + break; + case HDA_REG_RIRBLBASE: + pThis->u64RIRBBase &= UINT64_C(0xFFFFFFFF00000000); + pThis->u64RIRBBase |= pThis->au32Regs[iRegMem]; + break; + case HDA_REG_RIRBUBASE: + pThis->u64RIRBBase &= UINT64_C(0x00000000FFFFFFFF); + pThis->u64RIRBBase |= (uint64_t)pThis->au32Regs[iRegMem] << 32; + break; + case HDA_REG_DPLBASE: + pThis->u64DPBase = pThis->au32Regs[iRegMem] & DPBASE_ADDR_MASK; + Assert(pThis->u64DPBase % 128 == 0); /* Must be 128-byte aligned. */ + + /* Also make sure to handle the DMA position enable bit. */ + pThis->fDMAPosition = pThis->au32Regs[iRegMem] & RT_BIT_32(0); + +#ifndef IN_RING0 + LogRel(("HDA: DP base (lower) set: %#RGp\n", pThis->u64DPBase)); + LogRel(("HDA: DMA position buffer is %s\n", pThis->fDMAPosition ? "enabled" : "disabled")); +#else + return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */ +#endif + break; + case HDA_REG_DPUBASE: + pThis->u64DPBase = RT_MAKE_U64(RT_LO_U32(pThis->u64DPBase) & DPBASE_ADDR_MASK, pThis->au32Regs[iRegMem]); +#ifndef IN_RING0 + LogRel(("HDA: DP base (upper) set: %#RGp\n", pThis->u64DPBase)); +#else + return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */ +#endif + break; + default: + AssertMsgFailed(("Invalid index\n")); + break; + } + + LogFunc(("CORB base:%llx RIRB base: %llx DP base: %llx\n", + pThis->u64CORBBase, pThis->u64RIRBBase, pThis->u64DPBase)); + return rc; +} + +static VBOXSTRICTRC hdaRegWriteRIRBSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, iReg); + + uint8_t v = HDA_REG(pThis, RIRBSTS); + HDA_REG(pThis, RIRBSTS) &= ~(v & u32Value); + + HDA_PROCESS_INTERRUPT(pDevIns, pThis); + return VINF_SUCCESS; +} + +#ifdef IN_RING3 + +/** + * Retrieves a corresponding sink for a given mixer control. + * + * @return Pointer to the sink, NULL if no sink is found. + * @param pThisCC The ring-3 HDA device state. + * @param enmMixerCtl Mixer control to get the corresponding sink for. + */ +static PHDAMIXERSINK hdaR3MixerControlToSink(PHDASTATER3 pThisCC, PDMAUDIOMIXERCTL enmMixerCtl) +{ + PHDAMIXERSINK pSink; + + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_VOLUME_MASTER: + /* Fall through is intentional. */ + case PDMAUDIOMIXERCTL_FRONT: + pSink = &pThisCC->SinkFront; + break; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOMIXERCTL_CENTER_LFE: + pSink = &pThisCC->SinkCenterLFE; + break; + case PDMAUDIOMIXERCTL_REAR: + pSink = &pThisCC->SinkRear; + break; +# endif + case PDMAUDIOMIXERCTL_LINE_IN: + pSink = &pThisCC->SinkLineIn; + break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOMIXERCTL_MIC_IN: + pSink = &pThisCC->SinkMicIn; + break; +# endif + default: + AssertMsgFailed(("Unhandled mixer control\n")); + pSink = NULL; + break; + } + + return pSink; +} + +/** + * Adds a specific HDA driver to the driver chain. + * + * @returns VBox status code. + * @param pDevIns The HDA device instance. + * @param pThisCC The ring-3 HDA device state. + * @param pDrv HDA driver to add. + */ +static int hdaR3MixerAddDrv(PPDMDEVINS pDevIns, PHDASTATER3 pThisCC, PHDADRIVER pDrv) +{ + int rc = VINF_SUCCESS; + + PHDASTREAM pStream = pThisCC->SinkLineIn.pStreamShared; + if ( pStream + && AudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkLineIn.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + pStream = pThisCC->SinkMicIn.pStreamShared; + if ( pStream + && AudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkMicIn.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } +# endif + + pStream = pThisCC->SinkFront.pStreamShared; + if ( pStream + && AudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkFront.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + pStream = pThisCC->SinkCenterLFE.pStreamShared; + if ( pStream + && AudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkCenterLFE.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + pStream = pThisCC->SinkRear.pStreamShared; + if ( pStream + && AudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkRear.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } +# endif + + return rc; +} + +/** + * Removes a specific HDA driver from the driver chain and destroys its + * associated streams. + * + * @param pDevIns The device instance. + * @param pThisCC The ring-3 HDA device state. + * @param pDrv HDA driver to remove. + */ +static void hdaR3MixerRemoveDrv(PPDMDEVINS pDevIns, PHDASTATER3 pThisCC, PHDADRIVER pDrv) +{ + AssertPtrReturnVoid(pDrv); + + if (pDrv->LineIn.pMixStrm) + { + AudioMixerSinkRemoveStream(pThisCC->SinkLineIn.pMixSink, pDrv->LineIn.pMixStrm); + AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->LineIn.pMixStrm = NULL; + } + +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + if (pDrv->MicIn.pMixStrm) + { + AudioMixerSinkRemoveStream(pThisCC->SinkMicIn.pMixSink, pDrv->MicIn.pMixStrm); + AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->MicIn.pMixStrm = NULL; + } +# endif + + if (pDrv->Front.pMixStrm) + { + AudioMixerSinkRemoveStream(pThisCC->SinkFront.pMixSink, pDrv->Front.pMixStrm); + AudioMixerStreamDestroy(pDrv->Front.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->Front.pMixStrm = NULL; + } + +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + if (pDrv->CenterLFE.pMixStrm) + { + AudioMixerSinkRemoveStream(pThisCC->SinkCenterLFE.pMixSink, pDrv->CenterLFE.pMixStrm); + AudioMixerStreamDestroy(pDrv->CenterLFE.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->CenterLFE.pMixStrm = NULL; + } + + if (pDrv->Rear.pMixStrm) + { + AudioMixerSinkRemoveStream(pThisCC->SinkRear.pMixSink, pDrv->Rear.pMixStrm); + AudioMixerStreamDestroy(pDrv->Rear.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->Rear.pMixStrm = NULL; + } +# endif + + RTListNodeRemove(&pDrv->Node); +} + +/** + * Adds a driver stream to a specific mixer sink. + * + * @returns VBox status code (ignored by caller). + * @param pDevIns The HDA device instance. + * @param pMixSink Audio mixer sink to add audio streams to. + * @param pCfg Audio stream configuration to use for the audio + * streams to add. + * @param pDrv Driver stream to add. + */ +static int hdaR3MixerAddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv) +{ + AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + LogFunc(("szSink=%s, szStream=%s, cChannels=%RU8\n", pMixSink->pszName, pCfg->szName, PDMAudioPropsChannels(&pCfg->Props))); + + /* + * Get the matching stream driver. + */ + PHDADRIVERSTREAM pDrvStream = NULL; + if (pCfg->enmDir == PDMAUDIODIR_IN) + { + LogFunc(("enmPath=%d (src)\n", pCfg->enmPath)); + switch (pCfg->enmPath) + { + case PDMAUDIOPATH_IN_LINE: + pDrvStream = &pDrv->LineIn; + break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOPATH_IN_MIC: + pDrvStream = &pDrv->MicIn; + break; +# endif + default: + LogFunc(("returns VERR_NOT_SUPPORTED - enmPath=%d\n", pCfg->enmPath)); + return VERR_NOT_SUPPORTED; + } + } + else if (pCfg->enmDir == PDMAUDIODIR_OUT) + { + LogFunc(("enmDst=%d %s (dst)\n", pCfg->enmPath, PDMAudioPathGetName(pCfg->enmPath))); + switch (pCfg->enmPath) + { + case PDMAUDIOPATH_OUT_FRONT: + pDrvStream = &pDrv->Front; + break; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOPATH_OUT_CENTER_LFE: + pDrvStream = &pDrv->CenterLFE; + break; + case PDMAUDIOPATH_OUT_REAR: + pDrvStream = &pDrv->Rear; + break; +# endif + default: + LogFunc(("returns VERR_NOT_SUPPORTED - enmPath=%d %s\n", pCfg->enmPath, PDMAudioPathGetName(pCfg->enmPath))); + return VERR_NOT_SUPPORTED; + } + } + else + AssertFailedReturn(VERR_NOT_SUPPORTED); + + LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfg->szName)); + + AssertPtr(pDrvStream); + AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN)); + + PAUDMIXSTREAM pMixStrm = NULL; + int rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pCfg, pDevIns, &pMixStrm); + LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc)); + if (RT_SUCCESS(rc)) + { + rc = AudioMixerSinkAddStream(pMixSink, pMixStrm); + LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc)); + if (RT_FAILURE(rc)) + AudioMixerStreamDestroy(pMixStrm, pDevIns, true /*fImmediate*/); + } + + if (RT_SUCCESS(rc)) + pDrvStream->pMixStrm = pMixStrm; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds all current driver streams to a specific mixer sink. + * + * @returns VBox status code. + * @param pDevIns The HDA device instance. + * @param pThisCC The ring-3 HDA device state. + * @param pMixSink Audio mixer sink to add stream to. + * @param pCfg Audio stream configuration to use for the audio streams + * to add. + */ +static int hdaR3MixerAddDrvStreams(PPDMDEVINS pDevIns, PHDASTATER3 pThisCC, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + LogFunc(("Sink=%s, Stream=%s\n", pMixSink->pszName, pCfg->szName)); + + int rc; + if (AudioHlpStreamCfgIsValid(pCfg)) + { + rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props, pCfg->Device.cMsSchedulingHint); + if (RT_SUCCESS(rc)) + { + PHDADRIVER pDrv; + RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node) + { + /* We ignore failures here because one non-working driver shouldn't + be allowed to spoil it for everyone else. */ + int rc2 = hdaR3MixerAddDrvStream(pDevIns, pMixSink, pCfg, pDrv); + if (RT_FAILURE(rc2)) + LogFunc(("Attaching stream failed with %Rrc (ignored)\n", rc2)); + } + } + } + else + rc = VERR_INVALID_PARAMETER; + return rc; +} + + +/** + * Adds a new audio stream to a specific mixer control. + * + * Depending on the mixer control the stream then gets assigned to one of the + * internal mixer sinks, which in turn then handle the mixing of all connected + * streams to that sink. + * + * @return VBox status code. + * @param pCodec The codec instance data. + * @param enmMixerCtl Mixer control to assign new stream to. + * @param pCfg Stream configuration for the new stream. + */ +DECLHIDDEN(int) hdaR3MixerAddStream(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, PCPDMAUDIOSTREAMCFG pCfg) +{ + PHDASTATER3 pThisCC = RT_FROM_MEMBER(pCodec, HDASTATER3, Codec); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + int rc; + PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); + if (pSink) + { + rc = hdaR3MixerAddDrvStreams(pThisCC->pDevIns, pThisCC, pSink->pMixSink, pCfg); + + AssertPtr(pSink->pMixSink); + LogFlowFunc(("Sink=%s, Mixer control=%s\n", pSink->pMixSink->pszName, PDMAudioMixerCtlGetName(enmMixerCtl))); + } + else + rc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Removes a specified mixer control from the HDA's mixer. + * + * @return VBox status code. + * @param pCodec The codec instance data. + * @param enmMixerCtl Mixer control to remove. + * @param fImmediate Whether the backend should be allowed to + * finished draining (@c false) or if it must be + * destroyed immediately (@c true). + */ +DECLHIDDEN(int) hdaR3MixerRemoveStream(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, bool fImmediate) +{ + PHDASTATER3 pThisCC = RT_FROM_MEMBER(pCodec, HDASTATER3, Codec); + int rc; + + PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); + if (pSink) + { + PHDADRIVER pDrv; + RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node) + { + PAUDMIXSTREAM pMixStream = NULL; + switch (enmMixerCtl) + { + /* + * Input. + */ + case PDMAUDIOMIXERCTL_LINE_IN: + pMixStream = pDrv->LineIn.pMixStrm; + pDrv->LineIn.pMixStrm = NULL; + break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOMIXERCTL_MIC_IN: + pMixStream = pDrv->MicIn.pMixStrm; + pDrv->MicIn.pMixStrm = NULL; + break; +# endif + /* + * Output. + */ + case PDMAUDIOMIXERCTL_FRONT: + pMixStream = pDrv->Front.pMixStrm; + pDrv->Front.pMixStrm = NULL; + break; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOMIXERCTL_CENTER_LFE: + pMixStream = pDrv->CenterLFE.pMixStrm; + pDrv->CenterLFE.pMixStrm = NULL; + break; + case PDMAUDIOMIXERCTL_REAR: + pMixStream = pDrv->Rear.pMixStrm; + pDrv->Rear.pMixStrm = NULL; + break; +# endif + default: + AssertMsgFailed(("Mixer control %d not implemented\n", enmMixerCtl)); + break; + } + + if (pMixStream) + { + AudioMixerSinkRemoveStream(pSink->pMixSink, pMixStream); + AudioMixerStreamDestroy(pMixStream, pThisCC->pDevIns, fImmediate); + + pMixStream = NULL; + } + } + + AudioMixerSinkRemoveAllStreams(pSink->pMixSink); + rc = VINF_SUCCESS; + } + else + rc = VERR_NOT_FOUND; + + LogFunc(("Mixer control=%s, rc=%Rrc\n", PDMAudioMixerCtlGetName(enmMixerCtl), rc)); + return rc; +} + +/** + * Controls an input / output converter widget, that is, which converter is + * connected to which stream (and channel). + * + * @return VBox status code. + * @param pCodec The codec instance data. + * @param enmMixerCtl Mixer control to set SD stream number and channel for. + * @param uSD SD stream number (number + 1) to set. Set to 0 for unassign. + * @param uChannel Channel to set. Only valid if a valid SD stream number is specified. + * + * @note Is also called directly by the DevHDA code. + */ +DECLHIDDEN(int) hdaR3MixerControl(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel) +{ + PHDASTATER3 pThisCC = RT_FROM_MEMBER(pCodec, HDASTATER3, Codec); + PHDASTATE pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PHDASTATE); + LogFunc(("enmMixerCtl=%s, uSD=%RU8, uChannel=%RU8\n", PDMAudioMixerCtlGetName(enmMixerCtl), uSD, uChannel)); + + if (uSD == 0) /* Stream number 0 is reserved. */ + { + Log2Func(("Invalid SDn (%RU8) number for mixer control '%s', ignoring\n", uSD, PDMAudioMixerCtlGetName(enmMixerCtl))); + return VINF_SUCCESS; + } + /* uChannel is optional. */ + + /* SDn0 starts as 1. */ + Assert(uSD); + uSD--; + +# ifndef VBOX_WITH_AUDIO_HDA_MIC_IN + /* Only SDI0 (Line-In) is supported. */ + if ( hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN + && uSD >= 1) + { + LogRel2(("HDA: Dedicated Mic-In support not imlpemented / built-in (stream #%RU8), using Line-In (stream #0) instead\n", uSD)); + uSD = 0; + } +# endif + + int rc = VINF_SUCCESS; + + PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); + if (pSink) + { + AssertPtr(pSink->pMixSink); + + /* If this an output stream, determine the correct SD#. */ + if ( uSD < HDA_MAX_SDI + && AudioMixerSinkGetDir(pSink->pMixSink) == PDMAUDIODIR_OUT) + uSD += HDA_MAX_SDI; + + /* Make 100% sure we got a good stream number before continuing. */ + AssertLogRelReturn(uSD < RT_ELEMENTS(pThisCC->aStreams), VERR_NOT_IMPLEMENTED); + + /* Detach the existing stream from the sink. */ + PHDASTREAM const pOldStreamShared = pSink->pStreamShared; + PHDASTREAMR3 const pOldStreamR3 = pSink->pStreamR3; + if ( pOldStreamShared + && pOldStreamR3 + && ( pOldStreamShared->u8SD != uSD + || pOldStreamShared->u8Channel != uChannel) + ) + { + LogFunc(("Sink '%s' was assigned to stream #%RU8 (channel %RU8) before\n", + pSink->pMixSink->pszName, pOldStreamShared->u8SD, pOldStreamShared->u8Channel)); + Assert(PDMCritSectIsOwner(&pThis->CritSect)); + + /* Only disable the stream if the stream descriptor # has changed. */ + if (pOldStreamShared->u8SD != uSD) + hdaR3StreamEnable(pThis, pOldStreamShared, pOldStreamR3, false /*fEnable*/); + + if (pOldStreamR3->State.pAioRegSink) + { + AudioMixerSinkRemoveUpdateJob(pOldStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pOldStreamR3); + pOldStreamR3->State.pAioRegSink = NULL; + } + + pOldStreamR3->pMixSink = NULL; + + + pSink->pStreamShared = NULL; + pSink->pStreamR3 = NULL; + } + + /* Attach the new stream to the sink. + * Enabling the stream will be done by the guest via a separate SDnCTL call then. */ + if (pSink->pStreamShared == NULL) + { + LogRel2(("HDA: Setting sink '%s' to stream #%RU8 (channel %RU8), mixer control=%s\n", + pSink->pMixSink->pszName, uSD, uChannel, PDMAudioMixerCtlGetName(enmMixerCtl))); + + PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[uSD]; + PHDASTREAM pStreamShared = &pThis->aStreams[uSD]; + Assert(PDMCritSectIsOwner(&pThis->CritSect)); + + pSink->pStreamR3 = pStreamR3; + pSink->pStreamShared = pStreamShared; + + pStreamShared->u8Channel = uChannel; + pStreamR3->pMixSink = pSink; + + rc = VINF_SUCCESS; + } + } + else + rc = VERR_NOT_FOUND; + + if (RT_FAILURE(rc)) + LogRel(("HDA: Converter control for stream #%RU8 (channel %RU8) / mixer control '%s' failed with %Rrc, skipping\n", + uSD, uChannel, PDMAudioMixerCtlGetName(enmMixerCtl), rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets the volume of a specified mixer control. + * + * @return IPRT status code. + * @param pCodec The codec instance data. + * @param enmMixerCtl Mixer control to set volume for. + * @param pVol Pointer to volume data to set. + */ +DECLHIDDEN(int) hdaR3MixerSetVolume(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol) +{ + PHDASTATER3 pThisCC = RT_FROM_MEMBER(pCodec, HDASTATER3, Codec); + int rc; + + PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); + if ( pSink + && pSink->pMixSink) + { + LogRel2(("HDA: Setting volume for mixer sink '%s' to fMuted=%RTbool auChannels=%.*Rhxs\n", + pSink->pMixSink->pszName, pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels)); + + /* Set the volume. + * We assume that the codec already converted it to the correct range. */ + rc = AudioMixerSinkSetVolume(pSink->pMixSink, pVol); + } + else + rc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @callback_method_impl{FNTMTIMERDEV, Main routine for the stream's timer.} + */ +static DECLCALLBACK(void) hdaR3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + uintptr_t idxStream = (uintptr_t)pvUser; + AssertReturnVoid(idxStream < RT_ELEMENTS(pThis->aStreams)); + PHDASTREAM pStreamShared = &pThis->aStreams[idxStream]; + PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[idxStream]; + RT_NOREF(pTimer); + + Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + Assert(PDMDevHlpTimerIsLockOwner(pDevIns, pStreamShared->hTimer)); + + hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3); +} + +/** + * Soft reset of the device triggered via GCTL. + * + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pThisCC The ring-3 HDA device state. + */ +static void hdaR3GCTLReset(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC) +{ + LogFlowFuncEnter(); + Assert(PDMCritSectIsOwner(&pThis->CritSect)); + + /* + * Make sure all streams have stopped as these have both timers and + * asynchronous worker threads that would race us if we delay this work. + */ + for (size_t idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++) + { + PHDASTREAM const pStreamShared = &pThis->aStreams[idxStream]; + PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[idxStream]; + PAUDMIXSINK const pMixSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL; + if (pMixSink) + AudioMixerSinkLock(pMixSink); + + /* We're doing this unconditionally, hope that's not problematic in any way... */ + int rc = hdaR3StreamEnable(pThis, pStreamShared, &pThisCC->aStreams[idxStream], false /* fEnable */); + AssertLogRelMsg(RT_SUCCESS(rc) && !pStreamShared->State.fRunning, + ("Disabling stream #%u failed: %Rrc, fRunning=%d\n", idxStream, rc, pStreamShared->State.fRunning)); + pStreamShared->State.fRunning = false; + + hdaR3StreamReset(pThis, pThisCC, pStreamShared, &pThisCC->aStreams[idxStream], (uint8_t)idxStream); + + if (pMixSink) /* (FYI. pMixSink might not be what pStreamR3->pMixSink->pMixSink points at any longer) */ + AudioMixerSinkUnlock(pMixSink); + } + + /* + * Reset registers. + */ + HDA_REG(pThis, GCAP) = HDA_MAKE_GCAP(HDA_MAX_SDO, HDA_MAX_SDI, 0, 0, 1); /* see 6.2.1 */ + HDA_REG(pThis, VMIN) = 0x00; /* see 6.2.2 */ + HDA_REG(pThis, VMAJ) = 0x01; /* see 6.2.3 */ + HDA_REG(pThis, OUTPAY) = 0x003C; /* see 6.2.4 */ + HDA_REG(pThis, INPAY) = 0x001D; /* see 6.2.5 */ + HDA_REG(pThis, CORBSIZE) = 0x42; /* Up to 256 CORB entries see 6.2.1 */ + HDA_REG(pThis, RIRBSIZE) = 0x42; /* Up to 256 RIRB entries see 6.2.1 */ + HDA_REG(pThis, CORBRP) = 0x0; + HDA_REG(pThis, CORBWP) = 0x0; + HDA_REG(pThis, RIRBWP) = 0x0; + /* Some guests (like Haiku) don't set RINTCNT explicitly but expect an interrupt after each + * RIRB response -- so initialize RINTCNT to 1 by default. */ + HDA_REG(pThis, RINTCNT) = 0x1; + /* For newer devices, there is a capability list offset word at 0x14, linux read it, does + no checking and simply reads the dword it specifies. The list terminates when the lower + 16 bits are zero. See snd_hdac_bus_parse_capabilities. Table 5-2 in intel 341081-002 + specifies this to be 0xc00 and chaining with 0x800, 0x500 and 0x1f00. We just terminate + it at 0xc00 for now. */ + HDA_REG(pThis, LLCH) = 0xc00; + HDA_REG(pThis, MLCH) = 0x0; + HDA_REG(pThis, MLCD) = 0x0; + + /* + * Stop any audio currently playing and/or recording. + */ + pThisCC->SinkFront.pStreamShared = NULL; + pThisCC->SinkFront.pStreamR3 = NULL; + if (pThisCC->SinkFront.pMixSink) + AudioMixerSinkReset(pThisCC->SinkFront.pMixSink); +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + pThisCC->SinkMicIn.pStreamShared = NULL; + pThisCC->SinkMicIn.pStreamR3 = NULL; + if (pThisCC->SinkMicIn.pMixSink) + AudioMixerSinkReset(pThisCC->SinkMicIn.pMixSink); +# endif + pThisCC->SinkLineIn.pStreamShared = NULL; + pThisCC->SinkLineIn.pStreamR3 = NULL; + if (pThisCC->SinkLineIn.pMixSink) + AudioMixerSinkReset(pThisCC->SinkLineIn.pMixSink); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + pThisCC->SinkCenterLFE = NULL; + if (pThisCC->SinkCenterLFE.pMixSink) + AudioMixerSinkReset(pThisCC->SinkCenterLFE.pMixSink); + pThisCC->SinkRear.pStreamShared = NULL; + pThisCC->SinkRear.pStreamR3 = NULL; + if (pThisCC->SinkRear.pMixSink) + AudioMixerSinkReset(pThisCC->SinkRear.pMixSink); +# endif + + /* + * Reset the codec. + */ + hdaCodecReset(&pThisCC->Codec); + + /* + * Set some sensible defaults for which HDA sinks + * are connected to which stream number. + * + * We use SD0 for input and SD4 for output by default. + * These stream numbers can be changed by the guest dynamically lateron. + */ + ASMCompilerBarrier(); /* paranoia */ +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_MIC_IN , 1 /* SD0 */, 0 /* Channel */); +# endif + hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_LINE_IN , 1 /* SD0 */, 0 /* Channel */); + + hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_FRONT , 5 /* SD4 */, 0 /* Channel */); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_CENTER_LFE, 5 /* SD4 */, 0 /* Channel */); + hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_REAR , 5 /* SD4 */, 0 /* Channel */); +# endif + ASMCompilerBarrier(); /* paranoia */ + + /* Reset CORB. */ + pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE; + RT_ZERO(pThis->au32CorbBuf); + + /* Reset RIRB. */ + pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE; + RT_ZERO(pThis->au64RirbBuf); + + /* Clear our internal response interrupt counter. */ + pThis->u16RespIntCnt = 0; + + /* Clear stream tags <-> objects mapping table. */ + RT_ZERO(pThisCC->aTags); + + /* Emulation of codec "wake up" (HDA spec 5.5.1 and 6.5). */ + HDA_REG(pThis, STATESTS) = 0x1; + + /* Reset the wall clock. */ + pThis->tsWalClkStart = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer); + + LogFlowFuncLeave(); + LogRel(("HDA: Reset\n")); +} + +#else /* !IN_RING3 */ + +/** + * Checks if a dword read starting with @a idxRegDsc is safe. + * + * We can guarentee it only standard reader callbacks are used. + * @returns true if it will always succeed, false if it may return back to + * ring-3 or we're just not sure. + * @param idxRegDsc The first register descriptor in the DWORD being read. + */ +DECLINLINE(bool) hdaIsMultiReadSafeInRZ(unsigned idxRegDsc) +{ + int32_t cbLeft = 4; /* signed on purpose */ + do + { + if ( g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU24 + || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU16 + || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU8 + || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadUnimpl) + { /* okay */ } + else + { + Log4(("hdaIsMultiReadSafeInRZ: idxRegDsc=%u %s\n", idxRegDsc, g_aHdaRegMap[idxRegDsc].pszName)); + return false; + } + + idxRegDsc++; + if (idxRegDsc < RT_ELEMENTS(g_aHdaRegMap)) + cbLeft -= g_aHdaRegMap[idxRegDsc].off - g_aHdaRegMap[idxRegDsc - 1].off; + else + break; + } while (cbLeft > 0); + return true; +} + + +#endif /* !IN_RING3 */ + + +/* MMIO callbacks */ + +/** + * @callback_method_impl{FNIOMMMIONEWREAD, Looks up and calls the appropriate handler.} + * + * @note During implementation, we discovered so-called "forgotten" or "hole" + * registers whose description is not listed in the RPM, datasheet, or + * spec. + */ +static DECLCALLBACK(VBOXSTRICTRC) hdaMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + VBOXSTRICTRC rc; + RT_NOREF_PV(pvUser); + Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC); + + /* + * Look up and log. + */ + int idxRegDsc = hdaRegLookup(off); /* Register descriptor index. */ +#ifdef LOG_ENABLED + unsigned const cbLog = cb; + uint32_t offRegLog = (uint32_t)off; +# ifdef HDA_DEBUG_GUEST_RIP + if (LogIs6Enabled()) + { + PVMCPU pVCpu = (PVMCPU)PDMDevHlpGetVMCPU(pDevIns); + Log6Func(("cs:rip=%04x:%016RX64 rflags=%08RX32\n", CPUMGetGuestCS(pVCpu), CPUMGetGuestRIP(pVCpu), CPUMGetGuestEFlags(pVCpu))); + } +# endif +#endif + + Log3Func(("off=%#x cb=%#x\n", offRegLog, cb)); + Assert(cb == 4); Assert((off & 3) == 0); + + rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_IOM_R3_MMIO_READ); + if (rc == VINF_SUCCESS) + { + if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL) + LogFunc(("Access to registers except GCTL is blocked while resetting\n")); + + if (idxRegDsc >= 0) + { + /* ASSUMES gapless DWORD at end of map. */ + if (g_aHdaRegMap[idxRegDsc].cb == 4) + { + /* + * Straight forward DWORD access. + */ + rc = g_aHdaRegMap[idxRegDsc].pfnRead(pDevIns, pThis, idxRegDsc, (uint32_t *)pv); + Log3Func((" Read %s => %x (%Rrc)\n", g_aHdaRegMap[idxRegDsc].pszName, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc))); + STAM_COUNTER_INC(&pThis->aStatRegReads[idxRegDsc]); + } +#ifndef IN_RING3 + else if (!hdaIsMultiReadSafeInRZ(idxRegDsc)) + + { + STAM_COUNTER_INC(&pThis->aStatRegReadsToR3[idxRegDsc]); + rc = VINF_IOM_R3_MMIO_READ; + } +#endif + else + { + /* + * Multi register read (unless there are trailing gaps). + * ASSUMES that only DWORD reads have sideeffects. + */ + STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiReads)); + Log4(("hdaMmioRead: multi read: %#x LB %#x %s\n", off, cb, g_aHdaRegMap[idxRegDsc].pszName)); + uint32_t u32Value = 0; + unsigned cbLeft = 4; + do + { + uint32_t const cbReg = g_aHdaRegMap[idxRegDsc].cb; + uint32_t u32Tmp = 0; + + rc = g_aHdaRegMap[idxRegDsc].pfnRead(pDevIns, pThis, idxRegDsc, &u32Tmp); + Log4Func((" Read %s[%db] => %x (%Rrc)*\n", g_aHdaRegMap[idxRegDsc].pszName, cbReg, u32Tmp, VBOXSTRICTRC_VAL(rc))); + STAM_COUNTER_INC(&pThis->aStatRegReads[idxRegDsc]); +#ifdef IN_RING3 + if (rc != VINF_SUCCESS) + break; +#else + AssertMsgBreak(rc == VINF_SUCCESS, ("rc=%Rrc - impossible, we sanitized the readers!\n", VBOXSTRICTRC_VAL(rc))); +#endif + u32Value |= (u32Tmp & g_afMasks[cbReg]) << ((4 - cbLeft) * 8); + + cbLeft -= cbReg; + off += cbReg; + idxRegDsc++; + } while (cbLeft > 0 && g_aHdaRegMap[idxRegDsc].off == off); + + if (rc == VINF_SUCCESS) + *(uint32_t *)pv = u32Value; + else + Assert(!IOM_SUCCESS(rc)); + } + } + else + { + LogRel(("HDA: Invalid read access @0x%x (bytes=%u)\n", (uint32_t)off, cb)); + Log3Func((" Hole at %x is accessed for read\n", offRegLog)); + STAM_COUNTER_INC(&pThis->StatRegUnknownReads); + rc = VINF_IOM_MMIO_UNUSED_FF; + } + + DEVHDA_UNLOCK(pDevIns, pThis); + + /* + * Log the outcome. + */ +#ifdef LOG_ENABLED + if (cbLog == 4) + Log3Func((" Returning @%#05x -> %#010x %Rrc\n", offRegLog, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc))); + else if (cbLog == 2) + Log3Func((" Returning @%#05x -> %#06x %Rrc\n", offRegLog, *(uint16_t *)pv, VBOXSTRICTRC_VAL(rc))); + else if (cbLog == 1) + Log3Func((" Returning @%#05x -> %#04x %Rrc\n", offRegLog, *(uint8_t *)pv, VBOXSTRICTRC_VAL(rc))); +#endif + } + else + { + if (idxRegDsc >= 0) + STAM_COUNTER_INC(&pThis->aStatRegReadsToR3[idxRegDsc]); + } + return rc; +} + + +DECLINLINE(VBOXSTRICTRC) hdaWriteReg(PPDMDEVINS pDevIns, PHDASTATE pThis, int idxRegDsc, uint32_t u32Value, char const *pszLog) +{ + if ( (HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) + || idxRegDsc == HDA_REG_GCTL) + { /* likely */ } + else + { + Log(("hdaWriteReg: Warning: Access to %s is blocked while controller is in reset mode\n", g_aHdaRegMap[idxRegDsc].pszName)); +#if defined(IN_RING3) || defined(LOG_ENABLED) + LogRel2(("HDA: Warning: Access to register %s is blocked while controller is in reset mode\n", + g_aHdaRegMap[idxRegDsc].pszName)); +#endif + STAM_COUNTER_INC(&pThis->StatRegWritesBlockedByReset); + return VINF_SUCCESS; + } + + /* + * Handle RD (register description) flags. + */ + + /* For SDI / SDO: Check if writes to those registers are allowed while SDCTL's RUN bit is set. */ + if (idxRegDsc >= HDA_NUM_GENERAL_REGS) + { + /* + * Some OSes (like Win 10 AU) violate the spec by writing stuff to registers which are not supposed to be be touched + * while SDCTL's RUN bit is set. So just ignore those values. + */ + const uint32_t uSDCTL = HDA_STREAM_REG(pThis, CTL, HDA_SD_NUM_FROM_REG(pThis, CTL, idxRegDsc)); + if ( !(uSDCTL & HDA_SDCTL_RUN) + || (g_aHdaRegMap[idxRegDsc].fFlags & HDA_RD_F_SD_WRITE_RUN)) + { /* likely */ } + else + { + Log(("hdaWriteReg: Warning: Access to %s is blocked! %R[sdctl]\n", g_aHdaRegMap[idxRegDsc].pszName, uSDCTL)); +#if defined(IN_RING3) || defined(LOG_ENABLED) + LogRel2(("HDA: Warning: Access to register %s is blocked while the stream's RUN bit is set\n", + g_aHdaRegMap[idxRegDsc].pszName)); +#endif + STAM_COUNTER_INC(&pThis->StatRegWritesBlockedByRun); + return VINF_SUCCESS; + } + } + +#ifdef LOG_ENABLED + uint32_t const idxRegMem = g_aHdaRegMap[idxRegDsc].idxReg; + uint32_t const u32OldValue = pThis->au32Regs[idxRegMem]; +#endif + VBOXSTRICTRC rc = g_aHdaRegMap[idxRegDsc].pfnWrite(pDevIns, pThis, idxRegDsc, u32Value); + Log3Func(("Written value %#x to %s[%d byte]; %x => %x%s, rc=%d\n", u32Value, g_aHdaRegMap[idxRegDsc].pszName, + g_aHdaRegMap[idxRegDsc].cb, u32OldValue, pThis->au32Regs[idxRegMem], pszLog, VBOXSTRICTRC_VAL(rc))); +#ifndef IN_RING3 + if (rc == VINF_IOM_R3_MMIO_WRITE) + STAM_COUNTER_INC(&pThis->aStatRegWritesToR3[idxRegDsc]); + else +#endif + STAM_COUNTER_INC(&pThis->aStatRegWrites[idxRegDsc]); + + RT_NOREF(pszLog); + return rc; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE, + * Looks up and calls the appropriate handler.} + */ +static DECLCALLBACK(VBOXSTRICTRC) hdaMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + RT_NOREF_PV(pvUser); + Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC); + + /* + * Look up and log the access. + */ + int idxRegDsc = hdaRegLookup(off); +#if defined(IN_RING3) || defined(LOG_ENABLED) + uint32_t idxRegMem = idxRegDsc != -1 ? g_aHdaRegMap[idxRegDsc].idxReg : UINT32_MAX; +#endif + uint64_t u64Value; + if (cb == 4) u64Value = *(uint32_t const *)pv; + else if (cb == 2) u64Value = *(uint16_t const *)pv; + else if (cb == 1) u64Value = *(uint8_t const *)pv; + else if (cb == 8) u64Value = *(uint64_t const *)pv; + else + ASSERT_GUEST_MSG_FAILED_RETURN(("cb=%u %.*Rhxs\n", cb, cb, pv), + PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "odd write size: off=%RGp cb=%u\n", off, cb)); + + /* + * The behavior of accesses that aren't aligned on natural boundraries is + * undefined. Just reject them outright. + */ + ASSERT_GUEST_MSG_RETURN((off & (cb - 1)) == 0, ("off=%RGp cb=%u %.*Rhxs\n", off, cb, cb, pv), + PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "misaligned write access: off=%RGp cb=%u\n", off, cb)); + +#ifdef LOG_ENABLED + uint32_t const u32LogOldValue = idxRegDsc >= 0 ? pThis->au32Regs[idxRegMem] : UINT32_MAX; +# ifdef HDA_DEBUG_GUEST_RIP + if (LogIs6Enabled()) + { + PVMCPU pVCpu = (PVMCPU)PDMDevHlpGetVMCPU(pDevIns); + Log6Func(("cs:rip=%04x:%016RX64 rflags=%08RX32\n", CPUMGetGuestCS(pVCpu), CPUMGetGuestRIP(pVCpu), CPUMGetGuestEFlags(pVCpu))); + } +# endif +#endif + + /* + * Try for a direct hit first. + */ + VBOXSTRICTRC rc; + if (idxRegDsc >= 0 && g_aHdaRegMap[idxRegDsc].cb == cb) + { + DEVHDA_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); + + Log3Func(("@%#05x u%u=%#0*RX64 %s\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].pszName)); + rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value, ""); + Log3Func((" %#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX)); + + DEVHDA_UNLOCK(pDevIns, pThis); + } + /* + * Sub-register access. Supply missing bits as needed. + */ + else if ( idxRegDsc >= 0 + && cb < g_aHdaRegMap[idxRegDsc].cb) + { + DEVHDA_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); + + u64Value |= pThis->au32Regs[g_aHdaRegMap[idxRegDsc].idxReg] + & g_afMasks[g_aHdaRegMap[idxRegDsc].cb] + & ~g_afMasks[cb]; + Log4Func(("@%#05x u%u=%#0*RX64 cb=%#x cbReg=%x %s\n" + "hdaMmioWrite: Supplying missing bits (%#x): %#llx -> %#llx ...\n", + (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, cb, g_aHdaRegMap[idxRegDsc].cb, g_aHdaRegMap[idxRegDsc].pszName, + g_afMasks[g_aHdaRegMap[idxRegDsc].cb] & ~g_afMasks[cb], u64Value & g_afMasks[cb], u64Value)); + rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value, ""); + Log4Func((" %#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX)); + STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegSubWrite)); + + DEVHDA_UNLOCK(pDevIns, pThis); + } + /* + * Partial or multiple register access, loop thru the requested memory. + */ + else + { +#ifdef IN_RING3 + DEVHDA_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); + + if (idxRegDsc == -1) + Log4Func(("@%#05x u32=%#010x cb=%d\n", (uint32_t)off, *(uint32_t const *)pv, cb)); + else if (g_aHdaRegMap[idxRegDsc].cb == cb) + Log4Func(("@%#05x u%u=%#0*RX64 %s\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].pszName)); + else + Log4Func(("@%#05x u%u=%#0*RX64 %s - mismatch cbReg=%u\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, + g_aHdaRegMap[idxRegDsc].pszName, g_aHdaRegMap[idxRegDsc].cb)); + + /* + * If it's an access beyond the start of the register, shift the input + * value and fill in missing bits. Natural alignment rules means we + * will only see 1 or 2 byte accesses of this kind, so no risk of + * shifting out input values. + */ + if (idxRegDsc < 0) + { + idxRegDsc = hdaR3RegLookupWithin(off); + if (idxRegDsc != -1) + { + uint32_t const cbBefore = (uint32_t)off - g_aHdaRegMap[idxRegDsc].off; + Assert(cbBefore > 0 && cbBefore < 4); + off -= cbBefore; + idxRegMem = g_aHdaRegMap[idxRegDsc].idxReg; + u64Value <<= cbBefore * 8; + u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbBefore]; + Log4Func((" Within register, supplied %u leading bits: %#llx -> %#llx ...\n", + cbBefore * 8, ~(uint64_t)g_afMasks[cbBefore] & u64Value, u64Value)); + STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiWrites)); + } + else + STAM_COUNTER_INC(&pThis->StatRegUnknownWrites); + } + else + { + Log4(("hdaMmioWrite: multi write: %s\n", g_aHdaRegMap[idxRegDsc].pszName)); + STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiWrites)); + } + + /* Loop thru the write area, it may cover multiple registers. */ + rc = VINF_SUCCESS; + for (;;) + { + uint32_t cbReg; + if (idxRegDsc >= 0) + { + idxRegMem = g_aHdaRegMap[idxRegDsc].idxReg; + cbReg = g_aHdaRegMap[idxRegDsc].cb; + if (cb < cbReg) + { + u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbReg] & ~g_afMasks[cb]; + Log4Func((" Supplying missing bits (%#x): %#llx -> %#llx ...\n", + g_afMasks[cbReg] & ~g_afMasks[cb], u64Value & g_afMasks[cb], u64Value)); + } +# ifdef LOG_ENABLED + uint32_t uLogOldVal = pThis->au32Regs[idxRegMem]; +# endif + rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value & g_afMasks[cbReg], "*"); + Log4Func((" %#x -> %#x\n", uLogOldVal, pThis->au32Regs[idxRegMem])); + } + else + { + LogRel(("HDA: Invalid write access @0x%x\n", (uint32_t)off)); + cbReg = 1; + } + if (rc != VINF_SUCCESS) + break; + if (cbReg >= cb) + break; + + /* Advance. */ + off += cbReg; + cb -= cbReg; + u64Value >>= cbReg * 8; + if (idxRegDsc == -1) + idxRegDsc = hdaRegLookup(off); + else + { + idxRegDsc++; + if ( (unsigned)idxRegDsc >= RT_ELEMENTS(g_aHdaRegMap) + || g_aHdaRegMap[idxRegDsc].off != off) + idxRegDsc = -1; + } + } + + DEVHDA_UNLOCK(pDevIns, pThis); + +#else /* !IN_RING3 */ + /* Take the simple way out. */ + rc = VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ + } + + return rc; +} + +#ifdef IN_RING3 + + +/********************************************************************************************************************************* +* Saved state * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNSSMFIELDGETPUT, + * Version 6 saves the IOC flag in HDABDLEDESC::fFlags as a bool} + */ +static DECLCALLBACK(int) +hdaR3GetPutTrans_HDABDLEDESC_fFlags_6(PSSMHANDLE pSSM, const struct SSMFIELD *pField, void *pvStruct, + uint32_t fFlags, bool fGetOrPut, void *pvUser) +{ + PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; + RT_NOREF(pSSM, pField, pvStruct, fFlags); + AssertReturn(fGetOrPut, VERR_INTERNAL_ERROR_4); + bool fIoc; + int rc = pDevIns->pHlpR3->pfnSSMGetBool(pSSM, &fIoc); + if (RT_SUCCESS(rc)) + { + PHDABDLEDESC pDesc = (PHDABDLEDESC)pvStruct; + pDesc->fFlags = fIoc ? HDA_BDLE_F_IOC : 0; + } + return rc; +} + + +/** + * @callback_method_impl{FNSSMFIELDGETPUT, + * Versions 1 thru 4 save the IOC flag in HDASTREAMSTATE::DescfFlags as a bool} + */ +static DECLCALLBACK(int) +hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4(PSSMHANDLE pSSM, const struct SSMFIELD *pField, void *pvStruct, + uint32_t fFlags, bool fGetOrPut, void *pvUser) +{ + PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; + RT_NOREF(pSSM, pField, pvStruct, fFlags); + AssertReturn(fGetOrPut, VERR_INTERNAL_ERROR_4); + bool fIoc; + int rc = pDevIns->pHlpR3->pfnSSMGetBool(pSSM, &fIoc); + if (RT_SUCCESS(rc)) + { + HDABDLELEGACY *pState = (HDABDLELEGACY *)pvStruct; + pState->Desc.fFlags = fIoc ? HDA_BDLE_F_IOC : 0; + } + return rc; +} + + +static int hdaR3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3) +{ + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; +# ifdef LOG_ENABLED + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); +# endif + + Log2Func(("[SD%RU8]\n", pStreamShared->u8SD)); + + /* Save stream ID. */ + Assert(pStreamShared->u8SD < HDA_MAX_STREAMS); + int rc = pHlp->pfnSSMPutU8(pSSM, pStreamShared->u8SD); + AssertRCReturn(rc, rc); + + rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State, sizeof(pStreamShared->State), + 0 /*fFlags*/, g_aSSMStreamStateFields7, NULL); + AssertRCReturn(rc, rc); + + AssertCompile(sizeof(pStreamShared->State.idxCurBdle) == sizeof(uint8_t) && RT_ELEMENTS(pStreamShared->State.aBdl) == 256); + HDABDLEDESC TmpDesc = *(HDABDLEDESC *)&pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle]; + rc = pHlp->pfnSSMPutStructEx(pSSM, &TmpDesc, sizeof(TmpDesc), 0 /*fFlags*/, g_aSSMBDLEDescFields7, NULL); + AssertRCReturn(rc, rc); + + HDABDLESTATELEGACY TmpState = { pStreamShared->State.idxCurBdle, 0, pStreamShared->State.offCurBdle, 0 }; + rc = pHlp->pfnSSMPutStructEx(pSSM, &TmpState, sizeof(TmpState), 0 /*fFlags*/, g_aSSMBDLEStateFields7, NULL); + AssertRCReturn(rc, rc); + + PAUDMIXSINK pSink = NULL; + uint32_t cbCircBuf = 0; + uint32_t cbCircBufUsed = 0; + if (pStreamR3->State.pCircBuf) + { + cbCircBuf = (uint32_t)RTCircBufSize(pStreamR3->State.pCircBuf); + + /* We take the AIO lock here and releases it after saving the buffer, + otherwise the AIO thread could race us reading out the buffer data. */ + pSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL; + if ( !pSink + || RT_SUCCESS(AudioMixerSinkTryLock(pSink))) + { + cbCircBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); + if (cbCircBufUsed == 0 && pSink) + AudioMixerSinkUnlock(pSink); + } + } + + pHlp->pfnSSMPutU32(pSSM, cbCircBuf); + rc = pHlp->pfnSSMPutU32(pSSM, cbCircBufUsed); + + if (cbCircBufUsed > 0) + { + /* HACK ALERT! We cannot remove data from the buffer (live snapshot), + we use RTCircBufOffsetRead and RTCircBufAcquireReadBlock + creatively to get at the other buffer segment in case + of a wraparound. */ + size_t const offBuf = RTCircBufOffsetRead(pStreamR3->State.pCircBuf); + void *pvBuf = NULL; + size_t cbBuf = 0; + RTCircBufAcquireReadBlock(pStreamR3->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf); + Assert(cbBuf); + rc = pHlp->pfnSSMPutMem(pSSM, pvBuf, cbBuf); + if (cbBuf < cbCircBufUsed) + rc = pHlp->pfnSSMPutMem(pSSM, (uint8_t *)pvBuf - offBuf, cbCircBufUsed - cbBuf); + RTCircBufReleaseReadBlock(pStreamR3->State.pCircBuf, 0 /* Don't advance read pointer! */); + + if (pSink) + AudioMixerSinkUnlock(pSink); + } + + Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", pStreamR3->u8SD, HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD), + HDA_STREAM_REG(pThis, CBL, pStreamShared->u8SD), HDA_STREAM_REG(pThis, LVI, pStreamShared->u8SD))); + +#ifdef LOG_ENABLED + hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); +#endif + + return rc; +} + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) hdaR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + /* Save Codec nodes states. */ + hdaCodecSaveState(pDevIns, &pThisCC->Codec, pSSM); + + /* Save MMIO registers. */ + pHlp->pfnSSMPutU32(pSSM, RT_ELEMENTS(pThis->au32Regs)); + pHlp->pfnSSMPutMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); + + /* Save controller-specifc internals. */ + pHlp->pfnSSMPutU64(pSSM, pThis->tsWalClkStart); + pHlp->pfnSSMPutU8(pSSM, pThis->u8IRQL); + + /* Save number of streams. */ + pHlp->pfnSSMPutU32(pSSM, HDA_MAX_STREAMS); + + /* Save stream states. */ + for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) + { + int rc = hdaR3SaveStream(pDevIns, pSSM, &pThis->aStreams[i], &pThisCC->aStreams[i]); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNSSMDEVLOADDONE, + * Finishes stream setup and resuming.} + */ +static DECLCALLBACK(int) hdaR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + LogFlowFuncEnter(); + + /* + * Enable all previously active streams. + */ + for (size_t i = 0; i < HDA_MAX_STREAMS; i++) + { + PHDASTREAM pStreamShared = &pThis->aStreams[i]; + + bool fActive = RT_BOOL(HDA_STREAM_REG(pThis, CTL, i) & HDA_SDCTL_RUN); + if (fActive) + { + PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[i]; + + /* (Re-)enable the stream. */ + int rc2 = hdaR3StreamEnable(pThis, pStreamShared, pStreamR3, true /* fEnable */); + AssertRC(rc2); + + /* Add the stream to the device setup. */ + rc2 = hdaR3AddStream(pThisCC, &pStreamShared->State.Cfg); + AssertRC(rc2); + + /* Use the LPIB to find the current scheduling position. If this isn't + exactly on a scheduling item adjust LPIB down to the start of the + current. This isn't entirely ideal, but it avoid the IRQ counting + issue if we round it upwards. (it is also a lot simpler) */ + uint32_t uLpib = HDA_STREAM_REG(pThis, LPIB, i); + AssertLogRelMsgStmt(uLpib < pStreamShared->u32CBL, ("LPIB=%#RX32 CBL=%#RX32\n", uLpib, pStreamShared->u32CBL), + HDA_STREAM_REG(pThis, LPIB, i) = uLpib = 0); + + uint32_t off = 0; + for (uint32_t j = 0; j < pStreamShared->State.cSchedule; j++) + { + AssertReturn(pStreamShared->State.aSchedule[j].cbPeriod >= 1 && pStreamShared->State.aSchedule[j].cLoops >= 1, + pDevIns->pHlpR3->pfnSSMSetLoadError(pSSM, VERR_INTERNAL_ERROR_2, RT_SRC_POS, + "Stream #%u, sched #%u: cbPeriod=%u cLoops=%u\n", + pStreamShared->u8SD, j, + pStreamShared->State.aSchedule[j].cbPeriod, + pStreamShared->State.aSchedule[j].cLoops)); + uint32_t cbCur = pStreamShared->State.aSchedule[j].cbPeriod + * pStreamShared->State.aSchedule[j].cLoops; + if (uLpib >= off + cbCur) + off += cbCur; + else + { + uint32_t const offDelta = uLpib - off; + uint32_t idxLoop = offDelta / pStreamShared->State.aSchedule[j].cbPeriod; + uint32_t offLoop = offDelta % pStreamShared->State.aSchedule[j].cbPeriod; + if (offLoop) + { + /** @todo somehow bake this into the DMA timer logic. */ + LogFunc(("stream #%u: LPIB=%#RX32; adjusting due to scheduling clash: -%#x (j=%u idxLoop=%u cbPeriod=%#x)\n", + pStreamShared->u8SD, uLpib, offLoop, j, idxLoop, pStreamShared->State.aSchedule[j].cbPeriod)); + uLpib -= offLoop; + HDA_STREAM_REG(pThis, LPIB, i) = uLpib; + } + pStreamShared->State.idxSchedule = (uint16_t)j; + pStreamShared->State.idxScheduleLoop = (uint16_t)idxLoop; + off = UINT32_MAX; + break; + } + } + Assert(off == UINT32_MAX); + + /* Now figure out the current BDLE and the offset within it. */ + off = 0; + for (uint32_t j = 0; j < pStreamShared->State.cBdles; j++) + if (uLpib >= off + pStreamShared->State.aBdl[j].cb) + off += pStreamShared->State.aBdl[j].cb; + else + { + pStreamShared->State.idxCurBdle = j; + pStreamShared->State.offCurBdle = uLpib - off; + off = UINT32_MAX; + break; + } + AssertReturn(off == UINT32_MAX, pDevIns->pHlpR3->pfnSSMSetLoadError(pSSM, VERR_INTERNAL_ERROR_3, RT_SRC_POS, + "Stream #%u: LPIB=%#RX32 not found in loaded BDL\n", + pStreamShared->u8SD, uLpib)); + + /* Avoid going through the timer here by calling the stream's timer function directly. + * Should speed up starting the stream transfers. */ + PDMDevHlpTimerLockClock2(pDevIns, pStreamShared->hTimer, &pThis->CritSect, VERR_IGNORED); + uint64_t tsNow = hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3); + PDMDevHlpTimerUnlockClock2(pDevIns, pStreamShared->hTimer, &pThis->CritSect); + + hdaR3StreamMarkStarted(pDevIns, pThis, pStreamShared, tsNow); + } + } + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +/** + * Handles loading of all saved state versions older than the current one. + * + * @param pDevIns The device instance. + * @param pThis Pointer to the shared HDA state. + * @param pThisCC Pointer to the ring-3 HDA state. + * @param pSSM The saved state handle. + * @param uVersion Saved state version to load. + */ +static int hdaR3LoadExecLegacy(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PSSMHANDLE pSSM, uint32_t uVersion) +{ + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + + /* + * Load MMIO registers. + */ + uint32_t cRegs; + switch (uVersion) + { + case HDA_SAVED_STATE_VERSION_1: + /* Starting with r71199, we would save 112 instead of 113 + registers due to some code cleanups. This only affected trunk + builds in the 4.1 development period. */ + cRegs = 113; + if (pHlp->pfnSSMHandleRevision(pSSM) >= 71199) + { + uint32_t uVer = pHlp->pfnSSMHandleVersion(pSSM); + if ( VBOX_FULL_VERSION_GET_MAJOR(uVer) == 4 + && VBOX_FULL_VERSION_GET_MINOR(uVer) == 0 + && VBOX_FULL_VERSION_GET_BUILD(uVer) >= 51) + cRegs = 112; + } + break; + + case HDA_SAVED_STATE_VERSION_2: + case HDA_SAVED_STATE_VERSION_3: + cRegs = 112; + AssertCompile(RT_ELEMENTS(pThis->au32Regs) >= 112); + break; + + /* Since version 4 we store the register count to stay flexible. */ + case HDA_SAVED_STATE_VERSION_4: + case HDA_SAVED_STATE_VERSION_5: + case HDA_SAVED_STATE_VERSION_6: + rc = pHlp->pfnSSMGetU32(pSSM, &cRegs); + AssertRCReturn(rc, rc); + if (cRegs != RT_ELEMENTS(pThis->au32Regs)) + LogRel(("HDA: SSM version cRegs is %RU32, expected %RU32\n", cRegs, RT_ELEMENTS(pThis->au32Regs))); + break; + + default: + AssertLogRelMsgFailedReturn(("HDA: Internal Error! Didn't expect saved state version %RU32 ending up in hdaR3LoadExecLegacy!\n", + uVersion), VERR_INTERNAL_ERROR_5); + } + + if (cRegs >= RT_ELEMENTS(pThis->au32Regs)) + { + pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); + pHlp->pfnSSMSkip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs))); + } + else + pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs); + + /* Make sure to update the base addresses first before initializing any streams down below. */ + pThis->u64CORBBase = RT_MAKE_U64(HDA_REG(pThis, CORBLBASE), HDA_REG(pThis, CORBUBASE)); + pThis->u64RIRBBase = RT_MAKE_U64(HDA_REG(pThis, RIRBLBASE), HDA_REG(pThis, RIRBUBASE)); + pThis->u64DPBase = RT_MAKE_U64(HDA_REG(pThis, DPLBASE) & DPBASE_ADDR_MASK, HDA_REG(pThis, DPUBASE)); + + /* Also make sure to update the DMA position bit if this was enabled when saving the state. */ + pThis->fDMAPosition = RT_BOOL(HDA_REG(pThis, DPLBASE) & RT_BIT_32(0)); + + /* + * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. + * + * Note: Saved states < v5 store LVI (u32BdleMaxCvi) for + * *every* BDLE state, whereas it only needs to be stored + * *once* for every stream. Most of the BDLE state we can + * get out of the registers anyway, so just ignore those values. + * + * Also, only the current BDLE was saved, regardless whether + * there were more than one (and there are at least two entries, + * according to the spec). + */ + switch (uVersion) + { + case HDA_SAVED_STATE_VERSION_1: + case HDA_SAVED_STATE_VERSION_2: + case HDA_SAVED_STATE_VERSION_3: + case HDA_SAVED_STATE_VERSION_4: + { + /* Only load the internal states. + * The rest will be initialized from the saved registers later. */ + + /* Note 1: Only the *current* BDLE for a stream was saved! */ + /* Note 2: The stream's saving order is/was fixed, so don't touch! */ + + HDABDLELEGACY BDLE; + + /* Output */ + PHDASTREAM pStreamShared = &pThis->aStreams[4]; + rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[4], 4 /* Stream descriptor, hardcoded */); + AssertRCReturn(rc, rc); + RT_ZERO(BDLE); + rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); + AssertRCReturn(rc, rc); + pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */ + + /* Microphone-In */ + pStreamShared = &pThis->aStreams[2]; + rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[2], 2 /* Stream descriptor, hardcoded */); + AssertRCReturn(rc, rc); + rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); + AssertRCReturn(rc, rc); + pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */ + + /* Line-In */ + pStreamShared = &pThis->aStreams[0]; + rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[0], 0 /* Stream descriptor, hardcoded */); + AssertRCReturn(rc, rc); + rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); + AssertRCReturn(rc, rc); + pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */ + break; + } + + /* + * v5 & v6 - Since v5 we support flexible stream and BDLE counts. + */ + default: + { + /* Stream count. */ + uint32_t cStreams; + rc = pHlp->pfnSSMGetU32(pSSM, &cStreams); + AssertRCReturn(rc, rc); + if (cStreams > HDA_MAX_STREAMS) + return pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + N_("State contains %u streams while %u is the maximum supported"), + cStreams, HDA_MAX_STREAMS); + + /* Load stream states. */ + for (uint32_t i = 0; i < cStreams; i++) + { + uint8_t idStream; + rc = pHlp->pfnSSMGetU8(pSSM, &idStream); + AssertRCReturn(rc, rc); + + HDASTREAM StreamDummyShared; + HDASTREAMR3 StreamDummyR3; + PHDASTREAM pStreamShared = idStream < RT_ELEMENTS(pThis->aStreams) ? &pThis->aStreams[idStream] : &StreamDummyShared; + PHDASTREAMR3 pStreamR3 = idStream < RT_ELEMENTS(pThisCC->aStreams) ? &pThisCC->aStreams[idStream] : &StreamDummyR3; + AssertLogRelMsgStmt(idStream < RT_ELEMENTS(pThisCC->aStreams), + ("HDA stream ID=%RU8 not supported, skipping loadingit ...\n", idStream), + RT_ZERO(StreamDummyShared); RT_ZERO(StreamDummyR3)); + + rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, idStream); + if (RT_FAILURE(rc)) + { + LogRel(("HDA: Stream #%RU32: Setting up of stream %RU8 failed, rc=%Rrc\n", i, idStream, rc)); + break; + } + + /* + * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. + */ + if (uVersion == HDA_SAVED_STATE_VERSION_5) + { + struct V5HDASTREAMSTATE /* HDASTREAMSTATE + HDABDLE */ + { + uint16_t cBLDEs; + uint16_t uCurBDLE; + uint32_t u32BDLEIndex; + uint32_t cbBelowFIFOW; + uint32_t u32BufOff; + } Tmp; + static SSMFIELD const g_aV5State1Fields[] = + { + SSMFIELD_ENTRY(V5HDASTREAMSTATE, cBLDEs), + SSMFIELD_ENTRY(V5HDASTREAMSTATE, uCurBDLE), + SSMFIELD_ENTRY_TERM() + }; + rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State1Fields, NULL); + AssertRCReturn(rc, rc); + pStreamShared->State.idxCurBdle = (uint8_t)Tmp.uCurBDLE; /* not necessary */ + + for (uint16_t a = 0; a < Tmp.cBLDEs; a++) + { + static SSMFIELD const g_aV5State2Fields[] = + { + SSMFIELD_ENTRY(V5HDASTREAMSTATE, u32BDLEIndex), + SSMFIELD_ENTRY_OLD(au8FIFO, 256), + SSMFIELD_ENTRY(V5HDASTREAMSTATE, cbBelowFIFOW), + SSMFIELD_ENTRY_TERM() + }; + rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State2Fields, NULL); + AssertRCReturn(rc, rc); + } + } + else + { + rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State, sizeof(HDASTREAMSTATE), + 0 /* fFlags */, g_aSSMStreamStateFields6, NULL); + AssertRCReturn(rc, rc); + + HDABDLEDESC IgnDesc; + rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnDesc, sizeof(IgnDesc), 0 /* fFlags */, g_aSSMBDLEDescFields6, pDevIns); + AssertRCReturn(rc, rc); + + HDABDLESTATELEGACY IgnState; + rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnState, sizeof(IgnState), 0 /* fFlags */, g_aSSMBDLEStateFields6, NULL); + AssertRCReturn(rc, rc); + + Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", idStream, HDA_STREAM_REG(pThis, LPIB, idStream), + HDA_STREAM_REG(pThis, CBL, idStream), HDA_STREAM_REG(pThis, LVI, idStream))); +#ifdef LOG_ENABLED + hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); +#endif + } + + } /* for cStreams */ + break; + } /* default */ + } + + return rc; +} + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) hdaR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + LogRel2(("hdaR3LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass)); + + /* + * Load Codec nodes states. + */ + int rc = hdaR3CodecLoadState(pDevIns, &pThisCC->Codec, pSSM, uVersion); + if (RT_FAILURE(rc)) + { + LogRel(("HDA: Failed loading codec state (version %RU32, pass 0x%x), rc=%Rrc\n", uVersion, uPass, rc)); + return rc; + } + + if (uVersion <= HDA_SAVED_STATE_VERSION_6) /* Handle older saved states? */ + return hdaR3LoadExecLegacy(pDevIns, pThis, pThisCC, pSSM, uVersion); + + /* + * Load MMIO registers. + */ + uint32_t cRegs; + rc = pHlp->pfnSSMGetU32(pSSM, &cRegs); AssertRCReturn(rc, rc); + AssertRCReturn(rc, rc); + if (cRegs != RT_ELEMENTS(pThis->au32Regs)) + LogRel(("HDA: SSM version cRegs is %RU32, expected %RU32\n", cRegs, RT_ELEMENTS(pThis->au32Regs))); + + if (cRegs >= RT_ELEMENTS(pThis->au32Regs)) + { + pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); + rc = pHlp->pfnSSMSkip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs))); + AssertRCReturn(rc, rc); + } + else + { + rc = pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs); + AssertRCReturn(rc, rc); + } + + /* Make sure to update the base addresses first before initializing any streams down below. */ + pThis->u64CORBBase = RT_MAKE_U64(HDA_REG(pThis, CORBLBASE), HDA_REG(pThis, CORBUBASE)); + pThis->u64RIRBBase = RT_MAKE_U64(HDA_REG(pThis, RIRBLBASE), HDA_REG(pThis, RIRBUBASE)); + pThis->u64DPBase = RT_MAKE_U64(HDA_REG(pThis, DPLBASE) & DPBASE_ADDR_MASK, HDA_REG(pThis, DPUBASE)); + + /* Also make sure to update the DMA position bit if this was enabled when saving the state. */ + pThis->fDMAPosition = RT_BOOL(HDA_REG(pThis, DPLBASE) & RT_BIT_32(0)); + + /* + * Load controller-specific internals. + */ + if ( uVersion >= HDA_SAVED_STATE_WITHOUT_PERIOD + /* Don't annoy other team mates (forgot this for state v7): */ + || pHlp->pfnSSMHandleRevision(pSSM) >= 116273 + || pHlp->pfnSSMHandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 2, 0)) + { + pHlp->pfnSSMGetU64(pSSM, &pThis->tsWalClkStart); /* Was current wall clock */ + rc = pHlp->pfnSSMGetU8(pSSM, &pThis->u8IRQL); + AssertRCReturn(rc, rc); + + /* Convert the saved wall clock timestamp to a start timestamp. */ + if (uVersion < HDA_SAVED_STATE_WITHOUT_PERIOD && pThis->tsWalClkStart != 0) + { + uint64_t const cTimerTicksPerSec = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[0].hTimer); + AssertLogRel(cTimerTicksPerSec <= UINT32_MAX); + pThis->tsWalClkStart = ASMMultU64ByU32DivByU32(pThis->tsWalClkStart, + cTimerTicksPerSec, + 24000000 /* wall clock freq */); + pThis->tsWalClkStart = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer) - pThis->tsWalClkStart; + } + } + + /* + * Load streams. + */ + uint32_t cStreams; + rc = pHlp->pfnSSMGetU32(pSSM, &cStreams); + AssertRCReturn(rc, rc); + if (cStreams > HDA_MAX_STREAMS) + return pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + N_("State contains %u streams while %u is the maximum supported"), + cStreams, HDA_MAX_STREAMS); + Log2Func(("cStreams=%RU32\n", cStreams)); + + /* Load stream states. */ + for (uint32_t i = 0; i < cStreams; i++) + { + uint8_t idStream; + rc = pHlp->pfnSSMGetU8(pSSM, &idStream); + AssertRCReturn(rc, rc); + + /* Paranoia. */ + AssertLogRelMsgReturn(idStream < HDA_MAX_STREAMS, + ("HDA: Saved state contains bogus stream ID %RU8 for stream #%RU8", idStream, i), + VERR_SSM_INVALID_STATE); + + HDASTREAM StreamDummyShared; + HDASTREAMR3 StreamDummyR3; + PHDASTREAM pStreamShared = idStream < RT_ELEMENTS(pThis->aStreams) ? &pThis->aStreams[idStream] : &StreamDummyShared; + PHDASTREAMR3 pStreamR3 = idStream < RT_ELEMENTS(pThisCC->aStreams) ? &pThisCC->aStreams[idStream] : &StreamDummyR3; + AssertLogRelMsgStmt(idStream < RT_ELEMENTS(pThisCC->aStreams), + ("HDA stream ID=%RU8 not supported, skipping loadingit ...\n", idStream), + RT_ZERO(StreamDummyShared); RT_ZERO(StreamDummyR3)); + + PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED); /* timer code requires this */ + rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, idStream); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + if (RT_FAILURE(rc)) + { + LogRel(("HDA: Stream #%RU8: Setting up failed, rc=%Rrc\n", idStream, rc)); + /* Continue. */ + } + + rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State, sizeof(HDASTREAMSTATE), + 0 /* fFlags */, g_aSSMStreamStateFields7, NULL); + AssertRCReturn(rc, rc); + + /* + * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. + * Obsolete. Derived from LPID now. + */ + HDABDLEDESC IgnDesc; + rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnDesc, sizeof(IgnDesc), 0 /* fFlags */, g_aSSMBDLEDescFields7, NULL); + AssertRCReturn(rc, rc); + + HDABDLESTATELEGACY IgnState; + rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnState, sizeof(IgnState), 0 /* fFlags */, g_aSSMBDLEStateFields7, NULL); + AssertRCReturn(rc, rc); + + Log2Func(("[SD%RU8]\n", pStreamShared->u8SD)); + + /* + * Load period state if present. + */ + if (uVersion < HDA_SAVED_STATE_WITHOUT_PERIOD) + { + static SSMFIELD const s_aSSMStreamPeriodFields7[] = /* For the removed HDASTREAMPERIOD structure. */ + { + SSMFIELD_ENTRY_OLD(u64StartWalClk, sizeof(uint64_t)), + SSMFIELD_ENTRY_OLD(u64ElapsedWalClk, sizeof(uint64_t)), + SSMFIELD_ENTRY_OLD(cFramesTransferred, sizeof(uint32_t)), + SSMFIELD_ENTRY_OLD(cIntPending, sizeof(uint8_t)), /** @todo Not sure what we should for non-zero values on restore... ignoring it for now. */ + SSMFIELD_ENTRY_TERM() + }; + uint8_t bWhatever = 0; + rc = pHlp->pfnSSMGetStructEx(pSSM, &bWhatever, sizeof(bWhatever), 0 /* fFlags */, s_aSSMStreamPeriodFields7, NULL); + AssertRCReturn(rc, rc); + } + + /* + * Load internal DMA buffer. + */ + uint32_t cbCircBuf = 0; + pHlp->pfnSSMGetU32(pSSM, &cbCircBuf); /* cbCircBuf */ + uint32_t cbCircBufUsed = 0; + rc = pHlp->pfnSSMGetU32(pSSM, &cbCircBufUsed); /* cbCircBuf */ + AssertRCReturn(rc, rc); + + if (cbCircBuf) /* If 0, skip the buffer. */ + { + /* Paranoia. */ + AssertLogRelMsgReturn(cbCircBuf <= _32M, + ("HDA: Saved state contains bogus DMA buffer size (%RU32) for stream #%RU8", + cbCircBuf, idStream), + VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + AssertLogRelMsgReturn(cbCircBufUsed <= cbCircBuf, + ("HDA: Saved state contains invalid DMA buffer usage (%RU32/%RU32) for stream #%RU8", + cbCircBufUsed, cbCircBuf, idStream), + VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + /* Do we need to cre-create the circular buffer do fit the data size? */ + if ( pStreamR3->State.pCircBuf + && cbCircBuf != (uint32_t)RTCircBufSize(pStreamR3->State.pCircBuf)) + { + RTCircBufDestroy(pStreamR3->State.pCircBuf); + pStreamR3->State.pCircBuf = NULL; + } + + rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, cbCircBuf); + AssertRCReturn(rc, rc); + pStreamR3->State.StatDmaBufSize = cbCircBuf; + + if (cbCircBufUsed) + { + void *pvBuf = NULL; + size_t cbBuf = 0; + RTCircBufAcquireWriteBlock(pStreamR3->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf); + + AssertLogRelMsgReturn(cbBuf == cbCircBufUsed, ("cbBuf=%zu cbCircBufUsed=%zu\n", cbBuf, cbCircBufUsed), + VERR_INTERNAL_ERROR_3); + rc = pHlp->pfnSSMGetMem(pSSM, pvBuf, cbBuf); + AssertRCReturn(rc, rc); + pStreamShared->State.offWrite = cbCircBufUsed; + + RTCircBufReleaseWriteBlock(pStreamR3->State.pCircBuf, cbBuf); + + Assert(cbBuf == cbCircBufUsed); + } + } + + Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", idStream, HDA_STREAM_REG(pThis, LPIB, idStream), + HDA_STREAM_REG(pThis, CBL, idStream), HDA_STREAM_REG(pThis, LVI, idStream))); +#ifdef LOG_ENABLED + hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); +#endif + /** @todo (Re-)initialize active periods? */ + + } /* for cStreams */ + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/********************************************************************************************************************************* +* IPRT format type handlers * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtSDCTL(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + uint32_t uSDCTL = (uint32_t)(uintptr_t)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "SDCTL(raw:%#x, DIR:%s, TP:%RTbool, STRIPE:%x, DEIE:%RTbool, FEIE:%RTbool, IOCE:%RTbool, RUN:%RTbool, RESET:%RTbool)", + uSDCTL, + uSDCTL & HDA_SDCTL_DIR ? "OUT" : "IN", + RT_BOOL(uSDCTL & HDA_SDCTL_TP), + (uSDCTL & HDA_SDCTL_STRIPE_MASK) >> HDA_SDCTL_STRIPE_SHIFT, + RT_BOOL(uSDCTL & HDA_SDCTL_DEIE), + RT_BOOL(uSDCTL & HDA_SDCTL_FEIE), + RT_BOOL(uSDCTL & HDA_SDCTL_IOCE), + RT_BOOL(uSDCTL & HDA_SDCTL_RUN), + RT_BOOL(uSDCTL & HDA_SDCTL_SRST)); +} + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtSDFIFOS(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + uint32_t uSDFIFOS = (uint32_t)(uintptr_t)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "SDFIFOS(raw:%#x, sdfifos:%RU8 B)", uSDFIFOS, uSDFIFOS ? uSDFIFOS + 1 : 0); +} + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtSDFIFOW(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + uint32_t uSDFIFOW = (uint32_t)(uintptr_t)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "SDFIFOW(raw: %#0x, sdfifow:%d B)", uSDFIFOW, hdaSDFIFOWToBytes(uSDFIFOW)); +} + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtSDSTS(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + uint32_t uSdSts = (uint32_t)(uintptr_t)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "SDSTS(raw:%#0x, fifordy:%RTbool, dese:%RTbool, fifoe:%RTbool, bcis:%RTbool)", + uSdSts, + RT_BOOL(uSdSts & HDA_SDSTS_FIFORDY), + RT_BOOL(uSdSts & HDA_SDSTS_DESE), + RT_BOOL(uSdSts & HDA_SDSTS_FIFOE), + RT_BOOL(uSdSts & HDA_SDSTS_BCIS)); +} + + +/********************************************************************************************************************************* +* Debug Info Item Handlers * +*********************************************************************************************************************************/ + +/** Worker for hdaR3DbgInfo. */ +static int hdaR3DbgLookupRegByName(const char *pszArgs) +{ + if (pszArgs && *pszArgs != '\0') + for (int iReg = 0; iReg < HDA_NUM_REGS; ++iReg) + if (!RTStrICmp(g_aHdaRegMap[iReg].pszName, pszArgs)) + return iReg; + return -1; +} + +/** Worker for hdaR3DbgInfo. */ +static void hdaR3DbgPrintRegister(PPDMDEVINS pDevIns, PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iHdaIndex) +{ + /** @todo HDA_REG_IDX_NOMEM & GCAP both uses idxReg zero, no flag or anything + * to tell them appart. */ + if (g_aHdaRegMap[iHdaIndex].idxReg != 0 || g_aHdaRegMap[iHdaIndex].pfnRead != hdaRegReadWALCLK) + pHlp->pfnPrintf(pHlp, "%s: 0x%x\n", g_aHdaRegMap[iHdaIndex].pszName, pThis->au32Regs[g_aHdaRegMap[iHdaIndex].idxReg]); + else + { + uint64_t uWallNow = 0; + hdaQueryWallClock(pDevIns, pThis, false /*fDoDma*/, &uWallNow); + pHlp->pfnPrintf(pHlp, "%s: 0x%RX64\n", g_aHdaRegMap[iHdaIndex].pszName, uWallNow); + } +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV} + */ +static DECLCALLBACK(void) hdaR3DbgInfo(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + int idxReg = hdaR3DbgLookupRegByName(pszArgs); + if (idxReg != -1) + hdaR3DbgPrintRegister(pDevIns, pThis, pHlp, idxReg); + else + for (idxReg = 0; idxReg < HDA_NUM_REGS; ++idxReg) + hdaR3DbgPrintRegister(pDevIns, pThis, pHlp, idxReg); +} + +/** Worker for hdaR3DbgInfoStream. */ +static void hdaR3DbgPrintStream(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int idxStream) +{ + char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX]; + PHDASTREAM const pStream = &pThis->aStreams[idxStream]; + pHlp->pfnPrintf(pHlp, "Stream #%d: %s\n", idxStream, PDMAudioStrmCfgToString(&pStream->State.Cfg, szTmp, sizeof(szTmp))); + pHlp->pfnPrintf(pHlp, " SD%dCTL : %R[sdctl]\n", idxStream, HDA_STREAM_REG(pThis, CTL, idxStream)); + pHlp->pfnPrintf(pHlp, " SD%dCTS : %R[sdsts]\n", idxStream, HDA_STREAM_REG(pThis, STS, idxStream)); + pHlp->pfnPrintf(pHlp, " SD%dFIFOS: %R[sdfifos]\n", idxStream, HDA_STREAM_REG(pThis, FIFOS, idxStream)); + pHlp->pfnPrintf(pHlp, " SD%dFIFOW: %R[sdfifow]\n", idxStream, HDA_STREAM_REG(pThis, FIFOW, idxStream)); + pHlp->pfnPrintf(pHlp, " Current BDLE%02u: %s%#RX64 LB %#x%s - off=%#x\n", pStream->State.idxCurBdle, "%%" /*vboxdbg phys prefix*/, + pStream->State.aBdl[pStream->State.idxCurBdle].GCPhys, pStream->State.aBdl[pStream->State.idxCurBdle].cb, + pStream->State.aBdl[pStream->State.idxCurBdle].fFlags ? " IOC" : "", pStream->State.offCurBdle); +} + +/** Worker for hdaR3DbgInfoBDL. */ +static void hdaR3DbgPrintBDL(PPDMDEVINS pDevIns, PHDASTATE pThis, PCDBGFINFOHLP pHlp, int idxStream) +{ + const PHDASTREAM pStream = &pThis->aStreams[idxStream]; + PCPDMAUDIOPCMPROPS pProps = &pStream->State.Cfg.Props; + uint64_t const u64BaseDMA = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, idxStream), + HDA_STREAM_REG(pThis, BDPU, idxStream)); + uint16_t const u16LVI = HDA_STREAM_REG(pThis, LVI, idxStream); + uint32_t const u32CBL = HDA_STREAM_REG(pThis, CBL, idxStream); + uint8_t const idxCurBdle = pStream->State.idxCurBdle; + pHlp->pfnPrintf(pHlp, "Stream #%d BDL: %s%#011RX64 LB %#x (LVI=%u)\n", idxStream, "%%" /*vboxdbg phys prefix*/, + u64BaseDMA, u16LVI * sizeof(HDABDLEDESC), u16LVI); + if (u64BaseDMA || idxCurBdle != 0 || pStream->State.aBdl[idxCurBdle].GCPhys != 0 || pStream->State.aBdl[idxCurBdle].cb != 0) + pHlp->pfnPrintf(pHlp, " Current: BDLE%03u: %s%#011RX64 LB %#x%s - off=%#x LPIB=%#RX32\n", + pStream->State.idxCurBdle, "%%" /*vboxdbg phys prefix*/, + pStream->State.aBdl[idxCurBdle].GCPhys, pStream->State.aBdl[idxCurBdle].cb, + pStream->State.aBdl[idxCurBdle].fFlags ? " IOC" : "", pStream->State.offCurBdle, + HDA_STREAM_REG(pThis, LPIB, idxStream)); + if (!u64BaseDMA) + return; + + /* + * The BDL: + */ + uint64_t cbTotal = 0; + for (uint16_t i = 0; i < u16LVI + 1; i++) + { + HDABDLEDESC bd = {0, 0, 0}; + PDMDevHlpPCIPhysRead(pDevIns, u64BaseDMA + i * sizeof(HDABDLEDESC), &bd, sizeof(bd)); + + char szFlags[64]; + szFlags[0] = '\0'; + if (bd.fFlags & ~HDA_BDLE_F_IOC) + RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", bd.fFlags); + pHlp->pfnPrintf(pHlp, " %sBDLE%03u: %s%#011RX64 LB %#06x (%RU64 us) %s%s\n", idxCurBdle == i ? "=>" : " ", i, "%%", + bd.u64BufAddr, bd.u32BufSize, PDMAudioPropsBytesToMicro(pProps, bd.u32BufSize), + bd.fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags); + + if (memcmp(&bd, &pStream->State.aBdl[i], sizeof(bd)) != 0) + { + szFlags[0] = '\0'; + if (bd.fFlags & ~HDA_BDLE_F_IOC) + RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", bd.fFlags); + pHlp->pfnPrintf(pHlp, " !!!loaded: %s%#011RX64 LB %#06x %s%s\n", "%%", pStream->State.aBdl[i].GCPhys, + pStream->State.aBdl[i].cb, pStream->State.aBdl[i].fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags); + } + + cbTotal += bd.u32BufSize; + } + pHlp->pfnPrintf(pHlp, " Total: %#RX64 bytes (%RU64), %RU64 ms\n", cbTotal, cbTotal, + PDMAudioPropsBytesToMilli(pProps, (uint32_t)cbTotal)); + if (cbTotal != u32CBL) + pHlp->pfnPrintf(pHlp, " Warning: %#RX64 bytes does not match CBL (%#RX64)!\n", cbTotal, u32CBL); + + /* + * The scheduling plan. + */ + uint16_t const idxSchedule = pStream->State.idxSchedule; + pHlp->pfnPrintf(pHlp, " Scheduling: %u items, %u prologue. Current: %u, loop %u.\n", pStream->State.cSchedule, + pStream->State.cSchedulePrologue, idxSchedule, pStream->State.idxScheduleLoop); + for (uint16_t i = 0; i < pStream->State.cSchedule; i++) + pHlp->pfnPrintf(pHlp, " %s#%02u: %#x bytes, %u loop%s, %RU32 ticks. BDLE%u thru BDLE%u\n", + i == idxSchedule ? "=>" : " ", i, + pStream->State.aSchedule[i].cbPeriod, pStream->State.aSchedule[i].cLoops, + pStream->State.aSchedule[i].cLoops == 1 ? "" : "s", + pStream->State.aSchedule[i].cPeriodTicks, pStream->State.aSchedule[i].idxFirst, + pStream->State.aSchedule[i].idxFirst + pStream->State.aSchedule[i].cEntries - 1); +} + +/** Used by hdaR3DbgInfoStream and hdaR3DbgInfoBDL. */ +static int hdaR3DbgLookupStrmIdx(PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + if (pszArgs && *pszArgs) + { + int32_t idxStream; + int rc = RTStrToInt32Full(pszArgs, 0, &idxStream); + if (RT_SUCCESS(rc) && idxStream >= -1 && idxStream < HDA_MAX_STREAMS) + return idxStream; + pHlp->pfnPrintf(pHlp, "Argument '%s' is not a valid stream number!\n", pszArgs); + } + return -1; +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, hdastream} + */ +static DECLCALLBACK(void) hdaR3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + int idxStream = hdaR3DbgLookupStrmIdx(pHlp, pszArgs); + if (idxStream != -1) + hdaR3DbgPrintStream(pThis, pHlp, idxStream); + else + for (idxStream = 0; idxStream < HDA_MAX_STREAMS; ++idxStream) + hdaR3DbgPrintStream(pThis, pHlp, idxStream); +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, hdabdl} + */ +static DECLCALLBACK(void) hdaR3DbgInfoBDL(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + int idxStream = hdaR3DbgLookupStrmIdx(pHlp, pszArgs); + if (idxStream != -1) + hdaR3DbgPrintBDL(pDevIns, pThis, pHlp, idxStream); + else + { + for (idxStream = 0; idxStream < HDA_MAX_STREAMS; ++idxStream) + hdaR3DbgPrintBDL(pDevIns, pThis, pHlp, idxStream); + idxStream = -1; + } + + /* + * DMA stream positions: + */ + uint64_t const uDPBase = pThis->u64DPBase & DPBASE_ADDR_MASK; + pHlp->pfnPrintf(pHlp, "DMA counters %#011RX64 LB %#x, %s:\n", uDPBase, HDA_MAX_STREAMS * 2 * sizeof(uint32_t), + pThis->fDMAPosition ? "enabled" : "disabled"); + if (uDPBase) + { + struct + { + uint32_t off, uReserved; + } aPositions[HDA_MAX_STREAMS]; + RT_ZERO(aPositions); + PDMDevHlpPCIPhysRead(pDevIns, uDPBase , &aPositions[0], sizeof(aPositions)); + + for (unsigned i = 0; i < RT_ELEMENTS(aPositions); i++) + if (idxStream == -1 || i == (unsigned)idxStream) /* lazy bird */ + { + char szReserved[64]; + szReserved[0] = '\0'; + if (aPositions[i].uReserved != 0) + RTStrPrintf(szReserved, sizeof(szReserved), " reserved=%#x", aPositions[i].uReserved); + pHlp->pfnPrintf(pHlp, " Stream #%u DMA @ %#x%s\n", i, aPositions[i].off, szReserved); + } + } +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, hdcnodes} + */ +static DECLCALLBACK(void) hdaR3DbgInfoCodecNodes(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + hdaR3CodecDbgListNodes(&pThisCC->Codec, pHlp, pszArgs); +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, hdcselector} + */ +static DECLCALLBACK(void) hdaR3DbgInfoCodecSelector(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + hdaR3CodecDbgSelector(&pThisCC->Codec, pHlp, pszArgs); +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, hdamixer} + */ +static DECLCALLBACK(void) hdaR3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + if (pThisCC->pMixer) + AudioMixerDebug(pThisCC->pMixer, pHlp, pszArgs); + else + pHlp->pfnPrintf(pHlp, "Mixer not available\n"); +} + + +/********************************************************************************************************************************* +* PDMIBASE * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) hdaR3QueryInterface(struct PDMIBASE *pInterface, const char *pszIID) +{ + PHDASTATER3 pThisCC = RT_FROM_MEMBER(pInterface, HDASTATER3, IBase); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDEVREGR3 * +*********************************************************************************************************************************/ + +/** + * Worker for hdaR3Construct() and hdaR3Attach(). + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pThisCC The ring-3 HDA device state. + * @param uLUN The logical unit which is being detached. + * @param ppDrv Attached driver instance on success. Optional. + */ +static int hdaR3AttachInternal(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, unsigned uLUN, PHDADRIVER *ppDrv) +{ + PHDADRIVER pDrv = (PHDADRIVER)RTMemAllocZ(sizeof(HDADRIVER)); + AssertPtrReturn(pDrv, VERR_NO_MEMORY); + RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (HDA) for LUN #%u", uLUN); + + PPDMIBASE pDrvBase; + int rc = PDMDevHlpDriverAttach(pDevIns, uLUN, &pThisCC->IBase, &pDrvBase, pDrv->szDesc); + if (RT_SUCCESS(rc)) + { + pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR); + AssertPtr(pDrv->pConnector); + if (RT_VALID_PTR(pDrv->pConnector)) + { + pDrv->pDrvBase = pDrvBase; + pDrv->pHDAStateShared = pThis; + pDrv->pHDAStateR3 = pThisCC; + pDrv->uLUN = uLUN; + + /* Attach to driver list if not attached yet. */ + if (!pDrv->fAttached) + { + RTListAppend(&pThisCC->lstDrv, &pDrv->Node); + pDrv->fAttached = true; + } + + if (ppDrv) + *ppDrv = pDrv; + + /* + * While we're here, give the windows backends a hint about our typical playback + * configuration. + * Note! If 48000Hz is advertised to the guest, add it here. + */ + if ( pDrv->pConnector + && pDrv->pConnector->pfnStreamConfigHint) + { + PDMAUDIOSTREAMCFG Cfg; + RT_ZERO(Cfg); + Cfg.enmDir = PDMAUDIODIR_OUT; + Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT; + Cfg.Device.cMsSchedulingHint = 10; + Cfg.Backend.cFramesPreBuffering = UINT32_MAX; + PDMAudioPropsInit(&Cfg.Props, 2, true /*fSigned*/, 2, 44100); + RTStrPrintf(Cfg.szName, sizeof(Cfg.szName), "output 44.1kHz 2ch S16 (HDA config hint)"); + + pDrv->pConnector->pfnStreamConfigHint(pDrv->pConnector, &Cfg); /* (may trash CfgReq) */ + } + + LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector)); + return VINF_SUCCESS; + } + + rc = VERR_PDM_MISSING_INTERFACE_BELOW; + } + else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + LogFunc(("No attached driver for LUN #%u\n", uLUN)); + else + LogFunc(("Failed attaching driver for LUN #%u: %Rrc\n", uLUN, rc)); + RTMemFree(pDrv); + + LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnAttach} + */ +static DECLCALLBACK(int) hdaR3Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + RT_NOREF(fFlags); + LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); + + DEVHDA_LOCK_RETURN(pDevIns, pThis, VERR_IGNORED); + + PHDADRIVER pDrv; + int rc = hdaR3AttachInternal(pDevIns, pThis, pThisCC, uLUN, &pDrv); + if (RT_SUCCESS(rc)) + { + int rc2 = hdaR3MixerAddDrv(pDevIns, pThisCC, pDrv); + if (RT_FAILURE(rc2)) + LogFunc(("hdaR3MixerAddDrv failed with %Rrc (ignored)\n", rc2)); + } + + DEVHDA_UNLOCK(pDevIns, pThis); + return rc; +} + + +/** + * Worker for hdaR3Detach that does all but free pDrv. + * + * This is called to let the device detach from a driver for a specified LUN + * at runtime. + * + * @param pDevIns The device instance. + * @param pThisCC The ring-3 HDA device state. + * @param pDrv Driver to detach from device. + */ +static void hdaR3DetachInternal(PPDMDEVINS pDevIns, PHDASTATER3 pThisCC, PHDADRIVER pDrv) +{ + /* Remove the driver from our list and destory it's associated streams. + This also will un-set the driver as a recording source (if associated). */ + hdaR3MixerRemoveDrv(pDevIns, pThisCC, pDrv); + LogFunc(("LUN#%u detached\n", pDrv->uLUN)); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDetach} + */ +static DECLCALLBACK(void) hdaR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + RT_NOREF(fFlags); + LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags)); + + DEVHDA_LOCK(pDevIns, pThis); + + PHDADRIVER pDrv; + RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node) + { + if (pDrv->uLUN == iLUN) + { + hdaR3DetachInternal(pDevIns, pThisCC, pDrv); + RTMemFree(pDrv); + DEVHDA_UNLOCK(pDevIns, pThis); + return; + } + } + + DEVHDA_UNLOCK(pDevIns, pThis); + LogFunc(("LUN#%u was not found\n", iLUN)); +} + + +/** + * Powers off the device. + * + * @param pDevIns Device instance to power off. + */ +static DECLCALLBACK(void) hdaR3PowerOff(PPDMDEVINS pDevIns) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + + DEVHDA_LOCK_RETURN_VOID(pDevIns, pThis); + + LogRel2(("HDA: Powering off ...\n")); + +/** @todo r=bird: What this "releasing references" and whatever here is + * referring to, is apparently that the device is destroyed after the + * drivers, so creating trouble as those structures have been torn down + * already... Reverse order, like we do for power off? Need a new + * PDMDEVREG flag. */ + + /* Ditto goes for the codec, which in turn uses the mixer. */ + hdaR3CodecPowerOff(&pThisCC->Codec); + + /* This is to prevent us from calling into the mixer and mixer sink code + after it has been destroyed below. */ + for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) + pThisCC->aStreams[i].State.pAioRegSink = NULL; /* don't need to remove, we're destorying it. */ + + /* + * Note: Destroy the mixer while powering off and *not* in hdaR3Destruct, + * giving the mixer the chance to release any references held to + * PDM audio streams it maintains. + */ + if (pThisCC->pMixer) + { + AudioMixerDestroy(pThisCC->pMixer, pDevIns); + pThisCC->pMixer = NULL; + } + + DEVHDA_UNLOCK(pDevIns, pThis); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static DECLCALLBACK(void) hdaR3Reset(PPDMDEVINS pDevIns) +{ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + + LogFlowFuncEnter(); + + DEVHDA_LOCK_RETURN_VOID(pDevIns, pThis); + + /* + * 18.2.6,7 defines that values of this registers might be cleared on power on/reset + * hdaR3Reset shouldn't affects these registers. + */ + HDA_REG(pThis, WAKEEN) = 0x0; + + hdaR3GCTLReset(pDevIns, pThis, pThisCC); + + /* Indicate that HDA is not in reset. The firmware is supposed to (un)reset HDA, + * but we can take a shortcut. + */ + HDA_REG(pThis, GCTL) = HDA_GCTL_CRST; + + DEVHDA_UNLOCK(pDevIns, pThis); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) hdaR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + + if (PDMCritSectIsInitialized(&pThis->CritSect)) + PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED); + + PHDADRIVER pDrv; + while (!RTListIsEmpty(&pThisCC->lstDrv)) + { + pDrv = RTListGetFirst(&pThisCC->lstDrv, HDADRIVER, Node); + + RTListNodeRemove(&pDrv->Node); + RTMemFree(pDrv); + } + + hdaCodecDestruct(&pThisCC->Codec); + + for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) + hdaR3StreamDestroy(&pThisCC->aStreams[i]); + + /* We don't always go via PowerOff, so make sure the mixer is destroyed. */ + if (pThisCC->pMixer) + { + AudioMixerDestroy(pThisCC->pMixer, pDevIns); + pThisCC->pMixer = NULL; + } + + if (PDMCritSectIsInitialized(&pThis->CritSect)) + { + PDMCritSectLeave(&pThis->CritSect); + PDMR3CritSectDelete(&pThis->CritSect); + } + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) hdaR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + Assert(iInstance == 0); RT_NOREF(iInstance); + + /* + * Initialize the state sufficently to make the destructor work. + */ + pThis->uAlignmentCheckMagic = HDASTATE_ALIGNMENT_CHECK_MAGIC; + RTListInit(&pThisCC->lstDrv); + pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE; + pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE; + pThis->hCorbDmaTask = NIL_PDMTASKHANDLE; + + /** @todo r=bird: There are probably other things which should be + * initialized here before we start failing. */ + + /* + * Validate and read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, + "BufSizeInMs" + "|BufSizeOutMs" + "|DebugEnabled" + "|DebugPathOut" + "|DeviceName", + ""); + + /** @devcfgm{hda,BufSizeInMs,uint16_t,0,2000,0,ms} + * The size of the DMA buffer for input streams expressed in milliseconds. */ + int rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeInMs", &pThis->cMsCircBufIn, 0); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read 'BufSizeInMs' as 16-bit unsigned integer")); + if (pThis->cMsCircBufIn > 2000) + return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE, + N_("HDA configuration error: 'BufSizeInMs' is out of bound, max 2000 ms")); + + /** @devcfgm{hda,BufSizeOutMs,uint16_t,0,2000,0,ms} + * The size of the DMA buffer for output streams expressed in milliseconds. */ + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeOutMs", &pThis->cMsCircBufOut, 0); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read 'BufSizeOutMs' as 16-bit unsigned integer")); + if (pThis->cMsCircBufOut > 2000) + return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE, + N_("HDA configuration error: 'BufSizeOutMs' is out of bound, max 2000 ms")); + + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThisCC->Dbg.fEnabled, false); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read debugging enabled flag as boolean")); + + rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThisCC->Dbg.pszOutPath, NULL); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read debugging output path flag as string")); + if (pThisCC->Dbg.fEnabled) + LogRel2(("HDA: Debug output will be saved to '%s'\n", pThisCC->Dbg.pszOutPath)); + + /** @devcfgm{hda,DeviceName,string} + * Override the default device/vendor IDs for the emulated device: + * - "" - default + * - "Intel ICH6" + * - "Intel Sunrise Point" - great for macOS 10.15 + */ + char szDeviceName[32]; + rc = pHlp->pfnCFGMQueryStringDef(pCfg, "DeviceName", szDeviceName, sizeof(szDeviceName), ""); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("HDA configuration error: failed to read 'DeviceName' name string")); + enum + { + kDevice_Default, + kDevice_IntelIch6, + kDevice_IntelSunrisePoint /*skylake timeframe*/ + } enmDevice; + if (strcmp(szDeviceName, "") == 0) + enmDevice = kDevice_Default; + else if (strcmp(szDeviceName, "Intel ICH6") == 0) + enmDevice = kDevice_IntelIch6; + else if (strcmp(szDeviceName, "Intel Sunrise Point") == 0) + enmDevice = kDevice_IntelSunrisePoint; + else + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("HDA configuration error: Unknown 'DeviceName' name '%s'"), szDeviceName); + + /* + * Use our own critical section for the device instead of the default + * one provided by PDM. This allows fine-grained locking in combination + * with TM when timer-specific stuff is being called in e.g. the MMIO handlers. + */ + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "HDA"); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); + AssertRCReturn(rc, rc); + + /* + * Initialize data (most of it anyway). + */ + pThisCC->pDevIns = pDevIns; + /* IBase */ + pThisCC->IBase.pfnQueryInterface = hdaR3QueryInterface; + + /* PCI Device */ + PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; + PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev); + + switch (enmDevice) + { + case kDevice_Default: + PDMPciDevSetVendorId(pPciDev, HDA_PCI_VENDOR_ID); + PDMPciDevSetDeviceId(pPciDev, HDA_PCI_DEVICE_ID); + break; + case kDevice_IntelIch6: /* Our default intel device. */ + PDMPciDevSetVendorId(pPciDev, 0x8086); + PDMPciDevSetDeviceId(pPciDev, 0x2668); + break; + case kDevice_IntelSunrisePoint: /* this is supported by more recent macOS version, at least 10.15 */ + PDMPciDevSetVendorId(pPciDev, 0x8086); + PDMPciDevSetDeviceId(pPciDev, 0x9d70); + break; + } + + PDMPciDevSetCommand( pPciDev, 0x0000); /* 04 rw,ro - pcicmd. */ + PDMPciDevSetStatus( pPciDev, VBOX_PCI_STATUS_CAP_LIST); /* 06 rwc?,ro? - pcists. */ + PDMPciDevSetRevisionId( pPciDev, 0x01); /* 08 ro - rid. */ + PDMPciDevSetClassProg( pPciDev, 0x00); /* 09 ro - pi. */ + PDMPciDevSetClassSub( pPciDev, 0x03); /* 0a ro - scc; 03 == HDA. */ + PDMPciDevSetClassBase( pPciDev, 0x04); /* 0b ro - bcc; 04 == multimedia. */ + PDMPciDevSetHeaderType( pPciDev, 0x00); /* 0e ro - headtyp. */ + PDMPciDevSetBaseAddress( pPciDev, 0, /* 10 rw - MMIO */ + false /* fIoSpace */, false /* fPrefetchable */, true /* f64Bit */, 0x00000000); + PDMPciDevSetInterruptLine( pPciDev, 0x00); /* 3c rw. */ + PDMPciDevSetInterruptPin( pPciDev, 0x01); /* 3d ro - INTA#. */ + +# if defined(HDA_AS_PCI_EXPRESS) + PDMPciDevSetCapabilityList(pPciDev, 0x80); +# elif defined(VBOX_WITH_MSI_DEVICES) + PDMPciDevSetCapabilityList(pPciDev, 0x60); +# else + PDMPciDevSetCapabilityList(pPciDev, 0x50); /* ICH6 datasheet 18.1.16 */ +# endif + + /// @todo r=michaln: If there are really no PDMPciDevSetXx for these, the + /// meaning of these values needs to be properly documented! + /* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */ + PDMPciDevSetByte( pPciDev, 0x40, 0x01); + + /* Power Management */ + PDMPciDevSetByte( pPciDev, 0x50 + 0, VBOX_PCI_CAP_ID_PM); + PDMPciDevSetByte( pPciDev, 0x50 + 1, 0x0); /* next */ + PDMPciDevSetWord( pPciDev, 0x50 + 2, VBOX_PCI_PM_CAP_DSI | 0x02 /* version, PM1.1 */ ); + +# ifdef HDA_AS_PCI_EXPRESS + /* PCI Express */ + PDMPciDevSetByte( pPciDev, 0x80 + 0, VBOX_PCI_CAP_ID_EXP); /* PCI_Express */ + PDMPciDevSetByte( pPciDev, 0x80 + 1, 0x60); /* next */ + /* Device flags */ + PDMPciDevSetWord( pPciDev, 0x80 + 2, + 1 /* version */ + | (VBOX_PCI_EXP_TYPE_ROOT_INT_EP << 4) /* Root Complex Integrated Endpoint */ + | (100 << 9) /* MSI */ ); + /* Device capabilities */ + PDMPciDevSetDWord( pPciDev, 0x80 + 4, VBOX_PCI_EXP_DEVCAP_FLRESET); + /* Device control */ + PDMPciDevSetWord( pPciDev, 0x80 + 8, 0); + /* Device status */ + PDMPciDevSetWord( pPciDev, 0x80 + 10, 0); + /* Link caps */ + PDMPciDevSetDWord( pPciDev, 0x80 + 12, 0); + /* Link control */ + PDMPciDevSetWord( pPciDev, 0x80 + 16, 0); + /* Link status */ + PDMPciDevSetWord( pPciDev, 0x80 + 18, 0); + /* Slot capabilities */ + PDMPciDevSetDWord( pPciDev, 0x80 + 20, 0); + /* Slot control */ + PDMPciDevSetWord( pPciDev, 0x80 + 24, 0); + /* Slot status */ + PDMPciDevSetWord( pPciDev, 0x80 + 26, 0); + /* Root control */ + PDMPciDevSetWord( pPciDev, 0x80 + 28, 0); + /* Root capabilities */ + PDMPciDevSetWord( pPciDev, 0x80 + 30, 0); + /* Root status */ + PDMPciDevSetDWord( pPciDev, 0x80 + 32, 0); + /* Device capabilities 2 */ + PDMPciDevSetDWord( pPciDev, 0x80 + 36, 0); + /* Device control 2 */ + PDMPciDevSetQWord( pPciDev, 0x80 + 40, 0); + /* Link control 2 */ + PDMPciDevSetQWord( pPciDev, 0x80 + 48, 0); + /* Slot control 2 */ + PDMPciDevSetWord( pPciDev, 0x80 + 56, 0); +# endif /* HDA_AS_PCI_EXPRESS */ + + /* + * Register the PCI device. + */ + rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); + AssertRCReturn(rc, rc); + + /** @todo r=bird: The IOMMMIO_FLAGS_READ_DWORD flag isn't entirely optimal, + * as several frequently used registers aren't dword sized. 6.0 and earlier + * will go to ring-3 to handle accesses to any such register, where-as 6.1 and + * later will do trivial register reads in ring-0. Real optimal code would use + * IOMMMIO_FLAGS_READ_PASSTHRU and do the necessary extra work to deal with + * anything the guest may throw at us. */ + rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, 0x4000, PCI_ADDRESS_SPACE_MEM, hdaMmioWrite, hdaMmioRead, NULL /*pvUser*/, + IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_PASSTHRU, "HDA", &pThis->hMmio); + AssertRCReturn(rc, rc); + +# ifdef VBOX_WITH_MSI_DEVICES + PDMMSIREG MsiReg; + RT_ZERO(MsiReg); + MsiReg.cMsiVectors = 1; + MsiReg.iMsiCapOffset = 0x60; + MsiReg.iMsiNextOffset = 0x50; + rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); + if (RT_FAILURE(rc)) + { + /* That's OK, we can work without MSI */ + PDMPciDevSetCapabilityList(pPciDev, 0x50); + } +# endif + + /* Create task for continuing CORB DMA in ring-3. */ + rc = PDMDevHlpTaskCreate(pDevIns, PDMTASK_F_RZ, "HDA CORB DMA", + hdaR3CorbDmaTaskWorker, NULL /*pvUser*/, &pThis->hCorbDmaTask); + AssertRCReturn(rc,rc); + + rc = PDMDevHlpSSMRegisterEx(pDevIns, HDA_SAVED_STATE_VERSION, sizeof(*pThis), NULL /*pszBefore*/, + NULL /*pfnLivePrep*/, NULL /*pfnLiveExec*/, NULL /*pfnLiveVote*/, + NULL /*pfnSavePrep*/, hdaR3SaveExec, NULL /*pfnSaveDone*/, + NULL /*pfnLoadPrep*/, hdaR3LoadExec, hdaR3LoadDone); + AssertRCReturn(rc, rc); + + /* + * Attach drivers. We ASSUME they are configured consecutively without any + * gaps, so we stop when we hit the first LUN w/o a driver configured. + */ + for (unsigned iLun = 0; ; iLun++) + { + AssertBreak(iLun < UINT8_MAX); + LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun)); + rc = hdaR3AttachInternal(pDevIns, pThis, pThisCC, iLun, NULL /* ppDrv */); + if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + { + LogFunc(("cLUNs=%u\n", iLun)); + break; + } + AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc); + } + + /* + * Create the mixer. + */ + uint32_t fMixer = AUDMIXER_FLAGS_NONE; + if (pThisCC->Dbg.fEnabled) + fMixer |= AUDMIXER_FLAGS_DEBUG; + rc = AudioMixerCreate("HDA Mixer", fMixer, &pThisCC->pMixer); + AssertRCReturn(rc, rc); + + /* + * Add mixer output sinks. + */ +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + rc = AudioMixerCreateSink(pThisCC->pMixer, "Front", + PDMAUDIODIR_OUT, pDevIns, &pThisCC->SinkFront.pMixSink); + AssertRCReturn(rc, rc); + rc = AudioMixerCreateSink(pThisCC->pMixer, "Center+Subwoofer", + PDMAUDIODIR_OUT, pDevIns, &pThisCC->SinkCenterLFE.pMixSink); + AssertRCReturn(rc, rc); + rc = AudioMixerCreateSink(pThisCC->pMixer, "Rear", + PDMAUDIODIR_OUT, pDevIns, &pThisCC->SinkRear.pMixSink); + AssertRCReturn(rc, rc); +# else + rc = AudioMixerCreateSink(pThisCC->pMixer, "PCM Output", + PDMAUDIODIR_OUT, pDevIns, &pThisCC->SinkFront.pMixSink); + AssertRCReturn(rc, rc); +# endif /* VBOX_WITH_AUDIO_HDA_51_SURROUND */ + + /* + * Add mixer input sinks. + */ + rc = AudioMixerCreateSink(pThisCC->pMixer, "Line In", + PDMAUDIODIR_IN, pDevIns, &pThisCC->SinkLineIn.pMixSink); + AssertRCReturn(rc, rc); +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + rc = AudioMixerCreateSink(pThisCC->pMixer, "Microphone In", + PDMAUDIODIR_IN, pDevIns, &pThisCC->SinkMicIn.pMixSink); + AssertRCReturn(rc, rc); +# endif + + /* There is no master volume control. Set the master to max. */ + PDMAUDIOVOLUME Vol = PDMAUDIOVOLUME_INITIALIZER_MAX; + rc = AudioMixerSetMasterVolume(pThisCC->pMixer, &Vol); + AssertRCReturn(rc, rc); + + /* + * Initialize the codec. + */ + /* Construct the common + R3 codec part. */ + rc = hdaR3CodecConstruct(pDevIns, &pThisCC->Codec, 0 /* Codec index */, pCfg); + AssertRCReturn(rc, rc); + + /* ICH6 datasheet defines 0 values for SVID and SID (18.1.14-15), which together with values returned for + verb F20 should provide device/codec recognition. */ + Assert(pThisCC->Codec.Cfg.idVendor); + Assert(pThisCC->Codec.Cfg.idDevice); + PDMPciDevSetSubSystemVendorId(pPciDev, pThisCC->Codec.Cfg.idVendor); /* 2c ro - intel.) */ + PDMPciDevSetSubSystemId( pPciDev, pThisCC->Codec.Cfg.idDevice); /* 2e ro. */ + + /* + * Create the per stream timers and the asso. + * + * We must the critical section for the timers as the device has a + * noop section associated with it. + * + * Note: Use TMCLOCK_VIRTUAL_SYNC here, as the guest's HDA driver relies + * on exact (virtual) DMA timing and uses DMA Position Buffers + * instead of the LPIB registers. + */ + static const char * const s_apszNames[] = + { + "HDA SD0", "HDA SD1", "HDA SD2", "HDA SD3", + "HDA SD4", "HDA SD5", "HDA SD6", "HDA SD7", + }; + AssertCompile(RT_ELEMENTS(s_apszNames) == HDA_MAX_STREAMS); + for (size_t i = 0; i < HDA_MAX_STREAMS; i++) + { + rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, hdaR3Timer, (void *)(uintptr_t)i, + TMTIMER_FLAGS_NO_CRIT_SECT, s_apszNames[i], &pThis->aStreams[i].hTimer); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpTimerSetCritSect(pDevIns, pThis->aStreams[i].hTimer, &pThis->CritSect); + AssertRCReturn(rc, rc); + } + + /* + * Create all hardware streams. + */ + for (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i) + { + rc = hdaR3StreamConstruct(&pThis->aStreams[i], &pThisCC->aStreams[i], pThis, pThisCC, i /* u8SD */); + AssertRCReturn(rc, rc); + } + + hdaR3Reset(pDevIns); + + /* + * Info items and string formatter types. The latter is non-optional as + * the info handles use (at least some of) the custom types and we cannot + * accept screwing formatting. + */ + PDMDevHlpDBGFInfoRegister(pDevIns, "hda", "HDA registers. (hda [register case-insensitive])", hdaR3DbgInfo); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdabdl", + "HDA buffer descriptor list (BDL) and DMA stream positions. (hdabdl [stream number])", + hdaR3DbgInfoBDL); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdastream", "HDA stream info. (hdastream [stream number])", hdaR3DbgInfoStream); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdcnodes", "HDA codec nodes.", hdaR3DbgInfoCodecNodes); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdcselector", "HDA codec's selector states [node number].", hdaR3DbgInfoCodecSelector); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdamixer", "HDA mixer state.", hdaR3DbgInfoMixer); + + rc = RTStrFormatTypeRegister("sdctl", hdaR3StrFmtSDCTL, NULL); + AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); + rc = RTStrFormatTypeRegister("sdsts", hdaR3StrFmtSDSTS, NULL); + AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); + /** @todo the next two are rather pointless. */ + rc = RTStrFormatTypeRegister("sdfifos", hdaR3StrFmtSDFIFOS, NULL); + AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); + rc = RTStrFormatTypeRegister("sdfifow", hdaR3StrFmtSDFIFOW, NULL); + AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); + + /* + * Asserting sanity. + */ + AssertCompile(RT_ELEMENTS(pThis->au32Regs) < 256 /* assumption by HDAREGDESC::idxReg */); + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) + { + struct HDAREGDESC const *pReg = &g_aHdaRegMap[i]; + struct HDAREGDESC const *pNextReg = i + 1 < RT_ELEMENTS(g_aHdaRegMap) ? &g_aHdaRegMap[i + 1] : NULL; + + /* binary search order. */ + AssertReleaseMsg(!pNextReg || pReg->off + pReg->cb <= pNextReg->off, + ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", + i, pReg->off, pReg->cb, i + 1, pNextReg->off, pNextReg->cb)); + + /* alignment. */ + AssertReleaseMsg( pReg->cb == 1 + || (pReg->cb == 2 && (pReg->off & 1) == 0) + || (pReg->cb == 3 && (pReg->off & 3) == 0) + || (pReg->cb == 4 && (pReg->off & 3) == 0), + ("[%#x] = {%#x LB %#x}\n", i, pReg->off, pReg->cb)); + + /* registers are packed into dwords - with 3 exceptions with gaps at the end of the dword. */ + AssertRelease(((pReg->off + pReg->cb) & 3) == 0 || pNextReg); + if (pReg->off & 3) + { + struct HDAREGDESC const *pPrevReg = i > 0 ? &g_aHdaRegMap[i - 1] : NULL; + AssertReleaseMsg(pPrevReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->off, pReg->cb)); + if (pPrevReg) + AssertReleaseMsg(pPrevReg->off + pPrevReg->cb == pReg->off, + ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", + i - 1, pPrevReg->off, pPrevReg->cb, i + 1, pReg->off, pReg->cb)); + } +#if 0 + if ((pReg->offset + pReg->size) & 3) + { + AssertReleaseMsg(pNextReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); + if (pNextReg) + AssertReleaseMsg(pReg->offset + pReg->size == pNextReg->offset, + ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", + i, pReg->offset, pReg->size, i + 1, pNextReg->offset, pNextReg->size)); + } +#endif + /* The final entry is a full DWORD, no gaps! Allows shortcuts. */ + AssertReleaseMsg(pNextReg || ((pReg->off + pReg->cb) & 3) == 0, + ("[%#x] = {%#x LB %#x}\n", i, pReg->off, pReg->cb)); + } + Assert(strcmp(g_aHdaRegMap[HDA_REG_SSYNC].pszName, "SSYNC") == 0); + Assert(strcmp(g_aHdaRegMap[HDA_REG_DPUBASE].pszName, "DPUBASE") == 0); + Assert(strcmp(g_aHdaRegMap[HDA_REG_MLCH].pszName, "MLCH") == 0); + Assert(strcmp(g_aHdaRegMap[HDA_REG_SD3DPIB].pszName, "SD3DPIB") == 0); + Assert(strcmp(g_aHdaRegMap[HDA_REG_SD7EFIFOS].pszName, "SD7EFIFOS") == 0); + + /* + * Register statistics. + */ +# ifdef VBOX_WITH_STATISTICS + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "Input", STAMUNIT_TICKS_PER_CALL, "Profiling input."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "Output", STAMUNIT_TICKS_PER_CALL, "Profiling output."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead" , STAMUNIT_BYTES, "Bytes read (DMA) from the guest."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "BytesWritten", STAMUNIT_BYTES, "Bytes written (DMA) to the guest."); +# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatAccessDmaOutput, STAMTYPE_COUNTER, "AccessDmaOutput", STAMUNIT_COUNT, "Number of on-register-access DMA sub-transfers we've made."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatAccessDmaOutputToR3,STAMTYPE_COUNTER, "AccessDmaOutputToR3", STAMUNIT_COUNT, "Number of time the on-register-access DMA forced a ring-3 return."); +# endif + + AssertCompile(RT_ELEMENTS(g_aHdaRegMap) == HDA_NUM_REGS); + AssertCompile(RT_ELEMENTS(pThis->aStatRegReads) == HDA_NUM_REGS); + AssertCompile(RT_ELEMENTS(pThis->aStatRegReadsToR3) == HDA_NUM_REGS); + AssertCompile(RT_ELEMENTS(pThis->aStatRegWrites) == HDA_NUM_REGS); + AssertCompile(RT_ELEMENTS(pThis->aStatRegWritesToR3) == HDA_NUM_REGS); + for (size_t i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) + { + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegReads[i], STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, + g_aHdaRegMap[i].pszDesc, "Regs/%03x-%s-Reads", g_aHdaRegMap[i].off, g_aHdaRegMap[i].pszName); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegReadsToR3[i], STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + g_aHdaRegMap[i].pszDesc, "Regs/%03x-%s-Reads-ToR3", g_aHdaRegMap[i].off, g_aHdaRegMap[i].pszName); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegWrites[i], STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, + g_aHdaRegMap[i].pszDesc, "Regs/%03x-%s-Writes", g_aHdaRegMap[i].off, g_aHdaRegMap[i].pszName); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegWritesToR3[i], STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + g_aHdaRegMap[i].pszDesc, "Regs/%03x-%s-Writes-ToR3", g_aHdaRegMap[i].off, g_aHdaRegMap[i].pszName); + } + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiReadsR3, STAMTYPE_COUNTER, "RegMultiReadsR3", STAMUNIT_OCCURENCES, "Register read not targeting just one register, handled in ring-3"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiReadsRZ, STAMTYPE_COUNTER, "RegMultiReadsRZ", STAMUNIT_OCCURENCES, "Register read not targeting just one register, handled in ring-0"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiWritesR3, STAMTYPE_COUNTER, "RegMultiWritesR3", STAMUNIT_OCCURENCES, "Register writes not targeting just one register, handled in ring-3"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiWritesRZ, STAMTYPE_COUNTER, "RegMultiWritesRZ", STAMUNIT_OCCURENCES, "Register writes not targeting just one register, handled in ring-0"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegSubWriteR3, STAMTYPE_COUNTER, "RegSubWritesR3", STAMUNIT_OCCURENCES, "Trucated register writes, handled in ring-3"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegSubWriteRZ, STAMTYPE_COUNTER, "RegSubWritesRZ", STAMUNIT_OCCURENCES, "Trucated register writes, handled in ring-0"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegUnknownReads, STAMTYPE_COUNTER, "RegUnknownReads", STAMUNIT_OCCURENCES, "Reads of unknown registers."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegUnknownWrites, STAMTYPE_COUNTER, "RegUnknownWrites", STAMUNIT_OCCURENCES, "Writes to unknown registers."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegWritesBlockedByReset, STAMTYPE_COUNTER, "RegWritesBlockedByReset", STAMUNIT_OCCURENCES, "Writes blocked by pending reset (GCTL/CRST)"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegWritesBlockedByRun, STAMTYPE_COUNTER, "RegWritesBlockedByRun", STAMUNIT_OCCURENCES, "Writes blocked by byte RUN bit."); +# endif + + for (uint8_t idxStream = 0; idxStream < RT_ELEMENTS(pThisCC->aStreams); idxStream++) + { + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowProblems, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Number of internal DMA buffer problems.", "Stream%u/DMABufferProblems", idxStream); + if (hdaGetDirFromSD(idxStream) == PDMAUDIODIR_OUT) + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Number of internal DMA buffer overflows.", "Stream%u/DMABufferOverflows", idxStream); + else + { + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Number of internal DMA buffer underuns.", "Stream%u/DMABufferUnderruns", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrorBytes, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Number of bytes of silence added to cope with underruns.", "Stream%u/DMABufferSilence", idxStream); + } + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaSkippedPendingBcis, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "DMA transfer period skipped because of BCIS pending.", "Stream%u/DMASkippedPendingBCIS", idxStream); + + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Virtual internal buffer read position.", "Stream%u/offRead", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.cbCurDmaPeriod, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Bytes transfered per DMA timer callout.", "Stream%u/cbCurDmaPeriod", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, (void*)&pThis->aStreams[idxStream].State.fRunning, STAMTYPE_BOOL, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "True if the stream is in RUN mode.", "Stream%u/fRunning", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_HZ, + "The stream frequency.", "Stream%u/Cfg/Hz", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.cbFrame, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "The frame size.", "Stream%u/Cfg/FrameSize", idxStream); +#if 0 /** @todo this would require some callback or expansion. */ + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.cChannelsX, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "The number of channels.", "Stream%u/Cfg/Channels-Host", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Mapping.GuestProps.cChannels, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "The number of channels.", "Stream%u/Cfg/Channels-Guest", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.cbSample, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "The size of a sample (per channel).", "Stream%u/Cfg/cbSample", idxStream); +#endif + + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", idxStream); + + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStart, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL, + "Starting the stream.", "Stream%u/Start", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStop, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL, + "Stopping the stream.", "Stream%u/Stop", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReset, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL, + "Resetting the stream.", "Stream%u/Reset", idxStream); + } + + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) hdaRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */ + PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER0 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER0); + + int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, hdaMmioWrite, hdaMmioRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + +# if 0 /* Codec is not yet kosher enough for ring-0. @bugref{9890c64} */ + /* Construct the R0 codec part. */ + rc = hdaR0CodecConstruct(pDevIns, &pThis->Codec, &pThisCC->Codec); + AssertRCReturn(rc, rc); +# else + RT_NOREF(pThisCC); +# endif + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ + +/** + * The device registration structure. + */ +const PDMDEVREG g_DeviceHDA = +{ + /* .u32Version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "hda", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE + | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */, + /* .fClass = */ PDM_DEVREG_CLASS_AUDIO, + /* .cMaxInstances = */ 1, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(HDASTATE), + /* .cbInstanceCC = */ CTX_EXPR(sizeof(HDASTATER3), sizeof(HDASTATER0), 0), + /* .cbInstanceRC = */ 0, + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "Intel HD Audio Controller", +#if defined(IN_RING3) + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", + /* .pfnConstruct = */ hdaR3Construct, + /* .pfnDestruct = */ hdaR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ hdaR3Reset, + /* .pfnSuspend = */ NULL, + /* .pfnResume = */ NULL, + /* .pfnAttach = */ hdaR3Attach, + /* .pfnDetach = */ hdaR3Detach, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ hdaR3PowerOff, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ hdaRZConstruct, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RC) + /* .pfnConstruct = */ hdaRZConstruct, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +#endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHDA.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHDA.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHDA.cpp 2020-10-16 16:32:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHDA.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,5228 +0,0 @@ -/* $Id: DevHDA.cpp $ */ -/** @file - * DevHDA.cpp - VBox Intel HD Audio Controller. - * - * Implemented against the specifications found in "High Definition Audio - * Specification", Revision 1.0a June 17, 2010, and "Intel I/O Controller - * HUB 6 (ICH6) Family, Datasheet", document number 301473-002. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DEV_HDA -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -# include -#ifdef IN_RING3 -# include -# include -# include -#endif - -#include "VBoxDD.h" - -#include "AudioMixBuffer.h" -#include "AudioMixer.h" - -#include "DevHDA.h" -#include "DevHDACommon.h" - -#include "HDACodec.h" -#include "HDAStream.h" -#include "HDAStreamMap.h" -#include "HDAStreamPeriod.h" - -#include "DrvAudio.h" - - -/********************************************************************************************************************************* -* Defined Constants And Macros * -*********************************************************************************************************************************/ -//#define HDA_AS_PCI_EXPRESS - -/* Installs a DMA access handler (via PGM callback) to monitor - * HDA's DMA operations, that is, writing / reading audio stream data. - * - * !!! Note: Certain guests are *that* timing sensitive that when enabling !!! - * !!! such a handler will mess up audio completely (e.g. Windows 7). !!! */ -//#define HDA_USE_DMA_ACCESS_HANDLER -#ifdef HDA_USE_DMA_ACCESS_HANDLER -# include -#endif - -/* Uses the DMA access handler to read the written DMA audio (output) data. - * Only valid if HDA_USE_DMA_ACCESS_HANDLER is set. - * - * Also see the note / warning for HDA_USE_DMA_ACCESS_HANDLER. */ -//# define HDA_USE_DMA_ACCESS_HANDLER_WRITING - -/* Useful to debug the device' timing. */ -//#define HDA_DEBUG_TIMING - -/* To debug silence coming from the guest in form of audio gaps. - * Very crude implementation for now. */ -//#define HDA_DEBUG_SILENCE - -#if defined(VBOX_WITH_HP_HDA) -/* HP Pavilion dv4t-1300 */ -# define HDA_PCI_VENDOR_ID 0x103c -# define HDA_PCI_DEVICE_ID 0x30f7 -#elif defined(VBOX_WITH_INTEL_HDA) -/* Intel HDA controller */ -# define HDA_PCI_VENDOR_ID 0x8086 -# define HDA_PCI_DEVICE_ID 0x2668 -#elif defined(VBOX_WITH_NVIDIA_HDA) -/* nVidia HDA controller */ -# define HDA_PCI_VENDOR_ID 0x10de -# define HDA_PCI_DEVICE_ID 0x0ac0 -#else -# error "Please specify your HDA device vendor/device IDs" -#endif - -/** - * Acquires the HDA lock. - */ -#define DEVHDA_LOCK(a_pDevIns, a_pThis) \ - do { \ - int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \ - AssertRC(rcLock); \ - } while (0) - -/** - * Acquires the HDA lock or returns. - */ -#define DEVHDA_LOCK_RETURN(a_pDevIns, a_pThis, a_rcBusy) \ - do { \ - int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, a_rcBusy); \ - if (rcLock == VINF_SUCCESS) \ - { /* likely */ } \ - else \ - { \ - AssertRC(rcLock); \ - return rcLock; \ - } \ - } while (0) - -/** - * Acquires the HDA lock or returns. - */ -# define DEVHDA_LOCK_RETURN_VOID(a_pDevIns, a_pThis) \ - do { \ - int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \ - if (rcLock == VINF_SUCCESS) \ - { /* likely */ } \ - else \ - { \ - AssertRC(rcLock); \ - return; \ - } \ - } while (0) - -/** - * Releases the HDA lock. - */ -#define DEVHDA_UNLOCK(a_pDevIns, a_pThis) \ - do { PDMDevHlpCritSectLeave((a_pDevIns), &(a_pThis)->CritSect); } while (0) - -/** - * Acquires the TM lock and HDA lock, returns on failure. - */ -#define DEVHDA_LOCK_BOTH_RETURN(a_pDevIns, a_pThis, a_pStream, a_rcBusy) \ - do { \ - VBOXSTRICTRC rcLock = PDMDevHlpTimerLockClock2(pDevIns, (a_pStream)->hTimer, &(a_pThis)->CritSect, (a_rcBusy)); \ - if (RT_LIKELY(rcLock == VINF_SUCCESS)) \ - { /* likely */ } \ - else \ - return VBOXSTRICTRC_TODO(rcLock); \ - } while (0) - - -/********************************************************************************************************************************* -* Structures and Typedefs * -*********************************************************************************************************************************/ - -/** - * Structure defining a (host backend) driver stream. - * Each driver has its own instances of audio mixer streams, which then - * can go into the same (or even different) audio mixer sinks. - */ -typedef struct HDADRIVERSTREAM -{ - /** Associated mixer handle. */ - R3PTRTYPE(PAUDMIXSTREAM) pMixStrm; -} HDADRIVERSTREAM, *PHDADRIVERSTREAM; - -#ifdef HDA_USE_DMA_ACCESS_HANDLER -/** - * Struct for keeping an HDA DMA access handler context. - */ -typedef struct HDADMAACCESSHANDLER -{ - /** Node for storing this handler in our list in HDASTREAMSTATE. */ - RTLISTNODER3 Node; - /** Pointer to stream to which this access handler is assigned to. */ - R3PTRTYPE(PHDASTREAM) pStream; - /** Access handler type handle. */ - PGMPHYSHANDLERTYPE hAccessHandlerType; - /** First address this handler uses. */ - RTGCPHYS GCPhysFirst; - /** Last address this handler uses. */ - RTGCPHYS GCPhysLast; - /** Actual BDLE address to handle. */ - RTGCPHYS BDLEAddr; - /** Actual BDLE buffer size to handle. */ - RTGCPHYS BDLESize; - /** Whether the access handler has been registered or not. */ - bool fRegistered; - uint8_t Padding[3]; -} HDADMAACCESSHANDLER, *PHDADMAACCESSHANDLER; -#endif - -/** - * Struct for maintaining a host backend driver. - * This driver must be associated to one, and only one, - * HDA codec. The HDA controller does the actual multiplexing - * of HDA codec data to various host backend drivers then. - * - * This HDA device uses a timer in order to synchronize all - * read/write accesses across all attached LUNs / backends. - */ -typedef struct HDADRIVER -{ - /** Node for storing this driver in our device driver list of HDASTATE. */ - RTLISTNODER3 Node; - /** Pointer to shared HDA device state. */ - R3PTRTYPE(PHDASTATE) pHDAStateShared; - /** Pointer to the ring-3 HDA device state. */ - R3PTRTYPE(PHDASTATER3) pHDAStateR3; - /** Driver flags. */ - PDMAUDIODRVFLAGS fFlags; - uint8_t u32Padding0[2]; - /** LUN to which this driver has been assigned. */ - uint8_t uLUN; - /** Whether this driver is in an attached state or not. */ - bool fAttached; - /** Pointer to attached driver base interface. */ - R3PTRTYPE(PPDMIBASE) pDrvBase; - /** Audio connector interface to the underlying host backend. */ - R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector; - /** Mixer stream for line input. */ - HDADRIVERSTREAM LineIn; -#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - /** Mixer stream for mic input. */ - HDADRIVERSTREAM MicIn; -#endif - /** Mixer stream for front output. */ - HDADRIVERSTREAM Front; -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - /** Mixer stream for center/LFE output. */ - HDADRIVERSTREAM CenterLFE; - /** Mixer stream for rear output. */ - HDADRIVERSTREAM Rear; -#endif -} HDADRIVER; - - -/********************************************************************************************************************************* -* Internal Functions * -*********************************************************************************************************************************/ -#ifndef VBOX_DEVICE_STRUCT_TESTCASE -#ifdef IN_RING3 -static void hdaR3GCTLReset(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC); -#endif - -/** @name Register read/write stubs. - * @{ - */ -static FNHDAREGREAD hdaRegReadUnimpl; -static FNHDAREGWRITE hdaRegWriteUnimpl; -/** @} */ - -/** @name Global register set read/write functions. - * @{ - */ -static FNHDAREGWRITE hdaRegWriteGCTL; -static FNHDAREGREAD hdaRegReadLPIB; -static FNHDAREGREAD hdaRegReadWALCLK; -static FNHDAREGWRITE hdaRegWriteCORBWP; -static FNHDAREGWRITE hdaRegWriteCORBRP; -static FNHDAREGWRITE hdaRegWriteCORBCTL; -static FNHDAREGWRITE hdaRegWriteCORBSIZE; -static FNHDAREGWRITE hdaRegWriteCORBSTS; -static FNHDAREGWRITE hdaRegWriteRINTCNT; -static FNHDAREGWRITE hdaRegWriteRIRBWP; -static FNHDAREGWRITE hdaRegWriteRIRBSTS; -static FNHDAREGWRITE hdaRegWriteSTATESTS; -static FNHDAREGWRITE hdaRegWriteIRS; -static FNHDAREGREAD hdaRegReadIRS; -static FNHDAREGWRITE hdaRegWriteBase; -/** @} */ - -/** @name {IOB}SDn write functions. - * @{ - */ -static FNHDAREGWRITE hdaRegWriteSDCBL; -static FNHDAREGWRITE hdaRegWriteSDCTL; -static FNHDAREGWRITE hdaRegWriteSDSTS; -static FNHDAREGWRITE hdaRegWriteSDLVI; -static FNHDAREGWRITE hdaRegWriteSDFIFOW; -static FNHDAREGWRITE hdaRegWriteSDFIFOS; -static FNHDAREGWRITE hdaRegWriteSDFMT; -static FNHDAREGWRITE hdaRegWriteSDBDPL; -static FNHDAREGWRITE hdaRegWriteSDBDPU; -/** @} */ - -/** @name Generic register read/write functions. - * @{ - */ -static FNHDAREGREAD hdaRegReadU32; -static FNHDAREGWRITE hdaRegWriteU32; -static FNHDAREGREAD hdaRegReadU24; -#ifdef IN_RING3 -static FNHDAREGWRITE hdaRegWriteU24; -#endif -static FNHDAREGREAD hdaRegReadU16; -static FNHDAREGWRITE hdaRegWriteU16; -static FNHDAREGREAD hdaRegReadU8; -static FNHDAREGWRITE hdaRegWriteU8; -/** @} */ - -/** @name HDA device functions. - * @{ - */ -#ifdef IN_RING3 -static int hdaR3AddStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg); -static int hdaR3RemoveStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg); -# ifdef HDA_USE_DMA_ACCESS_HANDLER -static DECLCALLBACK(VBOXSTRICTRC) hdaR3DmaAccessHandler(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, void *pvPhys, - void *pvBuf, size_t cbBuf, - PGMACCESSTYPE enmAccessType, PGMACCESSORIGIN enmOrigin, void *pvUser); -# endif -#endif /* IN_RING3 */ -/** @} */ - -/** @name HDA mixer functions. - * @{ - */ -#ifdef IN_RING3 -static int hdaR3MixerAddDrvStream(PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv); -#endif -/** @} */ - -#ifdef IN_RING3 -static FNSSMFIELDGETPUT hdaR3GetPutTrans_HDABDLEDESC_fFlags_6; -static FNSSMFIELDGETPUT hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4; -#endif - - -/********************************************************************************************************************************* -* Global Variables * -*********************************************************************************************************************************/ - -/** No register description (RD) flags defined. */ -#define HDA_RD_F_NONE 0 -/** Writes to SD are allowed while RUN bit is set. */ -#define HDA_RD_F_SD_WRITE_RUN RT_BIT(0) - -/** Emits a single audio stream register set (e.g. OSD0) at a specified offset. */ -#define HDA_REG_MAP_STRM(offset, name) \ - /* offset size read mask write mask flags read callback write callback index + abbrev description */ \ - /* ------- ------- ---------- ---------- ------------------------- -------------- ----------------- ----------------------------- ----------- */ \ - /* Offset 0x80 (SD0) */ \ - { offset, 0x00003, 0x00FF001F, 0x00F0001F, HDA_RD_F_SD_WRITE_RUN, hdaRegReadU24 , hdaRegWriteSDCTL , HDA_REG_IDX_STRM(name, CTL) , #name " Stream Descriptor Control" }, \ - /* Offset 0x83 (SD0) */ \ - { offset + 0x3, 0x00001, 0x0000003C, 0x0000001C, HDA_RD_F_SD_WRITE_RUN, hdaRegReadU8 , hdaRegWriteSDSTS , HDA_REG_IDX_STRM(name, STS) , #name " Status" }, \ - /* Offset 0x84 (SD0) */ \ - { offset + 0x4, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadLPIB, hdaRegWriteU32 , HDA_REG_IDX_STRM(name, LPIB) , #name " Link Position In Buffer" }, \ - /* Offset 0x88 (SD0) */ \ - { offset + 0x8, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDCBL , HDA_REG_IDX_STRM(name, CBL) , #name " Cyclic Buffer Length" }, \ - /* Offset 0x8C (SD0) -- upper 8 bits are reserved */ \ - { offset + 0xC, 0x00002, 0x0000FFFF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDLVI , HDA_REG_IDX_STRM(name, LVI) , #name " Last Valid Index" }, \ - /* Reserved: FIFO Watermark. ** @todo Document this! */ \ - { offset + 0xE, 0x00002, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOW, HDA_REG_IDX_STRM(name, FIFOW), #name " FIFO Watermark" }, \ - /* Offset 0x90 (SD0) */ \ - { offset + 0x10, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOS, HDA_REG_IDX_STRM(name, FIFOS), #name " FIFO Size" }, \ - /* Offset 0x92 (SD0) */ \ - { offset + 0x12, 0x00002, 0x00007F7F, 0x00007F7F, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFMT , HDA_REG_IDX_STRM(name, FMT) , #name " Stream Format" }, \ - /* Reserved: 0x94 - 0x98. */ \ - /* Offset 0x98 (SD0) */ \ - { offset + 0x18, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDBDPL , HDA_REG_IDX_STRM(name, BDPL) , #name " Buffer Descriptor List Pointer-Lower Base Address" }, \ - /* Offset 0x9C (SD0) */ \ - { offset + 0x1C, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDBDPU , HDA_REG_IDX_STRM(name, BDPU) , #name " Buffer Descriptor List Pointer-Upper Base Address" } - -/** Defines a single audio stream register set (e.g. OSD0). */ -#define HDA_REG_MAP_DEF_STREAM(index, name) \ - HDA_REG_MAP_STRM(HDA_REG_DESC_SD0_BASE + (index * 32 /* 0x20 */), name) - -/** See 302349 p 6.2. */ -const HDAREGDESC g_aHdaRegMap[HDA_NUM_REGS] = -{ - /* offset size read mask write mask flags read callback write callback index + abbrev */ - /*------- ------- ---------- ---------- ----------------- ---------------- ------------------- ------------------------ */ - { 0x00000, 0x00002, 0x0000FFFB, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(GCAP) }, /* Global Capabilities */ - { 0x00002, 0x00001, 0x000000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(VMIN) }, /* Minor Version */ - { 0x00003, 0x00001, 0x000000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(VMAJ) }, /* Major Version */ - { 0x00004, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(OUTPAY) }, /* Output Payload Capabilities */ - { 0x00006, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(INPAY) }, /* Input Payload Capabilities */ - { 0x00008, 0x00004, 0x00000103, 0x00000103, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteGCTL , HDA_REG_IDX(GCTL) }, /* Global Control */ - { 0x0000c, 0x00002, 0x00007FFF, 0x00007FFF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(WAKEEN) }, /* Wake Enable */ - { 0x0000e, 0x00002, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteSTATESTS, HDA_REG_IDX(STATESTS) }, /* State Change Status */ - { 0x00010, 0x00002, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadUnimpl, hdaRegWriteUnimpl , HDA_REG_IDX(GSTS) }, /* Global Status */ - { 0x00018, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(OUTSTRMPAY) }, /* Output Stream Payload Capability */ - { 0x0001A, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(INSTRMPAY) }, /* Input Stream Payload Capability */ - { 0x00020, 0x00004, 0xC00000FF, 0xC00000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(INTCTL) }, /* Interrupt Control */ - { 0x00024, 0x00004, 0xC00000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , HDA_REG_IDX(INTSTS) }, /* Interrupt Status */ - { 0x00030, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadWALCLK, hdaRegWriteUnimpl , HDA_REG_IDX_NOMEM(WALCLK) }, /* Wall Clock Counter */ - { 0x00034, 0x00004, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(SSYNC) }, /* Stream Synchronization */ - { 0x00040, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(CORBLBASE) }, /* CORB Lower Base Address */ - { 0x00044, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(CORBUBASE) }, /* CORB Upper Base Address */ - { 0x00048, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteCORBWP , HDA_REG_IDX(CORBWP) }, /* CORB Write Pointer */ - { 0x0004A, 0x00002, 0x000080FF, 0x00008000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteCORBRP , HDA_REG_IDX(CORBRP) }, /* CORB Read Pointer */ - { 0x0004C, 0x00001, 0x00000003, 0x00000003, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBCTL , HDA_REG_IDX(CORBCTL) }, /* CORB Control */ - { 0x0004D, 0x00001, 0x00000001, 0x00000001, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBSTS , HDA_REG_IDX(CORBSTS) }, /* CORB Status */ - { 0x0004E, 0x00001, 0x000000F3, 0x00000003, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBSIZE, HDA_REG_IDX(CORBSIZE) }, /* CORB Size */ - { 0x00050, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(RIRBLBASE) }, /* RIRB Lower Base Address */ - { 0x00054, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(RIRBUBASE) }, /* RIRB Upper Base Address */ - { 0x00058, 0x00002, 0x000000FF, 0x00008000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteRIRBWP , HDA_REG_IDX(RIRBWP) }, /* RIRB Write Pointer */ - { 0x0005A, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteRINTCNT , HDA_REG_IDX(RINTCNT) }, /* Response Interrupt Count */ - { 0x0005C, 0x00001, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteU8 , HDA_REG_IDX(RIRBCTL) }, /* RIRB Control */ - { 0x0005D, 0x00001, 0x00000005, 0x00000005, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteRIRBSTS , HDA_REG_IDX(RIRBSTS) }, /* RIRB Status */ - { 0x0005E, 0x00001, 0x000000F3, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(RIRBSIZE) }, /* RIRB Size */ - { 0x00060, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(IC) }, /* Immediate Command */ - { 0x00064, 0x00004, 0x00000000, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , HDA_REG_IDX(IR) }, /* Immediate Response */ - { 0x00068, 0x00002, 0x00000002, 0x00000002, HDA_RD_F_NONE, hdaRegReadIRS , hdaRegWriteIRS , HDA_REG_IDX(IRS) }, /* Immediate Command Status */ - { 0x00070, 0x00004, 0xFFFFFFFF, 0xFFFFFF81, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(DPLBASE) }, /* DMA Position Lower Base */ - { 0x00074, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(DPUBASE) }, /* DMA Position Upper Base */ - /* 4 Serial Data In (SDI). */ - HDA_REG_MAP_DEF_STREAM(0, SD0), - HDA_REG_MAP_DEF_STREAM(1, SD1), - HDA_REG_MAP_DEF_STREAM(2, SD2), - HDA_REG_MAP_DEF_STREAM(3, SD3), - /* 4 Serial Data Out (SDO). */ - HDA_REG_MAP_DEF_STREAM(4, SD4), - HDA_REG_MAP_DEF_STREAM(5, SD5), - HDA_REG_MAP_DEF_STREAM(6, SD6), - HDA_REG_MAP_DEF_STREAM(7, SD7) -}; - -const HDAREGALIAS g_aHdaRegAliases[] = -{ - { 0x2084, HDA_REG_SD0LPIB }, - { 0x20a4, HDA_REG_SD1LPIB }, - { 0x20c4, HDA_REG_SD2LPIB }, - { 0x20e4, HDA_REG_SD3LPIB }, - { 0x2104, HDA_REG_SD4LPIB }, - { 0x2124, HDA_REG_SD5LPIB }, - { 0x2144, HDA_REG_SD6LPIB }, - { 0x2164, HDA_REG_SD7LPIB } -}; - -#ifdef IN_RING3 - -/** HDABDLEDESC field descriptors for the v7 saved state. */ -static SSMFIELD const g_aSSMBDLEDescFields7[] = -{ - SSMFIELD_ENTRY(HDABDLEDESC, u64BufAddr), - SSMFIELD_ENTRY(HDABDLEDESC, u32BufSize), - SSMFIELD_ENTRY(HDABDLEDESC, fFlags), - SSMFIELD_ENTRY_TERM() -}; - -/** HDABDLEDESC field descriptors for the v6 saved states. */ -static SSMFIELD const g_aSSMBDLEDescFields6[] = -{ - SSMFIELD_ENTRY(HDABDLEDESC, u64BufAddr), - SSMFIELD_ENTRY(HDABDLEDESC, u32BufSize), - SSMFIELD_ENTRY_CALLBACK(HDABDLEDESC, fFlags, hdaR3GetPutTrans_HDABDLEDESC_fFlags_6), - SSMFIELD_ENTRY_TERM() -}; - -/** HDABDLESTATE field descriptors for the v6+ saved state. */ -static SSMFIELD const g_aSSMBDLEStateFields6[] = -{ - SSMFIELD_ENTRY(HDABDLESTATE, u32BDLIndex), - SSMFIELD_ENTRY(HDABDLESTATE, cbBelowFIFOW), - SSMFIELD_ENTRY_OLD(FIFO, HDA_FIFO_MAX), /* Deprecated; now is handled in the stream's circular buffer. */ - SSMFIELD_ENTRY(HDABDLESTATE, u32BufOff), - SSMFIELD_ENTRY_TERM() -}; - -/** HDABDLESTATE field descriptors for the v7 saved state. */ -static SSMFIELD const g_aSSMBDLEStateFields7[] = -{ - SSMFIELD_ENTRY(HDABDLESTATE, u32BDLIndex), - SSMFIELD_ENTRY(HDABDLESTATE, cbBelowFIFOW), - SSMFIELD_ENTRY(HDABDLESTATE, u32BufOff), - SSMFIELD_ENTRY_TERM() -}; - -/** HDASTREAMSTATE field descriptors for the v6 saved state. */ -static SSMFIELD const g_aSSMStreamStateFields6[] = -{ - SSMFIELD_ENTRY_OLD(cBDLE, sizeof(uint16_t)), /* Deprecated. */ - SSMFIELD_ENTRY(HDASTREAMSTATE, uCurBDLE), - SSMFIELD_ENTRY_OLD(fStop, 1), /* Deprecated; see SSMR3PutBool(). */ - SSMFIELD_ENTRY_OLD(fRunning, 1), /* Deprecated; using the HDA_SDCTL_RUN bit is sufficient. */ - SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset), - SSMFIELD_ENTRY_TERM() -}; - -/** HDASTREAMSTATE field descriptors for the v7 saved state. */ -static SSMFIELD const g_aSSMStreamStateFields7[] = -{ - SSMFIELD_ENTRY(HDASTREAMSTATE, uCurBDLE), - SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset), - SSMFIELD_ENTRY(HDASTREAMSTATE, tsTransferNext), - SSMFIELD_ENTRY_TERM() -}; - -/** HDASTREAMPERIOD field descriptors for the v7 saved state. */ -static SSMFIELD const g_aSSMStreamPeriodFields7[] = -{ - SSMFIELD_ENTRY(HDASTREAMPERIOD, u64StartWalClk), - SSMFIELD_ENTRY(HDASTREAMPERIOD, u64ElapsedWalClk), - SSMFIELD_ENTRY(HDASTREAMPERIOD, cFramesTransferred), - SSMFIELD_ENTRY(HDASTREAMPERIOD, cIntPending), - SSMFIELD_ENTRY_TERM() -}; - -/** HDABDLE field descriptors for the v1 thru v4 saved states. */ -static SSMFIELD const g_aSSMStreamBdleFields1234[] = -{ - SSMFIELD_ENTRY(HDABDLE, Desc.u64BufAddr), /* u64BdleCviAddr */ - SSMFIELD_ENTRY_OLD(u32BdleMaxCvi, sizeof(uint32_t)), /* u32BdleMaxCvi */ - SSMFIELD_ENTRY(HDABDLE, State.u32BDLIndex), /* u32BdleCvi */ - SSMFIELD_ENTRY(HDABDLE, Desc.u32BufSize), /* u32BdleCviLen */ - SSMFIELD_ENTRY(HDABDLE, State.u32BufOff), /* u32BdleCviPos */ - SSMFIELD_ENTRY_CALLBACK(HDABDLE, Desc.fFlags, hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4), /* fBdleCviIoc */ - SSMFIELD_ENTRY(HDABDLE, State.cbBelowFIFOW), /* cbUnderFifoW */ - SSMFIELD_ENTRY_OLD(au8FIFO, 256), /* au8FIFO */ - SSMFIELD_ENTRY_TERM() -}; - -#endif /* IN_RING3 */ - -/** - * 32-bit size indexed masks, i.e. g_afMasks[2 bytes] = 0xffff. - */ -static uint32_t const g_afMasks[5] = -{ - UINT32_C(0), UINT32_C(0x000000ff), UINT32_C(0x0000ffff), UINT32_C(0x00ffffff), UINT32_C(0xffffffff) -}; - - - - -/** - * Retrieves the number of bytes of a FIFOW register. - * - * @return Number of bytes of a given FIFOW register. - */ -DECLINLINE(uint8_t) hdaSDFIFOWToBytes(uint32_t u32RegFIFOW) -{ - uint32_t cb; - switch (u32RegFIFOW) - { - case HDA_SDFIFOW_8B: cb = 8; break; - case HDA_SDFIFOW_16B: cb = 16; break; - case HDA_SDFIFOW_32B: cb = 32; break; - default: cb = 0; break; - } - - Assert(RT_IS_POWER_OF_TWO(cb)); - return cb; -} - -#ifdef IN_RING3 -/** - * Reschedules pending interrupts for all audio streams which have complete - * audio periods but did not have the chance to issue their (pending) interrupts yet. - * - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - */ -static void hdaR3ReschedulePendingInterrupts(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC) -{ - bool fInterrupt = false; - - for (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i) - { - PHDASTREAM pStream = &pThis->aStreams[i]; - if ( hdaR3StreamPeriodIsComplete( &pStream->State.Period) - && hdaR3StreamPeriodNeedsInterrupt(&pStream->State.Period) - && hdaR3WalClkSet(pThis, pThisCC, hdaR3StreamPeriodGetAbsElapsedWalClk(&pStream->State.Period), false /* fForce */)) - { - fInterrupt = true; - break; - } - } - - LogFunc(("fInterrupt=%RTbool\n", fInterrupt)); - - HDA_PROCESS_INTERRUPT(pDevIns, pThis); -} -#endif /* IN_RING3 */ - -/** - * Looks up a register at the exact offset given by @a offReg. - * - * @returns Register index on success, -1 if not found. - * @param offReg The register offset. - */ -static int hdaRegLookup(uint32_t offReg) -{ - /* - * Aliases. - */ - if (offReg >= g_aHdaRegAliases[0].offReg) - { - for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++) - if (offReg == g_aHdaRegAliases[i].offReg) - return g_aHdaRegAliases[i].idxAlias; - Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].offset < offReg); - return -1; - } - - /* - * Binary search the - */ - int idxEnd = RT_ELEMENTS(g_aHdaRegMap); - int idxLow = 0; - for (;;) - { - int idxMiddle = idxLow + (idxEnd - idxLow) / 2; - if (offReg < g_aHdaRegMap[idxMiddle].offset) - { - if (idxLow == idxMiddle) - break; - idxEnd = idxMiddle; - } - else if (offReg > g_aHdaRegMap[idxMiddle].offset) - { - idxLow = idxMiddle + 1; - if (idxLow >= idxEnd) - break; - } - else - return idxMiddle; - } - -#ifdef RT_STRICT - for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) - Assert(g_aHdaRegMap[i].offset != offReg); -#endif - return -1; -} - -#ifdef IN_RING3 - -/** - * Looks up a register covering the offset given by @a offReg. - * - * @returns Register index on success, -1 if not found. - * @param offReg The register offset. - */ -static int hdaR3RegLookupWithin(uint32_t offReg) -{ - /* - * Aliases. - */ - if (offReg >= g_aHdaRegAliases[0].offReg) - { - for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++) - { - uint32_t off = offReg - g_aHdaRegAliases[i].offReg; - if (off < 4 && off < g_aHdaRegMap[g_aHdaRegAliases[i].idxAlias].size) - return g_aHdaRegAliases[i].idxAlias; - } - Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].offset < offReg); - return -1; - } - - /* - * Binary search the register map. - */ - int idxEnd = RT_ELEMENTS(g_aHdaRegMap); - int idxLow = 0; - for (;;) - { - int idxMiddle = idxLow + (idxEnd - idxLow) / 2; - if (offReg < g_aHdaRegMap[idxMiddle].offset) - { - if (idxLow == idxMiddle) - break; - idxEnd = idxMiddle; - } - else if (offReg >= g_aHdaRegMap[idxMiddle].offset + g_aHdaRegMap[idxMiddle].size) - { - idxLow = idxMiddle + 1; - if (idxLow >= idxEnd) - break; - } - else - return idxMiddle; - } - -# ifdef RT_STRICT - for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) - Assert(offReg - g_aHdaRegMap[i].offset >= g_aHdaRegMap[i].size); -# endif - return -1; -} - - -/** - * Synchronizes the CORB / RIRB buffers between internal <-> device state. - * - * @returns IPRT status code. - * - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param fLocal Specify true to synchronize HDA state's CORB buffer with the device state, - * or false to synchronize the device state's RIRB buffer with the HDA state. - * - * @todo r=andy Break this up into two functions? - */ -static int hdaR3CmdSync(PPDMDEVINS pDevIns, PHDASTATE pThis, bool fLocal) -{ - int rc = VINF_SUCCESS; - if (fLocal) - { - if (pThis->u64CORBBase) - { - Assert(pThis->cbCorbBuf); - -/** @todo r=bird: An explanation is required why PDMDevHlpPhysRead is used with - * the CORB and PDMDevHlpPCIPhysWrite with RIRB below. There are - * similar unexplained inconsistencies in DevHDACommon.cpp. */ - rc = PDMDevHlpPhysRead(pDevIns, pThis->u64CORBBase, pThis->au32CorbBuf, - RT_MIN(pThis->cbCorbBuf, sizeof(pThis->au32CorbBuf))); - Log(("hdaR3CmdSync/CORB: read %RGp LB %#x (%Rrc)\n", pThis->u64CORBBase, pThis->cbCorbBuf, rc)); - AssertRCReturn(rc, rc); - } - } - else - { - if (pThis->u64RIRBBase) - { - Assert(pThis->cbRirbBuf); - - rc = PDMDevHlpPCIPhysWrite(pDevIns, pThis->u64RIRBBase, pThis->au64RirbBuf, - RT_MIN(pThis->cbRirbBuf, sizeof(pThis->au64RirbBuf))); - Log(("hdaR3CmdSync/RIRB: phys read %RGp LB %#x (%Rrc)\n", pThis->u64RIRBBase, pThis->cbRirbBuf, rc)); - AssertRCReturn(rc, rc); - } - } - -# ifdef DEBUG_CMD_BUFFER - LogFunc(("fLocal=%RTbool\n", fLocal)); - - uint8_t i = 0; - do - { - LogFunc(("CORB%02x: ", i)); - uint8_t j = 0; - do - { - const char *pszPrefix; - if ((i + j) == HDA_REG(pThis, CORBRP)) - pszPrefix = "[R]"; - else if ((i + j) == HDA_REG(pThis, CORBWP)) - pszPrefix = "[W]"; - else - pszPrefix = " "; /* three spaces */ - Log((" %s%08x", pszPrefix, pThis->pu32CorbBuf[i + j])); - j++; - } while (j < 8); - Log(("\n")); - i += 8; - } while (i != 0); - - do - { - LogFunc(("RIRB%02x: ", i)); - uint8_t j = 0; - do - { - const char *prefix; - if ((i + j) == HDA_REG(pThis, RIRBWP)) - prefix = "[W]"; - else - prefix = " "; - Log((" %s%016lx", prefix, pThis->pu64RirbBuf[i + j])); - } while (++j < 8); - Log(("\n")); - i += 8; - } while (i != 0); -# endif - return rc; -} - -/** - * Processes the next CORB buffer command in the queue. - * - * This will invoke the HDA codec verb dispatcher. - * - * @returns VBox status code suitable for MMIO write return. - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - */ -static int hdaR3CORBCmdProcess(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC) -{ - Log3Func(("CORB(RP:%x, WP:%x) RIRBWP:%x\n", HDA_REG(pThis, CORBRP), HDA_REG(pThis, CORBWP), HDA_REG(pThis, RIRBWP))); - - if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) - { - LogFunc(("CORB DMA not active, skipping\n")); - return VINF_SUCCESS; - } - - Assert(pThis->cbCorbBuf); - - int rc = hdaR3CmdSync(pDevIns, pThis, true /* Sync from guest */); - AssertRCReturn(rc, rc); - - /* - * Prepare local copies of relevant registers. - */ - uint16_t cIntCnt = HDA_REG(pThis, RINTCNT) & 0xff; - if (!cIntCnt) /* 0 means 256 interrupts. */ - cIntCnt = HDA_MAX_RINTCNT; - - uint32_t const cCorbEntries = RT_MIN(RT_MAX(pThis->cbCorbBuf, 1), sizeof(pThis->au32CorbBuf)) / HDA_CORB_ELEMENT_SIZE; - uint8_t const corbWp = HDA_REG(pThis, CORBWP) % cCorbEntries; - uint8_t corbRp = HDA_REG(pThis, CORBRP); - uint8_t rirbWp = HDA_REG(pThis, RIRBWP); - - /* - * The loop. - */ - Log3Func(("START CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt)); - while (corbRp != corbWp) - { - /* Fetch the command from the CORB. */ - corbRp = (corbRp + 1) /* Advance +1 as the first command(s) are at CORBWP + 1. */ % cCorbEntries; - uint32_t const uCmd = pThis->au32CorbBuf[corbRp]; - - /* - * Execute the command. - */ - uint64_t uResp = 0; - rc = pThisCC->pCodec->pfnLookup(pThisCC->pCodec, HDA_CODEC_CMD(uCmd, 0 /* Codec index */), &uResp); - if (RT_FAILURE(rc)) - LogFunc(("Codec lookup failed with rc=%Rrc\n", rc)); - Log3Func(("Codec verb %08x -> response %016RX64\n", uCmd, uResp)); - - if ( (uResp & CODEC_RESPONSE_UNSOLICITED) - && !(HDA_REG(pThis, GCTL) & HDA_GCTL_UNSOL)) - { - LogFunc(("Unexpected unsolicited response.\n")); - HDA_REG(pThis, CORBRP) = corbRp; - /** @todo r=andy No RIRB syncing to guest required in that case? */ - /** @todo r=bird: Why isn't RIRBWP updated here. The response might come - * after already processing several commands, can't it? (When you think - * about it, it is bascially the same question as Andy is asking.) */ - return VINF_SUCCESS; - } - - /* - * Store the response in the RIRB. - */ - AssertCompile(HDA_RIRB_SIZE == RT_ELEMENTS(pThis->au64RirbBuf)); - rirbWp = (rirbWp + 1) % HDA_RIRB_SIZE; - pThis->au64RirbBuf[rirbWp] = uResp; - - /* - * Send interrupt if needed. - */ - bool fSendInterrupt = false; - pThis->u16RespIntCnt++; - if (pThis->u16RespIntCnt >= cIntCnt) /* Response interrupt count reached? */ - { - pThis->u16RespIntCnt = 0; /* Reset internal interrupt response counter. */ - - Log3Func(("Response interrupt count reached (%RU16)\n", pThis->u16RespIntCnt)); - fSendInterrupt = true; - } - else if (corbRp == corbWp) /* Did we reach the end of the current command buffer? */ - { - Log3Func(("Command buffer empty\n")); - fSendInterrupt = true; - } - if (fSendInterrupt) - { - if (HDA_REG(pThis, RIRBCTL) & HDA_RIRBCTL_RINTCTL) /* Response Interrupt Control (RINTCTL) enabled? */ - { - HDA_REG(pThis, RIRBSTS) |= HDA_RIRBSTS_RINTFL; - HDA_PROCESS_INTERRUPT(pDevIns, pThis); - } - } - } - - /* - * Put register locals back. - */ - Log3Func(("END CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt)); - HDA_REG(pThis, CORBRP) = corbRp; - HDA_REG(pThis, RIRBWP) = rirbWp; - - /* - * Write out the response. - */ - rc = hdaR3CmdSync(pDevIns, pThis, false /* Sync to guest */); - AssertRC(rc); - - return rc; -} - -#endif /* IN_RING3 */ - -/* Register access handlers. */ - -static VBOXSTRICTRC hdaRegReadUnimpl(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) -{ - RT_NOREF(pDevIns, pThis, iReg); - *pu32Value = 0; - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteUnimpl(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns, pThis, iReg, u32Value); - return VINF_SUCCESS; -} - -/* U8 */ -static VBOXSTRICTRC hdaRegReadU8(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) -{ - Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xffffff00) == 0); - return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); -} - -static VBOXSTRICTRC hdaRegWriteU8(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - Assert((u32Value & 0xffffff00) == 0); - return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); -} - -/* U16 */ -static VBOXSTRICTRC hdaRegReadU16(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) -{ - Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xffff0000) == 0); - return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); -} - -static VBOXSTRICTRC hdaRegWriteU16(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - Assert((u32Value & 0xffff0000) == 0); - return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); -} - -/* U24 */ -static VBOXSTRICTRC hdaRegReadU24(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) -{ - Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xff000000) == 0); - return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); -} - -#ifdef IN_RING3 -static VBOXSTRICTRC hdaRegWriteU24(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - Assert((u32Value & 0xff000000) == 0); - return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); -} -#endif - -/* U32 */ -static VBOXSTRICTRC hdaRegReadU32(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) -{ - RT_NOREF(pDevIns); - - uint32_t const iRegMem = g_aHdaRegMap[iReg].mem_idx; - *pu32Value = pThis->au32Regs[iRegMem] & g_aHdaRegMap[iReg].readable; - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteU32(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns); - - uint32_t const iRegMem = g_aHdaRegMap[iReg].mem_idx; - pThis->au32Regs[iRegMem] = (u32Value & g_aHdaRegMap[iReg].writable) - | (pThis->au32Regs[iRegMem] & ~g_aHdaRegMap[iReg].writable); - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteGCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns, iReg); - - if (u32Value & HDA_GCTL_CRST) - { - /* Set the CRST bit to indicate that we're leaving reset mode. */ - HDA_REG(pThis, GCTL) |= HDA_GCTL_CRST; - LogFunc(("Guest leaving HDA reset\n")); - } - else - { -#ifdef IN_RING3 - /* Enter reset state. */ - LogFunc(("Guest entering HDA reset with DMA(RIRB:%s, CORB:%s)\n", - HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA ? "on" : "off", - HDA_REG(pThis, RIRBCTL) & HDA_RIRBCTL_RDMAEN ? "on" : "off")); - - /* Clear the CRST bit to indicate that we're in reset state. */ - HDA_REG(pThis, GCTL) &= ~HDA_GCTL_CRST; - - hdaR3GCTLReset(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)); -#else - return VINF_IOM_R3_MMIO_WRITE; -#endif - } - - if (u32Value & HDA_GCTL_FCNTRL) - { - /* Flush: GSTS:1 set, see 6.2.6. */ - HDA_REG(pThis, GSTS) |= HDA_GSTS_FSTS; /* Set the flush status. */ - /* DPLBASE and DPUBASE should be initialized with initial value (see 6.2.6). */ - } - - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteSTATESTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns); - - uint32_t v = HDA_REG_IND(pThis, iReg); - uint32_t nv = u32Value & HDA_STATESTS_SCSF_MASK; - - HDA_REG(pThis, STATESTS) &= ~(v & nv); /* Write of 1 clears corresponding bit. */ - - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegReadLPIB(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) -{ - RT_NOREF(pDevIns); - - const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, LPIB, iReg); - const uint32_t u32LPIB = HDA_STREAM_REG(pThis, LPIB, uSD); - *pu32Value = u32LPIB; - LogFlowFunc(("[SD%RU8] LPIB=%RU32, CBL=%RU32\n", uSD, u32LPIB, HDA_STREAM_REG(pThis, CBL, uSD))); - - return VINF_SUCCESS; -} - -#ifdef IN_RING3 -/** - * Returns the current maximum value the wall clock counter can be set to. - * - * This maximum value depends on all currently handled HDA streams and their own current timing. - * - * @return Current maximum value the wall clock counter can be set to. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - * - * @remark Does not actually set the wall clock counter. - * - * @todo r=bird: This function is in the wrong file. - */ -static uint64_t hdaR3WalClkGetMax(PHDASTATE pThis, PHDASTATER3 pThisCC) -{ - const uint64_t u64WalClkCur = ASMAtomicReadU64(&pThis->u64WalClk); - const uint64_t u64FrontAbsWalClk = pThisCC->SinkFront.pStreamShared - ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThisCC->SinkFront.pStreamShared->State.Period) : 0; -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND -# error "Implement me!" -# endif - const uint64_t u64LineInAbsWalClk = pThisCC->SinkLineIn.pStreamShared - ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThisCC->SinkLineIn.pStreamShared->State.Period) : 0; -# ifdef VBOX_WITH_HDA_MIC_IN - const uint64_t u64MicInAbsWalClk = pThisCC->SinkMicIn.pStreamShared - ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThisCC->SinkMicIn.pStreamShared->State.Period) : 0; -# endif - - uint64_t u64WalClkNew = RT_MAX(u64WalClkCur, u64FrontAbsWalClk); -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND -# error "Implement me!" -# endif - u64WalClkNew = RT_MAX(u64WalClkNew, u64LineInAbsWalClk); -# ifdef VBOX_WITH_HDA_MIC_IN - u64WalClkNew = RT_MAX(u64WalClkNew, u64MicInAbsWalClk); -# endif - - Log3Func(("%RU64 -> Front=%RU64, LineIn=%RU64 -> %RU64\n", - u64WalClkCur, u64FrontAbsWalClk, u64LineInAbsWalClk, u64WalClkNew)); - - return u64WalClkNew; -} -#endif /* IN_RING3 */ - -static VBOXSTRICTRC hdaRegReadWALCLK(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) -{ -#ifdef IN_RING3 /** @todo r=bird: No reason (except logging) for this to be ring-3 only! */ - RT_NOREF(pDevIns, iReg); - - const uint64_t u64WalClkCur = ASMAtomicReadU64(&pThis->u64WalClk); - *pu32Value = RT_LO_U32(u64WalClkCur); - - Log3Func(("%RU32 (max @ %RU64)\n", *pu32Value, hdaR3WalClkGetMax(pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)))); - return VINF_SUCCESS; -#else - RT_NOREF(pDevIns, pThis, iReg, pu32Value); - return VINF_IOM_R3_MMIO_READ; -#endif -} - -static VBOXSTRICTRC hdaRegWriteCORBRP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns, iReg); - if (u32Value & HDA_CORBRP_RST) - { - /* Do a CORB reset. */ - if (pThis->cbCorbBuf) - RT_ZERO(pThis->au32CorbBuf); - - LogRel2(("HDA: CORB reset\n")); - HDA_REG(pThis, CORBRP) = HDA_CORBRP_RST; /* Clears the pointer. */ - } - else - HDA_REG(pThis, CORBRP) &= ~HDA_CORBRP_RST; /* Only CORBRP_RST bit is writable. */ - - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteCORBCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ -#ifdef IN_RING3 - VBOXSTRICTRC rc = hdaRegWriteU8(pDevIns, pThis, iReg, u32Value); - AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); - - if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Start DMA engine. */ - rc = hdaR3CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)); - else - LogFunc(("CORB DMA not running, skipping\n")); - - return rc; -#else - RT_NOREF(pDevIns, pThis, iReg, u32Value); - return VINF_IOM_R3_MMIO_WRITE; -#endif -} - -static VBOXSTRICTRC hdaRegWriteCORBSIZE(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns, iReg); - - if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) /* Ignore request if CORB DMA engine is (still) running. */ - { - u32Value = (u32Value & HDA_CORBSIZE_SZ); - - uint16_t cEntries; - switch (u32Value) - { - case 0: /* 8 byte; 2 entries. */ - cEntries = 2; - break; - case 1: /* 64 byte; 16 entries. */ - cEntries = 16; - break; - case 2: /* 1 KB; 256 entries. */ - cEntries = HDA_CORB_SIZE; /* default. */ - break; - default: - LogRel(("HDA: Guest tried to set an invalid CORB size (0x%x), keeping default\n", u32Value)); - u32Value = 2; - cEntries = HDA_CORB_SIZE; /* Use default size. */ - break; - } - - uint32_t cbCorbBuf = cEntries * HDA_CORB_ELEMENT_SIZE; - Assert(cbCorbBuf <= sizeof(pThis->au32CorbBuf)); /* paranoia */ - - if (cbCorbBuf != pThis->cbCorbBuf) - { - RT_ZERO(pThis->au32CorbBuf); /* Clear CORB when setting a new size. */ - pThis->cbCorbBuf = cbCorbBuf; - } - - LogFunc(("CORB buffer size is now %RU32 bytes (%u entries)\n", pThis->cbCorbBuf, pThis->cbCorbBuf / HDA_CORB_ELEMENT_SIZE)); - - HDA_REG(pThis, CORBSIZE) = u32Value; - } - else - LogFunc(("CORB DMA is (still) running, skipping\n")); - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteCORBSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns, iReg); - - uint32_t v = HDA_REG(pThis, CORBSTS); - HDA_REG(pThis, CORBSTS) &= ~(v & u32Value); - - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteCORBWP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ -#ifdef IN_RING3 - VBOXSTRICTRC rc = hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); - AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); - - return hdaR3CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)); -#else - RT_NOREF(pDevIns, pThis, iReg, u32Value); - return VINF_IOM_R3_MMIO_WRITE; -#endif -} - -static VBOXSTRICTRC hdaRegWriteSDCBL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); -} - -static VBOXSTRICTRC hdaRegWriteSDCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ -#ifdef IN_RING3 - /* Get the stream descriptor number. */ - const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, CTL, iReg); - AssertReturn(uSD < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ - - /* - * Extract the stream tag the guest wants to use for this specific - * stream descriptor (SDn). This only can happen if the stream is in a non-running - * state, so we're doing the lookup and assignment here. - * - * So depending on the guest OS, SD3 can use stream tag 4, for example. - */ - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - uint8_t uTag = (u32Value >> HDA_SDCTL_NUM_SHIFT) & HDA_SDCTL_NUM_MASK; - ASSERT_GUEST_MSG_RETURN(uTag < RT_ELEMENTS(pThisCC->aTags), - ("SD%RU8: Invalid stream tag %RU8 (u32Value=%#x)!\n", uSD, uTag, u32Value), - VINF_SUCCESS /* Always return success to the MMIO handler. */); - - PHDASTREAM const pStreamShared = &pThis->aStreams[uSD]; - PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[uSD]; - - const bool fRun = RT_BOOL(u32Value & HDA_SDCTL_RUN); - const bool fReset = RT_BOOL(u32Value & HDA_SDCTL_SRST); - - /* If the run bit is set, we take the virtual-sync clock lock as well so we - can safely update timers via hdaR3TimerSet if necessary. We need to be - very careful with the fInReset and fInRun indicators here, as they may - change during the relocking if we need to acquire the clock lock. */ - const bool fNeedVirtualSyncClockLock = (u32Value & (HDA_SDCTL_RUN | HDA_SDCTL_SRST)) == HDA_SDCTL_RUN - && (HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN) == 0; - if (fNeedVirtualSyncClockLock) - { - DEVHDA_UNLOCK(pDevIns, pThis); - DEVHDA_LOCK_BOTH_RETURN(pDevIns, pThis, pStreamShared, VINF_IOM_R3_MMIO_WRITE); - } - - const bool fInRun = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN); - const bool fInReset = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_SRST); - - /*LogFunc(("[SD%RU8] fRun=%RTbool, fInRun=%RTbool, fReset=%RTbool, fInReset=%RTbool, %R[sdctl]\n", - uSD, fRun, fInRun, fReset, fInReset, u32Value));*/ - if (fInReset) - { - Assert(!fReset); - Assert(!fInRun && !fRun); - - /* Exit reset state. */ - ASMAtomicXchgBool(&pStreamShared->State.fInReset, false); - - /* Report that we're done resetting this stream by clearing SRST. */ - HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_SRST; - - LogFunc(("[SD%RU8] Reset exit\n", uSD)); - } - else if (fReset) - { - /* ICH6 datasheet 18.2.33 says that RUN bit should be cleared before initiation of reset. */ - Assert(!fInRun && !fRun); - - LogFunc(("[SD%RU8] Reset enter\n", uSD)); - - hdaR3StreamLock(pStreamR3); - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - hdaR3StreamAsyncIOLock(pStreamR3); -# endif - /* Make sure to remove the run bit before doing the actual stream reset. */ - HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_RUN; - - hdaR3StreamReset(pThis, pThisCC, pStreamShared, pStreamR3, uSD); - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - hdaR3StreamAsyncIOUnlock(pStreamR3); -# endif - hdaR3StreamUnlock(pStreamR3); - } - else - { - /* - * We enter here to change DMA states only. - */ - if (fInRun != fRun) - { - Assert(!fReset && !fInReset); - LogFunc(("[SD%RU8] State changed (fRun=%RTbool)\n", uSD, fRun)); - - hdaR3StreamLock(pStreamR3); - - int rc2 = VINF_SUCCESS; - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - if (fRun) - rc2 = hdaR3StreamAsyncIOCreate(pStreamR3); - - hdaR3StreamAsyncIOLock(pStreamR3); -# endif - if (fRun) - { - if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) - { - const uint8_t uStripeCtl = ((u32Value >> HDA_SDCTL_STRIPE_SHIFT) & HDA_SDCTL_STRIPE_MASK) + 1; - LogFunc(("[SD%RU8] Using %RU8 SDOs (stripe control)\n", uSD, uStripeCtl)); - if (uStripeCtl > 1) - LogRel2(("HDA: Warning: Striping output over more than one SDO for stream #%RU8 currently is not implemented " \ - "(%RU8 SDOs requested)\n", uSD, uStripeCtl)); - } - - /* Assign new values. */ - LogFunc(("[SD%RU8] Using stream tag=%RU8\n", uSD, uTag)); - PHDATAG pTag = &pThisCC->aTags[uTag]; - pTag->uTag = uTag; - pTag->pStreamR3 = &pThisCC->aStreams[uSD]; - -# ifdef LOG_ENABLED - PDMAUDIOPCMPROPS Props; - rc2 = hdaR3SDFMTToPCMProps(HDA_STREAM_REG(pThis, FMT, uSD), &Props); - AssertRC(rc2); - LogFunc(("[SD%RU8] %RU32Hz, %RU8bit, %RU8 channel(s)\n", - uSD, Props.uHz, Props.cbSample * 8 /* Bit */, Props.cChannels)); -# endif - /* (Re-)initialize the stream with current values. */ - rc2 = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, uSD); - if ( RT_SUCCESS(rc2) - /* Any vital stream change occurred so that we need to (re-)add the stream to our setup? - * Otherwise just skip this, as this costs a lot of performance. */ - && rc2 != VINF_NO_CHANGE) - { - /* Remove the old stream from the device setup. */ - rc2 = hdaR3RemoveStream(pThisCC, &pStreamShared->State.Cfg); - AssertRC(rc2); - - /* Add the stream to the device setup. */ - rc2 = hdaR3AddStream(pThisCC, &pStreamShared->State.Cfg); - AssertRC(rc2); - } - } - - if (RT_SUCCESS(rc2)) - { - /* Enable/disable the stream. */ - rc2 = hdaR3StreamEnable(pStreamShared, pStreamR3, fRun /* fEnable */); - AssertRC(rc2); - - if (fRun) - { - /* Keep track of running streams. */ - pThisCC->cStreamsActive++; - - /* (Re-)init the stream's period. */ - hdaR3StreamPeriodInit(&pStreamShared->State.Period, uSD, pStreamShared->u16LVI, - pStreamShared->u32CBL, &pStreamShared->State.Cfg); - - /* Begin a new period for this stream. */ - rc2 = hdaR3StreamPeriodBegin(&pStreamShared->State.Period, - hdaWalClkGetCurrent(pThis) /* Use current wall clock time */); - AssertRC(rc2); - - uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); - rc2 = hdaR3TimerSet(pDevIns, pStreamShared, tsNow + pStreamShared->State.cTransferTicks, false /* fForce */, tsNow); - AssertRC(rc2); - } - else - { - /* Keep track of running streams. */ - Assert(pThisCC->cStreamsActive); - if (pThisCC->cStreamsActive) - pThisCC->cStreamsActive--; - - /* Make sure to (re-)schedule outstanding (delayed) interrupts. */ - hdaR3ReschedulePendingInterrupts(pDevIns, pThis, pThisCC); - - /* Reset the period. */ - hdaR3StreamPeriodReset(&pStreamShared->State.Period); - } - } - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - hdaR3StreamAsyncIOUnlock(pStreamR3); -# endif - /* Make sure to leave the lock before (eventually) starting the timer. */ - hdaR3StreamUnlock(pStreamR3); - } - } - - if (fNeedVirtualSyncClockLock) - PDMDevHlpTimerUnlockClock(pDevIns, pStreamShared->hTimer); /* Caller will unlock pThis->CritSect. */ - - return hdaRegWriteU24(pDevIns, pThis, iReg, u32Value); -#else /* !IN_RING3 */ - RT_NOREF(pDevIns, pThis, iReg, u32Value); - return VINF_IOM_R3_MMIO_WRITE; -#endif /* !IN_RING3 */ -} - -static VBOXSTRICTRC hdaRegWriteSDSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ -#ifdef IN_RING3 - const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, STS, iReg); - AssertReturn(uSD < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ - PHDASTATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[uSD]; - PHDASTREAM const pStreamShared = &pThis->aStreams[uSD]; - - /* We only need to take the virtual-sync lock if we want to call - PDMDevHlpTimerGet or hdaR3TimerSet later. Only precondition for that - is that we've got a non-zero ticks-per-transfer value. */ - uint64_t const cTransferTicks = pStreamShared->State.cTransferTicks; - if (cTransferTicks) - { - DEVHDA_UNLOCK(pDevIns, pThis); - DEVHDA_LOCK_BOTH_RETURN(pDevIns, pThis, pStreamShared, VINF_IOM_R3_MMIO_WRITE); - } - - hdaR3StreamLock(pStreamR3); - - uint32_t v = HDA_REG_IND(pThis, iReg); - - /* Clear (zero) FIFOE, DESE and BCIS bits when writing 1 to it (6.2.33). */ - HDA_REG_IND(pThis, iReg) &= ~(u32Value & v); - -/** @todo r=bird: Check if we couldn't this stuff involving hdaR3StreamPeriodLock - * and IRQs after PDMDevHlpTimerUnlockClock. */ - - /* - * ... - */ - /* Some guests tend to write SDnSTS even if the stream is not running. - * So make sure to check if the RUN bit is set first. */ - const bool fRunning = pStreamShared->State.fRunning; - - Log3Func(("[SD%RU8] fRunning=%RTbool %R[sdsts]\n", uSD, fRunning, v)); - - PHDASTREAMPERIOD pPeriod = &pStreamShared->State.Period; - hdaR3StreamPeriodLock(pPeriod); - if (hdaR3StreamPeriodNeedsInterrupt(pPeriod)) - hdaR3StreamPeriodReleaseInterrupt(pPeriod); - if (hdaR3StreamPeriodIsComplete(pPeriod)) - { - /* Make sure to try to update the WALCLK register if a period is complete. - * Use the maximum WALCLK value all (active) streams agree to. */ - const uint64_t uWalClkMax = hdaR3WalClkGetMax(pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)); - if (uWalClkMax > hdaWalClkGetCurrent(pThis)) - hdaR3WalClkSet(pThis, pThisCC, uWalClkMax, false /* fForce */); - - hdaR3StreamPeriodEnd(pPeriod); - - if (fRunning) - hdaR3StreamPeriodBegin(pPeriod, hdaWalClkGetCurrent(pThis) /* Use current wall clock time */); - } - hdaR3StreamPeriodUnlock(pPeriod); /* Unlock before processing interrupt. */ - - HDA_PROCESS_INTERRUPT(pDevIns, pThis); - - /* - * ... - */ - uint64_t cTicksToNext = pStreamShared->State.cTransferTicks; - Assert(cTicksToNext == cTicksToNext); - if (cTicksToNext) /* Only do any calculations if the stream currently is set up for transfers. */ - { - const uint64_t tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); - Assert(tsNow >= pStreamShared->State.tsTransferLast); - - const uint64_t cTicksElapsed = tsNow - pStreamShared->State.tsTransferLast; -# ifdef LOG_ENABLED - const uint64_t cTicksTransferred = pStreamShared->State.cbTransferProcessed * pStreamShared->State.cTicksPerByte; -# endif - - Log3Func(("[SD%RU8] cTicksElapsed=%RU64, cTicksTransferred=%RU64, cTicksToNext=%RU64\n", - uSD, cTicksElapsed, cTicksTransferred, cTicksToNext)); - - Log3Func(("[SD%RU8] cbTransferProcessed=%RU32, cbTransferChunk=%RU32, cbTransferSize=%RU32\n", uSD, - pStreamShared->State.cbTransferProcessed, pStreamShared->State.cbTransferChunk, pStreamShared->State.cbTransferSize)); - - if (cTicksElapsed <= cTicksToNext) - cTicksToNext = cTicksToNext - cTicksElapsed; - else /* Catch up. */ - { - Log3Func(("[SD%RU8] Warning: Lagging behind (%RU64 ticks elapsed, maximum allowed is %RU64)\n", - uSD, cTicksElapsed, cTicksToNext)); - - LogRelMax2(64, ("HDA: Stream #%RU8 interrupt lagging behind (expected %uus, got %uus), trying to catch up ...\n", uSD, - (PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer) / pThis->uTimerHz) / 1000, - (tsNow - pStreamShared->State.tsTransferLast) / 1000)); - - cTicksToNext = 0; - } - - Log3Func(("[SD%RU8] -> cTicksToNext=%RU64\n", uSD, cTicksToNext)); - - /* Reset processed data counter. */ - pStreamShared->State.cbTransferProcessed = 0; - pStreamShared->State.tsTransferNext = tsNow + cTicksToNext; - - /* Only re-arm the timer if there were pending transfer interrupts left - * -- it could happen that we land in here if a guest writes to SDnSTS - * unconditionally. */ - if (pStreamShared->State.cTransferPendingInterrupts) - { - pStreamShared->State.cTransferPendingInterrupts--; - - /* Re-arm the timer. */ - LogFunc(("Timer set SD%RU8\n", uSD)); - hdaR3TimerSet(pDevIns, pStreamShared, tsNow + cTicksToNext, - true /* fForce - we just set tsTransferNext*/, 0 /*tsNow*/); - } - } - - if (cTransferTicks) - PDMDevHlpTimerUnlockClock(pDevIns, pStreamShared->hTimer); /* Caller will unlock pThis->CritSect. */ - hdaR3StreamUnlock(pStreamR3); - return VINF_SUCCESS; -#else /* !IN_RING3 */ - RT_NOREF(pDevIns, pThis, iReg, u32Value); - return VINF_IOM_R3_MMIO_WRITE; -#endif /* !IN_RING3 */ -} - -static VBOXSTRICTRC hdaRegWriteSDLVI(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - const size_t idxStream = HDA_SD_NUM_FROM_REG(pThis, LVI, iReg); - AssertReturn(idxStream < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ - -#ifdef HDA_USE_DMA_ACCESS_HANDLER - if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) - { - /* Try registering the DMA handlers. - * As we can't be sure in which order LVI + BDL base are set, try registering in both routines. */ - PHDASTREAM pStream = hdaGetStreamFromSD(pThis, idxStream); - if ( pStream - && hdaR3StreamRegisterDMAHandlers(pThis, pStream)) - LogFunc(("[SD%RU8] DMA logging enabled\n", pStream->u8SD)); - } -#endif - - ASSERT_GUEST_LOGREL_MSG(u32Value <= UINT8_MAX, /* Should be covered by the register write mask, but just to make sure. */ - ("LVI for stream #%zu must not be bigger than %RU8\n", idxStream, UINT8_MAX - 1)); - return hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); -} - -static VBOXSTRICTRC hdaRegWriteSDFIFOW(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - size_t const idxStream = HDA_SD_NUM_FROM_REG(pThis, FIFOW, iReg); - AssertReturn(idxStream < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ - - if (RT_LIKELY(hdaGetDirFromSD((uint8_t)idxStream) == PDMAUDIODIR_IN)) /* FIFOW for input streams only. */ - { /* likely */ } - else - { -#ifndef IN_RING0 - LogRel(("HDA: Warning: Guest tried to write read-only FIFOW to output stream #%RU8, ignoring\n", idxStream)); - return VINF_SUCCESS; -#else - return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */ -#endif - } - - uint32_t u32FIFOW = 0; - switch (u32Value) - { - case HDA_SDFIFOW_8B: - case HDA_SDFIFOW_16B: - case HDA_SDFIFOW_32B: - u32FIFOW = u32Value; - break; - default: - ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried writing unsupported FIFOW (0x%zx) to stream #%RU8, defaulting to 32 bytes\n", - u32Value, idxStream)); - u32FIFOW = HDA_SDFIFOW_32B; - break; - } - - if (u32FIFOW) /** @todo r=bird: Logic error. it will never be zero, so why this check? */ - { - pThis->aStreams[idxStream].u16FIFOW = hdaSDFIFOWToBytes(u32FIFOW); - LogFunc(("[SD%zu] Updating FIFOW to %u bytes\n", idxStream, pThis->aStreams[idxStream].u16FIFOW)); - return hdaRegWriteU16(pDevIns, pThis, iReg, u32FIFOW); - } - return VINF_SUCCESS; -} - -/** - * @note This method could be called for changing value on Output Streams only (ICH6 datasheet 18.2.39). - */ -static VBOXSTRICTRC hdaRegWriteSDFIFOS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, FIFOS, iReg); - - ASSERT_GUEST_LOGREL_MSG_RETURN(hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT, /* FIFOS for output streams only. */ - ("Guest tried writing read-only FIFOS to input stream #%RU8, ignoring\n", uSD), - VINF_SUCCESS); - - uint32_t u32FIFOS; - switch (u32Value) - { - case HDA_SDOFIFO_16B: - case HDA_SDOFIFO_32B: - case HDA_SDOFIFO_64B: - case HDA_SDOFIFO_128B: - case HDA_SDOFIFO_192B: - case HDA_SDOFIFO_256B: - u32FIFOS = u32Value; - break; - - default: - ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried writing unsupported FIFOS (0x%x) to stream #%RU8, defaulting to 192 bytes\n", - u32Value, uSD)); - u32FIFOS = HDA_SDOFIFO_192B; - break; - } - - return hdaRegWriteU16(pDevIns, pThis, iReg, u32FIFOS); -} - -#ifdef IN_RING3 - -/** - * Adds an audio output stream to the device setup using the given configuration. - * - * @returns IPRT status code. - * @param pThisCC The ring-3 HDA device state. - * @param pCfg Stream configuration to use for adding a stream. - */ -static int hdaR3AddStreamOut(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); - - LogFlowFunc(("Stream=%s\n", pCfg->szName)); - - int rc = VINF_SUCCESS; - - bool fUseFront = true; /* Always use front out by default. */ -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - bool fUseRear; - bool fUseCenter; - bool fUseLFE; - - fUseRear = fUseCenter = fUseLFE = false; - - /* - * Use commonly used setups for speaker configurations. - */ - - /** @todo Make the following configurable through mixer API and/or CFGM? */ - switch (pCfg->Props.cChannels) - { - case 3: /* 2.1: Front (Stereo) + LFE. */ - { - fUseLFE = true; - break; - } - - case 4: /* Quadrophonic: Front (Stereo) + Rear (Stereo). */ - { - fUseRear = true; - break; - } - - case 5: /* 4.1: Front (Stereo) + Rear (Stereo) + LFE. */ - { - fUseRear = true; - fUseLFE = true; - break; - } - - case 6: /* 5.1: Front (Stereo) + Rear (Stereo) + Center/LFE. */ - { - fUseRear = true; - fUseCenter = true; - fUseLFE = true; - break; - } - - default: /* Unknown; fall back to 2 front channels (stereo). */ - { - rc = VERR_NOT_SUPPORTED; - break; - } - } -# endif /* !VBOX_WITH_AUDIO_HDA_51_SURROUND */ - - if (rc == VERR_NOT_SUPPORTED) - { - LogRel2(("HDA: Warning: Unsupported channel count (%RU8), falling back to stereo channels (2)\n", pCfg->Props.cChannels)); - - /* Fall back to 2 channels (see below in fUseFront block). */ - rc = VINF_SUCCESS; - } - - do - { - if (RT_FAILURE(rc)) - break; - - if (fUseFront) - { - RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Front"); - - pCfg->u.enmDst = PDMAUDIOPLAYBACKDST_FRONT; - pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; - - pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels); - - rc = hdaCodecAddStream(pThisCC->pCodec, PDMAUDIOMIXERCTL_FRONT, pCfg); - } - -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - if ( RT_SUCCESS(rc) - && (fUseCenter || fUseLFE)) - { - RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Center/LFE"); - - pCfg->u.enmDst = PDMAUDIOPLAYBACKDST_CENTER_LFE; - pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; - - pCfg->Props.cChannels = (fUseCenter && fUseLFE) ? 2 : 1; - pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels); - - rc = hdaCodecAddStream(pThisCC->pCodec, PDMAUDIOMIXERCTL_CENTER_LFE, pCfg); - } - - if ( RT_SUCCESS(rc) - && fUseRear) - { - RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Rear"); - - pCfg->u.enmDst = PDMAUDIOPLAYBACKDST_REAR; - pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; - - pCfg->Props.cChannels = 2; - pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels); - - rc = hdaCodecAddStream(pThisCC->pCodec, PDMAUDIOMIXERCTL_REAR, pCfg); - } -# endif /* VBOX_WITH_AUDIO_HDA_51_SURROUND */ - - } while (0); - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Adds an audio input stream to the device setup using the given configuration. - * - * @returns IPRT status code. - * @param pThisCC The ring-3 HDA device state. - * @param pCfg Stream configuration to use for adding a stream. - */ -static int hdaR3AddStreamIn(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - AssertReturn(pCfg->enmDir == PDMAUDIODIR_IN, VERR_INVALID_PARAMETER); - - LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->u.enmSrc)); - - int rc; - switch (pCfg->u.enmSrc) - { - case PDMAUDIORECSRC_LINE: - rc = hdaCodecAddStream(pThisCC->pCodec, PDMAUDIOMIXERCTL_LINE_IN, pCfg); - break; -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - case PDMAUDIORECSRC_MIC: - rc = hdaCodecAddStream(pThisCC->pCodec, PDMAUDIOMIXERCTL_MIC_IN, pCfg); - break; -# endif - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Adds an audio stream to the device setup using the given configuration. - * - * @returns IPRT status code. - * @param pThisCC The ring-3 HDA device state. - * @param pCfg Stream configuration to use for adding a stream. - */ -static int hdaR3AddStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - int rc; - switch (pCfg->enmDir) - { - case PDMAUDIODIR_OUT: - rc = hdaR3AddStreamOut(pThisCC, pCfg); - break; - - case PDMAUDIODIR_IN: - rc = hdaR3AddStreamIn(pThisCC, pCfg); - break; - - default: - rc = VERR_NOT_SUPPORTED; - AssertFailed(); - break; - } - - LogFlowFunc(("Returning %Rrc\n", rc)); - - return rc; -} - -/** - * Removes an audio stream from the device setup using the given configuration. - * - * @returns IPRT status code. - * @param pThisCC The ring-3 HDA device state. - * @param pCfg Stream configuration to use for removing a stream. - */ -static int hdaR3RemoveStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - int rc = VINF_SUCCESS; - - PDMAUDIOMIXERCTL enmMixerCtl = PDMAUDIOMIXERCTL_UNKNOWN; - switch (pCfg->enmDir) - { - case PDMAUDIODIR_IN: - { - LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->u.enmSrc)); - - switch (pCfg->u.enmSrc) - { - case PDMAUDIORECSRC_UNKNOWN: break; - case PDMAUDIORECSRC_LINE: enmMixerCtl = PDMAUDIOMIXERCTL_LINE_IN; break; -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - case PDMAUDIORECSRC_MIC: enmMixerCtl = PDMAUDIOMIXERCTL_MIC_IN; break; -# endif - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - break; - } - - case PDMAUDIODIR_OUT: - { - LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->u.enmDst)); - - switch (pCfg->u.enmDst) - { - case PDMAUDIOPLAYBACKDST_UNKNOWN: break; - case PDMAUDIOPLAYBACKDST_FRONT: enmMixerCtl = PDMAUDIOMIXERCTL_FRONT; break; -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - case PDMAUDIOPLAYBACKDST_CENTER_LFE: enmMixerCtl = PDMAUDIOMIXERCTL_CENTER_LFE; break; - case PDMAUDIOPLAYBACKDST_REAR: enmMixerCtl = PDMAUDIOMIXERCTL_REAR; break; -# endif - default: - rc = VERR_NOT_SUPPORTED; - break; - } - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - if ( RT_SUCCESS(rc) - && enmMixerCtl != PDMAUDIOMIXERCTL_UNKNOWN) - { - rc = hdaCodecRemoveStream(pThisCC->pCodec, enmMixerCtl); - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -#endif /* IN_RING3 */ - -static VBOXSTRICTRC hdaRegWriteSDFMT(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - /* - * Write the wanted stream format into the register in any case. - * - * This is important for e.g. MacOS guests, as those try to initialize streams which are not reported - * by the device emulation (wants 4 channels, only have 2 channels at the moment). - * - * When ignoring those (invalid) formats, this leads to MacOS thinking that the device is malfunctioning - * and therefore disabling the device completely. - */ - return hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); -} - -/** - * Worker for writes to the BDPL and BDPU registers. - */ -DECLINLINE(VBOXSTRICTRC) hdaRegWriteSDBDPX(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value, uint8_t uSD) -{ -#ifndef HDA_USE_DMA_ACCESS_HANDLER - RT_NOREF(uSD); - return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); -#else -# ifdef IN_RING3 - if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) - { - /* Try registering the DMA handlers. - * As we can't be sure in which order LVI + BDL base are set, try registering in both routines. */ - PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD); - if ( pStream - && hdaR3StreamRegisterDMAHandlers(pThis, pStream)) - LogFunc(("[SD%RU8] DMA logging enabled\n", pStream->u8SD)); - } - return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); -# else - RT_NOREF(pDevIns, pThis, iReg, u32Value, uSD); - return VINF_IOM_R3_MMIO_WRITE; -# endif -#endif -} - -static VBOXSTRICTRC hdaRegWriteSDBDPL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - return hdaRegWriteSDBDPX(pDevIns, pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPL, iReg)); -} - -static VBOXSTRICTRC hdaRegWriteSDBDPU(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - return hdaRegWriteSDBDPX(pDevIns, pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPU, iReg)); -} - -static VBOXSTRICTRC hdaRegReadIRS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) -{ - /* regarding 3.4.3 we should mark IRS as busy in case CORB is active */ - if ( HDA_REG(pThis, CORBWP) != HDA_REG(pThis, CORBRP) - || (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) - HDA_REG(pThis, IRS) = HDA_IRS_ICB; /* busy */ - - return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); -} - -static VBOXSTRICTRC hdaRegWriteIRS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns, iReg); - - /* - * If the guest set the ICB bit of IRS register, HDA should process the verb in IC register, - * write the response to IR register, and set the IRV (valid in case of success) bit of IRS register. - */ - if ( (u32Value & HDA_IRS_ICB) - && !(HDA_REG(pThis, IRS) & HDA_IRS_ICB)) - { -#ifdef IN_RING3 - uint32_t uCmd = HDA_REG(pThis, IC); - - if (HDA_REG(pThis, CORBWP) != HDA_REG(pThis, CORBRP)) - { - /* - * 3.4.3: Defines behavior of immediate Command status register. - */ - LogRel(("HDA: Guest attempted process immediate verb (%x) with active CORB\n", uCmd)); - return VINF_SUCCESS; - } - - HDA_REG(pThis, IRS) = HDA_IRS_ICB; /* busy */ - - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - uint64_t uResp = 0; - int rc2 = pThisCC->pCodec->pfnLookup(pThisCC->pCodec, HDA_CODEC_CMD(uCmd, 0 /* LUN */), &uResp); - if (RT_FAILURE(rc2)) - LogFunc(("Codec lookup failed with rc2=%Rrc\n", rc2)); - - HDA_REG(pThis, IR) = (uint32_t)uResp; /** @todo r=andy Do we need a 64-bit response? */ - HDA_REG(pThis, IRS) = HDA_IRS_IRV; /* result is ready */ - /** @todo r=michaln We just set the IRS value, why are we clearing unset bits? */ - HDA_REG(pThis, IRS) &= ~HDA_IRS_ICB; /* busy is clear */ - - return VINF_SUCCESS; -#else /* !IN_RING3 */ - return VINF_IOM_R3_MMIO_WRITE; -#endif /* !IN_RING3 */ - } - - /* - * Once the guest read the response, it should clear the IRV bit of the IRS register. - */ - HDA_REG(pThis, IRS) &= ~(u32Value & HDA_IRS_IRV); - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteRIRBWP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns, iReg); - - if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */ - LogFunc(("CORB DMA (still) running, skipping\n")); - else - { - if (u32Value & HDA_RIRBWP_RST) - { - /* Do a RIRB reset. */ - if (pThis->cbRirbBuf) - RT_ZERO(pThis->au64RirbBuf); - - LogRel2(("HDA: RIRB reset\n")); - - HDA_REG(pThis, RIRBWP) = 0; - } - /* The remaining bits are O, see 6.2.22. */ - } - return VINF_SUCCESS; -} - -static VBOXSTRICTRC hdaRegWriteRINTCNT(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns); - if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */ - { - LogFunc(("CORB DMA is (still) running, skipping\n")); - return VINF_SUCCESS; - } - - VBOXSTRICTRC rc = hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); - AssertRC(VBOXSTRICTRC_VAL(rc)); - - /** @todo r=bird: Shouldn't we make sure the HDASTATE::u16RespIntCnt is below - * the new RINTCNT value? Or alterantively, make the DMA look take - * this into account instead... I'll do the later for now. */ - - LogFunc(("Response interrupt count is now %RU8\n", HDA_REG(pThis, RINTCNT) & 0xFF)); - return rc; -} - -static VBOXSTRICTRC hdaRegWriteBase(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns); - - VBOXSTRICTRC rc = hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); - AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); - - uint32_t const iRegMem = g_aHdaRegMap[iReg].mem_idx; - switch (iReg) - { - case HDA_REG_CORBLBASE: - pThis->u64CORBBase &= UINT64_C(0xFFFFFFFF00000000); - pThis->u64CORBBase |= pThis->au32Regs[iRegMem]; - break; - case HDA_REG_CORBUBASE: - pThis->u64CORBBase &= UINT64_C(0x00000000FFFFFFFF); - pThis->u64CORBBase |= (uint64_t)pThis->au32Regs[iRegMem] << 32; - break; - case HDA_REG_RIRBLBASE: - pThis->u64RIRBBase &= UINT64_C(0xFFFFFFFF00000000); - pThis->u64RIRBBase |= pThis->au32Regs[iRegMem]; - break; - case HDA_REG_RIRBUBASE: - pThis->u64RIRBBase &= UINT64_C(0x00000000FFFFFFFF); - pThis->u64RIRBBase |= (uint64_t)pThis->au32Regs[iRegMem] << 32; - break; - case HDA_REG_DPLBASE: - pThis->u64DPBase = pThis->au32Regs[iRegMem] & DPBASE_ADDR_MASK; - Assert(pThis->u64DPBase % 128 == 0); /* Must be 128-byte aligned. */ - - /* Also make sure to handle the DMA position enable bit. */ - pThis->fDMAPosition = pThis->au32Regs[iRegMem] & RT_BIT_32(0); - LogRel(("HDA: %s DMA position buffer\n", pThis->fDMAPosition ? "Enabled" : "Disabled")); - break; - case HDA_REG_DPUBASE: - pThis->u64DPBase = RT_MAKE_U64(RT_LO_U32(pThis->u64DPBase) & DPBASE_ADDR_MASK, pThis->au32Regs[iRegMem]); - break; - default: - AssertMsgFailed(("Invalid index\n")); - break; - } - - LogFunc(("CORB base:%llx RIRB base: %llx DP base: %llx\n", - pThis->u64CORBBase, pThis->u64RIRBBase, pThis->u64DPBase)); - return rc; -} - -static VBOXSTRICTRC hdaRegWriteRIRBSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) -{ - RT_NOREF(pDevIns, iReg); - - uint8_t v = HDA_REG(pThis, RIRBSTS); - HDA_REG(pThis, RIRBSTS) &= ~(v & u32Value); - - HDA_PROCESS_INTERRUPT(pDevIns, pThis); - return VINF_SUCCESS; -} - -#ifdef IN_RING3 - -/** - * Retrieves a corresponding sink for a given mixer control. - * - * @return Pointer to the sink, NULL if no sink is found. - * @param pThisCC The ring-3 HDA device state. - * @param enmMixerCtl Mixer control to get the corresponding sink for. - */ -static PHDAMIXERSINK hdaR3MixerControlToSink(PHDASTATER3 pThisCC, PDMAUDIOMIXERCTL enmMixerCtl) -{ - PHDAMIXERSINK pSink; - - switch (enmMixerCtl) - { - case PDMAUDIOMIXERCTL_VOLUME_MASTER: - /* Fall through is intentional. */ - case PDMAUDIOMIXERCTL_FRONT: - pSink = &pThisCC->SinkFront; - break; -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - case PDMAUDIOMIXERCTL_CENTER_LFE: - pSink = &pThisCC->SinkCenterLFE; - break; - case PDMAUDIOMIXERCTL_REAR: - pSink = &pThisCC->SinkRear; - break; -# endif - case PDMAUDIOMIXERCTL_LINE_IN: - pSink = &pThisCC->SinkLineIn; - break; -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - case PDMAUDIOMIXERCTL_MIC_IN: - pSink = &pThisCC->SinkMicIn; - break; -# endif - default: - AssertMsgFailed(("Unhandled mixer control\n")); - pSink = NULL; - break; - } - - return pSink; -} - -/** - * Adds a specific HDA driver to the driver chain. - * - * @return IPRT status code. - * @param pThisCC The ring-3 HDA device state. - * @param pDrv HDA driver to add. - */ -static int hdaR3MixerAddDrv(PHDASTATER3 pThisCC, PHDADRIVER pDrv) -{ - int rc = VINF_SUCCESS; - - PHDASTREAM pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkLineIn); - if ( pStream - && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) - { - int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkLineIn.pMixSink, &pStream->State.Cfg, pDrv); - if (RT_SUCCESS(rc)) - rc = rc2; - } - -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkMicIn); - if ( pStream - && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) - { - int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkMicIn.pMixSink, &pStream->State.Cfg, pDrv); - if (RT_SUCCESS(rc)) - rc = rc2; - } -# endif - - pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkFront); - if ( pStream - && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) - { - int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkFront.pMixSink, &pStream->State.Cfg, pDrv); - if (RT_SUCCESS(rc)) - rc = rc2; - } - -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkCenterLFE); - if ( pStream - && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) - { - int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkCenterLFE.pMixSink, &pStream->State.Cfg, pDrv); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkRear); - if ( pStream - && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) - { - int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkRear.pMixSink, &pStream->State.Cfg, pDrv); - if (RT_SUCCESS(rc)) - rc = rc2; - } -# endif - - return rc; -} - -/** - * Removes a specific HDA driver from the driver chain and destroys its - * associated streams. - * - * @param pThisCC The ring-3 HDA device state. - * @param pDrv HDA driver to remove. - */ -static void hdaR3MixerRemoveDrv(PHDASTATER3 pThisCC, PHDADRIVER pDrv) -{ - AssertPtrReturnVoid(pDrv); - - if (pDrv->LineIn.pMixStrm) - { - if (AudioMixerSinkGetRecordingSource(pThisCC->SinkLineIn.pMixSink) == pDrv->LineIn.pMixStrm) - AudioMixerSinkSetRecordingSource(pThisCC->SinkLineIn.pMixSink, NULL); - - AudioMixerSinkRemoveStream(pThisCC->SinkLineIn.pMixSink, pDrv->LineIn.pMixStrm); - AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm); - pDrv->LineIn.pMixStrm = NULL; - } - -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - if (pDrv->MicIn.pMixStrm) - { - if (AudioMixerSinkGetRecordingSource(pThisCC->SinkMicIn.pMixSink) == pDrv->MicIn.pMixStrm) - AudioMixerSinkSetRecordingSource(&pThisCC->SinkMicIn.pMixSink, NULL); - - AudioMixerSinkRemoveStream(pThisCC->SinkMicIn.pMixSink, pDrv->MicIn.pMixStrm); - AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm); - pDrv->MicIn.pMixStrm = NULL; - } -# endif - - if (pDrv->Front.pMixStrm) - { - AudioMixerSinkRemoveStream(pThisCC->SinkFront.pMixSink, pDrv->Front.pMixStrm); - AudioMixerStreamDestroy(pDrv->Front.pMixStrm); - pDrv->Front.pMixStrm = NULL; - } - -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - if (pDrv->CenterLFE.pMixStrm) - { - AudioMixerSinkRemoveStream(pThisCC->SinkCenterLFE.pMixSink, pDrv->CenterLFE.pMixStrm); - AudioMixerStreamDestroy(pDrv->CenterLFE.pMixStrm); - pDrv->CenterLFE.pMixStrm = NULL; - } - - if (pDrv->Rear.pMixStrm) - { - AudioMixerSinkRemoveStream(pThisCC->SinkRear.pMixSink, pDrv->Rear.pMixStrm); - AudioMixerStreamDestroy(pDrv->Rear.pMixStrm); - pDrv->Rear.pMixStrm = NULL; - } -# endif - - RTListNodeRemove(&pDrv->Node); -} - -/** - * Adds a driver stream to a specific mixer sink. - * - * @returns IPRT status code (ignored by caller). - * @param pMixSink Audio mixer sink to add audio streams to. - * @param pCfg Audio stream configuration to use for the audio streams to add. - * @param pDrv Driver stream to add. - */ -static int hdaR3MixerAddDrvStream(PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv) -{ - AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - LogFunc(("szSink=%s, szStream=%s, cChannels=%RU8\n", pMixSink->pszName, pCfg->szName, pCfg->Props.cChannels)); - - PPDMAUDIOSTREAMCFG pStreamCfg = DrvAudioHlpStreamCfgDup(pCfg); - if (!pStreamCfg) - return VERR_NO_MEMORY; - - LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pStreamCfg->szName)); - - int rc = VINF_SUCCESS; - - PHDADRIVERSTREAM pDrvStream = NULL; - - if (pStreamCfg->enmDir == PDMAUDIODIR_IN) - { - LogFunc(("enmRecSource=%d\n", pStreamCfg->u.enmSrc)); - - switch (pStreamCfg->u.enmSrc) - { - case PDMAUDIORECSRC_LINE: - pDrvStream = &pDrv->LineIn; - break; -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - case PDMAUDIORECSRC_MIC: - pDrvStream = &pDrv->MicIn; - break; -# endif - default: - rc = VERR_NOT_SUPPORTED; - break; - } - } - else if (pStreamCfg->enmDir == PDMAUDIODIR_OUT) - { - LogFunc(("enmPlaybackDest=%d\n", pStreamCfg->u.enmDst)); - - switch (pStreamCfg->u.enmDst) - { - case PDMAUDIOPLAYBACKDST_FRONT: - pDrvStream = &pDrv->Front; - break; -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - case PDMAUDIOPLAYBACKDST_CENTER_LFE: - pDrvStream = &pDrv->CenterLFE; - break; - case PDMAUDIOPLAYBACKDST_REAR: - pDrvStream = &pDrv->Rear; - break; -# endif - default: - rc = VERR_NOT_SUPPORTED; - break; - } - } - else - rc = VERR_NOT_SUPPORTED; - - if (RT_SUCCESS(rc)) - { - AssertPtr(pDrvStream); - AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN)); - - PAUDMIXSTREAM pMixStrm; - rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pStreamCfg, 0 /* fFlags */, &pMixStrm); - LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc)); - if (RT_SUCCESS(rc)) - { - rc = AudioMixerSinkAddStream(pMixSink, pMixStrm); - LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc)); - if (RT_SUCCESS(rc)) - { - /* If this is an input stream, always set the latest (added) stream - * as the recording source. */ - /** @todo Make the recording source dynamic (CFGM?). */ - if (pStreamCfg->enmDir == PDMAUDIODIR_IN) - { - PDMAUDIOBACKENDCFG Cfg; - rc = pDrv->pConnector->pfnGetConfig(pDrv->pConnector, &Cfg); - if (RT_SUCCESS(rc)) - { - if (Cfg.cMaxStreamsIn) /* At least one input source available? */ - { - rc = AudioMixerSinkSetRecordingSource(pMixSink, pMixStrm); - LogFlowFunc(("LUN#%RU8: Recording source for '%s' -> '%s', rc=%Rrc\n", - pDrv->uLUN, pStreamCfg->szName, Cfg.szName, rc)); - - if (RT_SUCCESS(rc)) - LogRel(("HDA: Set recording source for '%s' to '%s'\n", - pStreamCfg->szName, Cfg.szName)); - } - else - LogRel(("HDA: Backend '%s' currently is not offering any recording source for '%s'\n", - Cfg.szName, pStreamCfg->szName)); - } - else if (RT_FAILURE(rc)) - LogFunc(("LUN#%RU8: Unable to retrieve backend configuration for '%s', rc=%Rrc\n", - pDrv->uLUN, pStreamCfg->szName, rc)); - } - } - } - - if (RT_SUCCESS(rc)) - pDrvStream->pMixStrm = pMixStrm; - } - - DrvAudioHlpStreamCfgFree(pStreamCfg); - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Adds all current driver streams to a specific mixer sink. - * - * @returns IPRT status code. - * @param pThisCC The ring-3 HDA device state. - * @param pMixSink Audio mixer sink to add stream to. - * @param pCfg Audio stream configuration to use for the audio streams to add. - */ -static int hdaR3MixerAddDrvStreams(PHDASTATER3 pThisCC, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - LogFunc(("Sink=%s, Stream=%s\n", pMixSink->pszName, pCfg->szName)); - - if (!DrvAudioHlpStreamCfgIsValid(pCfg)) - return VERR_INVALID_PARAMETER; - - int rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props); - if (RT_FAILURE(rc)) - return rc; - - PHDADRIVER pDrv; - RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node) - { - int rc2 = hdaR3MixerAddDrvStream(pMixSink, pCfg, pDrv); - if (RT_FAILURE(rc2)) - LogFunc(("Attaching stream failed with %Rrc\n", rc2)); - - /* Do not pass failure to rc here, as there might be drivers which aren't - * configured / ready yet. */ - } - - return rc; -} - -/** - * @interface_method_impl{HDACODEC,pfnCbMixerAddStream} - */ -static DECLCALLBACK(int) hdaR3MixerAddStream(PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg) -{ - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - int rc; - - PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); - if (pSink) - { - rc = hdaR3MixerAddDrvStreams(pThisCC, pSink->pMixSink, pCfg); - - AssertPtr(pSink->pMixSink); - LogFlowFunc(("Sink=%s, Mixer control=%s\n", pSink->pMixSink->pszName, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl))); - } - else - rc = VERR_NOT_FOUND; - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * @interface_method_impl{HDACODEC,pfnCbMixerRemoveStream} - */ -static DECLCALLBACK(int) hdaR3MixerRemoveStream(PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl) -{ - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - int rc; - - PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); - if (pSink) - { - PHDADRIVER pDrv; - RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node) - { - PAUDMIXSTREAM pMixStream = NULL; - switch (enmMixerCtl) - { - /* - * Input. - */ - case PDMAUDIOMIXERCTL_LINE_IN: - pMixStream = pDrv->LineIn.pMixStrm; - pDrv->LineIn.pMixStrm = NULL; - break; -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - case PDMAUDIOMIXERCTL_MIC_IN: - pMixStream = pDrv->MicIn.pMixStrm; - pDrv->MicIn.pMixStrm = NULL; - break; -# endif - /* - * Output. - */ - case PDMAUDIOMIXERCTL_FRONT: - pMixStream = pDrv->Front.pMixStrm; - pDrv->Front.pMixStrm = NULL; - break; -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - case PDMAUDIOMIXERCTL_CENTER_LFE: - pMixStream = pDrv->CenterLFE.pMixStrm; - pDrv->CenterLFE.pMixStrm = NULL; - break; - case PDMAUDIOMIXERCTL_REAR: - pMixStream = pDrv->Rear.pMixStrm; - pDrv->Rear.pMixStrm = NULL; - break; -# endif - default: - AssertMsgFailed(("Mixer control %d not implemented\n", enmMixerCtl)); - break; - } - - if (pMixStream) - { - AudioMixerSinkRemoveStream(pSink->pMixSink, pMixStream); - AudioMixerStreamDestroy(pMixStream); - - pMixStream = NULL; - } - } - - AudioMixerSinkRemoveAllStreams(pSink->pMixSink); - rc = VINF_SUCCESS; - } - else - rc = VERR_NOT_FOUND; - - LogFunc(("Mixer control=%s, rc=%Rrc\n", DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), rc)); - return rc; -} - -/** - * @interface_method_impl{HDACODEC,pfnCbMixerControl} - * - * @note Is also called directly by the DevHDA code. - */ -static DECLCALLBACK(int) hdaR3MixerControl(PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - LogFunc(("enmMixerCtl=%s, uSD=%RU8, uChannel=%RU8\n", DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), uSD, uChannel)); - - if (uSD == 0) /* Stream number 0 is reserved. */ - { - Log2Func(("Invalid SDn (%RU8) number for mixer control '%s', ignoring\n", uSD, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl))); - return VINF_SUCCESS; - } - /* uChannel is optional. */ - - /* SDn0 starts as 1. */ - Assert(uSD); - uSD--; - -# ifndef VBOX_WITH_AUDIO_HDA_MIC_IN - /* Only SDI0 (Line-In) is supported. */ - if ( hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN - && uSD >= 1) - { - LogRel2(("HDA: Dedicated Mic-In support not imlpemented / built-in (stream #%RU8), using Line-In (stream #0) instead\n", uSD)); - uSD = 0; - } -# endif - - int rc = VINF_SUCCESS; - - PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); - if (pSink) - { - AssertPtr(pSink->pMixSink); - - /* If this an output stream, determine the correct SD#. */ - if ( uSD < HDA_MAX_SDI - && AudioMixerSinkGetDir(pSink->pMixSink) == AUDMIXSINKDIR_OUTPUT) - uSD += HDA_MAX_SDI; - - /* Make 100% sure we got a good stream number before continuing. */ - AssertLogRelReturn(uSD < RT_ELEMENTS(pThisCC->aStreams), VERR_NOT_IMPLEMENTED); - - /* Detach the existing stream from the sink. */ - if ( pSink->pStreamShared - && pSink->pStreamR3 - && ( pSink->pStreamShared->u8SD != uSD - || pSink->pStreamShared->u8Channel != uChannel) - ) - { - LogFunc(("Sink '%s' was assigned to stream #%RU8 (channel %RU8) before\n", - pSink->pMixSink->pszName, pSink->pStreamShared->u8SD, pSink->pStreamShared->u8Channel)); - - hdaR3StreamLock(pSink->pStreamR3); - - /* Only disable the stream if the stream descriptor # has changed. */ - if (pSink->pStreamShared->u8SD != uSD) - hdaR3StreamEnable(pSink->pStreamShared, pSink->pStreamR3, false /*fEnable*/); - - pSink->pStreamR3->pMixSink = NULL; - - hdaR3StreamUnlock(pSink->pStreamR3); - - pSink->pStreamShared = NULL; - pSink->pStreamR3 = NULL; - } - - /* Attach the new stream to the sink. - * Enabling the stream will be done by the gust via a separate SDnCTL call then. */ - if (pSink->pStreamShared == NULL) - { - LogRel2(("HDA: Setting sink '%s' to stream #%RU8 (channel %RU8), mixer control=%s\n", - pSink->pMixSink->pszName, uSD, uChannel, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl))); - - PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[uSD]; - PHDASTREAM pStreamShared = &pThis->aStreams[uSD]; - hdaR3StreamLock(pStreamR3); - - pSink->pStreamR3 = pStreamR3; - pSink->pStreamShared = pStreamShared; - - pStreamShared->u8Channel = uChannel; - pStreamR3->pMixSink = pSink; - - hdaR3StreamUnlock(pStreamR3); - rc = VINF_SUCCESS; - } - } - else - rc = VERR_NOT_FOUND; - - if (RT_FAILURE(rc)) - LogRel(("HDA: Converter control for stream #%RU8 (channel %RU8) / mixer control '%s' failed with %Rrc, skipping\n", - uSD, uChannel, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), rc)); - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * @interface_method_impl{HDACODEC,pfnCbMixerSetVolume} - */ -static DECLCALLBACK(int) hdaR3MixerSetVolume(PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol) -{ - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - int rc; - - PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); - if ( pSink - && pSink->pMixSink) - { - LogRel2(("HDA: Setting volume for mixer sink '%s' to %RU8/%RU8 (%s)\n", - pSink->pMixSink->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted ? "Muted" : "Unmuted")); - - /* Set the volume. - * We assume that the codec already converted it to the correct range. */ - rc = AudioMixerSinkSetVolume(pSink->pMixSink, pVol); - } - else - rc = VERR_NOT_FOUND; - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * @callback_method_impl{FNTMTIMERDEV, Main routine for the stream's timer.} - */ -static DECLCALLBACK(void) hdaR3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - uintptr_t idxStream = (uintptr_t)pvUser; - AssertReturnVoid(idxStream < RT_ELEMENTS(pThis->aStreams)); - PHDASTREAM pStreamShared = &pThis->aStreams[idxStream]; - PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[idxStream]; - TMTIMERHANDLE hTimer = pStreamShared->hTimer; - RT_NOREF(pTimer); - - Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); - Assert(PDMDevHlpTimerIsLockOwner(pDevIns, hTimer)); - - hdaR3StreamUpdate(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3, true /* fInTimer */); - - /* Flag indicating whether to kick the timer again for a new data processing round. */ - bool fSinkActive = false; - if (pStreamR3->pMixSink) - fSinkActive = AudioMixerSinkIsActive(pStreamR3->pMixSink->pMixSink); - - if (fSinkActive) - { - uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, hTimer); /* (For virtual sync this remains the same for the whole callout IIRC) */ - const bool fTimerScheduled = hdaR3StreamTransferIsScheduled(pStreamShared, tsNow); - Log3Func(("fSinksActive=%RTbool, fTimerScheduled=%RTbool\n", fSinkActive, fTimerScheduled)); - if (!fTimerScheduled) - hdaR3TimerSet(pDevIns, pStreamShared, tsNow + PDMDevHlpTimerGetFreq(pDevIns, hTimer) / pThis->uTimerHz, - true /*fForce*/, tsNow /*fixed*/ ); - } - else - Log3Func(("fSinksActive=%RTbool\n", fSinkActive)); -} - -# ifdef HDA_USE_DMA_ACCESS_HANDLER -/** - * HC access handler for the FIFO. - * - * @returns VINF_SUCCESS if the handler have carried out the operation. - * @returns VINF_PGM_HANDLER_DO_DEFAULT if the caller should carry out the access operation. - * @param pVM VM Handle. - * @param pVCpu The cross context CPU structure for the calling EMT. - * @param GCPhys The physical address the guest is writing to. - * @param pvPhys The HC mapping of that address. - * @param pvBuf What the guest is reading/writing. - * @param cbBuf How much it's reading/writing. - * @param enmAccessType The access type. - * @param enmOrigin Who is making the access. - * @param pvUser User argument. - */ -static DECLCALLBACK(VBOXSTRICTRC) hdaR3DmaAccessHandler(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, void *pvPhys, - void *pvBuf, size_t cbBuf, - PGMACCESSTYPE enmAccessType, PGMACCESSORIGIN enmOrigin, void *pvUser) -{ - RT_NOREF(pVM, pVCpu, pvPhys, pvBuf, enmOrigin); - - PHDADMAACCESSHANDLER pHandler = (PHDADMAACCESSHANDLER)pvUser; - AssertPtr(pHandler); - - PHDASTREAM pStream = pHandler->pStream; - AssertPtr(pStream); - - Assert(GCPhys >= pHandler->GCPhysFirst); - Assert(GCPhys <= pHandler->GCPhysLast); - Assert(enmAccessType == PGMACCESSTYPE_WRITE); - - /* Not within BDLE range? Bail out. */ - if ( (GCPhys < pHandler->BDLEAddr) - || (GCPhys + cbBuf > pHandler->BDLEAddr + pHandler->BDLESize)) - { - return VINF_PGM_HANDLER_DO_DEFAULT; - } - - switch (enmAccessType) - { - case PGMACCESSTYPE_WRITE: - { -# ifdef DEBUG - PHDASTREAMDEBUG pStreamDbg = &pStream->Dbg; - - const uint64_t tsNowNs = RTTimeNanoTS(); - const uint32_t tsElapsedMs = (tsNowNs - pStreamDbg->tsWriteSlotBegin) / 1000 / 1000; - - uint64_t cWritesHz = ASMAtomicReadU64(&pStreamDbg->cWritesHz); - uint64_t cbWrittenHz = ASMAtomicReadU64(&pStreamDbg->cbWrittenHz); - - if (tsElapsedMs >= (1000 / HDA_TIMER_HZ_DEFAULT)) - { - LogFunc(("[SD%RU8] %RU32ms elapsed, cbWritten=%RU64, cWritten=%RU64 -- %RU32 bytes on average per time slot (%zums)\n", - pStream->u8SD, tsElapsedMs, cbWrittenHz, cWritesHz, - ASMDivU64ByU32RetU32(cbWrittenHz, cWritesHz ? cWritesHz : 1), 1000 / HDA_TIMER_HZ_DEFAULT)); - - pStreamDbg->tsWriteSlotBegin = tsNowNs; - - cWritesHz = 0; - cbWrittenHz = 0; - } - - cWritesHz += 1; - cbWrittenHz += cbBuf; - - ASMAtomicIncU64(&pStreamDbg->cWritesTotal); - ASMAtomicAddU64(&pStreamDbg->cbWrittenTotal, cbBuf); - - ASMAtomicWriteU64(&pStreamDbg->cWritesHz, cWritesHz); - ASMAtomicWriteU64(&pStreamDbg->cbWrittenHz, cbWrittenHz); - - LogFunc(("[SD%RU8] Writing %3zu @ 0x%x (off %zu)\n", - pStream->u8SD, cbBuf, GCPhys, GCPhys - pHandler->BDLEAddr)); - - LogFunc(("[SD%RU8] cWrites=%RU64, cbWritten=%RU64 -> %RU32 bytes on average\n", - pStream->u8SD, pStreamDbg->cWritesTotal, pStreamDbg->cbWrittenTotal, - ASMDivU64ByU32RetU32(pStreamDbg->cbWrittenTotal, pStreamDbg->cWritesTotal))); -# endif - - if (pThis->fDebugEnabled) - { - RTFILE fh; - RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "hdaDMAAccessWrite.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTFileWrite(fh, pvBuf, cbBuf, NULL); - RTFileClose(fh); - } - -# ifdef HDA_USE_DMA_ACCESS_HANDLER_WRITING - PRTCIRCBUF pCircBuf = pStream->State.pCircBuf; - AssertPtr(pCircBuf); - - uint8_t *pbBuf = (uint8_t *)pvBuf; - while (cbBuf) - { - /* Make sure we only copy as much as the stream's FIFO can hold (SDFIFOS, 18.2.39). */ - void *pvChunk; - size_t cbChunk; - RTCircBufAcquireWriteBlock(pCircBuf, cbBuf, &pvChunk, &cbChunk); - - if (cbChunk) - { - memcpy(pvChunk, pbBuf, cbChunk); - - pbBuf += cbChunk; - Assert(cbBuf >= cbChunk); - cbBuf -= cbChunk; - } - else - { - //AssertMsg(RTCircBufFree(pCircBuf), ("No more space but still %zu bytes to write\n", cbBuf)); - break; - } - - LogFunc(("[SD%RU8] cbChunk=%zu\n", pStream->u8SD, cbChunk)); - - RTCircBufReleaseWriteBlock(pCircBuf, cbChunk); - } -# endif /* HDA_USE_DMA_ACCESS_HANDLER_WRITING */ - break; - } - - default: - AssertMsgFailed(("Access type not implemented\n")); - break; - } - - return VINF_PGM_HANDLER_DO_DEFAULT; -} -# endif /* HDA_USE_DMA_ACCESS_HANDLER */ - -/** - * Soft reset of the device triggered via GCTL. - * - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - */ -static void hdaR3GCTLReset(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC) -{ - LogFlowFuncEnter(); - - pThisCC->cStreamsActive = 0; - - HDA_REG(pThis, GCAP) = HDA_MAKE_GCAP(HDA_MAX_SDO, HDA_MAX_SDI, 0, 0, 1); /* see 6.2.1 */ - HDA_REG(pThis, VMIN) = 0x00; /* see 6.2.2 */ - HDA_REG(pThis, VMAJ) = 0x01; /* see 6.2.3 */ - HDA_REG(pThis, OUTPAY) = 0x003C; /* see 6.2.4 */ - HDA_REG(pThis, INPAY) = 0x001D; /* see 6.2.5 */ - HDA_REG(pThis, CORBSIZE) = 0x42; /* Up to 256 CORB entries see 6.2.1 */ - HDA_REG(pThis, RIRBSIZE) = 0x42; /* Up to 256 RIRB entries see 6.2.1 */ - HDA_REG(pThis, CORBRP) = 0x0; - HDA_REG(pThis, CORBWP) = 0x0; - HDA_REG(pThis, RIRBWP) = 0x0; - /* Some guests (like Haiku) don't set RINTCNT explicitly but expect an interrupt after each - * RIRB response -- so initialize RINTCNT to 1 by default. */ - HDA_REG(pThis, RINTCNT) = 0x1; - - /* - * Stop any audio currently playing and/or recording. - */ - pThisCC->SinkFront.pStreamShared = NULL; - pThisCC->SinkFront.pStreamR3 = NULL; - if (pThisCC->SinkFront.pMixSink) - AudioMixerSinkReset(pThisCC->SinkFront.pMixSink); -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - pThisCC->SinkMicIn.pStreamShared = NULL; - pThisCC->SinkMicIn.pStreamR3 = NULL; - if (pThisCC->SinkMicIn.pMixSink) - AudioMixerSinkReset(pThisCC->SinkMicIn.pMixSink); -# endif - pThisCC->SinkLineIn.pStreamShared = NULL; - pThisCC->SinkLineIn.pStreamR3 = NULL; - if (pThisCC->SinkLineIn.pMixSink) - AudioMixerSinkReset(pThisCC->SinkLineIn.pMixSink); -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - pThisCC->SinkCenterLFE = NULL; - if (pThisCC->SinkCenterLFE.pMixSink) - AudioMixerSinkReset(pThisCC->SinkCenterLFE.pMixSink); - pThisCC->SinkRear.pStreamShared = NULL; - pThisCC->SinkRear.pStreamR3 = NULL; - if (pThisCC->SinkRear.pMixSink) - AudioMixerSinkReset(pThisCC->SinkRear.pMixSink); -# endif - - /* - * Reset the codec. - */ - if ( pThisCC->pCodec - && pThisCC->pCodec->pfnReset) - { - pThisCC->pCodec->pfnReset(pThisCC->pCodec); - } - - /* - * Set some sensible defaults for which HDA sinks - * are connected to which stream number. - * - * We use SD0 for input and SD4 for output by default. - * These stream numbers can be changed by the guest dynamically lateron. - */ - ASMCompilerBarrier(); /* paranoia */ -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_MIC_IN , 1 /* SD0 */, 0 /* Channel */); -# endif - hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_LINE_IN , 1 /* SD0 */, 0 /* Channel */); - - hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_FRONT , 5 /* SD4 */, 0 /* Channel */); -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_CENTER_LFE, 5 /* SD4 */, 0 /* Channel */); - hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_REAR , 5 /* SD4 */, 0 /* Channel */); -# endif - ASMCompilerBarrier(); /* paranoia */ - - /* Reset CORB. */ - pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE; - RT_ZERO(pThis->au32CorbBuf); - - /* Reset RIRB. */ - pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE; - RT_ZERO(pThis->au64RirbBuf); - - /* Clear our internal response interrupt counter. */ - pThis->u16RespIntCnt = 0; - - for (size_t idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++) - { - int rc2 = hdaR3StreamEnable(&pThis->aStreams[idxStream], &pThisCC->aStreams[idxStream], false /* fEnable */); - if (RT_SUCCESS(rc2)) - { - /* Remove the RUN bit from SDnCTL in case the stream was in a running state before. */ - HDA_STREAM_REG(pThis, CTL, idxStream) &= ~HDA_SDCTL_RUN; - hdaR3StreamReset(pThis, pThisCC, &pThis->aStreams[idxStream], &pThisCC->aStreams[idxStream], (uint8_t)idxStream); - } - } - - /* Clear stream tags <-> objects mapping table. */ - RT_ZERO(pThisCC->aTags); - - /* Emulation of codec "wake up" (HDA spec 5.5.1 and 6.5). */ - HDA_REG(pThis, STATESTS) = 0x1; - - LogFlowFuncLeave(); - LogRel(("HDA: Reset\n")); -} - -#else /* !IN_RING3 */ - -/** - * Checks if a dword read starting with @a idxRegDsc is safe. - * - * We can guarentee it only standard reader callbacks are used. - * @returns true if it will always succeed, false if it may return back to - * ring-3 or we're just not sure. - * @param idxRegDsc The first register descriptor in the DWORD being read. - */ -DECLINLINE(bool) hdaIsMultiReadSafeInRZ(unsigned idxRegDsc) -{ - int32_t cbLeft = 4; /* signed on purpose */ - do - { - if ( g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU24 - || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU16 - || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU8 - || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadUnimpl) - { /* okay */ } - else - { - Log4(("hdaIsMultiReadSafeInRZ: idxRegDsc=%u %s\n", idxRegDsc, g_aHdaRegMap[idxRegDsc].abbrev)); - return false; - } - - idxRegDsc++; - if (idxRegDsc < RT_ELEMENTS(g_aHdaRegMap)) - cbLeft -= g_aHdaRegMap[idxRegDsc].offset - g_aHdaRegMap[idxRegDsc - 1].offset; - else - break; - } while (cbLeft > 0); - return true; -} - - -#endif /* !IN_RING3 */ - - -/* MMIO callbacks */ - -/** - * @callback_method_impl{FNIOMMMIONEWREAD, Looks up and calls the appropriate handler.} - * - * @note During implementation, we discovered so-called "forgotten" or "hole" - * registers whose description is not listed in the RPM, datasheet, or - * spec. - */ -static DECLCALLBACK(VBOXSTRICTRC) hdaMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - VBOXSTRICTRC rc; - RT_NOREF_PV(pvUser); - Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC); - - /* - * Look up and log. - */ - int idxRegDsc = hdaRegLookup(off); /* Register descriptor index. */ -#ifdef LOG_ENABLED - unsigned const cbLog = cb; - uint32_t offRegLog = (uint32_t)off; -#endif - - Log3Func(("off=%#x cb=%#x\n", offRegLog, cb)); - Assert(cb == 4); Assert((off & 3) == 0); - - rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_IOM_R3_MMIO_READ); - if (rc == VINF_SUCCESS) - { - if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL) - LogFunc(("Access to registers except GCTL is blocked while resetting\n")); - - if (idxRegDsc >= 0) - { - /* ASSUMES gapless DWORD at end of map. */ - if (g_aHdaRegMap[idxRegDsc].size == 4) - { - /* - * Straight forward DWORD access. - */ - rc = g_aHdaRegMap[idxRegDsc].pfnRead(pDevIns, pThis, idxRegDsc, (uint32_t *)pv); - Log3Func(("\tRead %s => %x (%Rrc)\n", g_aHdaRegMap[idxRegDsc].abbrev, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc))); - STAM_COUNTER_INC(&pThis->aStatRegReads[idxRegDsc]); - } -#ifndef IN_RING3 - else if (!hdaIsMultiReadSafeInRZ(idxRegDsc)) - - { - STAM_COUNTER_INC(&pThis->aStatRegReadsToR3[idxRegDsc]); - rc = VINF_IOM_R3_MMIO_READ; - } -#endif - else - { - /* - * Multi register read (unless there are trailing gaps). - * ASSUMES that only DWORD reads have sideeffects. - */ - STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiReads)); - Log4(("hdaMmioRead: multi read: %#x LB %#x %s\n", off, cb, g_aHdaRegMap[idxRegDsc].abbrev)); - uint32_t u32Value = 0; - unsigned cbLeft = 4; - do - { - uint32_t const cbReg = g_aHdaRegMap[idxRegDsc].size; - uint32_t u32Tmp = 0; - - rc = g_aHdaRegMap[idxRegDsc].pfnRead(pDevIns, pThis, idxRegDsc, &u32Tmp); - Log4Func(("\tRead %s[%db] => %x (%Rrc)*\n", g_aHdaRegMap[idxRegDsc].abbrev, cbReg, u32Tmp, VBOXSTRICTRC_VAL(rc))); - STAM_COUNTER_INC(&pThis->aStatRegReads[idxRegDsc]); -#ifdef IN_RING3 - if (rc != VINF_SUCCESS) - break; -#else - AssertMsgBreak(rc == VINF_SUCCESS, ("rc=%Rrc - impossible, we sanitized the readers!\n", VBOXSTRICTRC_VAL(rc))); -#endif - u32Value |= (u32Tmp & g_afMasks[cbReg]) << ((4 - cbLeft) * 8); - - cbLeft -= cbReg; - off += cbReg; - idxRegDsc++; - } while (cbLeft > 0 && g_aHdaRegMap[idxRegDsc].offset == off); - - if (rc == VINF_SUCCESS) - *(uint32_t *)pv = u32Value; - else - Assert(!IOM_SUCCESS(rc)); - } - } - else - { - LogRel(("HDA: Invalid read access @0x%x (bytes=%u)\n", (uint32_t)off, cb)); - Log3Func(("\tHole at %x is accessed for read\n", offRegLog)); - STAM_COUNTER_INC(&pThis->StatRegUnknownReads); - rc = VINF_IOM_MMIO_UNUSED_FF; - } - - DEVHDA_UNLOCK(pDevIns, pThis); - - /* - * Log the outcome. - */ -#ifdef LOG_ENABLED - if (cbLog == 4) - Log3Func(("\tReturning @%#05x -> %#010x %Rrc\n", offRegLog, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc))); - else if (cbLog == 2) - Log3Func(("\tReturning @%#05x -> %#06x %Rrc\n", offRegLog, *(uint16_t *)pv, VBOXSTRICTRC_VAL(rc))); - else if (cbLog == 1) - Log3Func(("\tReturning @%#05x -> %#04x %Rrc\n", offRegLog, *(uint8_t *)pv, VBOXSTRICTRC_VAL(rc))); -#endif - } - else - { - if (idxRegDsc >= 0) - STAM_COUNTER_INC(&pThis->aStatRegReadsToR3[idxRegDsc]); - } - return rc; -} - - -DECLINLINE(VBOXSTRICTRC) hdaWriteReg(PPDMDEVINS pDevIns, PHDASTATE pThis, int idxRegDsc, uint32_t u32Value, char const *pszLog) -{ - DEVHDA_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); - - if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL) - { - Log(("hdaWriteReg: Warning: Access to %s is blocked while controller is in reset mode\n", g_aHdaRegMap[idxRegDsc].abbrev)); - LogRel2(("HDA: Warning: Access to register %s is blocked while controller is in reset mode\n", - g_aHdaRegMap[idxRegDsc].abbrev)); - STAM_COUNTER_INC(&pThis->StatRegWritesBlockedByReset); - - DEVHDA_UNLOCK(pDevIns, pThis); - return VINF_SUCCESS; - } - - /* - * Handle RD (register description) flags. - */ - - /* For SDI / SDO: Check if writes to those registers are allowed while SDCTL's RUN bit is set. */ - if (idxRegDsc >= HDA_NUM_GENERAL_REGS) - { - const uint32_t uSDCTL = HDA_STREAM_REG(pThis, CTL, HDA_SD_NUM_FROM_REG(pThis, CTL, idxRegDsc)); - - /* - * Some OSes (like Win 10 AU) violate the spec by writing stuff to registers which are not supposed to be be touched - * while SDCTL's RUN bit is set. So just ignore those values. - */ - - /* Is the RUN bit currently set? */ - if ( RT_BOOL(uSDCTL & HDA_SDCTL_RUN) - /* Are writes to the register denied if RUN bit is set? */ - && !(g_aHdaRegMap[idxRegDsc].fFlags & HDA_RD_F_SD_WRITE_RUN)) - { - Log(("hdaWriteReg: Warning: Access to %s is blocked! %R[sdctl]\n", g_aHdaRegMap[idxRegDsc].abbrev, uSDCTL)); - LogRel2(("HDA: Warning: Access to register %s is blocked while the stream's RUN bit is set\n", - g_aHdaRegMap[idxRegDsc].abbrev)); - STAM_COUNTER_INC(&pThis->StatRegWritesBlockedByRun); - - DEVHDA_UNLOCK(pDevIns, pThis); - return VINF_SUCCESS; - } - } - -#ifdef LOG_ENABLED - uint32_t const idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; - uint32_t const u32OldValue = pThis->au32Regs[idxRegMem]; -#endif - VBOXSTRICTRC rc = g_aHdaRegMap[idxRegDsc].pfnWrite(pDevIns, pThis, idxRegDsc, u32Value); - Log3Func(("Written value %#x to %s[%d byte]; %x => %x%s, rc=%d\n", u32Value, g_aHdaRegMap[idxRegDsc].abbrev, - g_aHdaRegMap[idxRegDsc].size, u32OldValue, pThis->au32Regs[idxRegMem], pszLog, VBOXSTRICTRC_VAL(rc))); -#ifndef IN_RING3 - if (rc == VINF_IOM_R3_MMIO_WRITE) - STAM_COUNTER_INC(&pThis->aStatRegWritesToR3[idxRegDsc]); - else -#endif - STAM_COUNTER_INC(&pThis->aStatRegWrites[idxRegDsc]); - - DEVHDA_UNLOCK(pDevIns, pThis); - RT_NOREF(pszLog); - return rc; -} - - -/** - * @callback_method_impl{FNIOMMMIONEWWRITE, - * Looks up and calls the appropriate handler.} - */ -static DECLCALLBACK(VBOXSTRICTRC) hdaMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - RT_NOREF_PV(pvUser); - Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC); - - /* - * Look up and log the access. - */ - int idxRegDsc = hdaRegLookup(off); -#if defined(IN_RING3) || defined(LOG_ENABLED) - uint32_t idxRegMem = idxRegDsc != -1 ? g_aHdaRegMap[idxRegDsc].mem_idx : UINT32_MAX; -#endif - uint64_t u64Value; - if (cb == 4) u64Value = *(uint32_t const *)pv; - else if (cb == 2) u64Value = *(uint16_t const *)pv; - else if (cb == 1) u64Value = *(uint8_t const *)pv; - else if (cb == 8) u64Value = *(uint64_t const *)pv; - else - ASSERT_GUEST_MSG_FAILED_RETURN(("cb=%u %.*Rhxs\n", cb, cb, pv), - PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "odd write size: off=%RGp cb=%u\n", off, cb)); - - /* - * The behavior of accesses that aren't aligned on natural boundraries is - * undefined. Just reject them outright. - */ - ASSERT_GUEST_MSG_RETURN((off & (cb - 1)) == 0, ("off=%RGp cb=%u %.*Rhxs\n", off, cb, cb, pv), - PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "misaligned write access: off=%RGp cb=%u\n", off, cb)); - -#ifdef LOG_ENABLED - uint32_t const u32LogOldValue = idxRegDsc >= 0 ? pThis->au32Regs[idxRegMem] : UINT32_MAX; -#endif - - /* - * Try for a direct hit first. - */ - VBOXSTRICTRC rc; - if (idxRegDsc >= 0 && g_aHdaRegMap[idxRegDsc].size == cb) - { - Log3Func(("@%#05x u%u=%#0*RX64 %s\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].abbrev)); - rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value, ""); - Log3Func(("\t%#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX)); - } - /* - * Sub-register access. Supply missing bits as needed. - */ - else if ( idxRegDsc >= 0 - && cb < g_aHdaRegMap[idxRegDsc].size) - { - u64Value |= pThis->au32Regs[g_aHdaRegMap[idxRegDsc].mem_idx] - & g_afMasks[g_aHdaRegMap[idxRegDsc].size] - & ~g_afMasks[cb]; - Log4Func(("@%#05x u%u=%#0*RX64 cb=%#x cbReg=%x %s\n" - "\tSupplying missing bits (%#x): %#llx -> %#llx ...\n", - (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, cb, g_aHdaRegMap[idxRegDsc].size, g_aHdaRegMap[idxRegDsc].abbrev, - g_afMasks[g_aHdaRegMap[idxRegDsc].size] & ~g_afMasks[cb], u64Value & g_afMasks[cb], u64Value)); - rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value, ""); - Log4Func(("\t%#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX)); - STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegSubWrite)); - } - /* - * Partial or multiple register access, loop thru the requested memory. - */ - else - { -#ifdef IN_RING3 - if (idxRegDsc == -1) - Log4Func(("@%#05x u32=%#010x cb=%d\n", (uint32_t)off, *(uint32_t const *)pv, cb)); - else if (g_aHdaRegMap[idxRegDsc].size == cb) - Log4Func(("@%#05x u%u=%#0*RX64 %s\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].abbrev)); - else - Log4Func(("@%#05x u%u=%#0*RX64 %s - mismatch cbReg=%u\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, - g_aHdaRegMap[idxRegDsc].abbrev, g_aHdaRegMap[idxRegDsc].size)); - - /* - * If it's an access beyond the start of the register, shift the input - * value and fill in missing bits. Natural alignment rules means we - * will only see 1 or 2 byte accesses of this kind, so no risk of - * shifting out input values. - */ - if (idxRegDsc < 0) - { - idxRegDsc = hdaR3RegLookupWithin(off); - if (idxRegDsc != -1) - { - uint32_t const cbBefore = (uint32_t)off - g_aHdaRegMap[idxRegDsc].offset; - Assert(cbBefore > 0 && cbBefore < 4); - off -= cbBefore; - idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; - u64Value <<= cbBefore * 8; - u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbBefore]; - Log4Func(("\tWithin register, supplied %u leading bits: %#llx -> %#llx ...\n", - cbBefore * 8, ~g_afMasks[cbBefore] & u64Value, u64Value)); - STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiWrites)); - } - else - STAM_COUNTER_INC(&pThis->StatRegUnknownWrites); - } - else - { - Log4(("hdaMmioWrite: multi write: %s\n", g_aHdaRegMap[idxRegDsc].abbrev)); - STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiWrites)); - } - - /* Loop thru the write area, it may cover multiple registers. */ - rc = VINF_SUCCESS; - for (;;) - { - uint32_t cbReg; - if (idxRegDsc >= 0) - { - idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; - cbReg = g_aHdaRegMap[idxRegDsc].size; - if (cb < cbReg) - { - u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbReg] & ~g_afMasks[cb]; - Log4Func(("\tSupplying missing bits (%#x): %#llx -> %#llx ...\n", - g_afMasks[cbReg] & ~g_afMasks[cb], u64Value & g_afMasks[cb], u64Value)); - } -# ifdef LOG_ENABLED - uint32_t uLogOldVal = pThis->au32Regs[idxRegMem]; -# endif - rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value & g_afMasks[cbReg], "*"); - Log4Func(("\t%#x -> %#x\n", uLogOldVal, pThis->au32Regs[idxRegMem])); - } - else - { - LogRel(("HDA: Invalid write access @0x%x\n", (uint32_t)off)); - cbReg = 1; - } - if (rc != VINF_SUCCESS) - break; - if (cbReg >= cb) - break; - - /* Advance. */ - off += cbReg; - cb -= cbReg; - u64Value >>= cbReg * 8; - if (idxRegDsc == -1) - idxRegDsc = hdaRegLookup(off); - else - { - idxRegDsc++; - if ( (unsigned)idxRegDsc >= RT_ELEMENTS(g_aHdaRegMap) - || g_aHdaRegMap[idxRegDsc].offset != off) - idxRegDsc = -1; - } - } - -#else /* !IN_RING3 */ - /* Take the simple way out. */ - rc = VINF_IOM_R3_MMIO_WRITE; -#endif /* !IN_RING3 */ - } - - return rc; -} - -#ifdef IN_RING3 - - -/********************************************************************************************************************************* -* Saved state * -*********************************************************************************************************************************/ - -/** - * @callback_method_impl{FNSSMFIELDGETPUT, - * Version 6 saves the IOC flag in HDABDLEDESC::fFlags as a bool} - */ -static DECLCALLBACK(int) -hdaR3GetPutTrans_HDABDLEDESC_fFlags_6(PSSMHANDLE pSSM, const struct SSMFIELD *pField, void *pvStruct, - uint32_t fFlags, bool fGetOrPut, void *pvUser) -{ - PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; - RT_NOREF(pSSM, pField, pvStruct, fFlags); - AssertReturn(fGetOrPut, VERR_INTERNAL_ERROR_4); - bool fIoc; - int rc = pDevIns->pHlpR3->pfnSSMGetBool(pSSM, &fIoc); - if (RT_SUCCESS(rc)) - { - PHDABDLEDESC pDesc = (PHDABDLEDESC)pvStruct; - pDesc->fFlags = fIoc ? HDA_BDLE_F_IOC : 0; - } - return rc; -} - - -/** - * @callback_method_impl{FNSSMFIELDGETPUT, - * Versions 1 thru 4 save the IOC flag in HDASTREAMSTATE::DescfFlags as a bool} - */ -static DECLCALLBACK(int) -hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4(PSSMHANDLE pSSM, const struct SSMFIELD *pField, void *pvStruct, - uint32_t fFlags, bool fGetOrPut, void *pvUser) -{ - PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; - RT_NOREF(pSSM, pField, pvStruct, fFlags); - AssertReturn(fGetOrPut, VERR_INTERNAL_ERROR_4); - bool fIoc; - int rc = pDevIns->pHlpR3->pfnSSMGetBool(pSSM, &fIoc); - if (RT_SUCCESS(rc)) - { - PHDABDLE pState = (PHDABDLE)pvStruct; - pState->Desc.fFlags = fIoc ? HDA_BDLE_F_IOC : 0; - } - return rc; -} - - -static int hdaR3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3) -{ - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; -# ifdef LOG_ENABLED - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); -# endif - - Log2Func(("[SD%RU8]\n", pStreamShared->u8SD)); - - /* Save stream ID. */ - Assert(pStreamShared->u8SD < HDA_MAX_STREAMS); - int rc = pHlp->pfnSSMPutU8(pSSM, pStreamShared->u8SD); - AssertRCReturn(rc, rc); - - rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State, sizeof(pStreamShared->State), - 0 /*fFlags*/, g_aSSMStreamStateFields7, NULL); - AssertRCReturn(rc, rc); - - rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State.BDLE.Desc, sizeof(pStreamShared->State.BDLE.Desc), - 0 /*fFlags*/, g_aSSMBDLEDescFields7, NULL); - AssertRCReturn(rc, rc); - - rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State.BDLE.State, sizeof(pStreamShared->State.BDLE.State), - 0 /*fFlags*/, g_aSSMBDLEStateFields7, NULL); - AssertRCReturn(rc, rc); - - rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State.Period, sizeof(pStreamShared->State.Period), - 0 /* fFlags */, g_aSSMStreamPeriodFields7, NULL); - AssertRCReturn(rc, rc); - - uint32_t cbCircBufSize = 0; - uint32_t cbCircBufUsed = 0; - - if (pStreamR3->State.pCircBuf) - { - cbCircBufSize = (uint32_t)RTCircBufSize(pStreamR3->State.pCircBuf); - cbCircBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); - } - - rc = pHlp->pfnSSMPutU32(pSSM, cbCircBufSize); - AssertRCReturn(rc, rc); - - rc = pHlp->pfnSSMPutU32(pSSM, cbCircBufUsed); - AssertRCReturn(rc, rc); - - if (cbCircBufUsed) - { - /* - * We now need to get the circular buffer's data without actually modifying - * the internal read / used offsets -- otherwise we would end up with broken audio - * data after saving the state. - * - * So get the current read offset and serialize the buffer data manually based on that. - */ - size_t const offBuf = RTCircBufOffsetRead(pStreamR3->State.pCircBuf); - void *pvBuf; - size_t cbBuf; - RTCircBufAcquireReadBlock(pStreamR3->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf); - Assert(cbBuf); - if (cbBuf) - { - rc = pHlp->pfnSSMPutMem(pSSM, pvBuf, cbBuf); - AssertRC(rc); - if ( RT_SUCCESS(rc) - && cbBuf < cbCircBufUsed) - { - rc = pHlp->pfnSSMPutMem(pSSM, (uint8_t *)pvBuf - offBuf, cbCircBufUsed - cbBuf); - } - } - RTCircBufReleaseReadBlock(pStreamR3->State.pCircBuf, 0 /* Don't advance read pointer -- see comment above */); - } - - Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", pStreamR3->u8SD, HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD), - HDA_STREAM_REG(pThis, CBL, pStreamShared->u8SD), HDA_STREAM_REG(pThis, LVI, pStreamShared->u8SD))); - -#ifdef LOG_ENABLED - hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); -#endif - - return rc; -} - -/** - * @callback_method_impl{FNSSMDEVSAVEEXEC} - */ -static DECLCALLBACK(int) hdaR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - - /* Save Codec nodes states. */ - hdaCodecSaveState(pDevIns, pThisCC->pCodec, pSSM); - - /* Save MMIO registers. */ - pHlp->pfnSSMPutU32(pSSM, RT_ELEMENTS(pThis->au32Regs)); - pHlp->pfnSSMPutMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); - - /* Save controller-specifc internals. */ - pHlp->pfnSSMPutU64(pSSM, pThis->u64WalClk); - pHlp->pfnSSMPutU8(pSSM, pThis->u8IRQL); - - /* Save number of streams. */ - pHlp->pfnSSMPutU32(pSSM, HDA_MAX_STREAMS); - - /* Save stream states. */ - for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) - { - int rc = hdaR3SaveStream(pDevIns, pSSM, &pThis->aStreams[i], &pThisCC->aStreams[i]); - AssertRCReturn(rc, rc); - } - - return VINF_SUCCESS; -} - -/** - * Does required post processing when loading a saved state. - * - * @param pDevIns The device instance. - * @param pThis Pointer to the shared HDA state. - * @param pThisCC Pointer to the ring-3 HDA state. - */ -static int hdaR3LoadExecPost(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC) -{ - int rc = VINF_SUCCESS; /** @todo r=bird: Really, what's the point of this? */ - - /* - * Enable all previously active streams. - */ - for (size_t i = 0; i < HDA_MAX_STREAMS; i++) - { - PHDASTREAM pStreamShared = &pThis->aStreams[i]; - - bool fActive = RT_BOOL(HDA_STREAM_REG(pThis, CTL, i) & HDA_SDCTL_RUN); - if (fActive) - { - PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[i]; - - int rc2; -#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - /* Make sure to also create the async I/O thread before actually enabling the stream. */ - rc2 = hdaR3StreamAsyncIOCreate(pStreamR3); - AssertRC(rc2); - - /* ... and enabling it. */ - hdaR3StreamAsyncIOEnable(pStreamR3, true /* fEnable */); -#endif - /* Resume the stream's period. */ - hdaR3StreamPeriodResume(&pStreamShared->State.Period); - - /* (Re-)enable the stream. */ - rc2 = hdaR3StreamEnable(pStreamShared, pStreamR3, true /* fEnable */); - AssertRC(rc2); - - /* Add the stream to the device setup. */ - rc2 = hdaR3AddStream(pThisCC, &pStreamShared->State.Cfg); - AssertRC(rc2); - -#ifdef HDA_USE_DMA_ACCESS_HANDLER - /* (Re-)install the DMA handler. */ - hdaR3StreamRegisterDMAHandlers(pThis, pStreamShared); -#endif - if (hdaR3StreamTransferIsScheduled(pStreamShared, PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer))) - hdaR3TimerSet(pDevIns, pStreamShared, hdaR3StreamTransferGetNext(pStreamShared), true /*fForce*/, 0 /*tsNow*/); - - /* Also keep track of the currently active streams. */ - pThisCC->cStreamsActive++; - } - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * Handles loading of all saved state versions older than the current one. - * - * @param pDevIns The device instance. - * @param pThis Pointer to the shared HDA state. - * @param pThisCC Pointer to the ring-3 HDA state. - * @param pSSM Pointer to SSM handle. - * @param uVersion Saved state version to load. - */ -static int hdaR3LoadExecLegacy(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PSSMHANDLE pSSM, uint32_t uVersion) -{ - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - int rc; - - /* - * Load MMIO registers. - */ - uint32_t cRegs; - switch (uVersion) - { - case HDA_SAVED_STATE_VERSION_1: - /* Starting with r71199, we would save 112 instead of 113 - registers due to some code cleanups. This only affected trunk - builds in the 4.1 development period. */ - cRegs = 113; - if (pHlp->pfnSSMHandleRevision(pSSM) >= 71199) - { - uint32_t uVer = pHlp->pfnSSMHandleVersion(pSSM); - if ( VBOX_FULL_VERSION_GET_MAJOR(uVer) == 4 - && VBOX_FULL_VERSION_GET_MINOR(uVer) == 0 - && VBOX_FULL_VERSION_GET_BUILD(uVer) >= 51) - cRegs = 112; - } - break; - - case HDA_SAVED_STATE_VERSION_2: - case HDA_SAVED_STATE_VERSION_3: - cRegs = 112; - AssertCompile(RT_ELEMENTS(pThis->au32Regs) >= 112); - break; - - /* Since version 4 we store the register count to stay flexible. */ - case HDA_SAVED_STATE_VERSION_4: - case HDA_SAVED_STATE_VERSION_5: - case HDA_SAVED_STATE_VERSION_6: - rc = pHlp->pfnSSMGetU32(pSSM, &cRegs); - AssertRCReturn(rc, rc); - if (cRegs != RT_ELEMENTS(pThis->au32Regs)) - LogRel(("HDA: SSM version cRegs is %RU32, expected %RU32\n", cRegs, RT_ELEMENTS(pThis->au32Regs))); - break; - - default: - AssertLogRelMsgFailedReturn(("HDA: Internal Error! Didn't expect saved state version %RU32 ending up in hdaR3LoadExecLegacy!\n", - uVersion), VERR_INTERNAL_ERROR_5); - } - - if (cRegs >= RT_ELEMENTS(pThis->au32Regs)) - { - pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); - pHlp->pfnSSMSkip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs))); - } - else - pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs); - - /* Make sure to update the base addresses first before initializing any streams down below. */ - pThis->u64CORBBase = RT_MAKE_U64(HDA_REG(pThis, CORBLBASE), HDA_REG(pThis, CORBUBASE)); - pThis->u64RIRBBase = RT_MAKE_U64(HDA_REG(pThis, RIRBLBASE), HDA_REG(pThis, RIRBUBASE)); - pThis->u64DPBase = RT_MAKE_U64(HDA_REG(pThis, DPLBASE) & DPBASE_ADDR_MASK, HDA_REG(pThis, DPUBASE)); - - /* Also make sure to update the DMA position bit if this was enabled when saving the state. */ - pThis->fDMAPosition = RT_BOOL(HDA_REG(pThis, DPLBASE) & RT_BIT_32(0)); - - /* - * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. - * - * Note: Saved states < v5 store LVI (u32BdleMaxCvi) for - * *every* BDLE state, whereas it only needs to be stored - * *once* for every stream. Most of the BDLE state we can - * get out of the registers anyway, so just ignore those values. - * - * Also, only the current BDLE was saved, regardless whether - * there were more than one (and there are at least two entries, - * according to the spec). - */ - switch (uVersion) - { - case HDA_SAVED_STATE_VERSION_1: - case HDA_SAVED_STATE_VERSION_2: - case HDA_SAVED_STATE_VERSION_3: - case HDA_SAVED_STATE_VERSION_4: - { - /* Only load the internal states. - * The rest will be initialized from the saved registers later. */ - - /* Note 1: Only the *current* BDLE for a stream was saved! */ - /* Note 2: The stream's saving order is/was fixed, so don't touch! */ - - /* Output */ - PHDASTREAM pStreamShared = &pThis->aStreams[4]; - rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[4], 4 /* Stream descriptor, hardcoded */); - AssertRCReturn(rc, rc); - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE, sizeof(pStreamShared->State.BDLE), - 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); - AssertRCReturn(rc, rc); - pStreamShared->State.uCurBDLE = pStreamShared->State.BDLE.State.u32BDLIndex; - - /* Microphone-In */ - pStreamShared = &pThis->aStreams[2]; - rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[2], 2 /* Stream descriptor, hardcoded */); - AssertRCReturn(rc, rc); - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE, sizeof(pStreamShared->State.BDLE), - 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); - AssertRCReturn(rc, rc); - pStreamShared->State.uCurBDLE = pStreamShared->State.BDLE.State.u32BDLIndex; - - /* Line-In */ - pStreamShared = &pThis->aStreams[0]; - rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[0], 0 /* Stream descriptor, hardcoded */); - AssertRCReturn(rc, rc); - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE, sizeof(pStreamShared->State.BDLE), - 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); - AssertRCReturn(rc, rc); - pStreamShared->State.uCurBDLE = pStreamShared->State.BDLE.State.u32BDLIndex; - break; - } - - /* - * v5 & v6 - Since v5 we support flexible stream and BDLE counts. - */ - default: - { - /* Stream count. */ - uint32_t cStreams; - rc = pHlp->pfnSSMGetU32(pSSM, &cStreams); - AssertRCReturn(rc, rc); - if (cStreams > HDA_MAX_STREAMS) - return pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, - N_("State contains %u streams while %u is the maximum supported"), - cStreams, HDA_MAX_STREAMS); - - /* Load stream states. */ - for (uint32_t i = 0; i < cStreams; i++) - { - uint8_t idStream; - rc = pHlp->pfnSSMGetU8(pSSM, &idStream); - AssertRCReturn(rc, rc); - - HDASTREAM StreamDummyShared; - HDASTREAMR3 StreamDummyR3; - PHDASTREAM pStreamShared = idStream < RT_ELEMENTS(pThis->aStreams) ? &pThis->aStreams[idStream] : &StreamDummyShared; - PHDASTREAMR3 pStreamR3 = idStream < RT_ELEMENTS(pThisCC->aStreams) ? &pThisCC->aStreams[idStream] : &StreamDummyR3; - AssertLogRelMsgStmt(idStream < RT_ELEMENTS(pThisCC->aStreams), - ("HDA stream ID=%RU8 not supported, skipping loadingit ...\n", idStream), - RT_ZERO(StreamDummyShared); RT_ZERO(StreamDummyR3)); - - rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, idStream); - if (RT_FAILURE(rc)) - { - LogRel(("HDA: Stream #%RU32: Setting up of stream %RU8 failed, rc=%Rrc\n", i, idStream, rc)); - break; - } - - /* - * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. - */ - if (uVersion == HDA_SAVED_STATE_VERSION_5) - { - struct V5HDASTREAMSTATE /* HDASTREAMSTATE + HDABDLE */ - { - uint16_t cBLDEs; - uint16_t uCurBDLE; - uint32_t u32BDLEIndex; - uint32_t cbBelowFIFOW; - uint32_t u32BufOff; - } Tmp; - static SSMFIELD const g_aV5State1Fields[] = - { - SSMFIELD_ENTRY(V5HDASTREAMSTATE, cBLDEs), - SSMFIELD_ENTRY(V5HDASTREAMSTATE, uCurBDLE), - SSMFIELD_ENTRY_TERM() - }; - rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State1Fields, NULL); - AssertRCReturn(rc, rc); - pStreamShared->State.uCurBDLE = Tmp.uCurBDLE; - - for (uint16_t a = 0; a < Tmp.cBLDEs; a++) - { - static SSMFIELD const g_aV5State2Fields[] = - { - SSMFIELD_ENTRY(V5HDASTREAMSTATE, u32BDLEIndex), - SSMFIELD_ENTRY_OLD(au8FIFO, 256), - SSMFIELD_ENTRY(V5HDASTREAMSTATE, cbBelowFIFOW), - SSMFIELD_ENTRY_TERM() - }; - rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State2Fields, NULL); - AssertRCReturn(rc, rc); - if (Tmp.u32BDLEIndex == pStreamShared->State.uCurBDLE) - { - pStreamShared->State.BDLE.State.cbBelowFIFOW = Tmp.cbBelowFIFOW; - pStreamShared->State.BDLE.State.u32BufOff = Tmp.u32BufOff; - } - } - } - else - { - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State, sizeof(HDASTREAMSTATE), - 0 /* fFlags */, g_aSSMStreamStateFields6, NULL); - AssertRCReturn(rc, rc); - - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE.Desc, sizeof(pStreamShared->State.BDLE.Desc), - 0 /* fFlags */, g_aSSMBDLEDescFields6, pDevIns); - AssertRCReturn(rc, rc); - - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE.State, sizeof(HDABDLESTATE), - 0 /* fFlags */, g_aSSMBDLEStateFields6, NULL); - AssertRCReturn(rc, rc); - - Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", idStream, HDA_STREAM_REG(pThis, LPIB, idStream), - HDA_STREAM_REG(pThis, CBL, idStream), HDA_STREAM_REG(pThis, LVI, idStream))); -#ifdef LOG_ENABLED - hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); -#endif - } - - } /* for cStreams */ - break; - } /* default */ - } - - return rc; -} - -/** - * @callback_method_impl{FNSSMDEVLOADEXEC} - */ -static DECLCALLBACK(int) hdaR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - - Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); - - LogRel2(("hdaR3LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass)); - - /* - * Load Codec nodes states. - */ - int rc = hdaCodecLoadState(pDevIns, pThisCC->pCodec, pSSM, uVersion); - if (RT_FAILURE(rc)) - { - LogRel(("HDA: Failed loading codec state (version %RU32, pass 0x%x), rc=%Rrc\n", uVersion, uPass, rc)); - return rc; - } - - if (uVersion <= HDA_SAVED_STATE_VERSION_6) /* Handle older saved states? */ - { - rc = hdaR3LoadExecLegacy(pDevIns, pThis, pThisCC, pSSM, uVersion); - if (RT_SUCCESS(rc)) - rc = hdaR3LoadExecPost(pDevIns, pThis, pThisCC); - return rc; - } - - /* - * Load MMIO registers. - */ - uint32_t cRegs; - rc = pHlp->pfnSSMGetU32(pSSM, &cRegs); AssertRCReturn(rc, rc); - AssertRCReturn(rc, rc); - if (cRegs != RT_ELEMENTS(pThis->au32Regs)) - LogRel(("HDA: SSM version cRegs is %RU32, expected %RU32\n", cRegs, RT_ELEMENTS(pThis->au32Regs))); - - if (cRegs >= RT_ELEMENTS(pThis->au32Regs)) - { - pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); - rc = pHlp->pfnSSMSkip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs))); - AssertRCReturn(rc, rc); - } - else - { - rc = pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs); - AssertRCReturn(rc, rc); - } - - /* Make sure to update the base addresses first before initializing any streams down below. */ - pThis->u64CORBBase = RT_MAKE_U64(HDA_REG(pThis, CORBLBASE), HDA_REG(pThis, CORBUBASE)); - pThis->u64RIRBBase = RT_MAKE_U64(HDA_REG(pThis, RIRBLBASE), HDA_REG(pThis, RIRBUBASE)); - pThis->u64DPBase = RT_MAKE_U64(HDA_REG(pThis, DPLBASE) & DPBASE_ADDR_MASK, HDA_REG(pThis, DPUBASE)); - - /* Also make sure to update the DMA position bit if this was enabled when saving the state. */ - pThis->fDMAPosition = RT_BOOL(HDA_REG(pThis, DPLBASE) & RT_BIT_32(0)); - - /* - * Load controller-specifc internals. - * Don't annoy other team mates (forgot this for state v7). - */ - if ( pHlp->pfnSSMHandleRevision(pSSM) >= 116273 - || pHlp->pfnSSMHandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 2, 0)) - { - pHlp->pfnSSMGetU64(pSSM, &pThis->u64WalClk); - rc = pHlp->pfnSSMGetU8(pSSM, &pThis->u8IRQL); - AssertRCReturn(rc, rc); - } - - /* - * Load streams. - */ - uint32_t cStreams; - rc = pHlp->pfnSSMGetU32(pSSM, &cStreams); - AssertRCReturn(rc, rc); - if (cStreams > HDA_MAX_STREAMS) - return pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, - N_("State contains %u streams while %u is the maximum supported"), - cStreams, HDA_MAX_STREAMS); - Log2Func(("cStreams=%RU32\n", cStreams)); - - /* Load stream states. */ - for (uint32_t i = 0; i < cStreams; i++) - { - uint8_t idStream; - rc = pHlp->pfnSSMGetU8(pSSM, &idStream); - AssertRCReturn(rc, rc); - - /* Paranoia. */ - AssertLogRelMsgReturn(idStream < HDA_MAX_STREAMS, - ("HDA: Saved state contains bogus stream ID %RU8 for stream #%RU8", idStream, i), - VERR_SSM_INVALID_STATE); - - HDASTREAM StreamDummyShared; - HDASTREAMR3 StreamDummyR3; - PHDASTREAM pStreamShared = idStream < RT_ELEMENTS(pThis->aStreams) ? &pThis->aStreams[idStream] : &StreamDummyShared; - PHDASTREAMR3 pStreamR3 = idStream < RT_ELEMENTS(pThisCC->aStreams) ? &pThisCC->aStreams[idStream] : &StreamDummyR3; - AssertLogRelMsgStmt(idStream < RT_ELEMENTS(pThisCC->aStreams), - ("HDA stream ID=%RU8 not supported, skipping loadingit ...\n", idStream), - RT_ZERO(StreamDummyShared); RT_ZERO(StreamDummyR3)); - - rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, idStream); - if (RT_FAILURE(rc)) - { - LogRel(("HDA: Stream #%RU8: Setting up failed, rc=%Rrc\n", idStream, rc)); - /* Continue. */ - } - - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State, sizeof(HDASTREAMSTATE), - 0 /* fFlags */, g_aSSMStreamStateFields7, NULL); - AssertRCReturn(rc, rc); - - /* - * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. - */ - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE.Desc, sizeof(HDABDLEDESC), - 0 /* fFlags */, g_aSSMBDLEDescFields7, NULL); - AssertRCReturn(rc, rc); - - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE.State, sizeof(HDABDLESTATE), - 0 /* fFlags */, g_aSSMBDLEStateFields7, NULL); - AssertRCReturn(rc, rc); - - Log2Func(("[SD%RU8] %R[bdle]\n", pStreamShared->u8SD, &pStreamShared->State.BDLE)); - - /* - * Load period state. - */ - hdaR3StreamPeriodInit(&pStreamShared->State.Period, pStreamShared->u8SD, pStreamShared->u16LVI, - pStreamShared->u32CBL, &pStreamShared->State.Cfg); - - rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.Period, sizeof(pStreamShared->State.Period), - 0 /* fFlags */, g_aSSMStreamPeriodFields7, NULL); - AssertRCReturn(rc, rc); - - /* - * Load internal (FIFO) buffer. - */ - uint32_t cbCircBufSize = 0; - pHlp->pfnSSMGetU32(pSSM, &cbCircBufSize); /* cbCircBuf */ - uint32_t cbCircBufUsed = 0; - rc = pHlp->pfnSSMGetU32(pSSM, &cbCircBufUsed); /* cbCircBuf */ - AssertRCReturn(rc, rc); - - if (cbCircBufSize) /* If 0, skip the buffer. */ - { - /* Paranoia. */ - AssertLogRelMsgReturn(cbCircBufSize <= _1M, - ("HDA: Saved state contains bogus DMA buffer size (%RU32) for stream #%RU8", - cbCircBufSize, idStream), - VERR_SSM_DATA_UNIT_FORMAT_CHANGED); - AssertLogRelMsgReturn(cbCircBufUsed <= cbCircBufSize, - ("HDA: Saved state contains invalid DMA buffer usage (%RU32/%RU32) for stream #%RU8", - cbCircBufUsed, cbCircBufSize, idStream), - VERR_SSM_DATA_UNIT_FORMAT_CHANGED); - - /* Do we need to cre-create the circular buffer do fit the data size? */ - if ( pStreamR3->State.pCircBuf - && cbCircBufSize != (uint32_t)RTCircBufSize(pStreamR3->State.pCircBuf)) - { - RTCircBufDestroy(pStreamR3->State.pCircBuf); - pStreamR3->State.pCircBuf = NULL; - } - - rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, cbCircBufSize); - AssertRCReturn(rc, rc); - - if (cbCircBufUsed) - { - void *pvBuf; - size_t cbBuf; - RTCircBufAcquireWriteBlock(pStreamR3->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf); - - AssertLogRelMsgReturn(cbBuf == cbCircBufUsed, ("cbBuf=%zu cbCircBufUsed=%zu\n", cbBuf, cbCircBufUsed), - VERR_INTERNAL_ERROR_3); - rc = pHlp->pfnSSMGetMem(pSSM, pvBuf, cbBuf); - AssertRCReturn(rc, rc); - - RTCircBufReleaseWriteBlock(pStreamR3->State.pCircBuf, cbBuf); - - Assert(cbBuf == cbCircBufUsed); - } - } - - Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", idStream, HDA_STREAM_REG(pThis, LPIB, idStream), - HDA_STREAM_REG(pThis, CBL, idStream), HDA_STREAM_REG(pThis, LVI, idStream))); -#ifdef LOG_ENABLED - hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); -#endif - /** @todo (Re-)initialize active periods? */ - - } /* for cStreams */ - - rc = hdaR3LoadExecPost(pDevIns, pThis, pThisCC); - AssertRC(rc); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/********************************************************************************************************************************* -* IPRT format type handlers * -*********************************************************************************************************************************/ - -/** - * @callback_method_impl{FNRTSTRFORMATTYPE} - */ -static DECLCALLBACK(size_t) hdaR3StrFmtBDLE(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, - const char *pszType, void const *pvValue, - int cchWidth, int cchPrecision, unsigned fFlags, - void *pvUser) -{ - RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); - PHDABDLE pBDLE = (PHDABDLE)pvValue; - return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, - "BDLE(idx:%RU32, off:%RU32, fifow:%RU32, IOC:%RTbool, DMA[%RU32 bytes @ 0x%x])", - pBDLE->State.u32BDLIndex, pBDLE->State.u32BufOff, pBDLE->State.cbBelowFIFOW, - pBDLE->Desc.fFlags & HDA_BDLE_F_IOC, pBDLE->Desc.u32BufSize, pBDLE->Desc.u64BufAddr); -} - -/** - * @callback_method_impl{FNRTSTRFORMATTYPE} - */ -static DECLCALLBACK(size_t) hdaR3StrFmtSDCTL(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, - const char *pszType, void const *pvValue, - int cchWidth, int cchPrecision, unsigned fFlags, - void *pvUser) -{ - RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); - uint32_t uSDCTL = (uint32_t)(uintptr_t)pvValue; - return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, - "SDCTL(raw:%#x, DIR:%s, TP:%RTbool, STRIPE:%x, DEIE:%RTbool, FEIE:%RTbool, IOCE:%RTbool, RUN:%RTbool, RESET:%RTbool)", - uSDCTL, - uSDCTL & HDA_SDCTL_DIR ? "OUT" : "IN", - RT_BOOL(uSDCTL & HDA_SDCTL_TP), - (uSDCTL & HDA_SDCTL_STRIPE_MASK) >> HDA_SDCTL_STRIPE_SHIFT, - RT_BOOL(uSDCTL & HDA_SDCTL_DEIE), - RT_BOOL(uSDCTL & HDA_SDCTL_FEIE), - RT_BOOL(uSDCTL & HDA_SDCTL_IOCE), - RT_BOOL(uSDCTL & HDA_SDCTL_RUN), - RT_BOOL(uSDCTL & HDA_SDCTL_SRST)); -} - -/** - * @callback_method_impl{FNRTSTRFORMATTYPE} - */ -static DECLCALLBACK(size_t) hdaR3StrFmtSDFIFOS(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, - const char *pszType, void const *pvValue, - int cchWidth, int cchPrecision, unsigned fFlags, - void *pvUser) -{ - RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); - uint32_t uSDFIFOS = (uint32_t)(uintptr_t)pvValue; - return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "SDFIFOS(raw:%#x, sdfifos:%RU8 B)", uSDFIFOS, uSDFIFOS ? uSDFIFOS + 1 : 0); -} - -/** - * @callback_method_impl{FNRTSTRFORMATTYPE} - */ -static DECLCALLBACK(size_t) hdaR3StrFmtSDFIFOW(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, - const char *pszType, void const *pvValue, - int cchWidth, int cchPrecision, unsigned fFlags, - void *pvUser) -{ - RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); - uint32_t uSDFIFOW = (uint32_t)(uintptr_t)pvValue; - return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "SDFIFOW(raw: %#0x, sdfifow:%d B)", uSDFIFOW, hdaSDFIFOWToBytes(uSDFIFOW)); -} - -/** - * @callback_method_impl{FNRTSTRFORMATTYPE} - */ -static DECLCALLBACK(size_t) hdaR3StrFmtSDSTS(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, - const char *pszType, void const *pvValue, - int cchWidth, int cchPrecision, unsigned fFlags, - void *pvUser) -{ - RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); - uint32_t uSdSts = (uint32_t)(uintptr_t)pvValue; - return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, - "SDSTS(raw:%#0x, fifordy:%RTbool, dese:%RTbool, fifoe:%RTbool, bcis:%RTbool)", - uSdSts, - RT_BOOL(uSdSts & HDA_SDSTS_FIFORDY), - RT_BOOL(uSdSts & HDA_SDSTS_DESE), - RT_BOOL(uSdSts & HDA_SDSTS_FIFOE), - RT_BOOL(uSdSts & HDA_SDSTS_BCIS)); -} - - -/********************************************************************************************************************************* -* Debug Info Item Handlers * -*********************************************************************************************************************************/ - -static int hdaR3DbgLookupRegByName(const char *pszArgs) -{ - int iReg = 0; - for (; iReg < HDA_NUM_REGS; ++iReg) - if (!RTStrICmp(g_aHdaRegMap[iReg].abbrev, pszArgs)) - return iReg; - return -1; -} - - -static void hdaR3DbgPrintRegister(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iHdaIndex) -{ - Assert( pThis - && iHdaIndex >= 0 - && iHdaIndex < HDA_NUM_REGS); - pHlp->pfnPrintf(pHlp, "%s: 0x%x\n", g_aHdaRegMap[iHdaIndex].abbrev, pThis->au32Regs[g_aHdaRegMap[iHdaIndex].mem_idx]); -} - -/** - * @callback_method_impl{FNDBGFHANDLERDEV} - */ -static DECLCALLBACK(void) hdaR3DbgInfo(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - int iHdaRegisterIndex = hdaR3DbgLookupRegByName(pszArgs); - if (iHdaRegisterIndex != -1) - hdaR3DbgPrintRegister(pThis, pHlp, iHdaRegisterIndex); - else - { - for(iHdaRegisterIndex = 0; (unsigned int)iHdaRegisterIndex < HDA_NUM_REGS; ++iHdaRegisterIndex) - hdaR3DbgPrintRegister(pThis, pHlp, iHdaRegisterIndex); - } -} - -static void hdaR3DbgPrintStream(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iIdx) -{ - Assert( pThis - && iIdx >= 0 - && iIdx < HDA_MAX_STREAMS); - - const PHDASTREAM pStream = &pThis->aStreams[iIdx]; - - pHlp->pfnPrintf(pHlp, "Stream #%d:\n", iIdx); - pHlp->pfnPrintf(pHlp, "\tSD%dCTL : %R[sdctl]\n", iIdx, HDA_STREAM_REG(pThis, CTL, iIdx)); - pHlp->pfnPrintf(pHlp, "\tSD%dCTS : %R[sdsts]\n", iIdx, HDA_STREAM_REG(pThis, STS, iIdx)); - pHlp->pfnPrintf(pHlp, "\tSD%dFIFOS: %R[sdfifos]\n", iIdx, HDA_STREAM_REG(pThis, FIFOS, iIdx)); - pHlp->pfnPrintf(pHlp, "\tSD%dFIFOW: %R[sdfifow]\n", iIdx, HDA_STREAM_REG(pThis, FIFOW, iIdx)); - pHlp->pfnPrintf(pHlp, "\tBDLE : %R[bdle]\n", &pStream->State.BDLE); -} - -static void hdaR3DbgPrintBDLE(PPDMDEVINS pDevIns, PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iIdx) -{ - Assert( pThis - && iIdx >= 0 - && iIdx < HDA_MAX_STREAMS); - - const PHDASTREAM pStream = &pThis->aStreams[iIdx]; - const PHDABDLE pBDLE = &pStream->State.BDLE; - - pHlp->pfnPrintf(pHlp, "Stream #%d BDLE:\n", iIdx); - - uint64_t u64BaseDMA = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, iIdx), - HDA_STREAM_REG(pThis, BDPU, iIdx)); - uint16_t u16LVI = HDA_STREAM_REG(pThis, LVI, iIdx); - uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, iIdx); - - if (!u64BaseDMA) - return; - - pHlp->pfnPrintf(pHlp, "\tCurrent: %R[bdle]\n\n", pBDLE); - - pHlp->pfnPrintf(pHlp, "\tMemory:\n"); - - uint32_t cbBDLE = 0; - for (uint16_t i = 0; i < u16LVI + 1; i++) - { - HDABDLEDESC bd; - PDMDevHlpPhysRead(pDevIns, u64BaseDMA + i * sizeof(HDABDLEDESC), &bd, sizeof(bd)); - - pHlp->pfnPrintf(pHlp, "\t\t%s #%03d BDLE(adr:0x%llx, size:%RU32, ioc:%RTbool)\n", - pBDLE->State.u32BDLIndex == i ? "*" : " ", i, bd.u64BufAddr, bd.u32BufSize, bd.fFlags & HDA_BDLE_F_IOC); - - cbBDLE += bd.u32BufSize; - } - - pHlp->pfnPrintf(pHlp, "Total: %RU32 bytes\n", cbBDLE); - - if (cbBDLE != u32CBL) - pHlp->pfnPrintf(pHlp, "Warning: %RU32 bytes does not match CBL (%RU32)!\n", cbBDLE, u32CBL); - - pHlp->pfnPrintf(pHlp, "DMA counters (base @ 0x%llx):\n", u64BaseDMA); - if (!u64BaseDMA) /* No DMA base given? Bail out. */ - { - pHlp->pfnPrintf(pHlp, "\tNo counters found\n"); - return; - } - - for (int i = 0; i < u16LVI + 1; i++) - { - uint32_t uDMACnt; - PDMDevHlpPhysRead(pDevIns, (pThis->u64DPBase & DPBASE_ADDR_MASK) + (i * 2 * sizeof(uint32_t)), - &uDMACnt, sizeof(uDMACnt)); - - pHlp->pfnPrintf(pHlp, "\t#%03d DMA @ 0x%x\n", i , uDMACnt); - } -} - -static int hdaR3DbgLookupStrmIdx(PHDASTATE pThis, const char *pszArgs) -{ - RT_NOREF(pThis, pszArgs); - /** @todo Add args parsing. */ - return -1; -} - -/** - * @callback_method_impl{FNDBGFHANDLERDEV, hdastream} - */ -static DECLCALLBACK(void) hdaR3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - int iHdaStreamdex = hdaR3DbgLookupStrmIdx(pThis, pszArgs); - if (iHdaStreamdex != -1) - hdaR3DbgPrintStream(pThis, pHlp, iHdaStreamdex); - else - for(iHdaStreamdex = 0; iHdaStreamdex < HDA_MAX_STREAMS; ++iHdaStreamdex) - hdaR3DbgPrintStream(pThis, pHlp, iHdaStreamdex); -} - -/** - * @callback_method_impl{FNDBGFHANDLERDEV, hdabdle} - */ -static DECLCALLBACK(void) hdaR3DbgInfoBDLE(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - int iHdaStreamdex = hdaR3DbgLookupStrmIdx(pThis, pszArgs); - if (iHdaStreamdex != -1) - hdaR3DbgPrintBDLE(pDevIns, pThis, pHlp, iHdaStreamdex); - else - for (iHdaStreamdex = 0; iHdaStreamdex < HDA_MAX_STREAMS; ++iHdaStreamdex) - hdaR3DbgPrintBDLE(pDevIns, pThis, pHlp, iHdaStreamdex); -} - -/** - * @callback_method_impl{FNDBGFHANDLERDEV, hdcnodes} - */ -static DECLCALLBACK(void) hdaR3DbgInfoCodecNodes(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - - if (pThisCC->pCodec->pfnDbgListNodes) - pThisCC->pCodec->pfnDbgListNodes(pThisCC->pCodec, pHlp, pszArgs); - else - pHlp->pfnPrintf(pHlp, "Codec implementation doesn't provide corresponding callback\n"); -} - -/** - * @callback_method_impl{FNDBGFHANDLERDEV, hdcselector} - */ -static DECLCALLBACK(void) hdaR3DbgInfoCodecSelector(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - - if (pThisCC->pCodec->pfnDbgSelector) - pThisCC->pCodec->pfnDbgSelector(pThisCC->pCodec, pHlp, pszArgs); - else - pHlp->pfnPrintf(pHlp, "Codec implementation doesn't provide corresponding callback\n"); -} - -/** - * @callback_method_impl{FNDBGFHANDLERDEV, hdamixer} - */ -static DECLCALLBACK(void) hdaR3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - - if (pThisCC->pMixer) - AudioMixerDebug(pThisCC->pMixer, pHlp, pszArgs); - else - pHlp->pfnPrintf(pHlp, "Mixer not available\n"); -} - - -/********************************************************************************************************************************* -* PDMIBASE * -*********************************************************************************************************************************/ - -/** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) hdaR3QueryInterface(struct PDMIBASE *pInterface, const char *pszIID) -{ - PHDASTATER3 pThisCC = RT_FROM_MEMBER(pInterface, HDASTATER3, IBase); - - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); - return NULL; -} - - -/********************************************************************************************************************************* -* PDMDEVREGR3 * -*********************************************************************************************************************************/ - -/** - * Attach command, internal version. - * - * This is called to let the device attach to a driver for a specified LUN - * during runtime. This is not called during VM construction, the device - * constructor has to attach to all the available drivers. - * - * @returns VBox status code. - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - * @param uLUN The logical unit which is being detached. - * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. - * @param ppDrv Attached driver instance on success. Optional. - */ -static int hdaR3AttachInternal(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, unsigned uLUN, uint32_t fFlags, PHDADRIVER *ppDrv) -{ - RT_NOREF(fFlags); - - /* - * Attach driver. - */ - char *pszDesc; - if (RTStrAPrintf(&pszDesc, "Audio driver port (HDA) for LUN#%u", uLUN) <= 0) - AssertLogRelFailedReturn(VERR_NO_MEMORY); - - PPDMIBASE pDrvBase; - int rc = PDMDevHlpDriverAttach(pDevIns, uLUN, &pThisCC->IBase, &pDrvBase, pszDesc); - if (RT_SUCCESS(rc)) - { - PHDADRIVER pDrv = (PHDADRIVER)RTMemAllocZ(sizeof(HDADRIVER)); - if (pDrv) - { - pDrv->pDrvBase = pDrvBase; - pDrv->pHDAStateShared = pThis; - pDrv->pHDAStateR3 = pThisCC; - pDrv->uLUN = uLUN; - pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR); - AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN#%u has no host audio interface, rc=%Rrc\n", uLUN, rc)); - - /* - * For now we always set the driver at LUN 0 as our primary - * host backend. This might change in the future. - */ - if (pDrv->uLUN == 0) - pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY; - - LogFunc(("LUN#%u: pCon=%p, drvFlags=0x%x\n", uLUN, pDrv->pConnector, pDrv->fFlags)); - - /* Attach to driver list if not attached yet. */ - if (!pDrv->fAttached) - { - RTListAppend(&pThisCC->lstDrv, &pDrv->Node); - pDrv->fAttached = true; - } - - if (ppDrv) - *ppDrv = pDrv; - } - else - rc = VERR_NO_MEMORY; - } - else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) - LogFunc(("No attached driver for LUN #%u\n", uLUN)); - - if (RT_FAILURE(rc)) - { - /* Only free this string on failure; - * must remain valid for the live of the driver instance. */ - RTStrFree(pszDesc); - } - - LogFunc(("uLUN=%u, fFlags=0x%x, rc=%Rrc\n", uLUN, fFlags, rc)); - return rc; -} - -/** - * Detach command, internal version. - * - * This is called to let the device detach from a driver for a specified LUN - * during runtime. - * - * @returns VBox status code. - * @param pThisCC The ring-3 HDA device state. - * @param pDrv Driver to detach from device. - * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. - */ -static int hdaR3DetachInternal(PHDASTATER3 pThisCC, PHDADRIVER pDrv, uint32_t fFlags) -{ - RT_NOREF(fFlags); - - /* First, remove the driver from our list and destory it's associated streams. - * This also will un-set the driver as a recording source (if associated). */ - hdaR3MixerRemoveDrv(pThisCC, pDrv); - - /* Next, search backwards for a capable (attached) driver which now will be the - * new recording source. */ - PHDADRIVER pDrvCur; - RTListForEachReverse(&pThisCC->lstDrv, pDrvCur, HDADRIVER, Node) - { - if (!pDrvCur->pConnector) - continue; - - PDMAUDIOBACKENDCFG Cfg; - int rc2 = pDrvCur->pConnector->pfnGetConfig(pDrvCur->pConnector, &Cfg); - if (RT_FAILURE(rc2)) - continue; - - PHDADRIVERSTREAM pDrvStrm; -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - pDrvStrm = &pDrvCur->MicIn; - if ( pDrvStrm - && pDrvStrm->pMixStrm) - { - rc2 = AudioMixerSinkSetRecordingSource(pThisCC->SinkMicIn.pMixSink, pDrvStrm->pMixStrm); - if (RT_SUCCESS(rc2)) - LogRel2(("HDA: Set new recording source for 'Mic In' to '%s'\n", Cfg.szName)); - } -# endif - pDrvStrm = &pDrvCur->LineIn; - if ( pDrvStrm - && pDrvStrm->pMixStrm) - { - rc2 = AudioMixerSinkSetRecordingSource(pThisCC->SinkLineIn.pMixSink, pDrvStrm->pMixStrm); - if (RT_SUCCESS(rc2)) - LogRel2(("HDA: Set new recording source for 'Line In' to '%s'\n", Cfg.szName)); - } - } - - LogFunc(("uLUN=%u, fFlags=0x%x\n", pDrv->uLUN, fFlags)); - return VINF_SUCCESS; -} - -/** - * @interface_method_impl{PDMDEVREG,pfnAttach} - */ -static DECLCALLBACK(int) hdaR3Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - - DEVHDA_LOCK_RETURN(pDevIns, pThis, VERR_IGNORED); - - LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); - - PHDADRIVER pDrv; - int rc2 = hdaR3AttachInternal(pDevIns, pThis, pThisCC, uLUN, fFlags, &pDrv); - if (RT_SUCCESS(rc2)) - rc2 = hdaR3MixerAddDrv(pThisCC, pDrv); - - if (RT_FAILURE(rc2)) - LogFunc(("Failed with %Rrc\n", rc2)); - - DEVHDA_UNLOCK(pDevIns, pThis); - - return VINF_SUCCESS; -} - -/** - * @interface_method_impl{PDMDEVREG,pfnDetach} - */ -static DECLCALLBACK(void) hdaR3Detach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - - DEVHDA_LOCK(pDevIns, pThis); - - LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); - - PHDADRIVER pDrv, pDrvNext; - RTListForEachSafe(&pThisCC->lstDrv, pDrv, pDrvNext, HDADRIVER, Node) - { - if (pDrv->uLUN == uLUN) - { - int rc2 = hdaR3DetachInternal(pThisCC, pDrv, fFlags); - if (RT_SUCCESS(rc2)) - { - RTMemFree(pDrv); - pDrv = NULL; - } - - break; - } - } - - DEVHDA_UNLOCK(pDevIns, pThis); -} - -/** - * Powers off the device. - * - * @param pDevIns Device instance to power off. - */ -static DECLCALLBACK(void) hdaR3PowerOff(PPDMDEVINS pDevIns) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - - DEVHDA_LOCK_RETURN_VOID(pDevIns, pThis); - - LogRel2(("HDA: Powering off ...\n")); - - /* Ditto goes for the codec, which in turn uses the mixer. */ - hdaCodecPowerOff(pThisCC->pCodec); - - /* - * Note: Destroy the mixer while powering off and *not* in hdaR3Destruct, - * giving the mixer the chance to release any references held to - * PDM audio streams it maintains. - */ - if (pThisCC->pMixer) - { - AudioMixerDestroy(pThisCC->pMixer); - pThisCC->pMixer = NULL; - } - - DEVHDA_UNLOCK(pDevIns, pThis); -} - -/** - * Replaces a driver with a the NullAudio drivers. - * - * @returns VBox status code. - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - * @param pThis Device instance. - * @param iLun The logical unit which is being replaced. - */ -static int hdaR3ReconfigLunWithNullAudio(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, unsigned iLun) -{ - int rc = PDMDevHlpDriverReconfigure2(pDevIns, iLun, "AUDIO", "NullAudio"); - if (RT_SUCCESS(rc)) - rc = hdaR3AttachInternal(pDevIns, pThis, pThisCC, iLun, 0 /* fFlags */, NULL /* ppDrv */); - LogFunc(("pThis=%p, iLun=%u, rc=%Rrc\n", pThis, iLun, rc)); - return rc; -} - - -/** - * @interface_method_impl{PDMDEVREG,pfnReset} - */ -static DECLCALLBACK(void) hdaR3Reset(PPDMDEVINS pDevIns) -{ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - - LogFlowFuncEnter(); - - DEVHDA_LOCK_RETURN_VOID(pDevIns, pThis); - - /* - * 18.2.6,7 defines that values of this registers might be cleared on power on/reset - * hdaR3Reset shouldn't affects these registers. - */ - HDA_REG(pThis, WAKEEN) = 0x0; - - hdaR3GCTLReset(pDevIns, pThis, pThisCC); - - /* Indicate that HDA is not in reset. The firmware is supposed to (un)reset HDA, - * but we can take a shortcut. - */ - HDA_REG(pThis, GCTL) = HDA_GCTL_CRST; - - DEVHDA_UNLOCK(pDevIns, pThis); -} - - -/** - * @interface_method_impl{PDMDEVREG,pfnDestruct} - */ -static DECLCALLBACK(int) hdaR3Destruct(PPDMDEVINS pDevIns) -{ - PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - DEVHDA_LOCK(pDevIns, pThis); /** @todo r=bird: this will fail on early constructor failure. */ - - PHDADRIVER pDrv; - while (!RTListIsEmpty(&pThisCC->lstDrv)) - { - pDrv = RTListGetFirst(&pThisCC->lstDrv, HDADRIVER, Node); - - RTListNodeRemove(&pDrv->Node); - RTMemFree(pDrv); - } - - if (pThisCC->pCodec) - { - hdaCodecDestruct(pThisCC->pCodec); - - RTMemFree(pThisCC->pCodec); - pThisCC->pCodec = NULL; - } - - for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) - hdaR3StreamDestroy(&pThis->aStreams[i], &pThisCC->aStreams[i]); - - DEVHDA_UNLOCK(pDevIns, pThis); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMDEVREG,pfnConstruct} - */ -static DECLCALLBACK(int) hdaR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) -{ - PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - Assert(iInstance == 0); RT_NOREF(iInstance); - - /* - * Initialize the state sufficently to make the destructor work. - */ - pThis->uAlignmentCheckMagic = HDASTATE_ALIGNMENT_CHECK_MAGIC; - RTListInit(&pThisCC->lstDrv); - pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE; - pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE; - - /** @todo r=bird: There are probably other things which should be - * initialized here before we start failing. */ - - /* - * Validate and read configuration. - */ - PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "TimerHz|PosAdjustEnabled|PosAdjustFrames|DebugEnabled|DebugPathOut", ""); - - int rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, HDA_TIMER_HZ_DEFAULT /* Default value, if not set. */); - if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, rc, - N_("HDA configuration error: failed to read Hertz (Hz) rate as unsigned integer")); - - if (pThis->uTimerHz != HDA_TIMER_HZ_DEFAULT) - LogRel(("HDA: Using custom device timer rate (%RU16Hz)\n", pThis->uTimerHz)); - - rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "PosAdjustEnabled", &pThis->fPosAdjustEnabled, true); - if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, rc, - N_("HDA configuration error: failed to read position adjustment enabled as boolean")); - - if (!pThis->fPosAdjustEnabled) - LogRel(("HDA: Position adjustment is disabled\n")); - - rc = pHlp->pfnCFGMQueryU16Def(pCfg, "PosAdjustFrames", &pThis->cPosAdjustFrames, HDA_POS_ADJUST_DEFAULT); - if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, rc, - N_("HDA configuration error: failed to read position adjustment frames as unsigned integer")); - - if (pThis->cPosAdjustFrames) - LogRel(("HDA: Using custom position adjustment (%RU16 audio frames)\n", pThis->cPosAdjustFrames)); - - rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThisCC->Dbg.fEnabled, false); - if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, rc, - N_("HDA configuration error: failed to read debugging enabled flag as boolean")); - - rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThisCC->Dbg.pszOutPath, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH); - if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, rc, - N_("HDA configuration error: failed to read debugging output path flag as string")); - - if (pThisCC->Dbg.fEnabled) - LogRel2(("HDA: Debug output will be saved to '%s'\n", pThisCC->Dbg.pszOutPath)); - - /* - * Use our own critical section for the device instead of the default - * one provided by PDM. This allows fine-grained locking in combination - * with TM when timer-specific stuff is being called in e.g. the MMIO handlers. - */ - rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "HDA"); - AssertRCReturn(rc, rc); - - rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); - AssertRCReturn(rc, rc); - - /* - * Initialize data (most of it anyway). - */ - pThisCC->pDevIns = pDevIns; - /* IBase */ - pThisCC->IBase.pfnQueryInterface = hdaR3QueryInterface; - - /* PCI Device */ - PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; - PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev); - - PDMPciDevSetVendorId( pPciDev, HDA_PCI_VENDOR_ID); /* nVidia */ - PDMPciDevSetDeviceId( pPciDev, HDA_PCI_DEVICE_ID); /* HDA */ - - PDMPciDevSetCommand( pPciDev, 0x0000); /* 04 rw,ro - pcicmd. */ - PDMPciDevSetStatus( pPciDev, VBOX_PCI_STATUS_CAP_LIST); /* 06 rwc?,ro? - pcists. */ - PDMPciDevSetRevisionId( pPciDev, 0x01); /* 08 ro - rid. */ - PDMPciDevSetClassProg( pPciDev, 0x00); /* 09 ro - pi. */ - PDMPciDevSetClassSub( pPciDev, 0x03); /* 0a ro - scc; 03 == HDA. */ - PDMPciDevSetClassBase( pPciDev, 0x04); /* 0b ro - bcc; 04 == multimedia. */ - PDMPciDevSetHeaderType( pPciDev, 0x00); /* 0e ro - headtyp. */ - PDMPciDevSetBaseAddress( pPciDev, 0, /* 10 rw - MMIO */ - false /* fIoSpace */, false /* fPrefetchable */, true /* f64Bit */, 0x00000000); - PDMPciDevSetInterruptLine( pPciDev, 0x00); /* 3c rw. */ - PDMPciDevSetInterruptPin( pPciDev, 0x01); /* 3d ro - INTA#. */ - -#if defined(HDA_AS_PCI_EXPRESS) - PDMPciDevSetCapabilityList(pPciDev, 0x80); -#elif defined(VBOX_WITH_MSI_DEVICES) - PDMPciDevSetCapabilityList(pPciDev, 0x60); -#else - PDMPciDevSetCapabilityList(pPciDev, 0x50); /* ICH6 datasheet 18.1.16 */ -#endif - - /// @todo r=michaln: If there are really no PDMPciDevSetXx for these, the - /// meaning of these values needs to be properly documented! - /* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */ - PDMPciDevSetByte( pPciDev, 0x40, 0x01); - - /* Power Management */ - PDMPciDevSetByte( pPciDev, 0x50 + 0, VBOX_PCI_CAP_ID_PM); - PDMPciDevSetByte( pPciDev, 0x50 + 1, 0x0); /* next */ - PDMPciDevSetWord( pPciDev, 0x50 + 2, VBOX_PCI_PM_CAP_DSI | 0x02 /* version, PM1.1 */ ); - -#ifdef HDA_AS_PCI_EXPRESS - /* PCI Express */ - PDMPciDevSetByte( pPciDev, 0x80 + 0, VBOX_PCI_CAP_ID_EXP); /* PCI_Express */ - PDMPciDevSetByte( pPciDev, 0x80 + 1, 0x60); /* next */ - /* Device flags */ - PDMPciDevSetWord( pPciDev, 0x80 + 2, - 1 /* version */ - | (VBOX_PCI_EXP_TYPE_ROOT_INT_EP << 4) /* Root Complex Integrated Endpoint */ - | (100 << 9) /* MSI */ ); - /* Device capabilities */ - PDMPciDevSetDWord( pPciDev, 0x80 + 4, VBOX_PCI_EXP_DEVCAP_FLRESET); - /* Device control */ - PDMPciDevSetWord( pPciDev, 0x80 + 8, 0); - /* Device status */ - PDMPciDevSetWord( pPciDev, 0x80 + 10, 0); - /* Link caps */ - PDMPciDevSetDWord( pPciDev, 0x80 + 12, 0); - /* Link control */ - PDMPciDevSetWord( pPciDev, 0x80 + 16, 0); - /* Link status */ - PDMPciDevSetWord( pPciDev, 0x80 + 18, 0); - /* Slot capabilities */ - PDMPciDevSetDWord( pPciDev, 0x80 + 20, 0); - /* Slot control */ - PDMPciDevSetWord( pPciDev, 0x80 + 24, 0); - /* Slot status */ - PDMPciDevSetWord( pPciDev, 0x80 + 26, 0); - /* Root control */ - PDMPciDevSetWord( pPciDev, 0x80 + 28, 0); - /* Root capabilities */ - PDMPciDevSetWord( pPciDev, 0x80 + 30, 0); - /* Root status */ - PDMPciDevSetDWord( pPciDev, 0x80 + 32, 0); - /* Device capabilities 2 */ - PDMPciDevSetDWord( pPciDev, 0x80 + 36, 0); - /* Device control 2 */ - PDMPciDevSetQWord( pPciDev, 0x80 + 40, 0); - /* Link control 2 */ - PDMPciDevSetQWord( pPciDev, 0x80 + 48, 0); - /* Slot control 2 */ - PDMPciDevSetWord( pPciDev, 0x80 + 56, 0); -#endif - - /* - * Register the PCI device. - */ - rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); - AssertRCReturn(rc, rc); - - /** @todo r=bird: The IOMMMIO_FLAGS_READ_DWORD flag isn't entirely optimal, - * as several frequently used registers aren't dword sized. 6.0 and earlier - * will go to ring-3 to handle accesses to any such register, where-as 6.1 and - * later will do trivial register reads in ring-0. Real optimal code would use - * IOMMMIO_FLAGS_READ_PASSTHRU and do the necessary extra work to deal with - * anything the guest may throw at us. */ - rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, 0x4000, PCI_ADDRESS_SPACE_MEM, hdaMmioWrite, hdaMmioRead, NULL /*pvUser*/, - IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_PASSTHRU, "HDA", &pThis->hMmio); - AssertRCReturn(rc, rc); - -#ifdef VBOX_WITH_MSI_DEVICES - PDMMSIREG MsiReg; - RT_ZERO(MsiReg); - MsiReg.cMsiVectors = 1; - MsiReg.iMsiCapOffset = 0x60; - MsiReg.iMsiNextOffset = 0x50; - rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); - if (RT_FAILURE(rc)) - { - /* That's OK, we can work without MSI */ - PDMPciDevSetCapabilityList(pPciDev, 0x50); - } -#endif - - rc = PDMDevHlpSSMRegister(pDevIns, HDA_SAVED_STATE_VERSION, sizeof(*pThis), hdaR3SaveExec, hdaR3LoadExec); - AssertRCReturn(rc, rc); - -#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - LogRel(("HDA: Asynchronous I/O enabled\n")); -#endif - - /* - * Attach drivers. We ASSUME they are configured consecutively without any - * gaps, so we stop when we hit the first LUN w/o a driver configured. - */ - for (unsigned iLun = 0; ; iLun++) - { - AssertBreak(iLun < UINT8_MAX); - LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun)); - rc = hdaR3AttachInternal(pDevIns, pThis, pThisCC, iLun, 0 /* fFlags */, NULL /* ppDrv */); - if (rc == VERR_PDM_NO_ATTACHED_DRIVER) - { - LogFunc(("cLUNs=%u\n", iLun)); - break; - } - if (rc == VERR_AUDIO_BACKEND_INIT_FAILED) - { - hdaR3ReconfigLunWithNullAudio(pDevIns, pThis, pThisCC, iLun); /* Pretend attaching to the NULL audio backend will never fail. */ - PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", - N_("Host audio backend initialization has failed. Selecting the NULL audio backend with the consequence that no sound is audible")); - } - else - AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc); - } - - /* - * Create the mixer. - */ - rc = AudioMixerCreate("HDA Mixer", 0 /* uFlags */, &pThisCC->pMixer); - AssertRCReturn(rc, rc); - - /* - * Add mixer output sinks. - */ -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] Front", AUDMIXSINKDIR_OUTPUT, &pThisCC->SinkFront.pMixSink); - AssertRCReturn(rc, rc); - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] Center / Subwoofer", AUDMIXSINKDIR_OUTPUT, &pThisCC->SinkCenterLFE.pMixSink); - AssertRCReturn(rc, rc); - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] Rear", AUDMIXSINKDIR_OUTPUT, &pThisCC->SinkRear.pMixSink); - AssertRCReturn(rc, rc); -#else - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] PCM Output", AUDMIXSINKDIR_OUTPUT, &pThisCC->SinkFront.pMixSink); - AssertRCReturn(rc, rc); -#endif - - /* - * Add mixer input sinks. - */ - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Recording] Line In", AUDMIXSINKDIR_INPUT, &pThisCC->SinkLineIn.pMixSink); - AssertRCReturn(rc, rc); -#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Recording] Microphone In", AUDMIXSINKDIR_INPUT, &pThisCC->SinkMicIn.pMixSink); - AssertRCReturn(rc, rc); -#endif - - /* There is no master volume control. Set the master to max. */ - PDMAUDIOVOLUME vol = { false, 255, 255 }; - rc = AudioMixerSetMasterVolume(pThisCC->pMixer, &vol); - AssertRCReturn(rc, rc); - - /* Allocate codec. */ - PHDACODEC pCodec = (PHDACODEC)RTMemAllocZ(sizeof(HDACODEC)); - pThisCC->pCodec = pCodec; - AssertReturn(pCodec, VERR_NO_MEMORY); - - /* Set codec callbacks to this controller. */ - pCodec->pDevIns = pDevIns; - pCodec->pfnCbMixerAddStream = hdaR3MixerAddStream; - pCodec->pfnCbMixerRemoveStream = hdaR3MixerRemoveStream; - pCodec->pfnCbMixerControl = hdaR3MixerControl; - pCodec->pfnCbMixerSetVolume = hdaR3MixerSetVolume; - - /* Construct the codec. */ - rc = hdaCodecConstruct(pDevIns, pCodec, 0 /* Codec index */, pCfg); - AssertRCReturn(rc, rc); - - /* ICH6 datasheet defines 0 values for SVID and SID (18.1.14-15), which together with values returned for - verb F20 should provide device/codec recognition. */ - Assert(pCodec->u16VendorId); - Assert(pCodec->u16DeviceId); - PDMPciDevSetSubSystemVendorId(pPciDev, pCodec->u16VendorId); /* 2c ro - intel.) */ - PDMPciDevSetSubSystemId( pPciDev, pCodec->u16DeviceId); /* 2e ro. */ - - /* - * Create the per stream timers and the asso. - * - * We must the critical section for the timers as the device has a - * noop section associated with it. - * - * Note: Use TMCLOCK_VIRTUAL_SYNC here, as the guest's HDA driver relies - * on exact (virtual) DMA timing and uses DMA Position Buffers - * instead of the LPIB registers. - */ - static const char * const s_apszNames[] = - { - "HDA SD0", "HDA SD1", "HDA SD2", "HDA SD3", - "HDA SD4", "HDA SD5", "HDA SD6", "HDA SD7", - }; - AssertCompile(RT_ELEMENTS(s_apszNames) == HDA_MAX_STREAMS); - for (size_t i = 0; i < HDA_MAX_STREAMS; i++) - { - rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, hdaR3Timer, (void *)(uintptr_t)i, - TMTIMER_FLAGS_NO_CRIT_SECT, s_apszNames[i], &pThis->aStreams[i].hTimer); - AssertRCReturn(rc, rc); - - rc = PDMDevHlpTimerSetCritSect(pDevIns, pThis->aStreams[i].hTimer, &pThis->CritSect); - AssertRCReturn(rc, rc); - } - - /* - * Create all hardware streams. - */ - for (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i) - { - rc = hdaR3StreamConstruct(&pThis->aStreams[i], &pThisCC->aStreams[i], pThis, pThisCC, i /* u8SD */); - AssertRCReturn(rc, rc); - } - -#ifdef VBOX_WITH_AUDIO_HDA_ONETIME_INIT - /* - * Initialize the driver chain. - */ - PHDADRIVER pDrv; - RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node) - { - /* - * Only primary drivers are critical for the VM to run. Everything else - * might not worth showing an own error message box in the GUI. - */ - if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY)) - continue; - - PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector; - AssertPtr(pCon); - - bool fValidLineIn = AudioMixerStreamIsValid(pDrv->LineIn.pMixStrm); -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - bool fValidMicIn = AudioMixerStreamIsValid(pDrv->MicIn.pMixStrm); -# endif - bool fValidOut = AudioMixerStreamIsValid(pDrv->Front.pMixStrm); -# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - /** @todo Anything to do here? */ -# endif - - if ( !fValidLineIn -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - && !fValidMicIn -# endif - && !fValidOut) - { - LogRel(("HDA: Falling back to NULL backend (no sound audible)\n")); - hdaR3Reset(pDevIns); - hdaR3ReconfigLunWithNullAudio(pThis, pDrv->uLUN); - PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", - N_("No audio devices could be opened. " - "Selecting the NULL audio backend with the consequence that no sound is audible")); - } - else - { - bool fWarn = false; - - PDMAUDIOBACKENDCFG BackendCfg; - int rc2 = pCon->pfnGetConfig(pCon, &BackendCfg); - if (RT_SUCCESS(rc2)) - { - if (BackendCfg.cMaxStreamsIn) - { -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - /* If the audio backend supports two or more input streams at once, - * warn if one of our two inputs (microphone-in and line-in) failed to initialize. */ - if (BackendCfg.cMaxStreamsIn >= 2) - fWarn = !fValidLineIn || !fValidMicIn; - /* If the audio backend only supports one input stream at once (e.g. pure ALSA, and - * *not* ALSA via PulseAudio plugin!), only warn if both of our inputs failed to initialize. - * One of the two simply is not in use then. */ - else if (BackendCfg.cMaxStreamsIn == 1) - fWarn = !fValidLineIn && !fValidMicIn; - /* Don't warn if our backend is not able of supporting any input streams at all. */ -# else /* !VBOX_WITH_AUDIO_HDA_MIC_IN */ - /* We only have line-in as input source. */ - fWarn = !fValidLineIn; -# endif /* !VBOX_WITH_AUDIO_HDA_MIC_IN */ - } - - if ( !fWarn - && BackendCfg.cMaxStreamsOut) - fWarn = !fValidOut; - } - else - { - LogRel(("HDA: Unable to retrieve audio backend configuration for LUN #%RU8, rc=%Rrc\n", pDrv->uLUN, rc2)); - fWarn = true; - } - - if (fWarn) - { - char szMissingStreams[255]; - size_t len = 0; - if (!fValidLineIn) - { - LogRel(("HDA: WARNING: Unable to open PCM line input for LUN #%RU8!\n", pDrv->uLUN)); - len = RTStrPrintf(szMissingStreams, sizeof(szMissingStreams), "PCM Input"); - } -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - if (!fValidMicIn) - { - LogRel(("HDA: WARNING: Unable to open PCM microphone input for LUN #%RU8!\n", pDrv->uLUN)); - len += RTStrPrintf(szMissingStreams + len, - sizeof(szMissingStreams) - len, len ? ", PCM Microphone" : "PCM Microphone"); - } -# endif - if (!fValidOut) - { - LogRel(("HDA: WARNING: Unable to open PCM output for LUN #%RU8!\n", pDrv->uLUN)); - len += RTStrPrintf(szMissingStreams + len, - sizeof(szMissingStreams) - len, len ? ", PCM Output" : "PCM Output"); - } - - PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", - N_("Some HDA audio streams (%s) could not be opened. " - "Guest applications generating audio output or depending on audio input may hang. " - "Make sure your host audio device is working properly. " - "Check the logfile for error messages of the audio subsystem"), szMissingStreams); - } - } - } -#endif /* VBOX_WITH_AUDIO_HDA_ONETIME_INIT */ - - hdaR3Reset(pDevIns); - - /* - * Info items and string formatter types. The latter is non-optional as - * the info handles use (at least some of) the custom types and we cannot - * accept screwing formatting. - */ - PDMDevHlpDBGFInfoRegister(pDevIns, "hda", "HDA info. (hda [register case-insensitive])", hdaR3DbgInfo); - PDMDevHlpDBGFInfoRegister(pDevIns, "hdabdle", "HDA stream BDLE info. (hdabdle [stream number])", hdaR3DbgInfoBDLE); - PDMDevHlpDBGFInfoRegister(pDevIns, "hdastream", "HDA stream info. (hdastream [stream number])", hdaR3DbgInfoStream); - PDMDevHlpDBGFInfoRegister(pDevIns, "hdcnodes", "HDA codec nodes.", hdaR3DbgInfoCodecNodes); - PDMDevHlpDBGFInfoRegister(pDevIns, "hdcselector", "HDA codec's selector states [node number].", hdaR3DbgInfoCodecSelector); - PDMDevHlpDBGFInfoRegister(pDevIns, "hdamixer", "HDA mixer state.", hdaR3DbgInfoMixer); - - rc = RTStrFormatTypeRegister("bdle", hdaR3StrFmtBDLE, NULL); - AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); - rc = RTStrFormatTypeRegister("sdctl", hdaR3StrFmtSDCTL, NULL); - AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); - rc = RTStrFormatTypeRegister("sdsts", hdaR3StrFmtSDSTS, NULL); - AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); - rc = RTStrFormatTypeRegister("sdfifos", hdaR3StrFmtSDFIFOS, NULL); - AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); - rc = RTStrFormatTypeRegister("sdfifow", hdaR3StrFmtSDFIFOW, NULL); - AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); - - /* - * Asserting sanity. - */ - for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) - { - struct HDAREGDESC const *pReg = &g_aHdaRegMap[i]; - struct HDAREGDESC const *pNextReg = i + 1 < RT_ELEMENTS(g_aHdaRegMap) ? &g_aHdaRegMap[i + 1] : NULL; - - /* binary search order. */ - AssertReleaseMsg(!pNextReg || pReg->offset + pReg->size <= pNextReg->offset, - ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", - i, pReg->offset, pReg->size, i + 1, pNextReg->offset, pNextReg->size)); - - /* alignment. */ - AssertReleaseMsg( pReg->size == 1 - || (pReg->size == 2 && (pReg->offset & 1) == 0) - || (pReg->size == 3 && (pReg->offset & 3) == 0) - || (pReg->size == 4 && (pReg->offset & 3) == 0), - ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); - - /* registers are packed into dwords - with 3 exceptions with gaps at the end of the dword. */ - AssertRelease(((pReg->offset + pReg->size) & 3) == 0 || pNextReg); - if (pReg->offset & 3) - { - struct HDAREGDESC const *pPrevReg = i > 0 ? &g_aHdaRegMap[i - 1] : NULL; - AssertReleaseMsg(pPrevReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); - if (pPrevReg) - AssertReleaseMsg(pPrevReg->offset + pPrevReg->size == pReg->offset, - ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", - i - 1, pPrevReg->offset, pPrevReg->size, i + 1, pReg->offset, pReg->size)); - } -#if 0 - if ((pReg->offset + pReg->size) & 3) - { - AssertReleaseMsg(pNextReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); - if (pNextReg) - AssertReleaseMsg(pReg->offset + pReg->size == pNextReg->offset, - ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", - i, pReg->offset, pReg->size, i + 1, pNextReg->offset, pNextReg->size)); - } -#endif - /* The final entry is a full DWORD, no gaps! Allows shortcuts. */ - AssertReleaseMsg(pNextReg || ((pReg->offset + pReg->size) & 3) == 0, - ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); - } - -# ifdef VBOX_WITH_STATISTICS - /* - * Register statistics. - */ - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "Input", STAMUNIT_TICKS_PER_CALL, "Profiling input."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "Output", STAMUNIT_TICKS_PER_CALL, "Profiling output."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead" , STAMUNIT_BYTES, "Bytes read from HDA emulation."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "BytesWritten", STAMUNIT_BYTES, "Bytes written to HDA emulation."); - - AssertCompile(RT_ELEMENTS(g_aHdaRegMap) == HDA_NUM_REGS); - AssertCompile(RT_ELEMENTS(pThis->aStatRegReads) == HDA_NUM_REGS); - AssertCompile(RT_ELEMENTS(pThis->aStatRegReadsToR3) == HDA_NUM_REGS); - AssertCompile(RT_ELEMENTS(pThis->aStatRegWrites) == HDA_NUM_REGS); - AssertCompile(RT_ELEMENTS(pThis->aStatRegWritesToR3) == HDA_NUM_REGS); - for (size_t i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) - { - PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegReads[i], STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, - g_aHdaRegMap[i].desc, "Regs/%03x-%s-Reads", g_aHdaRegMap[i].offset, g_aHdaRegMap[i].abbrev); - PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegReadsToR3[i], STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, - g_aHdaRegMap[i].desc, "Regs/%03x-%s-Reads-ToR3", g_aHdaRegMap[i].offset, g_aHdaRegMap[i].abbrev); - PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegWrites[i], STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, - g_aHdaRegMap[i].desc, "Regs/%03x-%s-Writes", g_aHdaRegMap[i].offset, g_aHdaRegMap[i].abbrev); - PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegWritesToR3[i], STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, - g_aHdaRegMap[i].desc, "Regs/%03x-%s-Writes-ToR3", g_aHdaRegMap[i].offset, g_aHdaRegMap[i].abbrev); - } - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiReadsR3, STAMTYPE_COUNTER, "RegMultiReadsR3", STAMUNIT_OCCURENCES, "Register read not targeting just one register, handled in ring-3"); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiReadsRZ, STAMTYPE_COUNTER, "RegMultiReadsRZ", STAMUNIT_OCCURENCES, "Register read not targeting just one register, handled in ring-0"); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiWritesR3, STAMTYPE_COUNTER, "RegMultiWritesR3", STAMUNIT_OCCURENCES, "Register writes not targeting just one register, handled in ring-3"); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiWritesRZ, STAMTYPE_COUNTER, "RegMultiWritesRZ", STAMUNIT_OCCURENCES, "Register writes not targeting just one register, handled in ring-0"); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegSubWriteR3, STAMTYPE_COUNTER, "RegSubWritesR3", STAMUNIT_OCCURENCES, "Trucated register writes, handled in ring-3"); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegSubWriteRZ, STAMTYPE_COUNTER, "RegSubWritesRZ", STAMUNIT_OCCURENCES, "Trucated register writes, handled in ring-0"); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegUnknownReads, STAMTYPE_COUNTER, "RegUnknownReads", STAMUNIT_OCCURENCES, "Reads of unknown registers."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegUnknownWrites, STAMTYPE_COUNTER, "RegUnknownWrites", STAMUNIT_OCCURENCES, "Writes to unknown registers."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegWritesBlockedByReset, STAMTYPE_COUNTER, "RegWritesBlockedByReset", STAMUNIT_OCCURENCES, "Writes blocked by pending reset (GCTL/CRST)"); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegWritesBlockedByRun, STAMTYPE_COUNTER, "RegWritesBlockedByRun", STAMUNIT_OCCURENCES, "Writes blocked by byte RUN bit."); -# endif - - return VINF_SUCCESS; -} - -#else /* !IN_RING3 */ - -/** - * @callback_method_impl{PDMDEVREGR0,pfnConstruct} - */ -static DECLCALLBACK(int) hdaRZConstruct(PPDMDEVINS pDevIns) -{ - PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */ - PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); - - int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); - AssertRCReturn(rc, rc); - - rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, hdaMmioWrite, hdaMmioRead, NULL /*pvUser*/); - AssertRCReturn(rc, rc); - - return VINF_SUCCESS; -} - -#endif /* !IN_RING3 */ - -/** - * The device registration structure. - */ -const PDMDEVREG g_DeviceHDA = -{ - /* .u32Version = */ PDM_DEVREG_VERSION, - /* .uReserved0 = */ 0, - /* .szName = */ "hda", - /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, - /* .fClass = */ PDM_DEVREG_CLASS_AUDIO, - /* .cMaxInstances = */ 1, - /* .uSharedVersion = */ 42, - /* .cbInstanceShared = */ sizeof(HDASTATE), - /* .cbInstanceCC = */ CTX_EXPR(sizeof(HDASTATER3), 0, 0), - /* .cbInstanceRC = */ 0, - /* .cMaxPciDevices = */ 1, - /* .cMaxMsixVectors = */ 0, - /* .pszDescription = */ "Intel HD Audio Controller", -#if defined(IN_RING3) - /* .pszRCMod = */ "VBoxDDRC.rc", - /* .pszR0Mod = */ "VBoxDDR0.r0", - /* .pfnConstruct = */ hdaR3Construct, - /* .pfnDestruct = */ hdaR3Destruct, - /* .pfnRelocate = */ NULL, - /* .pfnMemSetup = */ NULL, - /* .pfnPowerOn = */ NULL, - /* .pfnReset = */ hdaR3Reset, - /* .pfnSuspend = */ NULL, - /* .pfnResume = */ NULL, - /* .pfnAttach = */ hdaR3Attach, - /* .pfnDetach = */ hdaR3Detach, - /* .pfnQueryInterface = */ NULL, - /* .pfnInitComplete = */ NULL, - /* .pfnPowerOff = */ hdaR3PowerOff, - /* .pfnSoftReset = */ NULL, - /* .pfnReserved0 = */ NULL, - /* .pfnReserved1 = */ NULL, - /* .pfnReserved2 = */ NULL, - /* .pfnReserved3 = */ NULL, - /* .pfnReserved4 = */ NULL, - /* .pfnReserved5 = */ NULL, - /* .pfnReserved6 = */ NULL, - /* .pfnReserved7 = */ NULL, -#elif defined(IN_RING0) - /* .pfnEarlyConstruct = */ NULL, - /* .pfnConstruct = */ hdaRZConstruct, - /* .pfnDestruct = */ NULL, - /* .pfnFinalDestruct = */ NULL, - /* .pfnRequest = */ NULL, - /* .pfnReserved0 = */ NULL, - /* .pfnReserved1 = */ NULL, - /* .pfnReserved2 = */ NULL, - /* .pfnReserved3 = */ NULL, - /* .pfnReserved4 = */ NULL, - /* .pfnReserved5 = */ NULL, - /* .pfnReserved6 = */ NULL, - /* .pfnReserved7 = */ NULL, -#elif defined(IN_RC) - /* .pfnConstruct = */ hdaRZConstruct, - /* .pfnReserved0 = */ NULL, - /* .pfnReserved1 = */ NULL, - /* .pfnReserved2 = */ NULL, - /* .pfnReserved3 = */ NULL, - /* .pfnReserved4 = */ NULL, - /* .pfnReserved5 = */ NULL, - /* .pfnReserved6 = */ NULL, - /* .pfnReserved7 = */ NULL, -#else -# error "Not in IN_RING3, IN_RING0 or IN_RC!" -#endif - /* .u32VersionEnd = */ PDM_DEVREG_VERSION -}; - -#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHda.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHda.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHda.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHda.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,908 @@ +/* $Id: DevHda.h $ */ +/** @file + * Intel HD Audio Controller Emulation - Structures. + */ + +/* + * Copyright (C) 2016-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DevHda_h +#define VBOX_INCLUDED_SRC_Audio_DevHda_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include "AudioMixer.h" + +/* + * Compile time feature configuration. + */ + +/** @def VBOX_HDA_WITH_ON_REG_ACCESS_DMA + * Enables doing DMA work on certain register accesses (LPIB, WALCLK) in + * addition to the DMA timer. All but the last frame can be done during + * register accesses (as we don't wish to leave the DMA timer w/o work to + * do in case that upsets it). */ +#if defined(DOXYGEN_RUNNING) || 0 +# define VBOX_HDA_WITH_ON_REG_ACCESS_DMA +#endif + +#ifdef DEBUG_andy +/** Enables strict mode, which checks for stuff which isn't supposed to happen. + * Be prepared for assertions coming in! */ +//# define HDA_STRICT +#endif + +/** @def HDA_AS_PCI_EXPRESS + * Enables PCI express hardware. */ +#if defined(DOXYGEN_RUNNING) || 0 +# define HDA_AS_PCI_EXPRESS +#endif + +/** @def HDA_DEBUG_SILENCE + * To debug silence coming from the guest in form of audio gaps. + * Very crude implementation for now. + * @todo probably borked atm */ +#if defined(DOXYGEN_RUNNING) || 0 +# define HDA_DEBUG_SILENCE +#endif + + +/* + * Common pointer types. + */ +/** Pointer to an HDA stream (SDI / SDO). */ +typedef struct HDASTREAMR3 *PHDASTREAMR3; +/** Pointer to a shared HDA device state. */ +typedef struct HDASTATE *PHDASTATE; +/** Pointer to a ring-3 HDA device state. */ +typedef struct HDASTATER3 *PHDASTATER3; +/** Pointer to an HDA mixer sink definition (ring-3). */ +typedef struct HDAMIXERSINK *PHDAMIXERSINK; + + +/* + * The rest of the headers. + */ +#include "DevHdaStream.h" +#include "DevHdaCodec.h" + + + +/** @name Stream counts. + * + * At the moment we support 4 input + 4 output streams max, which is 8 in total. + * Bidirectional streams are currently *not* supported. + * + * @note When changing any of those values, be prepared for some saved state + * fixups / trouble! + * @{ + */ +#define HDA_MAX_SDI 4 +#define HDA_MAX_SDO 4 +#define HDA_MAX_STREAMS (HDA_MAX_SDI + HDA_MAX_SDO) +/** @} */ +AssertCompile(HDA_MAX_SDI <= HDA_MAX_SDO); + + +/** @defgroup grp_hda_regs HDA Register Definitions + * + * There are two variants for most register defines: + * - HDA_REG_XXX: Index into g_aHdaRegMap + * - HDA_RMX_XXX: Index into HDASTATE::au32Regs + * + * Use the HDA_REG and HDA_STREAM_REG macros to access registers where possible. + * + * @note The au32Regs[] layout is kept unchanged for saved state compatibility, + * thus the HDA_RMX_XXX assignments are for all purposes set in stone. + * + * @{ */ + +/** Number of general registers. */ +#define HDA_NUM_GENERAL_REGS 36 +/** Number of stream registers (10 registers per stream). */ +#define HDA_NUM_STREAM_REGS (HDA_MAX_STREAMS * 10 /* Each stream descriptor has 10 registers */) +/** Number of register after the stream registers. */ +#define HDA_NUM_POST_STREAM_REGS (2 + HDA_MAX_STREAMS * 2) +/** Number of total registers in the HDA's register map. */ +#define HDA_NUM_REGS (HDA_NUM_GENERAL_REGS + HDA_NUM_STREAM_REGS + HDA_NUM_POST_STREAM_REGS) +/** Total number of stream tags (channels). Index 0 is reserved / invalid. */ +#define HDA_MAX_TAGS 16 + + +/** Offset of the SD0 register map. */ +#define HDA_REG_DESC_SD0_BASE 0x80 + +/* Registers */ +#define HDA_REG_IND_NAME(x) HDA_REG_##x +#define HDA_MEM_IND_NAME(x) HDA_RMX_##x + +/** Direct register access by HDASTATE::au32Reg index. */ +#define HDA_REG_BY_IDX(a_pThis, a_idxReg) ((a_pThis)->au32Regs[(a_idxReg)]) + +/** Accesses register @a ShortRegNm. */ +#if defined(VBOX_STRICT) && defined(VBOX_HDA_CAN_ACCESS_REG_MAP) +# define HDA_REG(a_pThis, a_ShortRegNm) (*hdaStrictRegAccessor(a_pThis, HDA_REG_IND_NAME(a_ShortRegNm), HDA_MEM_IND_NAME(a_ShortRegNm))) +#else +# define HDA_REG(a_pThis, a_ShortRegNm) HDA_REG_BY_IDX(a_pThis, HDA_MEM_IND_NAME(a_ShortRegNm)) +#endif + +/** Indirect register access via g_aHdaRegMap[].idxReg. */ +#define HDA_REG_IND(a_pThis, a_idxMap) HDA_REG_BY_IDX(a_pThis, g_aHdaRegMap[(a_idxMap)].idxReg) + + +#define HDA_REG_GCAP 0 /* Range 0x00 - 0x01 */ +#define HDA_RMX_GCAP 0 +/** + * GCAP HDASpec 3.3.2 This macro encodes the following information about HDA in a compact manner: + * + * oss (15:12) - Number of output streams supported. + * iss (11:8) - Number of input streams supported. + * bss (7:3) - Number of bidirectional streams supported. + * bds (2:1) - Number of serial data out (SDO) signals supported. + * b64sup (0) - 64 bit addressing supported. + */ +#define HDA_MAKE_GCAP(oss, iss, bss, bds, b64sup) \ + ( (((oss) & 0xF) << 12) \ + | (((iss) & 0xF) << 8) \ + | (((bss) & 0x1F) << 3) \ + | (((bds) & 0x3) << 2) \ + | ((b64sup) & 1)) + +#define HDA_REG_VMIN 1 /* 0x02 */ +#define HDA_RMX_VMIN 1 + +#define HDA_REG_VMAJ 2 /* 0x03 */ +#define HDA_RMX_VMAJ 2 + +#define HDA_REG_OUTPAY 3 /* 0x04-0x05 */ +#define HDA_RMX_OUTPAY 3 + +#define HDA_REG_INPAY 4 /* 0x06-0x07 */ +#define HDA_RMX_INPAY 4 + +#define HDA_REG_GCTL 5 /* 0x08-0x0B */ +#define HDA_RMX_GCTL 5 +#define HDA_GCTL_UNSOL RT_BIT(8) /* Accept Unsolicited Response Enable */ +#define HDA_GCTL_FCNTRL RT_BIT(1) /* Flush Control */ +#define HDA_GCTL_CRST RT_BIT(0) /* Controller Reset */ + +#define HDA_REG_WAKEEN 6 /* 0x0C */ +#define HDA_RMX_WAKEEN 6 + +#define HDA_REG_STATESTS 7 /* 0x0E */ +#define HDA_RMX_STATESTS 7 +#define HDA_STATESTS_SCSF_MASK 0x7 /* State Change Status Flags (6.2.8). */ + +#define HDA_REG_GSTS 8 /* 0x10-0x11*/ +#define HDA_RMX_GSTS 8 +#define HDA_GSTS_FSTS RT_BIT(1) /* Flush Status */ + +#define HDA_REG_LLCH 9 /* 0x14 */ +#define HDA_RMX_LLCH 114 + +#define HDA_REG_OUTSTRMPAY 10 /* 0x18 */ +#define HDA_RMX_OUTSTRMPAY 112 + +#define HDA_REG_INSTRMPAY 11 /* 0x1a */ +#define HDA_RMX_INSTRMPAY 113 + +#define HDA_REG_INTCTL 12 /* 0x20 */ +#define HDA_RMX_INTCTL 9 +#define HDA_INTCTL_GIE RT_BIT(31) /* Global Interrupt Enable */ +#define HDA_INTCTL_CIE RT_BIT(30) /* Controller Interrupt Enable */ +/** Bits 0-29 correspond to streams 0-29. */ +#define HDA_STRMINT_MASK 0xFF /* Streams 0-7 implemented. Applies to INTCTL and INTSTS. */ + +#define HDA_REG_INTSTS 13 /* 0x24 */ +#define HDA_RMX_INTSTS 10 +#define HDA_INTSTS_GIS RT_BIT(31) /* Global Interrupt Status */ +#define HDA_INTSTS_CIS RT_BIT(30) /* Controller Interrupt Status */ + +#define HDA_REG_WALCLK 14 /* 0x30 */ +/* NB: HDA_RMX_WALCLK is not defined because the register is not stored in memory. */ + +/** + * @note The HDA specification defines a SSYNC register at offset 0x38. The ICH6/ICH9 + * datahseet defines SSYNC at offset 0x34. The Linux HDA driver matches the datasheet. + * See also https://mailman.alsa-project.org/pipermail/alsa-devel/2011-March/037819.html + */ +#define HDA_REG_SSYNC 15 /* 0x34 */ +#define HDA_RMX_SSYNC 12 + +#define HDA_REG_NEW_SSYNC 16 /* 0x38 */ +#define HDA_RMX_NEW_SSYNC HDA_RMX_SSYNC + +#define HDA_REG_CORBLBASE 17 /* 0x40 */ +#define HDA_RMX_CORBLBASE 13 + +#define HDA_REG_CORBUBASE 18 /* 0x44 */ +#define HDA_RMX_CORBUBASE 14 + +#define HDA_REG_CORBWP 19 /* 0x48 */ +#define HDA_RMX_CORBWP 15 + +#define HDA_REG_CORBRP 20 /* 0x4A */ +#define HDA_RMX_CORBRP 16 +#define HDA_CORBRP_RST RT_BIT(15) /* CORB Read Pointer Reset */ + +#define HDA_REG_CORBCTL 21 /* 0x4C */ +#define HDA_RMX_CORBCTL 17 +#define HDA_CORBCTL_DMA RT_BIT(1) /* Enable CORB DMA Engine */ +#define HDA_CORBCTL_CMEIE RT_BIT(0) /* CORB Memory Error Interrupt Enable */ + +#define HDA_REG_CORBSTS 22 /* 0x4D */ +#define HDA_RMX_CORBSTS 18 + +#define HDA_REG_CORBSIZE 23 /* 0x4E */ +#define HDA_RMX_CORBSIZE 19 +#define HDA_CORBSIZE_SZ_CAP 0xF0 +#define HDA_CORBSIZE_SZ 0x3 + +/** Number of CORB buffer entries. */ +#define HDA_CORB_SIZE 256 +/** CORB element size (in bytes). */ +#define HDA_CORB_ELEMENT_SIZE 4 +/** Number of RIRB buffer entries. */ +#define HDA_RIRB_SIZE 256 +/** RIRB element size (in bytes). */ +#define HDA_RIRB_ELEMENT_SIZE 8 + +#define HDA_REG_RIRBLBASE 24 /* 0x50 */ +#define HDA_RMX_RIRBLBASE 20 + +#define HDA_REG_RIRBUBASE 25 /* 0x54 */ +#define HDA_RMX_RIRBUBASE 21 + +#define HDA_REG_RIRBWP 26 /* 0x58 */ +#define HDA_RMX_RIRBWP 22 +#define HDA_RIRBWP_RST RT_BIT(15) /* RIRB Write Pointer Reset */ + +#define HDA_REG_RINTCNT 27 /* 0x5A */ +#define HDA_RMX_RINTCNT 23 + +/** Maximum number of Response Interrupts. */ +#define HDA_MAX_RINTCNT 256 + +#define HDA_REG_RIRBCTL 28 /* 0x5C */ +#define HDA_RMX_RIRBCTL 24 +#define HDA_RIRBCTL_ROIC RT_BIT(2) /* Response Overrun Interrupt Control */ +#define HDA_RIRBCTL_RDMAEN RT_BIT(1) /* RIRB DMA Enable */ +#define HDA_RIRBCTL_RINTCTL RT_BIT(0) /* Response Interrupt Control */ + +#define HDA_REG_RIRBSTS 29 /* 0x5D */ +#define HDA_RMX_RIRBSTS 25 +#define HDA_RIRBSTS_RIRBOIS RT_BIT(2) /* Response Overrun Interrupt Status */ +#define HDA_RIRBSTS_RINTFL RT_BIT(0) /* Response Interrupt Flag */ + +#define HDA_REG_RIRBSIZE 30 /* 0x5E */ +#define HDA_RMX_RIRBSIZE 26 + +#define HDA_REG_IC 31 /* 0x60 */ +#define HDA_RMX_IC 27 + +#define HDA_REG_IR 32 /* 0x64 */ +#define HDA_RMX_IR 28 + +#define HDA_REG_IRS 33 /* 0x68 */ +#define HDA_RMX_IRS 29 +#define HDA_IRS_IRV RT_BIT(1) /* Immediate Result Valid */ +#define HDA_IRS_ICB RT_BIT(0) /* Immediate Command Busy */ + +#define HDA_REG_DPLBASE 34 /* 0x70 */ +#define HDA_RMX_DPLBASE 30 + +#define HDA_REG_DPUBASE 35 /* 0x74 */ +#define HDA_RMX_DPUBASE 31 + +#define DPBASE_ADDR_MASK (~(uint64_t)0x7f) + +#define HDA_STREAM_REG_DEF(name, num) (HDA_REG_SD##num##name) +#define HDA_STREAM_RMX_DEF(name, num) (HDA_RMX_SD##num##name) +/** @note sdnum here _MUST_ be stream reg number [0,7]. */ +#if defined(VBOX_STRICT) && defined(VBOX_HDA_CAN_ACCESS_REG_MAP) +# define HDA_STREAM_REG(pThis, name, sdnum) (*hdaStrictStreamRegAccessor((pThis), HDA_REG_SD0##name, HDA_RMX_SD0##name, (sdnum))) +#else +# define HDA_STREAM_REG(pThis, name, sdnum) (HDA_REG_BY_IDX((pThis), HDA_RMX_SD0##name + (sdnum) * 10)) +#endif + +#define HDA_SD_NUM_FROM_REG(pThis, func, reg) ((reg - HDA_STREAM_REG_DEF(func, 0)) / 10) +#define HDA_SD_TO_REG(a_Name, uSD) (HDA_STREAM_REG_DEF(a_Name, 0) + (uSD) * 10) + +/** @todo Condense marcos! */ + +#define HDA_REG_SD0CTL HDA_NUM_GENERAL_REGS /* 0x80; other streams offset by 0x20 */ +#define HDA_RMX_SD0CTL 32 +#define HDA_RMX_SD1CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 10) +#define HDA_RMX_SD2CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 20) +#define HDA_RMX_SD3CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 30) +#define HDA_RMX_SD4CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 40) +#define HDA_RMX_SD5CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 50) +#define HDA_RMX_SD6CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 60) +#define HDA_RMX_SD7CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 70) + +#define HDA_SDCTL_NUM_MASK 0xF +#define HDA_SDCTL_NUM_SHIFT 20 +#define HDA_SDCTL_DIR RT_BIT(19) /* Direction (Bidirectional streams only!) */ +#define HDA_SDCTL_TP RT_BIT(18) /* Traffic Priority (PCI Express) */ +#define HDA_SDCTL_STRIPE_MASK 0x3 +#define HDA_SDCTL_STRIPE_SHIFT 16 +#define HDA_SDCTL_DEIE RT_BIT(4) /* Descriptor Error Interrupt Enable */ +#define HDA_SDCTL_FEIE RT_BIT(3) /* FIFO Error Interrupt Enable */ +#define HDA_SDCTL_IOCE RT_BIT(2) /* Interrupt On Completion Enable */ +#define HDA_SDCTL_RUN RT_BIT(1) /* Stream Run */ +#define HDA_SDCTL_SRST RT_BIT(0) /* Stream Reset */ + +#define HDA_REG_SD0STS (HDA_NUM_GENERAL_REGS + 1) /* 0x83; other streams offset by 0x20 */ +#define HDA_RMX_SD0STS 33 +#define HDA_RMX_SD1STS (HDA_STREAM_RMX_DEF(STS, 0) + 10) +#define HDA_RMX_SD2STS (HDA_STREAM_RMX_DEF(STS, 0) + 20) +#define HDA_RMX_SD3STS (HDA_STREAM_RMX_DEF(STS, 0) + 30) +#define HDA_RMX_SD4STS (HDA_STREAM_RMX_DEF(STS, 0) + 40) +#define HDA_RMX_SD5STS (HDA_STREAM_RMX_DEF(STS, 0) + 50) +#define HDA_RMX_SD6STS (HDA_STREAM_RMX_DEF(STS, 0) + 60) +#define HDA_RMX_SD7STS (HDA_STREAM_RMX_DEF(STS, 0) + 70) + +#define HDA_SDSTS_FIFORDY RT_BIT(5) /* FIFO Ready */ +#define HDA_SDSTS_DESE RT_BIT(4) /* Descriptor Error */ +#define HDA_SDSTS_FIFOE RT_BIT(3) /* FIFO Error */ +#define HDA_SDSTS_BCIS RT_BIT(2) /* Buffer Completion Interrupt Status */ + +#define HDA_REG_SD0LPIB (HDA_NUM_GENERAL_REGS + 2) /* 0x84; other streams offset by 0x20 */ +#define HDA_REG_SD1LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 10) /* 0xA4 */ +#define HDA_REG_SD2LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 20) /* 0xC4 */ +#define HDA_REG_SD3LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 30) /* 0xE4 */ +#define HDA_REG_SD4LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 40) /* 0x104 */ +#define HDA_REG_SD5LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 50) /* 0x124 */ +#define HDA_REG_SD6LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 60) /* 0x144 */ +#define HDA_REG_SD7LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 70) /* 0x164 */ +#define HDA_RMX_SD0LPIB 34 +#define HDA_RMX_SD1LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 10) +#define HDA_RMX_SD2LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 20) +#define HDA_RMX_SD3LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 30) +#define HDA_RMX_SD4LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 40) +#define HDA_RMX_SD5LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 50) +#define HDA_RMX_SD6LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 60) +#define HDA_RMX_SD7LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 70) + +#define HDA_REG_SD0CBL (HDA_NUM_GENERAL_REGS + 3) /* 0x88; other streams offset by 0x20 */ +#define HDA_RMX_SD0CBL 35 +#define HDA_RMX_SD1CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 10) +#define HDA_RMX_SD2CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 20) +#define HDA_RMX_SD3CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 30) +#define HDA_RMX_SD4CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 40) +#define HDA_RMX_SD5CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 50) +#define HDA_RMX_SD6CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 60) +#define HDA_RMX_SD7CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 70) + +#define HDA_REG_SD0LVI (HDA_NUM_GENERAL_REGS + 4) /* 0x8C; other streams offset by 0x20 */ +#define HDA_RMX_SD0LVI 36 +#define HDA_RMX_SD1LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 10) +#define HDA_RMX_SD2LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 20) +#define HDA_RMX_SD3LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 30) +#define HDA_RMX_SD4LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 40) +#define HDA_RMX_SD5LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 50) +#define HDA_RMX_SD6LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 60) +#define HDA_RMX_SD7LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 70) + +#define HDA_REG_SD0FIFOW (HDA_NUM_GENERAL_REGS + 5) /* 0x8E; other streams offset by 0x20 */ +#define HDA_RMX_SD0FIFOW 37 +#define HDA_RMX_SD1FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 10) +#define HDA_RMX_SD2FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 20) +#define HDA_RMX_SD3FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 30) +#define HDA_RMX_SD4FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 40) +#define HDA_RMX_SD5FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 50) +#define HDA_RMX_SD6FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 60) +#define HDA_RMX_SD7FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 70) + +/* + * ICH6 datasheet defined limits for FIFOW values (18.2.38). + */ +#define HDA_SDFIFOW_8B 0x2 +#define HDA_SDFIFOW_16B 0x3 +#define HDA_SDFIFOW_32B 0x4 + +#define HDA_REG_SD0FIFOS (HDA_NUM_GENERAL_REGS + 6) /* 0x90; other streams offset by 0x20 */ +#define HDA_RMX_SD0FIFOS 38 +#define HDA_RMX_SD1FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 10) +#define HDA_RMX_SD2FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 20) +#define HDA_RMX_SD3FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 30) +#define HDA_RMX_SD4FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 40) +#define HDA_RMX_SD5FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 50) +#define HDA_RMX_SD6FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 60) +#define HDA_RMX_SD7FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 70) + +/* The ICH6 datasheet defines limits for FIFOS registers (18.2.39). + Formula: size - 1 + Other values not listed are not supported. */ + +#define HDA_SDIFIFO_120B 0x77 /* 8-, 16-, 20-, 24-, 32-bit Input Streams */ +#define HDA_SDIFIFO_160B 0x9F /* 20-, 24-bit Input Streams Streams */ + +#define HDA_SDOFIFO_16B 0x0F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_32B 0x1F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_64B 0x3F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_128B 0x7F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_192B 0xBF /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_256B 0xFF /* 20-, 24-bit Output Streams */ + +#define HDA_REG_SD0FMT (HDA_NUM_GENERAL_REGS + 7) /* 0x92; other streams offset by 0x20 */ +#define HDA_RMX_SD0FMT 39 +#define HDA_RMX_SD1FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 10) +#define HDA_RMX_SD2FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 20) +#define HDA_RMX_SD3FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 30) +#define HDA_RMX_SD4FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 40) +#define HDA_RMX_SD5FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 50) +#define HDA_RMX_SD6FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 60) +#define HDA_RMX_SD7FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 70) + +#define HDA_REG_SD0BDPL (HDA_NUM_GENERAL_REGS + 8) /* 0x98; other streams offset by 0x20 */ +#define HDA_RMX_SD0BDPL 40 +#define HDA_RMX_SD1BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 10) +#define HDA_RMX_SD2BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 20) +#define HDA_RMX_SD3BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 30) +#define HDA_RMX_SD4BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 40) +#define HDA_RMX_SD5BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 50) +#define HDA_RMX_SD6BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 60) +#define HDA_RMX_SD7BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 70) + +#define HDA_REG_SD0BDPU (HDA_NUM_GENERAL_REGS + 9) /* 0x9C; other streams offset by 0x20 */ +#define HDA_RMX_SD0BDPU 41 +#define HDA_RMX_SD1BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 10) +#define HDA_RMX_SD2BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 20) +#define HDA_RMX_SD3BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 30) +#define HDA_RMX_SD4BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 40) +#define HDA_RMX_SD5BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 50) +#define HDA_RMX_SD6BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 60) +#define HDA_RMX_SD7BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 70) + +#define HDA_CODEC_CAD_SHIFT 28 +/** Encodes the (required) LUN into a codec command. */ +#define HDA_CODEC_CMD(cmd, lun) ((cmd) | (lun << HDA_CODEC_CAD_SHIFT)) + +#define HDA_SDFMT_NON_PCM_SHIFT 15 +#define HDA_SDFMT_NON_PCM_MASK 0x1 +#define HDA_SDFMT_BASE_RATE_SHIFT 14 +#define HDA_SDFMT_BASE_RATE_MASK 0x1 +#define HDA_SDFMT_MULT_SHIFT 11 +#define HDA_SDFMT_MULT_MASK 0x7 +#define HDA_SDFMT_DIV_SHIFT 8 +#define HDA_SDFMT_DIV_MASK 0x7 +#define HDA_SDFMT_BITS_SHIFT 4 +#define HDA_SDFMT_BITS_MASK 0x7 +#define HDA_SDFMT_CHANNELS_MASK 0xF + +#define HDA_SDFMT_TYPE RT_BIT(15) +#define HDA_SDFMT_TYPE_PCM (0) +#define HDA_SDFMT_TYPE_NON_PCM (1) + +#define HDA_SDFMT_BASE RT_BIT(14) +#define HDA_SDFMT_BASE_48KHZ (0) +#define HDA_SDFMT_BASE_44KHZ (1) + +#define HDA_SDFMT_MULT_1X (0) +#define HDA_SDFMT_MULT_2X (1) +#define HDA_SDFMT_MULT_3X (2) +#define HDA_SDFMT_MULT_4X (3) + +#define HDA_SDFMT_DIV_1X (0) +#define HDA_SDFMT_DIV_2X (1) +#define HDA_SDFMT_DIV_3X (2) +#define HDA_SDFMT_DIV_4X (3) +#define HDA_SDFMT_DIV_5X (4) +#define HDA_SDFMT_DIV_6X (5) +#define HDA_SDFMT_DIV_7X (6) +#define HDA_SDFMT_DIV_8X (7) + +#define HDA_SDFMT_8_BIT (0) +#define HDA_SDFMT_16_BIT (1) +#define HDA_SDFMT_20_BIT (2) +#define HDA_SDFMT_24_BIT (3) +#define HDA_SDFMT_32_BIT (4) + +#define HDA_SDFMT_CHAN_MONO (0) +#define HDA_SDFMT_CHAN_STEREO (1) + +/** Emits a SDnFMT register format. + * Also being used in the codec's converter format. */ +#define HDA_SDFMT_MAKE(_afNonPCM, _aBaseRate, _aMult, _aDiv, _aBits, _aChan) \ + ( (((_afNonPCM) & HDA_SDFMT_NON_PCM_MASK) << HDA_SDFMT_NON_PCM_SHIFT) \ + | (((_aBaseRate) & HDA_SDFMT_BASE_RATE_MASK) << HDA_SDFMT_BASE_RATE_SHIFT) \ + | (((_aMult) & HDA_SDFMT_MULT_MASK) << HDA_SDFMT_MULT_SHIFT) \ + | (((_aDiv) & HDA_SDFMT_DIV_MASK) << HDA_SDFMT_DIV_SHIFT) \ + | (((_aBits) & HDA_SDFMT_BITS_MASK) << HDA_SDFMT_BITS_SHIFT) \ + | ( (_aChan) & HDA_SDFMT_CHANNELS_MASK)) + + +/* Post stream registers: */ +#define HDA_REG_MLCH (HDA_NUM_GENERAL_REGS + HDA_NUM_STREAM_REGS) /* 0xc00 */ +#define HDA_RMX_MLCH 115 +#define HDA_REG_MLCD (HDA_REG_MLCH + 1) /* 0xc04 */ +#define HDA_RMX_MLCD 116 + +/* Registers added/specific-to skylake/broxton: */ +#define HDA_SD_NUM_FROM_SKYLAKE_REG(a_Name, a_iMap) (((a_iMap) - HDA_STREAM_REG_DEF(a_Name, 0)) / 2) + +#define HDA_REG_SD0DPIB (HDA_REG_MLCD + 1) /* 0x1084 */ +#define HDA_REG_SD1DPIB (HDA_REG_SD0DPIB + 1*2) +#define HDA_REG_SD2DPIB (HDA_REG_SD0DPIB + 2*2) +#define HDA_REG_SD3DPIB (HDA_REG_SD0DPIB + 3*2) +#define HDA_REG_SD4DPIB (HDA_REG_SD0DPIB + 4*2) +#define HDA_REG_SD5DPIB (HDA_REG_SD0DPIB + 5*2) +#define HDA_REG_SD6DPIB (HDA_REG_SD0DPIB + 6*2) +#define HDA_REG_SD7DPIB (HDA_REG_SD0DPIB + 7*2) + +#define HDA_RMX_SD0DPIB HDA_RMX_SD0LPIB +#define HDA_RMX_SD1DPIB HDA_RMX_SD1LPIB +#define HDA_RMX_SD2DPIB HDA_RMX_SD2LPIB +#define HDA_RMX_SD3DPIB HDA_RMX_SD3LPIB +#define HDA_RMX_SD4DPIB HDA_RMX_SD4LPIB +#define HDA_RMX_SD5DPIB HDA_RMX_SD5LPIB +#define HDA_RMX_SD6DPIB HDA_RMX_SD6LPIB +#define HDA_RMX_SD7DPIB HDA_RMX_SD7LPIB + +#define HDA_REG_SD0EFIFOS (HDA_REG_SD0DPIB + 1) /* 0x1094 */ +#define HDA_REG_SD1EFIFOS (HDA_REG_SD0EFIFOS + 1*2) +#define HDA_REG_SD2EFIFOS (HDA_REG_SD0EFIFOS + 2*2) +#define HDA_REG_SD3EFIFOS (HDA_REG_SD0EFIFOS + 3*2) +#define HDA_REG_SD4EFIFOS (HDA_REG_SD0EFIFOS + 4*2) +#define HDA_REG_SD5EFIFOS (HDA_REG_SD0EFIFOS + 5*2) +#define HDA_REG_SD6EFIFOS (HDA_REG_SD0EFIFOS + 6*2) +#define HDA_REG_SD7EFIFOS (HDA_REG_SD0EFIFOS + 7*2) + +#define HDA_RMX_SD0EFIFOS 117 +#define HDA_RMX_SD1EFIFOS (HDA_RMX_SD0EFIFOS + 1) +#define HDA_RMX_SD2EFIFOS (HDA_RMX_SD0EFIFOS + 2) +#define HDA_RMX_SD3EFIFOS (HDA_RMX_SD0EFIFOS + 3) +#define HDA_RMX_SD4EFIFOS (HDA_RMX_SD0EFIFOS + 4) +#define HDA_RMX_SD5EFIFOS (HDA_RMX_SD0EFIFOS + 5) +#define HDA_RMX_SD6EFIFOS (HDA_RMX_SD0EFIFOS + 6) +#define HDA_RMX_SD7EFIFOS (HDA_RMX_SD0EFIFOS + 7) + +/** @} */ /* grp_hda_regs */ + + +/** + * Buffer descriptor list entry (BDLE). + * + * See 3.6.3 in HDA specs rev 1.0a (2010-06-17). + */ +typedef struct HDABDLEDESC +{ + /** Starting address of the actual buffer. Must be 128-bit aligned. */ + uint64_t u64BufAddr; + /** Size of the actual buffer (in bytes). */ + uint32_t u32BufSize; + /** HDA_BDLE_F_XXX. + * + * Bit 0: IOC - Interrupt on completion / HDA_BDLE_F_IOC. + * The controller will generate an interrupt when the last byte of the buffer + * has been fetched by the DMA engine. + * + * Bits 31:1 are reserved for further use and must be 0. */ + uint32_t fFlags; +} HDABDLEDESC, *PHDABDLEDESC; +AssertCompileSize(HDABDLEDESC, 16); /* Always 16 byte. Also must be aligned on 128-byte boundary. */ + +/** Interrupt on completion (IOC) flag. */ +#define HDA_BDLE_F_IOC RT_BIT(0) + + +/** + * HDA mixer sink definition (ring-3). + * + * Its purpose is to know which audio mixer sink is bound to which SDn + * (SDI/SDO) device stream. + * + * This is needed in order to handle interleaved streams (that is, multiple + * channels in one stream) or non-interleaved streams (each channel has a + * dedicated stream). + * + * This is only known to the actual device emulation level. + */ +typedef struct HDAMIXERSINK +{ + R3PTRTYPE(PHDASTREAM) pStreamShared; + R3PTRTYPE(PHDASTREAMR3) pStreamR3; + /** Pointer to the actual audio mixer sink. */ + R3PTRTYPE(PAUDMIXSINK) pMixSink; +} HDAMIXERSINK; + +/** + * Mapping a stream tag to an HDA stream (ring-3). + */ +typedef struct HDATAG +{ + /** Own stream tag. */ + uint8_t uTag; + uint8_t Padding[7]; + /** Pointer to associated stream. */ + R3PTRTYPE(PHDASTREAMR3) pStreamR3; +} HDATAG; +/** Pointer to a HDA stream tag mapping. */ +typedef HDATAG *PHDATAG; + +/** + * Shared ICH Intel HD audio controller state. + */ +typedef struct HDASTATE +{ + /** Critical section protecting the HDA state. */ + PDMCRITSECT CritSect; + /** Internal stream states (aligned on 64 byte boundrary). */ + HDASTREAM aStreams[HDA_MAX_STREAMS]; + /** The HDA's register set. */ + uint32_t au32Regs[HDA_NUM_REGS]; + /** CORB buffer base address. */ + uint64_t u64CORBBase; + /** RIRB buffer base address. */ + uint64_t u64RIRBBase; + /** DMA base address. + * Made out of DPLBASE + DPUBASE (3.3.32 + 3.3.33). */ + uint64_t u64DPBase; + /** Size in bytes of CORB buffer (#au32CorbBuf). */ + uint32_t cbCorbBuf; + /** Size in bytes of RIRB buffer (#au64RirbBuf). */ + uint32_t cbRirbBuf; + /** Response Interrupt Count (RINTCNT). */ + uint16_t u16RespIntCnt; + /** DMA position buffer enable bit. */ + bool fDMAPosition; + /** Current IRQ level. */ + uint8_t u8IRQL; + /** Config: Internal input DMA buffer size override, specified in milliseconds. + * Zero means default size according to buffer and stream config. + * @sa BufSizeInMs config value. */ + uint16_t cMsCircBufIn; + /** Config: Internal output DMA buffer size override, specified in milliseconds. + * Zero means default size according to buffer and stream config. + * @sa BufSizeOutMs config value. */ + uint16_t cMsCircBufOut; + /** The start time of the wall clock (WALCLK), measured on the virtual sync clock. */ + uint64_t tsWalClkStart; + /** CORB DMA task handle. + * We use this when there is stuff we cannot handle in ring-0. */ + PDMTASKHANDLE hCorbDmaTask; + /** The CORB buffer. */ + uint32_t au32CorbBuf[HDA_CORB_SIZE]; + /** Pointer to RIRB buffer. */ + uint64_t au64RirbBuf[HDA_RIRB_SIZE]; + + /** PCI Region \#0: 16KB of MMIO stuff. */ + IOMMMIOHANDLE hMmio; + +#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA + STAMCOUNTER StatAccessDmaOutput; + STAMCOUNTER StatAccessDmaOutputToR3; +#endif +#ifdef VBOX_WITH_STATISTICS + STAMPROFILE StatIn; + STAMPROFILE StatOut; + STAMCOUNTER StatBytesRead; + STAMCOUNTER StatBytesWritten; + + /** @name Register statistics. + * The array members run parallel to g_aHdaRegMap. + * @{ */ + STAMCOUNTER aStatRegReads[HDA_NUM_REGS]; + STAMCOUNTER aStatRegReadsToR3[HDA_NUM_REGS]; + STAMCOUNTER aStatRegWrites[HDA_NUM_REGS]; + STAMCOUNTER aStatRegWritesToR3[HDA_NUM_REGS]; + STAMCOUNTER StatRegMultiReadsRZ; + STAMCOUNTER StatRegMultiReadsR3; + STAMCOUNTER StatRegMultiWritesRZ; + STAMCOUNTER StatRegMultiWritesR3; + STAMCOUNTER StatRegSubWriteRZ; + STAMCOUNTER StatRegSubWriteR3; + STAMCOUNTER StatRegUnknownReads; + STAMCOUNTER StatRegUnknownWrites; + STAMCOUNTER StatRegWritesBlockedByReset; + STAMCOUNTER StatRegWritesBlockedByRun; + /** @} */ +#endif + +#ifdef DEBUG + /** Debug stuff. + * @todo Make STAM values out some of this? */ + struct + { +# if 0 /* unused */ + /** Timestamp (in ns) of the last timer callback (hdaTimer). + * Used to calculate the time actually elapsed between two timer callbacks. */ + uint64_t tsTimerLastCalledNs; +# endif + /** IRQ debugging information. */ + struct + { + /** Timestamp (in ns) of last processed (asserted / deasserted) IRQ. */ + uint64_t tsProcessedLastNs; + /** Timestamp (in ns) of last asserted IRQ. */ + uint64_t tsAssertedNs; +# if 0 /* unused */ + /** How many IRQs have been asserted already. */ + uint64_t cAsserted; + /** Accumulated elapsed time (in ns) of all IRQ being asserted. */ + uint64_t tsAssertedTotalNs; + /** Timestamp (in ns) of last deasserted IRQ. */ + uint64_t tsDeassertedNs; + /** How many IRQs have been deasserted already. */ + uint64_t cDeasserted; + /** Accumulated elapsed time (in ns) of all IRQ being deasserted. */ + uint64_t tsDeassertedTotalNs; +# endif + } IRQ; + } Dbg; +#endif + /** This is for checking that the build was correctly configured in all contexts. + * This is set to HDASTATE_ALIGNMENT_CHECK_MAGIC. */ + uint64_t uAlignmentCheckMagic; +} HDASTATE; +AssertCompileMemberAlignment(HDASTATE, aStreams, 64); +/** Pointer to a shared HDA device state. */ +typedef HDASTATE *PHDASTATE; + +/** Value for HDASTATE:uAlignmentCheckMagic. */ +#define HDASTATE_ALIGNMENT_CHECK_MAGIC UINT64_C(0x1298afb75893e059) + +/** + * Ring-0 ICH Intel HD audio controller state. + */ +typedef struct HDASTATER0 +{ +# if 0 /* Codec is not yet kosher enough for ring-0. @bugref{9890c64} */ + /** Pointer to HDA codec to use. */ + HDACODECR0 Codec; +# else + uint32_t u32Dummy; +# endif +} HDASTATER0; +/** Pointer to a ring-0 HDA device state. */ +typedef HDASTATER0 *PHDASTATER0; + +/** + * Ring-3 ICH Intel HD audio controller state. + */ +typedef struct HDASTATER3 +{ + /** Internal stream states. */ + HDASTREAMR3 aStreams[HDA_MAX_STREAMS]; + /** Mapping table between stream tags and stream states. */ + HDATAG aTags[HDA_MAX_TAGS]; + /** R3 Pointer to the device instance. */ + PPDMDEVINSR3 pDevIns; + /** The base interface for LUN\#0. */ + PDMIBASE IBase; + /** List of associated LUN drivers (HDADRIVER). */ + RTLISTANCHORR3 lstDrv; + /** The device' software mixer. */ + R3PTRTYPE(PAUDIOMIXER) pMixer; + /** HDA sink for (front) output. */ + HDAMIXERSINK SinkFront; +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + /** HDA sink for center / LFE output. */ + HDAMIXERSINK SinkCenterLFE; + /** HDA sink for rear output. */ + HDAMIXERSINK SinkRear; +#endif + /** HDA mixer sink for line input. */ + HDAMIXERSINK SinkLineIn; +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + /** Audio mixer sink for microphone input. */ + HDAMIXERSINK SinkMicIn; +#endif + /** Debug stuff. */ + struct + { + /** Whether debugging is enabled or not. */ + bool fEnabled; + /** Path where to dump the debug output to. + * Can be NULL, in which the system's temporary directory will be used then. */ + R3PTRTYPE(char *) pszOutPath; + } Dbg; + /** Align the codec state on a cache line. */ + uint64_t au64Padding[3]; + /** The HDA codec state. */ + HDACODECR3 Codec; +} HDASTATER3; +AssertCompileMemberAlignment(HDASTATER3, Codec, 64); + + +/** Pointer to the context specific HDA state (HDASTATER3 or HDASTATER0). */ +typedef CTX_SUFF(PHDASTATE) PHDASTATECC; + + +/** @def HDA_PROCESS_INTERRUPT + * Wrapper around hdaProcessInterrupt that supplies the source function name + * string in logging builds. */ +#if defined(LOG_ENABLED) || defined(DOXYGEN_RUNNING) +void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis, const char *pszSource); +# define HDA_PROCESS_INTERRUPT(a_pDevIns, a_pThis) hdaProcessInterrupt((a_pDevIns), (a_pThis), __FUNCTION__) +#else +void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis); +# define HDA_PROCESS_INTERRUPT(a_pDevIns, a_pThis) hdaProcessInterrupt((a_pDevIns), (a_pThis)) +#endif + +/** + * Returns the audio direction of a specified stream descriptor. + * + * The register layout specifies that input streams (SDI) come first, + * followed by the output streams (SDO). So every stream ID below HDA_MAX_SDI + * is an input stream, whereas everything >= HDA_MAX_SDI is an output stream. + * + * @note SDnFMT register does not provide that information, so we have to judge + * for ourselves. + * + * @return Audio direction. + * @param uSD The stream number. + */ +DECLINLINE(PDMAUDIODIR) hdaGetDirFromSD(uint8_t uSD) +{ + if (uSD < HDA_MAX_SDI) + return PDMAUDIODIR_IN; + AssertReturn(uSD < HDA_MAX_STREAMS, PDMAUDIODIR_UNKNOWN); + return PDMAUDIODIR_OUT; +} + +/* Used by hdaR3StreamSetUp: */ +uint8_t hdaSDFIFOWToBytes(uint16_t u16RegFIFOW); + +#if defined(VBOX_STRICT) && defined(VBOX_HDA_CAN_ACCESS_REG_MAP) +/* Only in DevHda.cpp: */ +DECLINLINE(uint32_t *) hdaStrictRegAccessor(PHDASTATE pThis, uint32_t idxMap, uint32_t idxReg); +DECLINLINE(uint32_t *) hdaStrictStreamRegAccessor(PHDASTATE pThis, uint32_t idxMap0, uint32_t idxReg0, size_t idxStream); +#endif /* VBOX_STRICT && VBOX_HDA_CAN_ACCESS_REG_MAP */ + + +/** @name HDA device functions used by the codec. + * @{ */ +DECLHIDDEN(int) hdaR3MixerAddStream(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, PCPDMAUDIOSTREAMCFG pCfg); +DECLHIDDEN(int) hdaR3MixerRemoveStream(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, bool fImmediate); +DECLHIDDEN(int) hdaR3MixerControl(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel); +DECLHIDDEN(int) hdaR3MixerSetVolume(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol); +/** @} */ + + +/** @name Saved state versions for the HDA device + * @{ */ +/** The current staved state version. + * @note Only for the registration call. Never used for tests. */ +#define HDA_SAVED_STATE_VERSION HDA_SAVED_STATE_WITHOUT_PERIOD + +/** Removed period and redefined wall clock. */ +#define HDA_SAVED_STATE_WITHOUT_PERIOD 8 +/** Added (Controller): Current wall clock value (this independent from WALCLK register value). + * Added (Controller): Current IRQ level. + * Added (Per stream): Ring buffer. This is optional and can be skipped if (not) needed. + * Added (Per stream): Struct g_aSSMStreamStateFields7. + * Added (Per stream): Struct g_aSSMStreamPeriodFields7. + * Added (Current BDLE per stream): Struct g_aSSMBDLEDescFields7. + * Added (Current BDLE per stream): Struct g_aSSMBDLEStateFields7. */ +#define HDA_SAVED_STATE_VERSION_7 7 +/** Saves the current BDLE state. + * @since 5.0.14 (r104839) */ +#define HDA_SAVED_STATE_VERSION_6 6 +/** Introduced dynamic number of streams + stream identifiers for serialization. + * Bug: Did not save the BDLE states correctly. + * Those will be skipped on load then. + * @since 5.0.12 (r104520) */ +#define HDA_SAVED_STATE_VERSION_5 5 +/** Since this version the number of MMIO registers can be flexible. */ +#define HDA_SAVED_STATE_VERSION_4 4 +#define HDA_SAVED_STATE_VERSION_3 3 +#define HDA_SAVED_STATE_VERSION_2 2 +#define HDA_SAVED_STATE_VERSION_1 1 +/** @} */ + +#endif /* !VBOX_INCLUDED_SRC_Audio_DevHda_h */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHDA.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHDA.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHDA.h 2020-10-16 16:32:55.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHDA.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,256 +0,0 @@ -/* $Id: DevHDA.h $ */ -/** @file - * DevHDA.h - VBox Intel HD Audio Controller. - */ - -/* - * Copyright (C) 2016-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_DevHDA_h -#define VBOX_INCLUDED_SRC_Audio_DevHDA_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include - -#include - -#include "AudioMixer.h" - -#include "HDACodec.h" -#include "HDAStream.h" -#include "HDAStreamMap.h" -#include "HDAStreamPeriod.h" - - - -/** - * HDA mixer sink definition (ring-3). - * - * Its purpose is to know which audio mixer sink is bound to which SDn - * (SDI/SDO) device stream. - * - * This is needed in order to handle interleaved streams (that is, multiple - * channels in one stream) or non-interleaved streams (each channel has a - * dedicated stream). - * - * This is only known to the actual device emulation level. - */ -typedef struct HDAMIXERSINK -{ - R3PTRTYPE(PHDASTREAM) pStreamShared; - R3PTRTYPE(PHDASTREAMR3) pStreamR3; - /** Pointer to the actual audio mixer sink. */ - R3PTRTYPE(PAUDMIXSINK) pMixSink; -} HDAMIXERSINK; -/** Pointer to an HDA mixer sink definition (ring-3). */ -typedef HDAMIXERSINK *PHDAMIXERSINK; - -/** - * Mapping a stream tag to an HDA stream (ring-3). - */ -typedef struct HDATAG -{ - /** Own stream tag. */ - uint8_t uTag; - uint8_t Padding[7]; - /** Pointer to associated stream. */ - R3PTRTYPE(PHDASTREAMR3) pStreamR3; -} HDATAG; -/** Pointer to a HDA stream tag mapping. */ -typedef HDATAG *PHDATAG; - -/** - * Shared ICH Intel HD audio controller state. - */ -typedef struct HDASTATE -{ - /** Critical section protecting the HDA state. */ - PDMCRITSECT CritSect; - /** The HDA's register set. */ - uint32_t au32Regs[HDA_NUM_REGS]; - /** Internal stream states. */ - HDASTREAM aStreams[HDA_MAX_STREAMS]; - /** CORB buffer base address. */ - uint64_t u64CORBBase; - /** RIRB buffer base address. */ - uint64_t u64RIRBBase; - /** DMA base address. - * Made out of DPLBASE + DPUBASE (3.3.32 + 3.3.33). */ - uint64_t u64DPBase; - /** Size in bytes of CORB buffer (#au32CorbBuf). */ - uint32_t cbCorbBuf; - /** Size in bytes of RIRB buffer (#au64RirbBuf). */ - uint32_t cbRirbBuf; - /** Response Interrupt Count (RINTCNT). */ - uint16_t u16RespIntCnt; - /** Position adjustment (in audio frames). - * - * This is not an official feature of the HDA specs, but used by - * certain OS drivers (e.g. snd_hda_intel) to work around certain - * quirks by "real" HDA hardware implementations. - * - * The position adjustment specifies how many audio frames - * a stream is ahead from its actual reading/writing position when - * starting a stream. - */ - uint16_t cPosAdjustFrames; - /** Whether the position adjustment is enabled or not. */ - bool fPosAdjustEnabled; - /** DMA position buffer enable bit. */ - bool fDMAPosition; - /** Current IRQ level. */ - uint8_t u8IRQL; -#ifdef VBOX_STRICT - /** Wall clock (WALCLK) stale count. - * This indicates the number of set wall clock values which did not actually - * move the counter forward (stale). */ - uint8_t u8WalClkStaleCnt; -#else - uint8_t bPadding1; -#endif - /** The device timer Hz rate. Defaults to HDA_TIMER_HZ_DEFAULT. */ - uint16_t uTimerHz; - /** Padding for alignment. */ - uint16_t au16Padding3[3]; - /** Last updated wall clock (WALCLK) counter. */ - uint64_t u64WalClk; - /** The CORB buffer. */ - uint32_t au32CorbBuf[HDA_CORB_SIZE]; - /** Pointer to RIRB buffer. */ - uint64_t au64RirbBuf[HDA_RIRB_SIZE]; - - /** PCI Region \#0: 16KB of MMIO stuff. */ - IOMMMIOHANDLE hMmio; - -#ifdef VBOX_WITH_STATISTICS - STAMPROFILE StatIn; - STAMPROFILE StatOut; - STAMCOUNTER StatBytesRead; - STAMCOUNTER StatBytesWritten; - - /** @name Register statistics. - * The array members run parallel to g_aHdaRegMap. - * @{ */ - STAMCOUNTER aStatRegReads[HDA_NUM_REGS]; - STAMCOUNTER aStatRegReadsToR3[HDA_NUM_REGS]; - STAMCOUNTER aStatRegWrites[HDA_NUM_REGS]; - STAMCOUNTER aStatRegWritesToR3[HDA_NUM_REGS]; - STAMCOUNTER StatRegMultiReadsRZ; - STAMCOUNTER StatRegMultiReadsR3; - STAMCOUNTER StatRegMultiWritesRZ; - STAMCOUNTER StatRegMultiWritesR3; - STAMCOUNTER StatRegSubWriteRZ; - STAMCOUNTER StatRegSubWriteR3; - STAMCOUNTER StatRegUnknownReads; - STAMCOUNTER StatRegUnknownWrites; - STAMCOUNTER StatRegWritesBlockedByReset; - STAMCOUNTER StatRegWritesBlockedByRun; - /** @} */ -#endif - -#ifdef DEBUG - /** Debug stuff. - * @todo Make STAM values out some of this? */ - struct - { -# if 0 /* unused */ - /** Timestamp (in ns) of the last timer callback (hdaTimer). - * Used to calculate the time actually elapsed between two timer callbacks. */ - uint64_t tsTimerLastCalledNs; -# endif - /** IRQ debugging information. */ - struct - { - /** Timestamp (in ns) of last processed (asserted / deasserted) IRQ. */ - uint64_t tsProcessedLastNs; - /** Timestamp (in ns) of last asserted IRQ. */ - uint64_t tsAssertedNs; -# if 0 /* unused */ - /** How many IRQs have been asserted already. */ - uint64_t cAsserted; - /** Accumulated elapsed time (in ns) of all IRQ being asserted. */ - uint64_t tsAssertedTotalNs; - /** Timestamp (in ns) of last deasserted IRQ. */ - uint64_t tsDeassertedNs; - /** How many IRQs have been deasserted already. */ - uint64_t cDeasserted; - /** Accumulated elapsed time (in ns) of all IRQ being deasserted. */ - uint64_t tsDeassertedTotalNs; -# endif - } IRQ; - } Dbg; -#endif - /** This is for checking that the build was correctly configured in all contexts. - * This is set to HDASTATE_ALIGNMENT_CHECK_MAGIC. */ - uint64_t uAlignmentCheckMagic; -} HDASTATE; -/** Pointer to a shared HDA device state. */ -typedef HDASTATE *PHDASTATE; - -/** Value for HDASTATE:uAlignmentCheckMagic. */ -#define HDASTATE_ALIGNMENT_CHECK_MAGIC UINT64_C(0x1298afb75893e059) - - -/** - * Ring-3 ICH Intel HD audio controller state. - */ -typedef struct HDASTATER3 -{ - /** Internal stream states. */ - HDASTREAMR3 aStreams[HDA_MAX_STREAMS]; - /** Mapping table between stream tags and stream states. */ - HDATAG aTags[HDA_MAX_TAGS]; - /** Number of active (running) SDn streams. */ - uint8_t cStreamsActive; - uint8_t abPadding0[7]; - /** R3 Pointer to the device instance. */ - PPDMDEVINSR3 pDevIns; - /** The base interface for LUN\#0. */ - PDMIBASE IBase; - /** Pointer to HDA codec to use. */ - R3PTRTYPE(PHDACODEC) pCodec; - /** List of associated LUN drivers (HDADRIVER). */ - RTLISTANCHORR3 lstDrv; - /** The device' software mixer. */ - R3PTRTYPE(PAUDIOMIXER) pMixer; - /** HDA sink for (front) output. */ - HDAMIXERSINK SinkFront; -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - /** HDA sink for center / LFE output. */ - HDAMIXERSINK SinkCenterLFE; - /** HDA sink for rear output. */ - HDAMIXERSINK SinkRear; -#endif - /** HDA mixer sink for line input. */ - HDAMIXERSINK SinkLineIn; -#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - /** Audio mixer sink for microphone input. */ - HDAMIXERSINK SinkMicIn; -#endif - /** Debug stuff. */ - struct - { - /** Whether debugging is enabled or not. */ - bool fEnabled; - /** Path where to dump the debug output to. - * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH. */ - R3PTRTYPE(char *) pszOutPath; - } Dbg; -} HDASTATER3; -/** Pointer to a ring-3 HDA device state. */ -typedef HDASTATER3 *PHDASTATER3; - - -#endif /* !VBOX_INCLUDED_SRC_Audio_DevHDA_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHdaStream.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHdaStream.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHdaStream.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHdaStream.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,2530 @@ +/* $Id: DevHdaStream.cpp $ */ +/** @file + * Intel HD Audio Controller Emulation - Streams. + */ + +/* + * Copyright (C) 2017-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_HDA +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "AudioHlp.h" + +#include "DevHda.h" + +#ifdef VBOX_WITH_DTRACE +# include "dtrace/VBoxDD.h" +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#if defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA) +static void hdaStreamSetPositionAbs(PHDASTREAM pStreamShared, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t uLPIB); +#endif +#ifdef IN_RING3 +# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA +static void hdaR3StreamFlushDmaBounceBufferOutput(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3); +# endif +static uint32_t hdaR3StreamHandleDmaBufferOverrun(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PAUDMIXSINK pSink, + uint32_t cbNeeded, uint64_t nsNow, + const char *pszCaller, uint32_t const cbStreamFree); +static void hdaR3StreamUpdateDma(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, + PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3); +#endif + + +#ifdef IN_RING3 + +/** + * Creates an HDA stream. + * + * @returns VBox status code. + * @param pStreamShared The HDA stream to construct - shared bits. + * @param pStreamR3 The HDA stream to construct - ring-3 bits. + * @param pThis The shared HDA device instance. + * @param pThisCC The ring-3 HDA device instance. + * @param uSD Stream descriptor number to assign. + */ +int hdaR3StreamConstruct(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PHDASTATE pThis, PHDASTATER3 pThisCC, uint8_t uSD) +{ + pStreamR3->u8SD = uSD; + pStreamShared->u8SD = uSD; + pStreamR3->pMixSink = NULL; + pStreamR3->pHDAStateShared = pThis; + pStreamR3->pHDAStateR3 = pThisCC; + Assert(pStreamShared->hTimer != NIL_TMTIMERHANDLE); /* hdaR3Construct initalized this one already. */ + + pStreamShared->State.fInReset = false; + pStreamShared->State.fRunning = false; + + AssertPtr(pStreamR3->pHDAStateR3); + AssertPtr(pStreamR3->pHDAStateR3->pDevIns); + + const bool fIsInput = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN; + + if (fIsInput) + { + pStreamShared->State.Cfg.enmPath = PDMAUDIOPATH_UNKNOWN; + pStreamShared->State.Cfg.enmDir = PDMAUDIODIR_IN; + } + else + { + pStreamShared->State.Cfg.enmPath = PDMAUDIOPATH_UNKNOWN; + pStreamShared->State.Cfg.enmDir = PDMAUDIODIR_OUT; + } + + pStreamR3->Dbg.Runtime.fEnabled = pThisCC->Dbg.fEnabled; + + if (pStreamR3->Dbg.Runtime.fEnabled) + { + int rc2 = AudioHlpFileCreateF(&pStreamR3->Dbg.Runtime.pFileStream, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV, + pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/, + fIsInput ? "hdaStreamWriteSD%RU8" : "hdaStreamReadSD%RU8", uSD); + AssertRC(rc2); + + /* pFileDMARaw */ + rc2 = AudioHlpFileCreateF(&pStreamR3->Dbg.Runtime.pFileDMARaw, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV, + pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/, + fIsInput ? "hdaDMARawWriteSD%RU8" : "hdaDMARawReadSD%RU8", uSD); + AssertRC(rc2); + + /* pFileDMAMapped */ + rc2 = AudioHlpFileCreateF(&pStreamR3->Dbg.Runtime.pFileDMAMapped, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV, + pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/, + fIsInput ? "hdaDMAWriteMappedSD%RU8" : "hdaDMAReadMappedSD%RU8", uSD); + AssertRC(rc2); + + /* Delete stale debugging files from a former run. */ + AudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileStream); + AudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileDMARaw); + AudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileDMAMapped); + } + + return VINF_SUCCESS; +} + +/** + * Destroys an HDA stream. + * + * @param pStreamR3 The HDA stream to destroy - ring-3 bits. + */ +void hdaR3StreamDestroy(PHDASTREAMR3 pStreamR3) +{ + LogFlowFunc(("[SD%RU8] Destroying ...\n", pStreamR3->u8SD)); + int rc2; + + if (pStreamR3->State.pAioRegSink) + { + rc2 = AudioMixerSinkRemoveUpdateJob(pStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3); + AssertRC(rc2); + pStreamR3->State.pAioRegSink = NULL; + } + + if (pStreamR3->State.pCircBuf) + { + RTCircBufDestroy(pStreamR3->State.pCircBuf); + pStreamR3->State.pCircBuf = NULL; + pStreamR3->State.StatDmaBufSize = 0; + pStreamR3->State.StatDmaBufUsed = 0; + } + + if (pStreamR3->Dbg.Runtime.fEnabled) + { + AudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileStream); + pStreamR3->Dbg.Runtime.pFileStream = NULL; + + AudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileDMARaw); + pStreamR3->Dbg.Runtime.pFileDMARaw = NULL; + + AudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileDMAMapped); + pStreamR3->Dbg.Runtime.pFileDMAMapped = NULL; + } + + LogFlowFuncLeave(); +} + + +/** + * Converts an HDA stream's SDFMT register into a given PCM properties structure. + * + * @returns VBox status code. + * @param u16SDFMT The HDA stream's SDFMT value to convert. + * @param pProps PCM properties structure to hold converted result on success. + */ +int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + +# define EXTRACT_VALUE(v, mask, shift) ((v & ((mask) << (shift))) >> (shift)) + + int rc = VINF_SUCCESS; + + uint32_t u32Hz = EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BASE_RATE_MASK, HDA_SDFMT_BASE_RATE_SHIFT) + ? 44100 : 48000; + uint32_t u32HzMult = 1; + uint32_t u32HzDiv = 1; + + switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_MULT_MASK, HDA_SDFMT_MULT_SHIFT)) + { + case 0: u32HzMult = 1; break; + case 1: u32HzMult = 2; break; + case 2: u32HzMult = 3; break; + case 3: u32HzMult = 4; break; + default: + LogFunc(("Unsupported multiplier %x\n", + EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_MULT_MASK, HDA_SDFMT_MULT_SHIFT))); + rc = VERR_NOT_SUPPORTED; + break; + } + switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_DIV_MASK, HDA_SDFMT_DIV_SHIFT)) + { + case 0: u32HzDiv = 1; break; + case 1: u32HzDiv = 2; break; + case 2: u32HzDiv = 3; break; + case 3: u32HzDiv = 4; break; + case 4: u32HzDiv = 5; break; + case 5: u32HzDiv = 6; break; + case 6: u32HzDiv = 7; break; + case 7: u32HzDiv = 8; break; + default: + LogFunc(("Unsupported divisor %x\n", + EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_DIV_MASK, HDA_SDFMT_DIV_SHIFT))); + rc = VERR_NOT_SUPPORTED; + break; + } + + uint8_t cbSample = 0; + switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT)) + { + case 0: + cbSample = 1; + break; + case 1: + cbSample = 2; + break; + case 4: + cbSample = 4; + break; + default: + AssertMsgFailed(("Unsupported bits per sample %x\n", + EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT))); + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(rc)) + { + PDMAudioPropsInit(pProps, cbSample, true /*fSigned*/, (u16SDFMT & 0xf) + 1 /*cChannels*/, u32Hz * u32HzMult / u32HzDiv); + /** @todo is there anything we need to / can do about channel assignments? */ + } + +# undef EXTRACT_VALUE + return rc; +} + +# ifdef LOG_ENABLED +void hdaR3BDLEDumpAll(PPDMDEVINS pDevIns, PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE) +{ + LogFlowFunc(("BDLEs @ 0x%x (%RU16):\n", u64BDLBase, cBDLE)); + if (!u64BDLBase) + return; + + uint32_t cbBDLE = 0; + for (uint16_t i = 0; i < cBDLE; i++) + { + HDABDLEDESC bd; + PDMDevHlpPhysRead(pDevIns, u64BDLBase + i * sizeof(HDABDLEDESC), &bd, sizeof(bd)); + + LogFunc(("\t#%03d BDLE(adr:0x%llx, size:%RU32, ioc:%RTbool)\n", + i, bd.u64BufAddr, bd.u32BufSize, bd.fFlags & HDA_BDLE_F_IOC)); + + cbBDLE += bd.u32BufSize; + } + + LogFlowFunc(("Total: %RU32 bytes\n", cbBDLE)); + + if (!pThis->u64DPBase) /* No DMA base given? Bail out. */ + return; + + LogFlowFunc(("DMA counters:\n")); + + for (int i = 0; i < cBDLE; i++) + { + uint32_t uDMACnt; + PDMDevHlpPhysRead(pDevIns, (pThis->u64DPBase & DPBASE_ADDR_MASK) + (i * 2 * sizeof(uint32_t)), + &uDMACnt, sizeof(uDMACnt)); + + LogFlowFunc(("\t#%03d DMA @ 0x%x\n", i , uDMACnt)); + } +} +# endif /* LOG_ENABLED */ + + +/** + * Appends a item to the scheduler. + * + * @returns VBox status code. + * @param pStreamShared The stream which scheduler should be modified. + * @param cbCur The period length in guest bytes. + * @param cbMaxPeriod The max period in guest bytes. + * @param idxLastBdle The last BDLE in the period. + * @param pProps The PCM properties. + * @param pcbBorrow Where to account for bytes borrowed across buffers + * to align scheduling items on frame boundraries. + */ +static int hdaR3StreamAddScheduleItem(PHDASTREAM pStreamShared, uint32_t cbCur, uint32_t cbMaxPeriod, + uint32_t idxLastBdle, PCPDMAUDIOPCMPROPS pProps, uint32_t *pcbBorrow) +{ + /* Check that we've got room (shouldn't ever be a problem). */ + size_t idx = pStreamShared->State.cSchedule; + AssertLogRelReturn(idx + 1 < RT_ELEMENTS(pStreamShared->State.aSchedule), VERR_INTERNAL_ERROR_5); + + /* Figure out the BDLE range for this period. */ + uint32_t const idxFirstBdle = idx == 0 ? 0 + : RT_MIN((uint32_t)( pStreamShared->State.aSchedule[idx - 1].idxFirst + + pStreamShared->State.aSchedule[idx - 1].cEntries), + idxLastBdle); + + pStreamShared->State.aSchedule[idx].idxFirst = (uint8_t)idxFirstBdle; + pStreamShared->State.aSchedule[idx].cEntries = idxLastBdle >= idxFirstBdle + ? idxLastBdle - idxFirstBdle + 1 + : pStreamShared->State.cBdles - idxFirstBdle + idxLastBdle + 1; + + /* Deal with borrowing due to unaligned IOC buffers. */ + uint32_t const cbBorrowed = *pcbBorrow; + if (cbBorrowed < cbCur) + cbCur -= cbBorrowed; + else + { + /* Note. We can probably gloss over this, but it's not a situation a sane guest would put us, so don't bother for now. */ + ASSERT_GUEST_MSG_FAILED(("#%u: cbBorrow=%#x cbCur=%#x BDLE[%u..%u]\n", + pStreamShared->u8SD, cbBorrowed, cbCur, idxFirstBdle, idxLastBdle)); + LogRelMax(32, ("HDA: Stream #%u has a scheduling error: cbBorrow=%#x cbCur=%#x BDLE[%u..%u]\n", + pStreamShared->u8SD, cbBorrowed, cbCur, idxFirstBdle, idxLastBdle)); + return VERR_OUT_OF_RANGE; + } + + uint32_t cbCurAligned = PDMAudioPropsRoundUpBytesToFrame(pProps, cbCur); + *pcbBorrow = cbCurAligned - cbCur; + + /* Do we need to split up the period? */ + if (cbCurAligned <= cbMaxPeriod) + { + pStreamShared->State.aSchedule[idx].cbPeriod = cbCurAligned; + pStreamShared->State.aSchedule[idx].cLoops = 1; + } + else + { + /* Reduce till we've below the threshold. */ + uint32_t cbLoop = cbCurAligned; + do + cbLoop = cbLoop / 2; + while (cbLoop > cbMaxPeriod); + cbLoop = PDMAudioPropsRoundUpBytesToFrame(pProps, cbLoop); + + /* Complete the scheduling item. */ + pStreamShared->State.aSchedule[idx].cbPeriod = cbLoop; + pStreamShared->State.aSchedule[idx].cLoops = cbCurAligned / cbLoop; + + /* If there is a remainder, add it as a separate entry (this is + why the schedule must be more than twice the size of the BDL).*/ + cbCurAligned %= cbLoop; + if (cbCurAligned) + { + pStreamShared->State.aSchedule[idx + 1] = pStreamShared->State.aSchedule[idx]; + idx++; + pStreamShared->State.aSchedule[idx].cbPeriod = cbCurAligned; + pStreamShared->State.aSchedule[idx].cLoops = 1; + } + } + + /* Done. */ + pStreamShared->State.cSchedule = (uint16_t)(idx + 1); + + return VINF_SUCCESS; +} + +/** + * Creates the DMA timer schedule for the stream + * + * This is called from the stream setup code. + * + * @returns VBox status code. + * @param pStreamShared The stream to create a schedule for. The BDL + * must be loaded. + * @param cSegments Number of BDL segments. + * @param cBufferIrqs Number of the BDLEs with IOC=1. + * @param cbTotal The total BDL length in guest bytes. + * @param cbMaxPeriod Max period in guest bytes. This is in case the + * guest want to play the whole "Der Ring des + * Nibelungen" cycle in one go. + * @param cTimerTicksPerSec The DMA timer frequency. + * @param pProps The PCM properties. + */ +static int hdaR3StreamCreateSchedule(PHDASTREAM pStreamShared, uint32_t cSegments, uint32_t cBufferIrqs, uint32_t cbTotal, + uint32_t cbMaxPeriod, uint64_t cTimerTicksPerSec, PCPDMAUDIOPCMPROPS pProps) +{ + int rc; + + /* + * Reset scheduling state. + */ + RT_ZERO(pStreamShared->State.aSchedule); + pStreamShared->State.cSchedule = 0; + pStreamShared->State.cSchedulePrologue = 0; + pStreamShared->State.idxSchedule = 0; + pStreamShared->State.idxScheduleLoop = 0; + + /* + * Do the basic schedule compilation. + */ + uint32_t cPotentialPrologue = 0; + uint32_t cbBorrow = 0; + uint32_t cbCur = 0; + uint32_t cbMin = UINT32_MAX; + pStreamShared->State.aSchedule[0].idxFirst = 0; + for (uint32_t i = 0; i < cSegments; i++) + { + cbCur += pStreamShared->State.aBdl[i].cb; + if (pStreamShared->State.aBdl[i].cb < cbMin) + cbMin = pStreamShared->State.aBdl[i].cb; + if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC) + { + rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur, cbMaxPeriod, i, pProps, &cbBorrow); + ASSERT_GUEST_RC_RETURN(rc, rc); + + if (cPotentialPrologue == 0) + cPotentialPrologue = pStreamShared->State.cSchedule; + cbCur = 0; + } + } + + /* + * Deal with any loose ends. + */ + if (cbCur && cBufferIrqs == 0) + { + /* + * No IOC. Vista ends up here, typically with three buffers configured. + * + * The perferred option here is to aim at processing one average BDLE with + * each DMA timer period, since that best matches how we update LPIB at + * present. + * + * The second alternative is to divide the whole span up into 3-4 periods + * to try increase our chances of keeping ahead of the guest. We may need + * to pick this if there are too few buffer descriptor or they are too small. + * + * However, what we probably should be doing is to do real DMA work whenever + * the guest reads a DMA related register (like LPIB) and just do 3-4 DMA + * timer periods, however we'll be postponing the DMA timer every time we + * return to ring-3 and signal the AIO, so in the end we'd probably not use + * the timer callback at all. (This is assuming a small shared per-stream + * buffer for keeping the DMA data in and that it's size will force a return + * to ring-3 often enough to keep the AIO thread going at a reasonable rate.) + */ + Assert(cbCur == cbTotal); + + /* Match the BDLEs 1:1 if there are 3 or more and that the smallest one + is at least 5ms big. */ + if (cSegments >= 3 && PDMAudioPropsBytesToMilli(pProps, cbMin) >= 5 /*ms*/) + { + for (uint32_t i = 0; i < cSegments; i++) + { + rc = hdaR3StreamAddScheduleItem(pStreamShared, pStreamShared->State.aBdl[i].cb, cbMaxPeriod, i, pProps, &cbBorrow); + ASSERT_GUEST_RC_RETURN(rc, rc); + } + } + /* Otherwise, just divide the work into 3 or 4 portions and hope for the best. + It seems, though, that this only really work for windows vista if we avoid + working accross buffer lines. */ + /** @todo This can be simplified/relaxed/uncluttered if we do DMA work when LPIB + * is read, assuming ofc that LPIB is read before each buffer update. */ + else + { + uint32_t const cPeriods = cSegments != 3 && PDMAudioPropsBytesToMilli(pProps, cbCur) >= 4 * 5 /*ms*/ + ? 4 : cSegments != 2 ? 3 : 2; + uint32_t const cbPeriod = PDMAudioPropsFloorBytesToFrame(pProps, cbCur / cPeriods); + uint32_t iBdle = 0; + uint32_t offBdle = 0; + for (uint32_t iPeriod = 0; iPeriod < cPeriods; iPeriod++) + { + if (iPeriod + 1 < cPeriods) + { + offBdle += cbPeriod; + while (iBdle < cSegments && offBdle >= pStreamShared->State.aBdl[iBdle].cb) + offBdle -= pStreamShared->State.aBdl[iBdle++].cb; + rc = hdaR3StreamAddScheduleItem(pStreamShared, cbPeriod, cbMaxPeriod, offBdle != 0 ? iBdle : iBdle - 1, + pProps, &cbBorrow); + } + else + rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur - iPeriod * cbPeriod, cbMaxPeriod, cSegments - 1, + pProps, &cbBorrow); + ASSERT_GUEST_RC_RETURN(rc, rc); + } + + } + } + else if (cbCur) + { + /* The last BDLE didn't have IOC set, so we must continue processing + from the start till we hit one that has. */ + uint32_t i; + for (i = 0; i < cSegments; i++) + { + cbCur += pStreamShared->State.aBdl[i].cb; + if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC) + break; + } + rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur, cbMaxPeriod, i, pProps, &cbBorrow); + ASSERT_GUEST_RC_RETURN(rc, rc); + + /* The initial scheduling items covering the wrap around area are + considered a prologue and must not repeated later. */ + Assert(cPotentialPrologue); + pStreamShared->State.cSchedulePrologue = (uint8_t)cPotentialPrologue; + } + + AssertLogRelMsgReturn(cbBorrow == 0, ("HDA: Internal scheduling error on stream #%u: cbBorrow=%#x cbTotal=%#x cbCur=%#x\n", + pStreamShared->u8SD, cbBorrow, cbTotal, cbCur), + VERR_INTERNAL_ERROR_3); + + /* + * If there is just one BDLE with IOC set, we have to make sure + * we've got at least two periods scheduled, otherwise there is + * a very good chance the guest will overwrite the start of the + * buffer before we ever get around to reading it. + */ + if (cBufferIrqs == 1) + { + uint32_t i = pStreamShared->State.cSchedulePrologue; + Assert(i < pStreamShared->State.cSchedule); + if ( i + 1 == pStreamShared->State.cSchedule + && pStreamShared->State.aSchedule[i].cLoops == 1) + { + uint32_t const cbFirstHalf = PDMAudioPropsFloorBytesToFrame(pProps, pStreamShared->State.aSchedule[i].cbPeriod / 2); + uint32_t const cbOtherHalf = pStreamShared->State.aSchedule[i].cbPeriod - cbFirstHalf; + pStreamShared->State.aSchedule[i].cbPeriod = cbFirstHalf; + if (cbFirstHalf == cbOtherHalf) + pStreamShared->State.aSchedule[i].cLoops = 2; + else + { + pStreamShared->State.aSchedule[i + 1] = pStreamShared->State.aSchedule[i]; + pStreamShared->State.aSchedule[i].cbPeriod = cbOtherHalf; + pStreamShared->State.cSchedule++; + } + } + } + + /* + * Go over the schduling entries and calculate the timer ticks for each period. + */ + LogRel2(("HDA: Stream #%u schedule: %u items, %u prologue\n", + pStreamShared->u8SD, pStreamShared->State.cSchedule, pStreamShared->State.cSchedulePrologue)); + uint64_t const cbPerSec = PDMAudioPropsFramesToBytes(pProps, pProps->uHz); + for (uint32_t i = 0; i < pStreamShared->State.cSchedule; i++) + { + uint64_t const cTicks = ASMMultU64ByU32DivByU32(cTimerTicksPerSec, pStreamShared->State.aSchedule[i].cbPeriod, cbPerSec); + AssertLogRelMsgReturn((uint32_t)cTicks == cTicks, ("cTicks=%RU64 (%#RX64)\n", cTicks, cTicks), VERR_INTERNAL_ERROR_4); + pStreamShared->State.aSchedule[i].cPeriodTicks = RT_MAX((uint32_t)cTicks, 16); + LogRel2(("HDA: #%u: %u ticks / %u bytes, %u loops, BDLE%u L %u\n", i, pStreamShared->State.aSchedule[i].cPeriodTicks, + pStreamShared->State.aSchedule[i].cbPeriod, pStreamShared->State.aSchedule[i].cLoops, + pStreamShared->State.aSchedule[i].idxFirst, pStreamShared->State.aSchedule[i].cEntries)); + } + + return VINF_SUCCESS; +} + + +/** + * Sets up ((re-)iniitalizes) an HDA stream. + * + * @returns VBox status code. VINF_NO_CHANGE if the stream does not need + * be set-up again because the stream's (hardware) parameters did + * not change. + * @param pDevIns The device instance. + * @param pThis The shared HDA device state (for HW register + * parameters). + * @param pStreamShared HDA stream to set up, shared portion. + * @param pStreamR3 HDA stream to set up, ring-3 portion. + * @param uSD Stream descriptor number to assign it. + */ +int hdaR3StreamSetUp(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD) +{ + /* This must be valid all times. */ + AssertReturn(uSD < HDA_MAX_STREAMS, VERR_INVALID_PARAMETER); + + /* These member can only change on data corruption, despite what the code does further down (bird). */ + AssertReturn(pStreamShared->u8SD == uSD, VERR_WRONG_ORDER); + AssertReturn(pStreamR3->u8SD == uSD, VERR_WRONG_ORDER); + + const uint64_t u64BDLBase = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, uSD), + HDA_STREAM_REG(pThis, BDPU, uSD)); + const uint16_t u16LVI = HDA_STREAM_REG(pThis, LVI, uSD); + const uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, uSD); + const uint8_t u8FIFOS = HDA_STREAM_REG(pThis, FIFOS, uSD) + 1; + uint8_t u8FIFOW = hdaSDFIFOWToBytes(HDA_STREAM_REG(pThis, FIFOW, uSD)); + const uint16_t u16FMT = HDA_STREAM_REG(pThis, FMT, uSD); + + /* Is the bare minimum set of registers configured for the stream? + * If not, bail out early, as there's nothing to do here for us (yet). */ + if ( !u64BDLBase + || !u16LVI + || !u32CBL + || !u8FIFOS + || !u8FIFOW + || !u16FMT) + { + LogFunc(("[SD%RU8] Registers not set up yet, skipping (re-)initialization\n", uSD)); + return VINF_SUCCESS; + } + + /* + * Convert the config to PDM PCM properties and configure the stream. + */ + PPDMAUDIOSTREAMCFG pCfg = &pStreamShared->State.Cfg; + int rc = hdaR3SDFMTToPCMProps(u16FMT, &pCfg->Props); + if (RT_SUCCESS(rc)) + pCfg->enmDir = hdaGetDirFromSD(uSD); + else + { + LogRelMax(32, ("HDA: Warning: Format 0x%x for stream #%RU8 not supported\n", HDA_STREAM_REG(pThis, FMT, uSD), uSD)); + return rc; + } + + ASSERT_GUEST_LOGREL_MSG_RETURN( PDMAudioPropsFrameSize(&pCfg->Props) > 0 + && u32CBL % PDMAudioPropsFrameSize(&pCfg->Props) == 0, + ("CBL for stream #%RU8 does not align to frame size (u32CBL=%u cbFrameSize=%u)\n", + uSD, u32CBL, PDMAudioPropsFrameSize(&pCfg->Props)), + VERR_INVALID_PARAMETER); + + /* Make sure the guest behaves regarding the stream's FIFO. */ + ASSERT_GUEST_LOGREL_MSG_STMT(u8FIFOW <= u8FIFOS, + ("Guest tried setting a bigger FIFOW (%RU8) than FIFOS (%RU8), limiting\n", u8FIFOW, u8FIFOS), + u8FIFOW = u8FIFOS /* ASSUMES that u8FIFOS has been validated. */); + + pStreamShared->u8SD = uSD; + + /* Update all register copies so that we later know that something has changed. */ + pStreamShared->u64BDLBase = u64BDLBase; + pStreamShared->u16LVI = u16LVI; + pStreamShared->u32CBL = u32CBL; + pStreamShared->u8FIFOS = u8FIFOS; + pStreamShared->u8FIFOW = u8FIFOW; + pStreamShared->u16FMT = u16FMT; + + /* The the stream's name, based on the direction. */ + switch (pCfg->enmDir) + { + case PDMAUDIODIR_IN: +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN +# error "Implement me!" +# else + pCfg->enmPath = PDMAUDIOPATH_IN_LINE; + RTStrCopy(pCfg->szName, sizeof(pCfg->szName), "Line In"); +# endif + break; + + case PDMAUDIODIR_OUT: + /* Destination(s) will be set in hdaR3AddStreamOut(), + * based on the channels / stream layout. */ + break; + + default: + AssertFailedReturn(VERR_NOT_SUPPORTED); + break; + } + + LogRel2(("HDA: Stream #%RU8 DMA @ 0x%x (%RU32 bytes = %RU64ms total)\n", uSD, pStreamShared->u64BDLBase, + pStreamShared->u32CBL, PDMAudioPropsBytesToMilli(&pCfg->Props, pStreamShared->u32CBL))); + + /* + * Load the buffer descriptor list. + * + * Section 3.6.2 states that "the BDL should not be modified unless the RUN + * bit is 0", so it should be within the specs to read it once here and not + * re-read any BDLEs later. + */ + /* Reset BDL state. */ + RT_ZERO(pStreamShared->State.aBdl); + pStreamShared->State.offCurBdle = 0; + pStreamShared->State.idxCurBdle = 0; + + uint32_t /*const*/ cTransferFragments = (pStreamShared->u16LVI & 0xff) + 1; + if (cTransferFragments <= 1) + LogRel(("HDA: Warning: Stream #%RU8 transfer buffer count invalid: (%RU16)! Buggy guest audio driver!\n", uSD, pStreamShared->u16LVI)); + AssertLogRelReturn(cTransferFragments <= RT_ELEMENTS(pStreamShared->State.aBdl), VERR_INTERNAL_ERROR_5); + pStreamShared->State.cBdles = cTransferFragments; + + /* Load them. */ + rc = PDMDevHlpPCIPhysRead(pDevIns, u64BDLBase, pStreamShared->State.aBdl, + sizeof(pStreamShared->State.aBdl[0]) * cTransferFragments); + AssertRC(rc); + + /* Check what we just loaded. Refuse overly large buffer lists. */ + uint64_t cbTotal = 0; + uint32_t cBufferIrqs = 0; + for (uint32_t i = 0; i < cTransferFragments; i++) + { + if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC) + cBufferIrqs++; + cbTotal += pStreamShared->State.aBdl[i].cb; + } + ASSERT_GUEST_STMT_RETURN(cbTotal < _2G, + LogRelMax(32, ("HDA: Error: Stream #%u is configured with an insane amount of buffer space - refusing do work with it: %RU64 (%#RX64) bytes.\n", + uSD, cbTotal, cbTotal)), + VERR_NOT_SUPPORTED); + ASSERT_GUEST_STMT_RETURN(cbTotal == u32CBL, + LogRelMax(32, ("HDA: Warning: Stream #%u has a mismatch between CBL and configured buffer space: %RU32 (%#RX32) vs %RU64 (%#RX64)\n", + uSD, u32CBL, u32CBL, cbTotal, cbTotal)), + VERR_NOT_SUPPORTED); + + /* + * Create a DMA timer schedule. + */ + rc = hdaR3StreamCreateSchedule(pStreamShared, cTransferFragments, cBufferIrqs, (uint32_t)cbTotal, + PDMAudioPropsMilliToBytes(&pCfg->Props, 100 /** @todo make configurable */), + PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer), &pCfg->Props); + if (RT_FAILURE(rc)) + return rc; + + pStreamShared->State.cbCurDmaPeriod = pStreamShared->State.aSchedule[0].cbPeriod; + + /* + * Calculate the transfer Hz for use in the circular buffer calculation + * and the average period for the scheduling hint. + */ + uint32_t cbMaxPeriod = 0; + uint32_t cbMinPeriod = UINT32_MAX; + uint64_t cTicks = 0; + uint32_t cPeriods = 0; + for (uint32_t i = pStreamShared->State.cSchedulePrologue; i < pStreamShared->State.cSchedule; i++) + { + uint32_t cbPeriod = pStreamShared->State.aSchedule[i].cbPeriod; + cbMaxPeriod = RT_MAX(cbMaxPeriod, cbPeriod); + cbMinPeriod = RT_MIN(cbMinPeriod, cbPeriod); + cPeriods += pStreamShared->State.aSchedule[i].cLoops; + cTicks += pStreamShared->State.aSchedule[i].cPeriodTicks * pStreamShared->State.aSchedule[i].cLoops; + } + /* Only consider the prologue in relation to the max period. */ + for (uint32_t i = 0; i < pStreamShared->State.cSchedulePrologue; i++) + cbMaxPeriod = RT_MAX(cbMaxPeriod, pStreamShared->State.aSchedule[i].cbPeriod); + + AssertLogRelReturn(cPeriods > 0, VERR_INTERNAL_ERROR_3); + uint64_t const cbTransferPerSec = RT_MAX(PDMAudioPropsFramesToBytes(&pCfg->Props, pCfg->Props.uHz), + 4096 /* zero div prevention: min is 6kHz, picked 4k in case I'm mistaken */); + unsigned uTransferHz = cbTransferPerSec * 1000 / cbMaxPeriod; + LogRel2(("HDA: Stream #%RU8 needs a %u.%03u Hz timer rate (period: %u..%u host bytes)\n", + uSD, uTransferHz / 1000, uTransferHz % 1000, cbMinPeriod, cbMaxPeriod)); + uTransferHz /= 1000; + + if (uTransferHz > 400) /* Anything above 400 Hz looks fishy -- tell the user. */ + LogRelMax(32, ("HDA: Warning: Calculated transfer Hz rate for stream #%RU8 looks incorrect (%u), please re-run with audio debug mode and report a bug\n", + uSD, uTransferHz)); + + pStreamShared->State.cbAvgTransfer = (uint32_t)(cbTotal + cPeriods - 1) / cPeriods; + + /* Calculate the average scheduling period length in nanoseconds. */ + uint64_t const cTimerResolution = PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer); + Assert(cTimerResolution <= UINT32_MAX); + uint64_t const cNsPerPeriod = ASMMultU64ByU32DivByU32(cTicks / cPeriods, RT_NS_1SEC, cTimerResolution); + AssertLogRelReturn(cNsPerPeriod > 0, VERR_INTERNAL_ERROR_3); + + /* For input streams we must determin a pre-buffering requirement. + We use the initial delay as a basis here, though we must have at + least two max periods worth of data queued up due to the way we + work the AIO thread. */ + pStreamShared->State.fInputPreBuffered = false; + pStreamShared->State.cbInputPreBuffer = cbMaxPeriod * 2; + + /* + * Set up data transfer stuff. + */ + /* Set I/O scheduling hint for the backends. */ + pCfg->Device.cMsSchedulingHint = cNsPerPeriod > RT_NS_1MS ? (cNsPerPeriod + RT_NS_1MS / 2) / RT_NS_1MS : 1; + LogRel2(("HDA: Stream #%RU8 set scheduling hint for the backends to %RU32ms\n", uSD, pCfg->Device.cMsSchedulingHint)); + + /* Make sure to also update the stream's DMA counter (based on its current LPIB value). */ + /** @todo r=bird: We use LPIB as-is here, so if it's not zero we have to + * locate the right place in the schedule and whatnot... + * + * This is a similar scenario as when loading state saved, btw. + */ + if (HDA_STREAM_REG(pThis, LPIB, uSD) != 0) + LogRel2(("HDA: Warning! Stream #%RU8 is set up with LPIB=%#RX32 instead of zero!\n", uSD, HDA_STREAM_REG(pThis, LPIB, uSD))); + hdaStreamSetPositionAbs(pStreamShared, pDevIns, pThis, HDA_STREAM_REG(pThis, LPIB, uSD)); + +# ifdef LOG_ENABLED + hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); +# endif + + /* + * Set up internal ring buffer. + */ + + /* (Re-)Allocate the stream's internal DMA buffer, + * based on the timing *and* PCM properties we just got above. */ + if (pStreamR3->State.pCircBuf) + { + RTCircBufDestroy(pStreamR3->State.pCircBuf); + pStreamR3->State.pCircBuf = NULL; + pStreamR3->State.StatDmaBufSize = 0; + pStreamR3->State.StatDmaBufUsed = 0; + } + pStreamShared->State.offWrite = 0; + pStreamShared->State.offRead = 0; + + /* + * The default internal ring buffer size must be: + * + * - Large enough for at least three periodic DMA transfers. + * + * It is critically important that we don't experience underruns + * in the DMA OUT code, because it will cause the buffer processing + * to get skewed and possibly overlap with what the guest is updating. + * At the time of writing (2021-03-05) there is no code for getting + * back into sync there. + * + * - Large enough for at least three I/O scheduling hints. + * + * We want to lag behind a DMA period or two, but there must be + * sufficent space for the AIO thread to get schedule and shuffle + * data thru the mixer and onto the host audio hardware. + * + * - Both above with plenty to spare. + * + * So, just take the longest of the two periods and multipling it by 6. + * We aren't not talking about very large base buffers heres, so size isn't + * an issue. + * + * Note: Use pCfg->Props as PCM properties here, as we only want to store the + * samples we actually need, in other words, skipping the interleaved + * channels we don't support / need to save space. + */ + uint32_t cbCircBuf = PDMAudioPropsMilliToBytes(&pCfg->Props, RT_MS_1SEC * 6 / uTransferHz); + LogRel2(("HDA: Stream #%RU8 default ring buffer size is %RU32 bytes / %RU64 ms\n", + uSD, cbCircBuf, PDMAudioPropsBytesToMilli(&pCfg->Props, cbCircBuf))); + + uint32_t msCircBufCfg = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN ? pThis->cMsCircBufIn : pThis->cMsCircBufOut; + if (msCircBufCfg) /* Anything set via CFGM? */ + { + cbCircBuf = PDMAudioPropsMilliToBytes(&pCfg->Props, msCircBufCfg); + LogRel2(("HDA: Stream #%RU8 is using a custom ring buffer size of %RU32 bytes / %RU64 ms\n", + uSD, cbCircBuf, PDMAudioPropsBytesToMilli(&pCfg->Props, cbCircBuf))); + } + + /* Serious paranoia: */ + ASSERT_GUEST_LOGREL_MSG_STMT(cbCircBuf % PDMAudioPropsFrameSize(&pCfg->Props) == 0, + ("Ring buffer size (%RU32) for stream #%RU8 not aligned to the (host) frame size (%RU8)\n", + cbCircBuf, uSD, PDMAudioPropsFrameSize(&pCfg->Props)), + rc = VERR_INVALID_PARAMETER); + ASSERT_GUEST_LOGREL_MSG_STMT(cbCircBuf, ("Ring buffer size for stream #%RU8 is invalid\n", uSD), + rc = VERR_INVALID_PARAMETER); + if (RT_SUCCESS(rc)) + { + rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, cbCircBuf); + if (RT_SUCCESS(rc)) + { + pStreamR3->State.StatDmaBufSize = cbCircBuf; + + /* + * Forward the timer frequency hint to TM as well for better accuracy on + * systems w/o preemption timers (also good for 'info timers'). + */ + PDMDevHlpTimerSetFrequencyHint(pDevIns, pStreamShared->hTimer, uTransferHz); + } + } + + if (RT_FAILURE(rc)) + LogRelMax(32, ("HDA: Initializing stream #%RU8 failed with %Rrc\n", uSD, rc)); + +# ifdef VBOX_WITH_DTRACE + VBOXDD_HDA_STREAM_SETUP((uint32_t)uSD, rc, pStreamShared->State.Cfg.Props.uHz, + pStreamShared->State.aSchedule[pStreamShared->State.cSchedule - 1].cPeriodTicks, + pStreamShared->State.aSchedule[pStreamShared->State.cSchedule - 1].cbPeriod); +# endif + return rc; +} + + +/** + * Worker for hdaR3StreamReset(). + * + * @returns The default mixer sink, NULL if none found. + * @param pThisCC The ring-3 HDA device state. + * @param uSD SD# to return mixer sink for. + * NULL if not found / handled. + */ +static PHDAMIXERSINK hdaR3GetDefaultSink(PHDASTATER3 pThisCC, uint8_t uSD) +{ + if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN) + { + const uint8_t uFirstSDI = 0; + + if (uSD == uFirstSDI) /* First SDI. */ + return &pThisCC->SinkLineIn; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + if (uSD == uFirstSDI + 1) + return &pThisCC->SinkMicIn; +# else + /* If we don't have a dedicated Mic-In sink, use the always present Line-In sink. */ + return &pThisCC->SinkLineIn; +# endif + } + else + { + const uint8_t uFirstSDO = HDA_MAX_SDI; + + if (uSD == uFirstSDO) + return &pThisCC->SinkFront; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + if (uSD == uFirstSDO + 1) + return &pThisCC->SinkCenterLFE; + if (uSD == uFirstSDO + 2) + return &pThisCC->SinkRear; +# endif + } + + return NULL; +} + + +/** + * Resets an HDA stream. + * + * @param pThis The shared HDA device state. + * @param pThisCC The ring-3 HDA device state. + * @param pStreamShared HDA stream to reset (shared). + * @param pStreamR3 HDA stream to reset (ring-3). + * @param uSD Stream descriptor (SD) number to use for this stream. + */ +void hdaR3StreamReset(PHDASTATE pThis, PHDASTATER3 pThisCC, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD) +{ + LogFunc(("[SD%RU8] Reset\n", uSD)); + + /* + * Assert some sanity. + */ + AssertPtr(pThis); + AssertPtr(pStreamShared); + AssertPtr(pStreamR3); + Assert(uSD < HDA_MAX_STREAMS); + Assert(pStreamShared->u8SD == uSD); + Assert(pStreamR3->u8SD == uSD); + AssertMsg(!pStreamShared->State.fRunning, ("[SD%RU8] Cannot reset stream while in running state\n", uSD)); + + /* + * Set reset state. + */ + Assert(ASMAtomicReadBool(&pStreamShared->State.fInReset) == false); /* No nested calls. */ + ASMAtomicXchgBool(&pStreamShared->State.fInReset, true); + + /* + * Second, initialize the registers. + */ + /* See 6.2.33: Clear on reset. */ + HDA_STREAM_REG(pThis, STS, uSD) = 0; + /* According to the ICH6 datasheet, 0x40000 is the default value for stream descriptor register 23:20 + * bits are reserved for stream number 18.2.33, resets SDnCTL except SRST bit. */ + HDA_STREAM_REG(pThis, CTL, uSD) = HDA_SDCTL_TP | (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_SRST); + /* ICH6 defines default values (120 bytes for input and 192 bytes for output descriptors) of FIFO size. 18.2.39. */ + HDA_STREAM_REG(pThis, FIFOS, uSD) = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN ? HDA_SDIFIFO_120B : HDA_SDOFIFO_192B; + /* See 18.2.38: Always defaults to 0x4 (32 bytes). */ + HDA_STREAM_REG(pThis, FIFOW, uSD) = HDA_SDFIFOW_32B; + HDA_STREAM_REG(pThis, LPIB, uSD) = 0; + HDA_STREAM_REG(pThis, CBL, uSD) = 0; + HDA_STREAM_REG(pThis, LVI, uSD) = 0; + HDA_STREAM_REG(pThis, FMT, uSD) = 0; + HDA_STREAM_REG(pThis, BDPU, uSD) = 0; + HDA_STREAM_REG(pThis, BDPL, uSD) = 0; + + /* Assign the default mixer sink to the stream. */ + pStreamR3->pMixSink = hdaR3GetDefaultSink(pThisCC, uSD); + if (pStreamR3->State.pAioRegSink) + { + int rc2 = AudioMixerSinkRemoveUpdateJob(pStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3); + AssertRC(rc2); + pStreamR3->State.pAioRegSink = NULL; + } + + /* Reset transfer stuff. */ + pStreamShared->State.cTransferPendingInterrupts = 0; + pStreamShared->State.tsTransferLast = 0; + pStreamShared->State.tsTransferNext = 0; + + /* Initialize timestamps. */ + pStreamShared->State.tsLastTransferNs = 0; + pStreamShared->State.tsLastReadNs = 0; + pStreamShared->State.tsStart = 0; + + RT_ZERO(pStreamShared->State.aBdl); + RT_ZERO(pStreamShared->State.aSchedule); + pStreamShared->State.offCurBdle = 0; + pStreamShared->State.cBdles = 0; + pStreamShared->State.idxCurBdle = 0; + pStreamShared->State.cSchedulePrologue = 0; + pStreamShared->State.cSchedule = 0; + pStreamShared->State.idxSchedule = 0; + pStreamShared->State.idxScheduleLoop = 0; + pStreamShared->State.fInputPreBuffered = false; + + if (pStreamR3->State.pCircBuf) + RTCircBufReset(pStreamR3->State.pCircBuf); + pStreamShared->State.offWrite = 0; + pStreamShared->State.offRead = 0; + + /* Report that we're done resetting this stream. */ + HDA_STREAM_REG(pThis, CTL, uSD) = 0; + +# ifdef VBOX_WITH_DTRACE + VBOXDD_HDA_STREAM_RESET((uint32_t)uSD); +# endif + LogFunc(("[SD%RU8] Reset\n", uSD)); + + /* Exit reset mode. */ + ASMAtomicXchgBool(&pStreamShared->State.fInReset, false); +} + +/** + * Enables or disables an HDA audio stream. + * + * @returns VBox status code. + * @param pThis The shared HDA device state. + * @param pStreamShared HDA stream to enable or disable - shared bits. + * @param pStreamR3 HDA stream to enable or disable - ring-3 bits. + * @param fEnable Whether to enable or disble the stream. + */ +int hdaR3StreamEnable(PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fEnable) +{ + AssertPtr(pStreamR3); + AssertPtr(pStreamShared); + + LogFunc(("[SD%RU8] fEnable=%RTbool, pMixSink=%p\n", pStreamShared->u8SD, fEnable, pStreamR3->pMixSink)); + + /* First, enable or disable the stream and the stream's sink, if any. */ + int rc = VINF_SUCCESS; + PAUDMIXSINK const pSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL; + if (pSink) + { + if (fEnable) + { + if (pStreamR3->State.pAioRegSink != pSink) + { + if (pStreamR3->State.pAioRegSink) + { + rc = AudioMixerSinkRemoveUpdateJob(pStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3); + AssertRC(rc); + } + rc = AudioMixerSinkAddUpdateJob(pSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3, + pStreamShared->State.Cfg.Device.cMsSchedulingHint); + AssertLogRelRC(rc); + pStreamR3->State.pAioRegSink = RT_SUCCESS(rc) ? pSink : NULL; + } + rc = AudioMixerSinkStart(pSink); + } + else + rc = AudioMixerSinkDrainAndStop(pSink, + pStreamR3->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf) : 0); + } + if ( RT_SUCCESS(rc) + && fEnable + && pStreamR3->Dbg.Runtime.fEnabled) + { + Assert(AudioHlpPcmPropsAreValid(&pStreamShared->State.Cfg.Props)); + + if (fEnable) + { + if (!AudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileStream)) + { + int rc2 = AudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileStream, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS, + &pStreamShared->State.Cfg.Props); + AssertRC(rc2); + } + + if (!AudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileDMARaw)) + { + int rc2 = AudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileDMARaw, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS, + &pStreamShared->State.Cfg.Props); + AssertRC(rc2); + } + + if (!AudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileDMAMapped)) + { + int rc2 = AudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileDMAMapped, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS, + &pStreamShared->State.Cfg.Props); + AssertRC(rc2); + } + } + } + + if (RT_SUCCESS(rc)) + { + if (fEnable) + pStreamShared->State.tsTransferLast = 0; /* Make sure it's not stale and messes up WALCLK calculations. */ + pStreamShared->State.fRunning = fEnable; + + /* + * Set the FIFORDY bit when we start running and clear it when stopping. + * + * This prevents Linux from timing out in snd_hdac_stream_sync when starting + * a stream. Technically, Linux also uses the SSYNC feature there, but we + * can get away with just setting the FIFORDY bit for now. + */ + if (fEnable) + HDA_STREAM_REG(pThis, STS, pStreamShared->u8SD) |= HDA_SDSTS_FIFORDY; + else + HDA_STREAM_REG(pThis, STS, pStreamShared->u8SD) &= ~HDA_SDSTS_FIFORDY; + } + + LogFunc(("[SD%RU8] rc=%Rrc\n", pStreamShared->u8SD, rc)); + return rc; +} + +/** + * Marks the stream as started. + * + * Used after the stream has been enabled and the DMA timer has been armed. + */ +void hdaR3StreamMarkStarted(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, uint64_t tsNow) +{ + pStreamShared->State.tsLastReadNs = RTTimeNanoTS(); + pStreamShared->State.tsStart = tsNow; + Log3Func(("#%u: tsStart=%RU64 tsLastReadNs=%RU64\n", + pStreamShared->u8SD, pStreamShared->State.tsStart, pStreamShared->State.tsLastReadNs)); + RT_NOREF(pDevIns, pThis); +} + +/** + * Marks the stream as stopped. + */ +void hdaR3StreamMarkStopped(PHDASTREAM pStreamShared) +{ + Log3Func(("#%u\n", pStreamShared->u8SD)); + RT_NOREF(pStreamShared); +} + +#endif /* IN_RING3 */ +#if defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA) + +/** + * Updates an HDA stream's current read or write buffer position (depending on the stream type) by + * setting its associated LPIB register and DMA position buffer (if enabled) to an absolute value. + * + * @param pStreamShared HDA stream to update read / write position for (shared). + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param uLPIB Absolute position (in bytes) to set current read / write position to. + */ +static void hdaStreamSetPositionAbs(PHDASTREAM pStreamShared, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t uLPIB) +{ + AssertPtrReturnVoid(pStreamShared); + AssertMsgStmt(uLPIB <= pStreamShared->u32CBL, ("%#x\n", uLPIB), uLPIB = pStreamShared->u32CBL); + + Log3Func(("[SD%RU8] LPIB=%RU32 (DMA Position Buffer Enabled: %RTbool)\n", pStreamShared->u8SD, uLPIB, pThis->fDMAPosition)); + + /* Update LPIB in any case. */ + HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD) = uLPIB; + + /* Do we need to tell the current DMA position? */ + if (pThis->fDMAPosition) + { + /* + * Linux switched to using the position buffers some time during 2.6.x. + * 2.6.12 used LPIB, 2.6.17 defaulted to DMA position buffers, between + * the two version things were being changing quite a bit. + * + * Since 2.6.17, they will treat a zero DMA position value during the first + * period/IRQ as reason to fall back to LPIB mode (see azx_position_ok in + * 2.6.27+, and azx_pcm_pointer before that). They later also added + * UINT32_MAX to the values causing same. + * + * Since 2.6.35 azx_position_ok will read the wall clock register before + * determining the position. + */ + int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, + pThis->u64DPBase + (pStreamShared->u8SD * 2 * sizeof(uint32_t)), + (void *)&uLPIB, sizeof(uint32_t)); + AssertRC(rc2); + } +} + + +/** + * Updates an HDA stream's current read or write buffer position (depending on the stream type) by + * adding a value to its associated LPIB register and DMA position buffer (if enabled). + * + * @note Handles automatic CBL wrap-around. + * + * @param pStreamShared HDA stream to update read / write position for (shared). + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param cbToAdd Position (in bytes) to add to the current read / write position. + */ +static void hdaStreamSetPositionAdd(PHDASTREAM pStreamShared, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t cbToAdd) +{ + if (cbToAdd) /* No need to update anything if 0. */ + { + uint32_t const uCBL = pStreamShared->u32CBL; + if (uCBL) /* paranoia */ + { + uint32_t uNewLpid = HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD) + cbToAdd; +# if 1 /** @todo r=bird: this is wrong according to the spec */ + uNewLpid %= uCBL; +# else + /* The spec says it goes to CBL then wraps arpimd to 1, not back to zero. See 3.3.37. */ + if (uNewLpid > uCBL) + uNewLpid %= uCBL; +# endif + hdaStreamSetPositionAbs(pStreamShared, pDevIns, pThis, uNewLpid); + } + } +} + +#endif /* IN_RING3 || VBOX_HDA_WITH_ON_REG_ACCESS_DMA */ +#ifdef IN_RING3 + +/** + * Retrieves the available size of (buffered) audio data (in bytes) of a given HDA stream. + * + * @returns Available data (in bytes). + * @param pStreamR3 HDA stream to retrieve size for (ring-3). + */ +static uint32_t hdaR3StreamGetUsed(PHDASTREAMR3 pStreamR3) +{ + AssertPtrReturn(pStreamR3, 0); + + if (pStreamR3->State.pCircBuf) + return (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); + return 0; +} + +/** + * Retrieves the free size of audio data (in bytes) of a given HDA stream. + * + * @returns Free data (in bytes). + * @param pStreamR3 HDA stream to retrieve size for (ring-3). + */ +static uint32_t hdaR3StreamGetFree(PHDASTREAMR3 pStreamR3) +{ + AssertPtrReturn(pStreamR3, 0); + + if (pStreamR3->State.pCircBuf) + return (uint32_t)RTCircBufFree(pStreamR3->State.pCircBuf); + return 0; +} + +#endif /* IN_RING3 */ +#if defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA) + +/** + * Get the current address and number of bytes left in the current BDLE. + * + * @returns The current physical address. + * @param pStreamShared The stream to check. + * @param pcbLeft The number of bytes left at the returned address. + */ +DECLINLINE(RTGCPHYS) hdaStreamDmaBufGet(PHDASTREAM pStreamShared, uint32_t *pcbLeft) +{ + uint8_t idxBdle = pStreamShared->State.idxCurBdle; + AssertStmt(idxBdle < pStreamShared->State.cBdles, idxBdle = 0); + + uint32_t const cbCurBdl = pStreamShared->State.aBdl[idxBdle].cb; + uint32_t offCurBdle = pStreamShared->State.offCurBdle; + AssertStmt(pStreamShared->State.offCurBdle <= cbCurBdl, offCurBdle = cbCurBdl); + + *pcbLeft = cbCurBdl - offCurBdle; + return pStreamShared->State.aBdl[idxBdle].GCPhys + offCurBdle; +} + +/** + * Checks if the current BDLE is completed. + * + * @retval true if complete + * @retval false if not. + * @param pStreamShared The stream to check. + */ +DECLINLINE(bool) hdaStreamDmaBufIsComplete(PHDASTREAM pStreamShared) +{ + uint8_t const idxBdle = pStreamShared->State.idxCurBdle; + AssertReturn(idxBdle < pStreamShared->State.cBdles, true); + + uint32_t const cbCurBdl = pStreamShared->State.aBdl[idxBdle].cb; + uint32_t const offCurBdle = pStreamShared->State.offCurBdle; + Assert(offCurBdle <= cbCurBdl); + return offCurBdle >= cbCurBdl; +} + +/** + * Checks if the current BDLE needs a completion IRQ. + * + * @retval true if IRQ is needed. + * @retval false if not. + * @param pStreamShared The stream to check. + */ +DECLINLINE(bool) hdaStreamDmaBufNeedsIrq(PHDASTREAM pStreamShared) +{ + uint8_t const idxBdle = pStreamShared->State.idxCurBdle; + AssertReturn(idxBdle < pStreamShared->State.cBdles, false); + return (pStreamShared->State.aBdl[idxBdle].fFlags & HDA_BDLE_F_IOC) != 0; +} + +/** + * Advances the DMA engine to the next BDLE. + * + * @param pStreamShared The stream which DMA engine is to be updated. + */ +DECLINLINE(void) hdaStreamDmaBufAdvanceToNext(PHDASTREAM pStreamShared) +{ + uint8_t idxBdle = pStreamShared->State.idxCurBdle; + Assert(pStreamShared->State.offCurBdle == pStreamShared->State.aBdl[idxBdle].cb); + + if (idxBdle < pStreamShared->State.cBdles - 1) + idxBdle++; + else + idxBdle = 0; + pStreamShared->State.idxCurBdle = idxBdle; + pStreamShared->State.offCurBdle = 0; +} + +#endif /* defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA) */ +#ifdef IN_RING3 + +/** + * Common do-DMA prologue code. + * + * @retval true if DMA processing can take place + * @retval false if caller should return immediately. + * @param pThis The shared HDA device state. + * @param pStreamShared HDA stream to update (shared). + * @param pStreamR3 HDA stream to update (ring-3). + * @param uSD The stream ID (for asserting). + * @param tsNowNs The current RTTimeNano() value. + * @param pszFunction The function name (for logging). + */ +DECLINLINE(bool) hdaR3StreamDoDmaPrologue(PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD, + uint64_t tsNowNs, const char *pszFunction) +{ + RT_NOREF(uSD, pszFunction); + + /* + * Check if we should skip town... + */ + /* Stream not running (anymore)? */ + if (pStreamShared->State.fRunning) + { /* likely */ } + else + { + Log3(("%s: [SD%RU8] Not running, skipping transfer\n", pszFunction, uSD)); + return false; + } + + if (!(HDA_STREAM_REG(pThis, STS, uSD) & HDA_SDSTS_BCIS)) + { /* likely */ } + else + { + /** @todo r=bird: This is a bit fishy. We should make effort the reschedule + * the transfer immediately after the guest clears the interrupt. + * The same fishy code is present in AC'97 with just a little + * explanation as here, see @bugref{9890#c95}. + * + * The reasoning is probably that the developer noticed some windows + * versions don't like having their BCIS interrupts bundled. There were + * comments to that effect elsewhere, probably as a result of a fixed + * uTimerHz approach to DMA scheduling. However, pausing DMA for a + * period isn't going to help us with the host backends, as they don't + * pause and will want samples ASAP. So, we should at least unpause + * DMA as quickly as we possible when BCIS is cleared. We might even + * not skip it iff the DMA work here doesn't involve raising any IOC, + * which is possible although unlikely. */ + Log3(("%s: [SD%RU8] BCIS bit set, skipping transfer\n", pszFunction, uSD)); + STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaSkippedPendingBcis); + Log(("%s: [SD%RU8] BCIS bit set, skipping transfer\n", pszFunction, uSD)); +# ifdef HDA_STRICT + /* Timing emulation bug or guest is misbehaving -- let me know. */ + AssertMsgFailed(("%s: BCIS bit for stream #%RU8 still set when it shouldn't\n", pszFunction, uSD)); +# endif + return false; + } + + /* + * Stream sanity checks. + */ + /* Register sanity checks. */ + Assert(uSD < HDA_MAX_STREAMS); + Assert(pStreamShared->u64BDLBase); + Assert(pStreamShared->u32CBL); + Assert(pStreamShared->u8FIFOS); + + /* State sanity checks. */ + Assert(ASMAtomicReadBool(&pStreamShared->State.fInReset) == false); + Assert(ASMAtomicReadBool(&pStreamShared->State.fRunning)); + + /* + * Some timestamp stuff for logging/debugging. + */ + /*const uint64_t tsNowNs = RTTimeNanoTS();*/ + Log3(("%s: [SD%RU8] tsDeltaNs=%'RU64 ns\n", pszFunction, uSD, tsNowNs - pStreamShared->State.tsLastTransferNs)); + pStreamShared->State.tsLastTransferNs = tsNowNs; + + return true; +} + +/** + * Common do-DMA epilogue. + * + * @param pDevIns The device instance. + * @param pStreamShared The HDA stream (shared). + * @param pStreamR3 The HDA stream (ring-3). + */ +DECLINLINE(void) hdaR3StreamDoDmaEpilogue(PPDMDEVINS pDevIns, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3) +{ + /* + * We must update this in the epilogue rather than in the prologue + * as it is used for WALCLK calculation and we must make sure the + * guest doesn't think we've processed the current period till we + * actually have. + */ + pStreamShared->State.tsTransferLast = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); + + /* + * Update the buffer statistics. + */ + pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); +} + +#endif /* IN_RING3 */ + +#if defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA) +/** + * Completes a BDLE at the end of a DMA loop iteration, if possible. + * + * @retval true if buffer completed and new loaded. + * @retval false if buffer not completed. + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pStreamShared HDA stream to update (shared). + * @param pszFunction The function name (for logging). + */ +DECLINLINE(bool) hdaStreamDoDmaMaybeCompleteBuffer(PPDMDEVINS pDevIns, PHDASTATE pThis, + PHDASTREAM pStreamShared, const char *pszFunction) +{ + RT_NOREF(pszFunction); + + /* + * Is the buffer descriptor complete. + */ + if (hdaStreamDmaBufIsComplete(pStreamShared)) + { + Log3(("%s: [SD%RU8] Completed BDLE%u %#RX64 LB %#RX32 fFlags=%#x\n", pszFunction, pStreamShared->u8SD, + pStreamShared->State.idxCurBdle, pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].GCPhys, + pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].cb, + pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].fFlags)); + + /* Does the current BDLE require an interrupt to be sent? */ + if (hdaStreamDmaBufNeedsIrq(pStreamShared)) + { + /* If the IOCE ("Interrupt On Completion Enable") bit of the SDCTL + register is set we need to generate an interrupt. */ + if (HDA_STREAM_REG(pThis, CTL, pStreamShared->u8SD) & HDA_SDCTL_IOCE) + { + /* Assert the interrupt before actually fetching the next BDLE below. */ + pStreamShared->State.cTransferPendingInterrupts = 1; + Log3(("%s: [SD%RU8] Scheduling interrupt\n", pszFunction, pStreamShared->u8SD)); + + /* Trigger an interrupt first and let hdaRegWriteSDSTS() deal with + * ending / beginning of a period. */ + /** @todo r=bird: What does the above comment mean? */ + HDA_STREAM_REG(pThis, STS, pStreamShared->u8SD) |= HDA_SDSTS_BCIS; + HDA_PROCESS_INTERRUPT(pDevIns, pThis); + } + } + + /* + * Advance to the next BDLE. + */ + hdaStreamDmaBufAdvanceToNext(pStreamShared); + return true; + } + + Log3(("%s: [SD%RU8] Incomplete BDLE%u %#RX64 LB %#RX32 fFlags=%#x: off=%#RX32\n", pszFunction, pStreamShared->u8SD, + pStreamShared->State.idxCurBdle, pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].GCPhys, + pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].cb, + pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].fFlags, pStreamShared->State.offCurBdle)); + return false; +} +#endif /* IN_RING3 || VBOX_HDA_WITH_ON_REG_ACCESS_DMA */ + +#ifdef IN_RING3 + +/** + * Does DMA transfer for an HDA input stream. + * + * Reads audio data from the HDA stream's internal DMA buffer and writing to + * guest memory. + * + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pStreamShared HDA stream to update (shared). + * @param pStreamR3 HDA stream to update (ring-3). + * @param cbToConsume The max amount of data to consume from the + * internal DMA buffer. The caller will make sure + * this is always the transfer size fo the current + * period (unless something is seriously wrong). + * @param fWriteSilence Whether to feed the guest silence rather than + * fetching bytes from the internal DMA buffer. + * This is set initially while we pre-buffer a + * little bit of input, so we can better handle + * time catch-ups and other schduling fun. + * @param tsNowNs The current RTTimeNano() value. + * + * @remarks Caller owns the stream lock. + */ +static void hdaR3StreamDoDmaInput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, + PHDASTREAMR3 pStreamR3, uint32_t const cbToConsume, bool fWriteSilence, uint64_t tsNowNs) +{ + uint8_t const uSD = pStreamShared->u8SD; + LogFlowFunc(("ENTER - #%u cbToConsume=%#x%s\n", uSD, cbToConsume, fWriteSilence ? " silence" : "")); + + /* + * Common prologue. + */ + if (hdaR3StreamDoDmaPrologue(pThis, pStreamShared, pStreamR3, uSD, tsNowNs, "hdaR3StreamDoDmaInput")) + { /* likely */ } + else + return; + + /* + * + * The DMA copy loop. + * + * Note! Unaligned BDLEs shouldn't be a problem since the circular buffer + * doesn't care about alignment. Only, we have to read the rest + * of the incomplete frame from it ASAP. + */ + PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf; + uint32_t cbLeft = cbToConsume; + Assert(cbLeft == pStreamShared->State.cbCurDmaPeriod); + Assert(PDMAudioPropsIsSizeAligned(&pStreamShared->State.Cfg.Props, cbLeft)); + + while (cbLeft > 0) + { + STAM_PROFILE_START(&pThis->StatIn, a); + + /* + * Figure out how much we can read & write in this iteration. + */ + uint32_t cbChunk = 0; + RTGCPHYS GCPhys = hdaStreamDmaBufGet(pStreamShared, &cbChunk); + + if (cbChunk <= cbLeft) + { /* very likely */ } + else + cbChunk = cbLeft; + + uint32_t cbWritten = 0; + if (!fWriteSilence) + { + /* + * Write the host data directly into the guest buffers. + */ + while (cbChunk > 0) + { + /* Grab internal DMA buffer space and read into it. */ + void /*const*/ *pvBufSrc; + size_t cbBufSrc; + RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvBufSrc, &cbBufSrc); + AssertBreakStmt(cbBufSrc, RTCircBufReleaseReadBlock(pCircBuf, 0)); + + int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, GCPhys, pvBufSrc, cbBufSrc); + AssertRC(rc2); + +# ifdef HDA_DEBUG_SILENCE + fix me if relevant; +# endif + if (RT_LIKELY(!pStreamR3->Dbg.Runtime.pFileDMARaw)) + { /* likely */ } + else + AudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, pvBufSrc, cbBufSrc); + +# ifdef VBOX_WITH_DTRACE + VBOXDD_HDA_STREAM_DMA_IN((uint32_t)uSD, (uint32_t)cbBufSrc, pStreamShared->State.offRead); +# endif + pStreamShared->State.offRead += cbBufSrc; + RTCircBufReleaseReadBlock(pCircBuf, cbBufSrc); + STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbBufSrc); + + /* advance */ + cbChunk -= (uint32_t)cbBufSrc; + cbWritten += (uint32_t)cbBufSrc; + GCPhys += cbBufSrc; + pStreamShared->State.offCurBdle += (uint32_t)cbBufSrc; + } + } + /* + * Write silence. Since we only do signed formats, we can use the zero + * buffers from IPRT as source here. + */ + else + { + Assert(PDMAudioPropsIsSigned(&pStreamShared->State.Cfg.Props)); + while (cbChunk > 0) + { + /* Write it to the guest buffer. */ + uint32_t cbToWrite = RT_MIN(sizeof(g_abRTZero64K), cbChunk); + int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, GCPhys, g_abRTZero64K, cbToWrite); + AssertRC(rc2); + STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbToWrite); + + /* advance */ + cbWritten += cbToWrite; + cbChunk -= cbToWrite; + GCPhys += cbToWrite; + pStreamShared->State.offCurBdle += cbToWrite; + } + } + + cbLeft -= cbWritten; + STAM_PROFILE_STOP(&pThis->StatIn, a); + + /* + * Complete the buffer if necessary (common with the output DMA code). + * + * Must update the DMA position before we do this as the buffer IRQ may + * fire on another vCPU and run in parallel to us, although it is very + * unlikely it can make much progress as long as we're sitting on the + * lock, it could still read the DMA position (Linux won't, as it reads + * WALCLK and possibly SDnSTS before the DMA position). + */ + hdaStreamSetPositionAdd(pStreamShared, pDevIns, pThis, cbWritten); + hdaStreamDoDmaMaybeCompleteBuffer(pDevIns, pThis, pStreamShared, "hdaR3StreamDoDmaInput"); + } + + Assert(cbLeft == 0); /* There shall be no break statements in the above loop, so cbLeft is always zero here! */ + + /* + * Common epilogue. + */ + hdaR3StreamDoDmaEpilogue(pDevIns, pStreamShared, pStreamR3); + + /* + * Log and leave. + */ + Log3Func(("LEAVE - [SD%RU8] %#RX32/%#RX32 @ %#RX64 - cTransferPendingInterrupts=%RU8\n", + uSD, cbToConsume, pStreamShared->State.cbCurDmaPeriod, pStreamShared->State.offRead - cbToConsume, + pStreamShared->State.cTransferPendingInterrupts)); +} + + +/** + * Input streams: Pulls data from the mixer, putting it in the internal DMA + * buffer. + * + * @param pStreamShared HDA stream to update (shared). + * @param pStreamR3 HDA stream to update (ring-3 bits). + * @param pSink The mixer sink to pull from. + */ +static void hdaR3StreamPullFromMixer(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PAUDMIXSINK pSink) +{ +# ifdef LOG_ENABLED + uint64_t const offWriteOld = pStreamShared->State.offWrite; +# endif + pStreamShared->State.offWrite = AudioMixerSinkTransferToCircBuf(pSink, + pStreamR3->State.pCircBuf, + pStreamShared->State.offWrite, + pStreamR3->u8SD, + pStreamR3->Dbg.Runtime.fEnabled + ? pStreamR3->Dbg.Runtime.pFileStream : NULL); + + Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD, + pStreamShared->State.offWrite - offWriteOld, pStreamShared->State.offWrite)); + + /* Update buffer stats. */ + pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); +} + + +/** + * Does DMA transfer for an HDA output stream. + * + * This transfers one DMA timer period worth of data from the guest and into the + * internal DMA buffer. + * + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pStreamShared HDA stream to update (shared). + * @param pStreamR3 HDA stream to update (ring-3). + * @param cbToProduce The max amount of data to produce (i.e. put into + * the circular buffer). Unless something is going + * seriously wrong, this will always be transfer + * size for the current period. + * @param tsNowNs The current RTTimeNano() value. + * + * @remarks Caller owns the stream lock. + */ +static void hdaR3StreamDoDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, + PHDASTREAMR3 pStreamR3, uint32_t const cbToProduce, uint64_t tsNowNs) +{ + uint8_t const uSD = pStreamShared->u8SD; + LogFlowFunc(("ENTER - #%u cbToProduce=%#x\n", uSD, cbToProduce)); + + /* + * Common prologue. + */ + if (hdaR3StreamDoDmaPrologue(pThis, pStreamShared, pStreamR3, uSD, tsNowNs, "hdaR3StreamDoDmaOutput")) + { /* likely */ } + else + return; + + /* + * + * The DMA copy loop. + * + * Note! Unaligned BDLEs shouldn't be a problem since the circular buffer + * doesn't care about alignment. Only, we have to write the rest + * of the incomplete frame to it ASAP. + */ + PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf; + uint32_t cbLeft = cbToProduce; +# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA + Assert(cbLeft <= pStreamShared->State.cbCurDmaPeriod); /* a little pointless with the DMA'ing on LPIB read. */ +# else + Assert(cbLeft == pStreamShared->State.cbCurDmaPeriod); +# endif + Assert(PDMAudioPropsIsSizeAligned(&pStreamShared->State.Cfg.Props, cbLeft)); + + while (cbLeft > 0) + { + STAM_PROFILE_START(&pThis->StatOut, a); + + /* + * Figure out how much we can read & write in this iteration. + */ + uint32_t cbChunk = 0; + RTGCPHYS GCPhys = hdaStreamDmaBufGet(pStreamShared, &cbChunk); + + if (cbChunk <= cbLeft) + { /* very likely */ } + else + cbChunk = cbLeft; + + /* + * Read the guest data directly into the internal DMA buffer. + */ + uint32_t cbRead = 0; + while (cbChunk > 0) + { + /* Grab internal DMA buffer space and read into it. */ + void *pvBufDst; + size_t cbBufDst; + RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvBufDst, &cbBufDst); + AssertBreakStmt(cbBufDst, RTCircBufReleaseWriteBlock(pCircBuf, 0)); + + int rc2 = PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pvBufDst, cbBufDst); + AssertRC(rc2); + +# ifdef HDA_DEBUG_SILENCE + fix me if relevant; +# endif + if (RT_LIKELY(!pStreamR3->Dbg.Runtime.pFileDMARaw)) + { /* likely */ } + else + AudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, pvBufDst, cbBufDst); + +# ifdef VBOX_WITH_DTRACE + VBOXDD_HDA_STREAM_DMA_OUT((uint32_t)uSD, (uint32_t)cbBufDst, pStreamShared->State.offWrite); +# endif + pStreamShared->State.offWrite += cbBufDst; + RTCircBufReleaseWriteBlock(pCircBuf, cbBufDst); + STAM_COUNTER_ADD(&pThis->StatBytesRead, cbBufDst); + + /* advance */ + cbChunk -= (uint32_t)cbBufDst; + cbRead += (uint32_t)cbBufDst; + GCPhys += cbBufDst; + pStreamShared->State.offCurBdle += (uint32_t)cbBufDst; + } + + cbLeft -= cbRead; + STAM_PROFILE_STOP(&pThis->StatOut, a); + + /* + * Complete the buffer if necessary (common with the input DMA code). + * + * Must update the DMA position before we do this as the buffer IRQ may + * fire on another vCPU and run in parallel to us, although it is very + * unlikely it can make much progress as long as we're sitting on the + * lock, it could still read the DMA position (Linux won't, as it reads + * WALCLK and possibly SDnSTS before the DMA position). + */ + hdaStreamSetPositionAdd(pStreamShared, pDevIns, pThis, cbRead); + hdaStreamDoDmaMaybeCompleteBuffer(pDevIns, pThis, pStreamShared, "hdaR3StreamDoDmaOutput"); + } + + Assert(cbLeft == 0); /* There shall be no break statements in the above loop, so cbLeft is always zero here! */ + + /* + * Common epilogue. + */ + hdaR3StreamDoDmaEpilogue(pDevIns, pStreamShared, pStreamR3); + + /* + * Log and leave. + */ + Log3Func(("LEAVE - [SD%RU8] %#RX32/%#RX32 @ %#RX64 - cTransferPendingInterrupts=%RU8\n", + uSD, cbToProduce, pStreamShared->State.cbCurDmaPeriod, pStreamShared->State.offWrite - cbToProduce, + pStreamShared->State.cTransferPendingInterrupts)); +} + +#endif /* IN_RING3 */ +#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA + +/** + * Do DMA output transfer on LPIB/WALCLK register access. + * + * @returns VINF_SUCCESS or VINF_IOM_R3_MMIO_READ. + * @param pDevIns The device instance. + * @param pThis The shared instance data. + * @param pStreamShared The shared stream data. + * @param tsNow The current time on the timer clock. + * @param cbToTransfer How much to transfer. + */ +VBOXSTRICTRC hdaStreamDoOnAccessDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, + uint64_t tsNow, uint32_t cbToTransfer) +{ + AssertReturn(cbToTransfer > 0, VINF_SUCCESS); + int rc = VINF_SUCCESS; + + /* + * Check if we're exceeding the available buffer, go to ring-3 to + * handle that (we would perhaps always take this path when in ring-3). + */ + uint32_t cbDma = pStreamShared->State.cbDma; + ASMCompilerBarrier(); + if ( cbDma >= sizeof(pStreamShared->State.abDma) /* paranoia */ + || cbToTransfer >= sizeof(pStreamShared->State.abDma) /* paranoia */ + || cbDma + cbToTransfer > sizeof(pStreamShared->State.abDma)) + { +# ifndef IN_RING3 + STAM_REL_COUNTER_INC(&pThis->StatAccessDmaOutputToR3); + LogFlowFunc(("[SD%RU8] out of DMA buffer space (%#x, need %#x) -> VINF_IOM_R3_MMIO_READ\n", + pStreamShared->u8SD, sizeof(pStreamShared->State.abDma) - pStreamShared->State.cbDma, cbToTransfer)); + return VINF_IOM_R3_MMIO_READ; +# else /* IN_RING3 */ + /* + * Flush the bounce buffer, then do direct transfers to the + * internal DMA buffer (updates LPIB). + */ + PHDASTATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + uintptr_t const idxStream = pStreamShared->u8SD; + AssertReturn(idxStream < RT_ELEMENTS(pThisCC->aStreams), VERR_INTERNAL_ERROR_4); + PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[idxStream]; + + hdaR3StreamFlushDmaBounceBufferOutput(pStreamShared, pStreamR3); + + uint32_t cbStreamFree = hdaR3StreamGetFree(pStreamR3); + if (cbStreamFree >= cbToTransfer) + { /* likely */ } + else + { + PAUDMIXSINK pSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL; + if (pSink) + cbStreamFree = hdaR3StreamHandleDmaBufferOverrun(pStreamShared, pStreamR3, pSink, cbToTransfer, RTTimeNanoTS(), + "hdaStreamDoOnAccessDmaOutput", cbStreamFree); + else + { + LogFunc(("[SD%RU8] No sink and insufficient internal DMA buffer space (%#x) - won't do anything\n", + pStreamShared->u8SD, cbStreamFree)); + return VINF_SUCCESS; + } + cbToTransfer = RT_MIN(cbToTransfer, cbStreamFree); + if (cbToTransfer < PDMAudioPropsFrameSize(&pStreamShared->State.Cfg.Props)) + { + LogFunc(("[SD%RU8] No internal DMA buffer space (%#x) - won't do anything\n", pStreamShared->u8SD, cbStreamFree)); + return VINF_SUCCESS; + } + } + hdaR3StreamDoDmaOutput(pDevIns, pThis, pStreamShared, pStreamR3, cbToTransfer, RTTimeNanoTS()); + pStreamShared->State.cbDmaTotal += cbToTransfer; +# endif /* IN_RING3 */ + } + else + { + /* + * Transfer into the DMA bounce buffer. + */ + LogFlowFunc(("[SD%RU8] Transfering %#x bytes to DMA bounce buffer (cbDma=%#x cbDmaTotal=%#x) (%p/%u)\n", + pStreamShared->u8SD, cbToTransfer, cbDma, pStreamShared->State.cbDmaTotal, pStreamShared, pStreamShared->u8SD)); + uint32_t cbLeft = cbToTransfer; + do + { + uint32_t cbChunk = 0; + RTGCPHYS GCPhys = hdaStreamDmaBufGet(pStreamShared, &cbChunk); + + bool fMustAdvanceBuffer; + if (cbLeft < cbChunk) + { + fMustAdvanceBuffer = false; + cbChunk = cbLeft; + } + else + fMustAdvanceBuffer = true; + + /* Read the guest data directly into the DMA bounce buffer. */ + int rc2 = PDMDevHlpPCIPhysRead(pDevIns, GCPhys, &pStreamShared->State.abDma[cbDma], cbChunk); + AssertRC(rc2); + + /* We update offWrite and StatBytesRead here even if we haven't moved the data + to the internal DMA buffer yet, because we want the dtrace even to fire here. */ +# ifdef VBOX_WITH_DTRACE + VBOXDD_HDA_STREAM_DMA_OUT((uint32_t)pStreamShared->u8SD, cbChunk, pStreamShared->State.offWrite); +# endif + pStreamShared->State.offWrite += cbChunk; + STAM_COUNTER_ADD(&pThis->StatBytesRead, cbChunk); + + /* advance */ + pStreamShared->State.offCurBdle += cbChunk; + pStreamShared->State.cbDmaTotal += cbChunk; + cbDma += cbChunk; + pStreamShared->State.cbDma = cbDma; + cbLeft -= cbChunk; + Log6Func(("cbLeft=%#x cbDma=%#x cbDmaTotal=%#x offCurBdle=%#x idxCurBdle=%#x (%p/%u)\n", + cbLeft, cbDma, pStreamShared->State.cbDmaTotal, pStreamShared->State.offCurBdle, + pStreamShared->State.idxCurBdle, pStreamShared, pStreamShared->u8SD)); + + /* Next buffer. */ + bool fAdvanced = hdaStreamDoDmaMaybeCompleteBuffer(pDevIns, pThis, pStreamShared, "hdaStreamDoOnAccessDmaOutput"); + AssertMsgStmt(fMustAdvanceBuffer == fAdvanced, ("%d %d\n", fMustAdvanceBuffer, fAdvanced), rc = VERR_INTERNAL_ERROR_3); + } while (cbLeft > 0); + + /* + * Advance LPIB and update the last transfer time (for WALCLK). + */ + pStreamShared->State.tsTransferLast = tsNow; + hdaStreamSetPositionAdd(pStreamShared, pDevIns, pThis, cbToTransfer - cbLeft); + } + +# ifdef VBOX_STRICT + uint32_t idxSched = pStreamShared->State.idxSchedule; + AssertStmt(idxSched < RT_MIN(RT_ELEMENTS(pStreamShared->State.aSchedule), pStreamShared->State.cSchedule), idxSched = 0); + uint32_t const cbPeriod = pStreamShared->State.aSchedule[idxSched].cbPeriod; + AssertMsg(pStreamShared->State.cbDmaTotal < cbPeriod, ("%#x vs %#x\n", pStreamShared->State.cbDmaTotal, cbPeriod)); +# endif + + STAM_REL_COUNTER_INC(&pThis->StatAccessDmaOutput); + return rc; +} + + +/** + * Consider doing DMA output transfer on LPIB/WALCLK register access. + * + * @returns VINF_SUCCESS or VINF_IOM_R3_MMIO_READ. + * @param pDevIns The device instance. + * @param pThis The shared instance data. + * @param pStreamShared The shared stream data. + * @param tsNow The current time on the timer clock. Used to do the + * calculation. + */ +VBOXSTRICTRC hdaStreamMaybeDoOnAccessDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, uint64_t tsNow) +{ + Assert(pStreamShared->State.fRunning); /* caller should check this */ + + /* + * Calculate where the DMA engine should be according to the clock, if we can. + */ + uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamShared->State.Cfg.Props); + uint32_t const cbPeriod = pStreamShared->State.cbCurDmaPeriod; + if (cbPeriod > cbFrame) + { + AssertMsg(pStreamShared->State.cbDmaTotal < cbPeriod, ("%#x vs %#x\n", pStreamShared->State.cbDmaTotal, cbPeriod)); + uint64_t const tsTransferNext = pStreamShared->State.tsTransferNext; + uint32_t cbFuture; + if (tsNow < tsTransferNext) + { + /** @todo ASSUMES nanosecond clock ticks, need to make this + * resolution independent. */ + cbFuture = PDMAudioPropsNanoToBytes(&pStreamShared->State.Cfg.Props, tsTransferNext - tsNow); + cbFuture = RT_MIN(cbFuture, cbPeriod - cbFrame); + } + else + { + /* We've hit/overshot the timer deadline. Return to ring-3 if we're + not already there to increase the chance that we'll help expidite + the timer. If we're already in ring-3, do all but the last frame. */ +# ifndef IN_RING3 + LogFunc(("[SD%RU8] DMA period expired: tsNow=%RU64 >= tsTransferNext=%RU64 -> VINF_IOM_R3_MMIO_READ\n", + tsNow, tsTransferNext)); + return VINF_IOM_R3_MMIO_READ; +# else + cbFuture = cbPeriod - cbFrame; + LogFunc(("[SD%RU8] DMA period expired: tsNow=%RU64 >= tsTransferNext=%RU64 -> cbFuture=%#x (cbPeriod=%#x - cbFrame=%#x)\n", + tsNow, tsTransferNext, cbFuture, cbPeriod, cbFrame)); +# endif + } + uint32_t const offNow = PDMAudioPropsFloorBytesToFrame(&pStreamShared->State.Cfg.Props, cbPeriod - cbFuture); + + /* + * Should we transfer a little? Minimum is 64 bytes (semi-random, + * suspect real hardware might be doing some cache aligned stuff, + * which might soon get complicated if you take unaligned buffers + * into consideration and which cache line size (128 bytes is just + * as likely as 64 or 32 bytes)). + */ + uint32_t cbDmaTotal = pStreamShared->State.cbDmaTotal; + if (cbDmaTotal + 64 <= offNow) + { +# ifdef LOG_ENABLED + uint32_t const uOldLpib = HDA_STREAM_REG(pThis, CBL, pStreamShared->u8SD); +# endif + VBOXSTRICTRC rcStrict = hdaStreamDoOnAccessDmaOutput(pDevIns, pThis, pStreamShared, tsNow, offNow - cbDmaTotal); + LogFlowFunc(("[SD%RU8] LPIB=%#RX32 -> LPIB=%#RX32 offNow=%#x rcStrict=%Rrc\n", pStreamShared->u8SD, + uOldLpib, HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD), offNow, VBOXSTRICTRC_VAL(rcStrict) )); + return rcStrict; + } + + /* + * Do nothing. + */ + LogFlowFunc(("[SD%RU8] Skipping DMA transfer: cbDmaTotal=%#x offNow=%#x\n", pStreamShared->u8SD, cbDmaTotal, offNow)); + } + else + LogFunc(("[SD%RU8] cbPeriod=%#x <= cbFrame=%#x\n", pStreamShared->u8SD, cbPeriod, cbFrame)); + return VINF_SUCCESS; +} + +#endif /* VBOX_HDA_WITH_ON_REG_ACCESS_DMA */ +#ifdef IN_RING3 + +/** + * Output streams: Pushes data to the mixer. + * + * @param pStreamShared HDA stream to update (shared bits). + * @param pStreamR3 HDA stream to update (ring-3 bits). + * @param pSink The mixer sink to push to. + * @param nsNow The current RTTimeNanoTS() value. + */ +static void hdaR3StreamPushToMixer(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PAUDMIXSINK pSink, uint64_t nsNow) +{ +# ifdef LOG_ENABLED + uint64_t const offReadOld = pStreamShared->State.offRead; +# endif + pStreamShared->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink, + pStreamR3->State.pCircBuf, + pStreamShared->State.offRead, + pStreamR3->u8SD, + pStreamR3->Dbg.Runtime.fEnabled + ? pStreamR3->Dbg.Runtime.pFileStream : NULL); + + Assert(nsNow >= pStreamShared->State.tsLastReadNs); + Log3Func(("[SD%RU8] nsDeltaLastRead=%RI64 transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD, + nsNow - pStreamShared->State.tsLastReadNs, pStreamShared->State.offRead - offReadOld, pStreamShared->State.offRead)); + RT_NOREF(pStreamShared, nsNow); + + /* Update buffer stats. */ + pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); +} + + +/** + * Deals with a DMA buffer overrun. + * + * Makes sure we return with @a cbNeeded bytes of free space in pCircBuf. + * + * @returns Number of bytes free in the internal DMA buffer. + * @param pStreamShared The shared data for the HDA stream. + * @param pStreamR3 The ring-3 data for the HDA stream. + * @param pSink The mixer sink (valid). + * @param cbNeeded How much space we need (in bytes). + * @param nsNow Current RTNanoTimeTS() timestamp. + * @param cbStreamFree The current amount of free buffer space. + * @param pszCaller The caller (for logging). + */ +static uint32_t hdaR3StreamHandleDmaBufferOverrun(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PAUDMIXSINK pSink, + uint32_t cbNeeded, uint64_t nsNow, + const char *pszCaller, uint32_t const cbStreamFree) +{ + STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowProblems); + Log(("%s: Warning! Stream #%u has insufficient space free: %#x bytes, need %#x. Will try move data out of the buffer...\n", + pszCaller, pStreamShared->u8SD, cbStreamFree, cbNeeded)); + RT_NOREF(pszCaller, cbStreamFree); + + int rc = AudioMixerSinkTryLock(pSink); + if (RT_SUCCESS(rc)) + { + hdaR3StreamPushToMixer(pStreamShared, pStreamR3, pSink, nsNow); + AudioMixerSinkUpdate(pSink, 0, 0); + AudioMixerSinkUnlock(pSink); + } + else + RTThreadYield(); + + uint32_t const cbRet = hdaR3StreamGetFree(pStreamR3); + Log(("%s: Gained %u bytes.\n", pszCaller, cbRet - cbStreamFree)); + if (cbRet >= cbNeeded) + return cbRet; + + /* + * Unable to make sufficient space. Drop the whole buffer content. + * + * This is needed in order to keep the device emulation running at a + * constant rate, at the cost of losing valid (but too much) data. + */ + STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowErrors); + LogRel2(("HDA: Warning: Hit stream #%RU8 overflow, dropping %u bytes of audio data (%s)\n", + pStreamShared->u8SD, hdaR3StreamGetUsed(pStreamR3), pszCaller)); +# ifdef HDA_STRICT + AssertMsgFailed(("Hit stream #%RU8 overflow -- timing bug?\n", pStreamShared->u8SD)); +# endif +/** + * + * @todo r=bird: I don't think RTCircBufReset is entirely safe w/o + * owning the AIO lock. See the note in the documentation about it not being + * multi-threading aware (safe). Wish I'd verified this code much earlier. + * Sigh^3! + * + */ + RTCircBufReset(pStreamR3->State.pCircBuf); + pStreamShared->State.offWrite = 0; + pStreamShared->State.offRead = 0; + return hdaR3StreamGetFree(pStreamR3); +} + + +# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA +/** + * Flushes the DMA bounce buffer content to the internal DMA buffer. + * + * @param pStreamShared The shared data of the stream to have its DMA bounce + * buffer flushed. + * @param pStreamR3 The ring-3 stream data for same. + */ +static void hdaR3StreamFlushDmaBounceBufferOutput(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3) +{ + uint32_t cbDma = pStreamShared->State.cbDma; + LogFlowFunc(("cbDma=%#x\n", cbDma)); + if (cbDma) + { + AssertReturnVoid(cbDma <= sizeof(pStreamShared->State.abDma)); + PRTCIRCBUF const pCircBuf = pStreamR3->State.pCircBuf; + if (pCircBuf) + { + uint32_t offDma = 0; + while (offDma < cbDma) + { + uint32_t const cbSrcLeft = cbDma - offDma; + + /* + * Grab a chunk of the internal DMA buffer. + */ + void *pvBufDst = NULL; + size_t cbBufDst = 0; + RTCircBufAcquireWriteBlock(pCircBuf, cbSrcLeft, &pvBufDst, &cbBufDst); + if (cbBufDst > 0) + { /* likely */ } + else + { + /* We've got buffering trouble. */ + RTCircBufReleaseWriteBlock(pCircBuf, 0); + + PAUDMIXSINK pSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL; + if (pSink) + hdaR3StreamHandleDmaBufferOverrun(pStreamShared, pStreamR3, pSink, cbSrcLeft, RTTimeNanoTS(), + "hdaR3StreamFlushDmaBounceBufferOutput", 0 /*cbStreamFree*/); + else + { + LogFunc(("Stream #%u has no sink. Dropping the rest of the data\n", pStreamR3->u8SD)); + break; + } + + RTCircBufAcquireWriteBlock(pCircBuf, cbSrcLeft, &pvBufDst, &cbBufDst); + AssertBreakStmt(cbBufDst, RTCircBufReleaseWriteBlock(pCircBuf, 0)); + } + + /* + * Copy the samples into it and write it to the debug file if open. + * + * We do not fire the dtrace probe here nor update offRead as that was + * done already (not sure that was a good idea?). + */ + memcpy(pvBufDst, &pStreamShared->State.abDma[offDma], cbBufDst); + + if (RT_LIKELY(!pStreamR3->Dbg.Runtime.pFileDMARaw)) + { /* likely */ } + else + AudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, pvBufDst, cbBufDst); + + RTCircBufReleaseWriteBlock(pCircBuf, cbBufDst); + + offDma += (uint32_t)cbBufDst; + } + } + + /* + * Mark the buffer empty. + */ + pStreamShared->State.cbDma = 0; + } +} +# endif /* VBOX_HDA_WITH_ON_REG_ACCESS_DMA */ + + +/** + * The stream's main function when called by the timer. + * + * @note This function also will be called without timer invocation when + * starting (enabling) the stream to minimize startup latency. + * + * @returns Current timer time if the timer is enabled, otherwise zero. + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pThisCC The ring-3 HDA device state. + * @param pStreamShared HDA stream to update (shared bits). + * @param pStreamR3 HDA stream to update (ring-3 bits). + */ +uint64_t hdaR3StreamTimerMain(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, + PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3) +{ + Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + Assert(PDMDevHlpTimerIsLockOwner(pDevIns, pStreamShared->hTimer)); + + /* Do the work: */ + hdaR3StreamUpdateDma(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3); + + /* Re-arm the timer if the sink is still active: */ + if ( pStreamShared->State.fRunning + && pStreamR3->pMixSink + && AudioMixerSinkIsActive(pStreamR3->pMixSink->pMixSink)) + { + /* Advance the schduling: */ + uint32_t idxSched = pStreamShared->State.idxSchedule; + AssertStmt(idxSched < RT_ELEMENTS(pStreamShared->State.aSchedule), idxSched = 0); + uint32_t idxLoop = pStreamShared->State.idxScheduleLoop + 1; + if (idxLoop >= pStreamShared->State.aSchedule[idxSched].cLoops) + { + idxSched += 1; + if ( idxSched >= pStreamShared->State.cSchedule + || idxSched >= RT_ELEMENTS(pStreamShared->State.aSchedule) /*paranoia^2*/) + { + idxSched = pStreamShared->State.cSchedulePrologue; + AssertStmt(idxSched < RT_ELEMENTS(pStreamShared->State.aSchedule), idxSched = 0); + } + pStreamShared->State.idxSchedule = idxSched; + idxLoop = 0; + } + pStreamShared->State.idxScheduleLoop = (uint16_t)idxLoop; + + /* Do the actual timer re-arming. */ + uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); /* (For virtual sync this remains the same for the whole callout IIRC) */ + uint64_t const tsTransferNext = tsNow + pStreamShared->State.aSchedule[idxSched].cPeriodTicks; + Log3Func(("[SD%RU8] fSinkActive=true, tsTransferNext=%RU64 (in %RU64)\n", + pStreamShared->u8SD, tsTransferNext, tsTransferNext - tsNow)); + int rc = PDMDevHlpTimerSet(pDevIns, pStreamShared->hTimer, tsTransferNext); + AssertRC(rc); + + /* Some legacy stuff: */ + pStreamShared->State.tsTransferNext = tsTransferNext; + pStreamShared->State.cbCurDmaPeriod = pStreamShared->State.aSchedule[idxSched].cbPeriod; + + return tsNow; + } + + Log3Func(("[SD%RU8] fSinkActive=false\n", pStreamShared->u8SD)); + return 0; +} + + +/** + * Updates a HDA stream by doing DMA transfers. + * + * Will do mixer transfers too to try fix an overrun/underrun situation. + * + * The host sink(s) set the overall pace (bird: no it doesn't, the DMA timer + * does - we just hope like heck it matches the speed at which the *backend* + * host audio driver processes samples). + * + * @param pDevIns The device instance. + * @param pThis The shared HDA device state. + * @param pThisCC The ring-3 HDA device state. + * @param pStreamShared HDA stream to update (shared bits). + * @param pStreamR3 HDA stream to update (ring-3 bits). + */ +static void hdaR3StreamUpdateDma(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, + PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3) +{ + RT_NOREF(pThisCC); + int rc2; + + /* + * Make sure we're running and got an active mixer sink. + */ + if (RT_LIKELY(pStreamShared->State.fRunning)) + { /* likely */ } + else + return; + + PAUDMIXSINK pSink = NULL; + if (pStreamR3->pMixSink) + pSink = pStreamR3->pMixSink->pMixSink; + if (RT_LIKELY(AudioMixerSinkIsActive(pSink))) + { /* likely */ } + else + return; + + /* + * Get scheduling info common to both input and output streams. + */ + const uint64_t tsNowNs = RTTimeNanoTS(); + uint32_t idxSched = pStreamShared->State.idxSchedule; + AssertStmt(idxSched < RT_MIN(RT_ELEMENTS(pStreamShared->State.aSchedule), pStreamShared->State.cSchedule), idxSched = 0); + uint32_t cbPeriod = pStreamShared->State.aSchedule[idxSched].cbPeriod; + + /* + * Output streams (SDO). + */ + if (hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_OUT) + { +# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA + /* Subtract already transferred bytes and flush the DMA bounce buffer. */ + uint32_t cbDmaTotal = pStreamShared->State.cbDmaTotal; + if (cbDmaTotal > 0) + { + AssertStmt(cbDmaTotal < cbPeriod, cbDmaTotal = cbPeriod); + cbPeriod -= cbDmaTotal; + pStreamShared->State.cbDmaTotal = 0; + hdaR3StreamFlushDmaBounceBufferOutput(pStreamShared, pStreamR3); + } + else + Assert(pStreamShared->State.cbDma == 0); +# endif + + /* + * Check how much room we have in our DMA buffer. There should be at + * least one period worth of space there or we're in an overflow situation. + */ + uint32_t cbStreamFree = hdaR3StreamGetFree(pStreamR3); + if (cbStreamFree >= cbPeriod) + { /* likely */ } + else + cbStreamFree = hdaR3StreamHandleDmaBufferOverrun(pStreamShared, pStreamR3, pSink, cbPeriod, tsNowNs, + "hdaR3StreamUpdateDma", cbStreamFree); + + /* + * Do the DMA transfer. + */ + uint64_t const offWriteBefore = pStreamShared->State.offWrite; + hdaR3StreamDoDmaOutput(pDevIns, pThis, pStreamShared, pStreamR3, RT_MIN(cbStreamFree, cbPeriod), tsNowNs); + + /* + * Should we push data to down thru the mixer to and to the host drivers? + */ + bool fKickAioThread = pStreamShared->State.offWrite > offWriteBefore + || hdaR3StreamGetFree(pStreamR3) < pStreamShared->State.cbAvgTransfer * 2; + + Log3Func(("msDelta=%RU64 (vs %u) cbStreamFree=%#x (vs %#x) => fKickAioThread=%RTbool\n", + (tsNowNs - pStreamShared->State.tsLastReadNs) / RT_NS_1MS, + pStreamShared->State.Cfg.Device.cMsSchedulingHint, cbStreamFree, + pStreamShared->State.cbAvgTransfer * 2, fKickAioThread)); + + if (fKickAioThread) + { + /* Notify the async I/O worker thread that there's work to do. */ + Log5Func(("Notifying AIO thread\n")); + rc2 = AudioMixerSinkSignalUpdateJob(pSink); + AssertRC(rc2); + /* Update last read timestamp for logging/debugging. */ + pStreamShared->State.tsLastReadNs = tsNowNs; + } + } + /* + * Input stream (SDI). + */ + else + { + Assert(hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_IN); + + /* + * See how much data we've got buffered... + */ + bool fWriteSilence = false; + uint32_t cbStreamUsed = hdaR3StreamGetUsed(pStreamR3); + if (pStreamShared->State.fInputPreBuffered && cbStreamUsed >= cbPeriod) + { /*likely*/ } + /* + * Because it may take a while for the input stream to get going (at + * least with pulseaudio), we feed the guest silence till we've + * pre-buffer a reasonable amount of audio. + */ + else if (!pStreamShared->State.fInputPreBuffered) + { + if (cbStreamUsed < pStreamShared->State.cbInputPreBuffer) + { + Log3(("hdaR3StreamUpdateDma: Pre-buffering (got %#x out of %#x bytes)...\n", + cbStreamUsed, pStreamShared->State.cbInputPreBuffer)); + fWriteSilence = true; + } + else + { + Log3(("hdaR3StreamUpdateDma: Completed pre-buffering (got %#x, needed %#x bytes).\n", + cbStreamUsed, pStreamShared->State.cbInputPreBuffer)); + pStreamShared->State.fInputPreBuffered = true; + fWriteSilence = true; /* For now, just do the most conservative thing. */ + } + cbStreamUsed = cbPeriod; + } + /* + * When we're low on data, we must really try fetch some ourselves + * as buffer underruns must not happen. + */ + else + { + /** @todo We're ending up here to frequently with pulse audio at least (just + * watch the stream stats in the statistcs viewer, and way to often we + * have to inject silence bytes. I suspect part of the problem is + * that the HDA device require a much better latency than what the + * pulse audio is configured for by default (10 ms vs 150ms). */ + STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowProblems); + Log(("hdaR3StreamUpdateDma: Warning! Stream #%u has insufficient data available: %u bytes, need %u. Will try move pull more data into the buffer...\n", + pStreamShared->u8SD, cbStreamUsed, cbPeriod)); + int rc = AudioMixerSinkTryLock(pSink); + if (RT_SUCCESS(rc)) + { + AudioMixerSinkUpdate(pSink, cbStreamUsed, cbPeriod); + hdaR3StreamPullFromMixer(pStreamShared, pStreamR3, pSink); + AudioMixerSinkUnlock(pSink); + } + else + RTThreadYield(); + Log(("hdaR3StreamUpdateDma: Gained %u bytes.\n", hdaR3StreamGetUsed(pStreamR3) - cbStreamUsed)); + cbStreamUsed = hdaR3StreamGetUsed(pStreamR3); + if (cbStreamUsed < cbPeriod) + { + /* Unable to find sufficient input data by simple prodding. + In order to keep a constant byte stream following thru the DMA + engine into the guest, we will try again and then fall back on + filling the gap with silence. */ + uint32_t cbSilence = 0; + do + { + AudioMixerSinkLock(pSink); + + cbStreamUsed = hdaR3StreamGetUsed(pStreamR3); + if (cbStreamUsed < cbPeriod) + { + hdaR3StreamPullFromMixer(pStreamShared, pStreamR3, pSink); + cbStreamUsed = hdaR3StreamGetUsed(pStreamR3); + while (cbStreamUsed < cbPeriod) + { + void *pvDstBuf; + size_t cbDstBuf; + RTCircBufAcquireWriteBlock(pStreamR3->State.pCircBuf, cbPeriod - cbStreamUsed, + &pvDstBuf, &cbDstBuf); + RT_BZERO(pvDstBuf, cbDstBuf); + RTCircBufReleaseWriteBlock(pStreamR3->State.pCircBuf, cbDstBuf); + cbSilence += (uint32_t)cbDstBuf; + cbStreamUsed += (uint32_t)cbDstBuf; + } + } + + AudioMixerSinkUnlock(pSink); + } while (cbStreamUsed < cbPeriod); + if (cbSilence > 0) + { + STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowErrors); + STAM_REL_COUNTER_ADD(&pStreamR3->State.StatDmaFlowErrorBytes, cbSilence); + LogRel2(("HDA: Warning: Stream #%RU8 underrun, added %u bytes of silence (%u us)\n", pStreamShared->u8SD, + cbSilence, PDMAudioPropsBytesToMicro(&pStreamShared->State.Cfg.Props, cbSilence))); + } + } + } + + /* + * Do the DMA'ing. + */ + if (cbStreamUsed) + hdaR3StreamDoDmaInput(pDevIns, pThis, pStreamShared, pStreamR3, + RT_MIN(cbStreamUsed, cbPeriod), fWriteSilence, tsNowNs); + + /* + * We should always kick the AIO thread. + */ + /** @todo This isn't entirely ideal. If we get into an underrun situation, + * we ideally want the AIO thread to run right before the DMA timer + * rather than right after it ran. */ + Log5Func(("Notifying AIO thread\n")); + rc2 = AudioMixerSinkSignalUpdateJob(pSink); + AssertRC(rc2); + pStreamShared->State.tsLastReadNs = tsNowNs; + } +} + + +/** + * @callback_method_impl{FNAUDMIXSINKUPDATE} + * + * For output streams this moves data from the internal DMA buffer (in which + * hdaR3StreamUpdateDma put it), thru the mixer and to the various backend audio + * devices. + * + * For input streams this pulls data from the backend audio device(s), thru the + * mixer and puts it in the internal DMA buffer ready for hdaR3StreamUpdateDma + * to pump into guest memory. + */ +DECLCALLBACK(void) hdaR3StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser) +{ + PHDASTATE const pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); + PHDASTATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); + PHDASTREAMR3 const pStreamR3 = (PHDASTREAMR3)pvUser; + PHDASTREAM const pStreamShared = &pThis->aStreams[pStreamR3 - &pThisCC->aStreams[0]]; + Assert(pStreamR3 - &pThisCC->aStreams[0] == pStreamR3->u8SD); + Assert(pStreamShared->u8SD == pStreamR3->u8SD); + RT_NOREF(pSink); + + /* + * Make sure we haven't change sink and that it's still active (it + * should be or we wouldn't have been called). + */ + AssertReturnVoid(pStreamR3->pMixSink && pSink == pStreamR3->pMixSink->pMixSink); + AssertReturnVoid(AudioMixerSinkIsActive(pSink)); + + /* + * Output streams (SDO). + */ + if (hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_OUT) + hdaR3StreamPushToMixer(pStreamShared, pStreamR3, pSink, RTTimeNanoTS()); + /* + * Input stream (SDI). + */ + else + { + Assert(hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_IN); + hdaR3StreamPullFromMixer(pStreamShared, pStreamR3, pSink); + } +} + +#endif /* IN_RING3 */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHdaStream.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHdaStream.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevHdaStream.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevHdaStream.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,320 @@ +/* $Id: DevHdaStream.h $ */ +/** @file + * Intel HD Audio Controller Emulation - Streams. + */ + +/* + * Copyright (C) 2017-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DevHdaStream_h +#define VBOX_INCLUDED_SRC_Audio_DevHdaStream_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifndef VBOX_INCLUDED_SRC_Audio_DevHda_h +# error "Only include DevHda.h!" +#endif + + +/** + * Structure containing HDA stream debug stuff, configurable at runtime. + */ +typedef struct HDASTREAMDEBUGRT +{ + /** Whether debugging is enabled or not. */ + bool fEnabled; + uint8_t Padding[7]; + /** File for dumping stream reads / writes. + * For input streams, this dumps data being written to the device FIFO, + * whereas for output streams this dumps data being read from the device FIFO. */ + R3PTRTYPE(PAUDIOHLPFILE) pFileStream; + /** File for dumping raw DMA reads / writes. + * For input streams, this dumps data being written to the device DMA, + * whereas for output streams this dumps data being read from the device DMA. */ + R3PTRTYPE(PAUDIOHLPFILE) pFileDMARaw; + /** File for dumping mapped (that is, extracted) DMA reads / writes. */ + R3PTRTYPE(PAUDIOHLPFILE) pFileDMAMapped; +} HDASTREAMDEBUGRT; + +/** + * Structure containing HDA stream debug information. + */ +typedef struct HDASTREAMDEBUG +{ + /** Runtime debug info. */ + HDASTREAMDEBUGRT Runtime; + uint64_t au64Alignment[2]; +} HDASTREAMDEBUG; + +/** + * Internal state of a HDA stream. + */ +typedef struct HDASTREAMSTATE +{ + /** Flag indicating whether this stream currently is + * in reset mode and therefore not acccessible by the guest. */ + volatile bool fInReset; + /** Flag indicating if the stream is in running state or not. */ + volatile bool fRunning; + /** How many interrupts are pending due to + * BDLE interrupt-on-completion (IOC) bits set. */ + uint8_t cTransferPendingInterrupts; + /** Input streams only: Set when we switch from feeding the guest silence and + * commits to proving actual audio input bytes. */ + bool fInputPreBuffered; + /** Input streams only: The number of bytes we need to prebuffer. */ + uint32_t cbInputPreBuffer; + /** Timestamp (absolute, in timer ticks) of the last DMA data transfer. + * @note This is used for wall clock (WALCLK) calculations. */ + uint64_t volatile tsTransferLast; + /** The stream's current configuration (matches SDnFMT). */ + PDMAUDIOSTREAMCFG Cfg; + /** Timestamp (real time, in ns) of last DMA transfer. */ + uint64_t tsLastTransferNs; + /** Timestamp (real time, in ns) of last stream read (to backends). + * When running in async I/O mode, this differs from \a tsLastTransferNs, + * because reading / processing will be done in a separate stream. */ + uint64_t tsLastReadNs; + + /** The start time for the playback (on the timer clock). */ + uint64_t tsStart; + + /** @name DMA engine + * @{ */ + /** Timestamp (absolute, in timer ticks) of the next DMA data transfer. + * Next for determining the next scheduling window. + * Can be 0 if no next transfer is scheduled. */ + uint64_t tsTransferNext; + /** The size of the current DMA transfer period. */ + uint32_t cbCurDmaPeriod; + /** The size of an average transfer. */ + uint32_t cbAvgTransfer; + + /** Current circular buffer read offset (for tracing & logging). */ + uint64_t offRead; + /** Current circular buffer write offset (for tracing & logging). */ + uint64_t offWrite; + + /** The offset into the current BDLE. */ + uint32_t offCurBdle; + /** LVI + 1 */ + uint16_t cBdles; + /** The index of the current BDLE. + * This is the entry which period is currently "running" on the DMA timer. */ + uint8_t idxCurBdle; + /** The number of prologue scheduling steps. + * This is used when the tail BDLEs doesn't have IOC set. */ + uint8_t cSchedulePrologue; + /** Number of scheduling steps. */ + uint16_t cSchedule; + /** Current scheduling step. */ + uint16_t idxSchedule; + /** Current loop number within the current scheduling step. */ + uint32_t idxScheduleLoop; + + /** Buffer descriptors and additional timer scheduling state. + * (Same as HDABDLEDESC, with more sensible naming.) */ + struct + { + /** The buffer address. */ + uint64_t GCPhys; + /** The buffer size (guest bytes). */ + uint32_t cb; + /** The flags (only bit 0 is defined). */ + uint32_t fFlags; + } aBdl[256]; + /** Scheduling steps. */ + struct + { + /** Number of timer ticks per period. + * ASSUMES that we don't need a full second and that the timer resolution + * isn't much higher than nanoseconds. */ + uint32_t cPeriodTicks; + /** The period length in host bytes. */ + uint32_t cbPeriod; + /** Number of times to repeat the period. */ + uint32_t cLoops; + /** The BDL index of the first entry. */ + uint8_t idxFirst; + /** The number of BDL entries. */ + uint8_t cEntries; + uint8_t abPadding[2]; + } aSchedule[512+8]; + +#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA + /** Number of valid bytes in abDma. + * @note Volatile to prevent the compiler from re-reading it after we've + * validated the value in ring-0. */ + uint32_t volatile cbDma; + /** Total number of bytes going via abDma this timer period. */ + uint32_t cbDmaTotal; + /** DMA bounce buffer for ring-0 register reads (LPIB). */ + uint8_t abDma[2048 - 8]; +#endif + /** @} */ +} HDASTREAMSTATE; +AssertCompileSizeAlignment(HDASTREAMSTATE, 16); +AssertCompileMemberAlignment(HDASTREAMSTATE, aBdl, 8); +AssertCompileMemberAlignment(HDASTREAMSTATE, aBdl, 16); +AssertCompileMemberAlignment(HDASTREAMSTATE, aSchedule, 16); + +/** + * An HDA stream (SDI / SDO) - shared. + * + * @note This HDA stream has nothing to do with a regular audio stream handled + * by the audio connector or the audio mixer. This HDA stream is a serial + * data in/out stream (SDI/SDO) defined in hardware and can contain + * multiple audio streams in one single SDI/SDO (interleaving streams). + * + * Contains only register values which do *not* change until a stream reset + * occurs. + */ +typedef struct HDASTREAM +{ + /** Internal state of this stream. */ + HDASTREAMSTATE State; + + /** Stream descriptor number (SDn). */ + uint8_t u8SD; + /** Current channel index. + * For a stereo stream, this is u8Channel + 1. */ + uint8_t u8Channel; + /** FIFO Watermark (checked + translated in bytes, FIFOW). + * This will be update from hdaRegWriteSDFIFOW() and also copied + * hdaR3StreamInit() for some reason. */ + uint8_t u8FIFOW; + + /** @name Register values at stream setup. + * These will all be copied in hdaR3StreamInit(). + * @{ */ + /** FIFO Size (checked + translated in bytes, FIFOS). + * This is supposedly the max number of bytes we'll be DMA'ing in one chunk + * and correspondingly the LPIB & wall clock update jumps. However, we're + * not at all being honest with the guest about this. */ + uint8_t u8FIFOS; + /** Cyclic Buffer Length (SDnCBL) - Represents the size of the ring buffer. */ + uint32_t u32CBL; + /** Last Valid Index (SDnLVI). */ + uint16_t u16LVI; + /** Format (SDnFMT). */ + uint16_t u16FMT; + uint8_t abPadding[4]; + /** DMA base address (SDnBDPU - SDnBDPL). */ + uint64_t u64BDLBase; + /** @} */ + + /** The timer for pumping data thru the attached LUN drivers. */ + TMTIMERHANDLE hTimer; + + /** Pad the structure size to a 64 byte alignment. */ + uint64_t au64Padding1[2]; +} HDASTREAM; +AssertCompileMemberAlignment(HDASTREAM, State.aBdl, 16); +AssertCompileMemberAlignment(HDASTREAM, State.aSchedule, 16); +AssertCompileSizeAlignment(HDASTREAM, 64); +/** Pointer to an HDA stream (SDI / SDO). */ +typedef HDASTREAM *PHDASTREAM; + + +/** + * An HDA stream (SDI / SDO) - ring-3 bits. + */ +typedef struct HDASTREAMR3 +{ + /** Stream descriptor number (SDn). */ + uint8_t u8SD; + uint8_t abPadding[7]; + /** The shared state for the parent HDA device. */ + R3PTRTYPE(PHDASTATE) pHDAStateShared; + /** The ring-3 state for the parent HDA device. */ + R3PTRTYPE(PHDASTATER3) pHDAStateR3; + /** Pointer to HDA sink this stream is attached to. */ + R3PTRTYPE(PHDAMIXERSINK) pMixSink; + /** Internal state of this stream. */ + struct + { + /** Circular buffer (FIFO) for holding DMA'ed data. */ + R3PTRTYPE(PRTCIRCBUF) pCircBuf; + /** The mixer sink this stream has registered AIO update callback with. + * This is NULL till we register it, typically in hdaR3StreamEnable. + * (The problem with following the pMixSink assignment is that hdaR3StreamReset + * sets it without updating the HDA sink structure, so things get out of + * wack in hdaR3MixerControl later in the initial device reset.) */ + PAUDMIXSINK pAioRegSink; + + /** Size of the DMA buffer (pCircBuf) in bytes. */ + uint32_t StatDmaBufSize; + /** Number of used bytes in the DMA buffer (pCircBuf). */ + uint32_t StatDmaBufUsed; + /** Counter for all under/overflows problems. */ + STAMCOUNTER StatDmaFlowProblems; + /** Counter for unresovled under/overflows problems. */ + STAMCOUNTER StatDmaFlowErrors; + /** Number of bytes involved in unresolved flow errors. */ + STAMCOUNTER StatDmaFlowErrorBytes; + /** DMA skipped because buffer interrupt pending. */ + STAMCOUNTER StatDmaSkippedPendingBcis; + + STAMPROFILE StatStart; + STAMPROFILE StatReset; + STAMPROFILE StatStop; + } State; + /** Debug bits. */ + HDASTREAMDEBUG Dbg; + uint64_t au64Alignment[3]; +} HDASTREAMR3; +AssertCompileSizeAlignment(HDASTREAMR3, 64); +/** Pointer to an HDA stream (SDI / SDO). */ +typedef HDASTREAMR3 *PHDASTREAMR3; + +/** @name Stream functions (all contexts). + * @{ + */ +VBOXSTRICTRC hdaStreamDoOnAccessDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, + uint64_t tsNow, uint32_t cbToTransfer); +VBOXSTRICTRC hdaStreamMaybeDoOnAccessDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, + PHDASTREAM pStreamShared, uint64_t tsNow); +/** @} */ + +#ifdef IN_RING3 + +/** @name Stream functions (ring-3). + * @{ + */ +int hdaR3StreamConstruct(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PHDASTATE pThis, + PHDASTATER3 pThisCC, uint8_t uSD); +void hdaR3StreamDestroy(PHDASTREAMR3 pStreamR3); +int hdaR3StreamSetUp(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, + PHDASTREAMR3 pStreamR3, uint8_t uSD); +void hdaR3StreamReset(PHDASTATE pThis, PHDASTATER3 pThisCC, + PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD); +int hdaR3StreamEnable(PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fEnable); +void hdaR3StreamMarkStarted(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, uint64_t tsNow); +void hdaR3StreamMarkStopped(PHDASTREAM pStreamShared); + +uint64_t hdaR3StreamTimerMain(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, + PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3); +DECLCALLBACK(void) hdaR3StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser); +/** @} */ + +/** @name Helper functions associated with the stream code. + * @{ */ +int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps); +# ifdef LOG_ENABLED +void hdaR3BDLEDumpAll(PPDMDEVINS pDevIns, PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE); +# endif +/** @} */ + +#endif /* IN_RING3 */ +#endif /* !VBOX_INCLUDED_SRC_Audio_DevHdaStream_h */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevIchAc97.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevIchAc97.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevIchAc97.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevIchAc97.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #ifdef IN_RING3 @@ -33,19 +35,19 @@ # include # include # include +# include #endif #include "VBoxDD.h" #include "AudioMixBuffer.h" #include "AudioMixer.h" -#include "DrvAudio.h" +#include "AudioHlp.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ - /** Current saved state version. */ #define AC97_SAVED_STATE_VERSION 1 @@ -55,9 +57,11 @@ /** Maximum number of streams we support. */ #define AC97_MAX_STREAMS 3 -/** Maximum FIFO size (in bytes). */ +/** Maximum FIFO size (in bytes) - unused. */ #define AC97_FIFO_MAX 256 +/** @name AC97_SR_XXX - Status Register Bits (AC97_NABM_OFF_SR, PI_SR, PO_SR, MC_SR). + * @{ */ #define AC97_SR_FIFOE RT_BIT(4) /**< rwc, FIFO error. */ #define AC97_SR_BCIS RT_BIT(3) /**< rwc, Buffer completion interrupt status. */ #define AC97_SR_LVBCI RT_BIT(2) /**< rwc, Last valid buffer completion interrupt. */ @@ -67,7 +71,10 @@ #define AC97_SR_WCLEAR_MASK (AC97_SR_FIFOE | AC97_SR_BCIS | AC97_SR_LVBCI) #define AC97_SR_RO_MASK (AC97_SR_DCH | AC97_SR_CELV) #define AC97_SR_INT_MASK (AC97_SR_FIFOE | AC97_SR_BCIS | AC97_SR_LVBCI) +/** @} */ +/** @name AC97_CR_XXX - Control Register Bits (AC97_NABM_OFF_CR, PI_CR, PO_CR, MC_CR). + * @{ */ #define AC97_CR_IOCE RT_BIT(4) /**< rw, Interrupt On Completion Enable. */ #define AC97_CR_FEIE RT_BIT(3) /**< rw FIFO Error Interrupt Enable. */ #define AC97_CR_LVBIE RT_BIT(2) /**< rw Last Valid Buffer Interrupt Enable. */ @@ -75,11 +82,17 @@ #define AC97_CR_RPBM RT_BIT(0) /**< rw Run/Pause Bus Master. */ #define AC97_CR_VALID_MASK (RT_BIT(5) - 1) #define AC97_CR_DONT_CLEAR_MASK (AC97_CR_IOCE | AC97_CR_FEIE | AC97_CR_LVBIE) +/** @} */ +/** @name AC97_GC_XXX - Global Control Bits (see AC97_GLOB_CNT). + * @{ */ #define AC97_GC_WR 4 /**< rw Warm reset. */ #define AC97_GC_CR 2 /**< rw Cold reset. */ #define AC97_GC_VALID_MASK (RT_BIT(6) - 1) +/** @} */ +/** @name AC97_GS_XXX - Global Status Bits (AC97_GLOB_STA). + * @{ */ #define AC97_GS_MD3 RT_BIT(17) /**< rw */ #define AC97_GS_AD3 RT_BIT(16) /**< rw */ #define AC97_GS_RCS RT_BIT(15) /**< rwc */ @@ -110,14 +123,17 @@ | AC97_GS_MIINT) #define AC97_GS_VALID_MASK (RT_BIT(18) - 1) #define AC97_GS_WCLEAR_MASK (AC97_GS_RCS | AC97_GS_S1R1 | AC97_GS_S0R1 | AC97_GS_GSCI) +/** @} */ -/** @name Buffer Descriptor (BD). +/** @name Buffer Descriptor (BDLE, BDL). * @{ */ #define AC97_BD_IOC RT_BIT(31) /**< Interrupt on Completion. */ #define AC97_BD_BUP RT_BIT(30) /**< Buffer Underrun Policy. */ #define AC97_BD_LEN_MASK 0xFFFF /**< Mask for the BDL buffer length. */ +#define AC97_BD_LEN_CTL_MBZ UINT32_C(0x3fff0000) /**< Must-be-zero mask for AC97BDLE.ctl_len. */ + #define AC97_MAX_BDLE 32 /**< Maximum number of BDLEs. */ /** @} */ @@ -206,7 +222,7 @@ * @{ */ #define BUP_SET RT_BIT_32(0) #define BUP_LAST RT_BIT_32(1) -/** @} */ +/** @} */ /** @name AC'97 source indices. * @note The order of these indices is fixed (also applies for saved states) for @@ -285,17 +301,23 @@ /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ -/** The ICH AC'97 (Intel) controller. */ +/** The ICH AC'97 (Intel) controller (shared). */ typedef struct AC97STATE *PAC97STATE; +/** The ICH AC'97 (Intel) controller (ring-3). */ +typedef struct AC97STATER3 *PAC97STATER3; /** * Buffer Descriptor List Entry (BDLE). + * + * (See section 3.2.1 in Intel document number 252751-001, or section 1.2.2.1 in + * Intel document number 302349-003.) */ typedef struct AC97BDLE { /** Location of data buffer (bits 31:1). */ uint32_t addr; - /** Flags (bits 31 + 30) and length (bits 15:0) of data buffer (in audio samples). */ + /** Flags (bits 31 + 30) and length (bits 15:0) of data buffer (in audio samples). + * @todo split up into two 16-bit fields. */ uint32_t ctl_len; } AC97BDLE; AssertCompileSize(AC97BDLE, 8); @@ -304,6 +326,9 @@ /** * Bus master register set for an audio stream. + * + * (See section 16.2 in Intel document 301473-002, or section 2.2 in Intel + * document 302349-003.) */ typedef struct AC97BMREGS { @@ -311,7 +336,7 @@ uint8_t civ; /**< ro 0, Current index value. */ uint8_t lvi; /**< rw 0, Last valid index. */ uint16_t sr; /**< rw 1, Status register. */ - uint16_t picb; /**< ro 0, Position in current buffer (in samples). */ + uint16_t picb; /**< ro 0, Position in current buffer (samples left to process). */ uint8_t piv; /**< ro 0, Prefetched index value. */ uint8_t cr; /**< rw 0, Control register. */ int32_t bd_valid; /**< Whether current BDLE is initialized or not. */ @@ -321,67 +346,66 @@ /** Pointer to the BM registers of an audio stream. */ typedef AC97BMREGS *PAC97BMREGS; -#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO -/** - * Asynchronous I/O state for an AC'97 stream. - */ -typedef struct AC97STREAMSTATEAIO -{ - /** Thread handle for the actual I/O thread. */ - RTTHREAD Thread; - /** Event for letting the thread know there is some data to process. */ - RTSEMEVENT Event; - /** Critical section for synchronizing access. */ - RTCRITSECT CritSect; - /** Started indicator. */ - volatile bool fStarted; - /** Shutdown indicator. */ - volatile bool fShutdown; - /** Whether the thread should do any data processing or not. */ - volatile bool fEnabled; - bool afPadding[5]; -} AC97STREAMSTATEAIO; -/** Pointer to the async I/O state for an AC'97 stream. */ -typedef AC97STREAMSTATEAIO *PAC97STREAMSTATEAIO; -#endif - - /** * The internal state of an AC'97 stream. */ typedef struct AC97STREAMSTATE { - /** Criticial section for this stream. */ + /** Critical section for this stream. */ RTCRITSECT CritSect; /** Circular buffer (FIFO) for holding DMA'ed data. */ R3PTRTYPE(PRTCIRCBUF) pCircBuf; #if HC_ARCH_BITS == 32 uint32_t Padding; #endif + /** Current circular buffer read offset (for tracing & logging). */ + uint64_t offRead; + /** Current circular buffer write offset (for tracing & logging). */ + uint64_t offWrite; /** The stream's current configuration. */ PDMAUDIOSTREAMCFG Cfg; //+108 -#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - /** Asynchronous I/O state members. */ - AC97STREAMSTATEAIO AIO; -#endif /** Timestamp of the last DMA data transfer. */ uint64_t tsTransferLast; /** Timestamp of the next DMA data transfer. * Next for determining the next scheduling window. * Can be 0 if no next transfer is scheduled. */ uint64_t tsTransferNext; - /** Transfer chunk size (in bytes) of a transfer period. */ - uint32_t cbTransferChunk; /** The stream's timer Hz rate. * This value can can be different from the device's default Hz rate, * depending on the rate the stream expects (e.g. for 5.1 speaker setups). * Set in R3StreamInit(). */ uint16_t uTimerHz; - uint8_t Padding3[2]; - /** (Virtual) clock ticks per transfer. */ - uint64_t cTransferTicks; - /** Timestamp (in ns) of last stream update. */ + /** Set if we've registered the asynchronous update job. */ + bool fRegisteredAsyncUpdateJob; + /** Input streams only: Set when we switch from feeding the guest silence and + * commits to proving actual audio input bytes. */ + bool fInputPreBuffered; + /** This is ZERO if stream setup succeeded, otherwise it's the RTTimeNanoTS() at + * which to retry setting it up. The latter applies only to same + * parameters. */ + uint64_t nsRetrySetup; + /** Timestamp (in ns) of last stream update. */ uint64_t tsLastUpdateNs; + + /** Size of the DMA buffer (pCircBuf) in bytes. */ + uint32_t StatDmaBufSize; + /** Number of used bytes in the DMA buffer (pCircBuf). */ + uint32_t StatDmaBufUsed; + /** Counter for all under/overflows problems. */ + STAMCOUNTER StatDmaFlowProblems; + /** Counter for unresovled under/overflows problems. */ + STAMCOUNTER StatDmaFlowErrors; + /** Number of bytes involved in unresolved flow errors. */ + STAMCOUNTER StatDmaFlowErrorBytes; + STAMCOUNTER StatDmaSkippedDch; + STAMCOUNTER StatDmaSkippedPendingBcis; + STAMPROFILE StatStart; + STAMPROFILE StatReset; + STAMPROFILE StatStop; + STAMPROFILE StatReSetUpChanged; + STAMPROFILE StatReSetUpSame; + STAMCOUNTER StatWriteLviRecover; + STAMCOUNTER StatWriteCr; } AC97STREAMSTATE; AssertCompileSizeAlignment(AC97STREAMSTATE, 8); /** Pointer to internal state of an AC'97 stream. */ @@ -398,11 +422,11 @@ /** File for dumping stream reads / writes. * For input streams, this dumps data being written to the device FIFO, * whereas for output streams this dumps data being read from the device FIFO. */ - R3PTRTYPE(PPDMAUDIOFILE) pFileStream; + R3PTRTYPE(PAUDIOHLPFILE) pFileStream; /** File for dumping DMA reads / writes. * For input streams, this dumps data being written to the device DMA, * whereas for output streams this dumps data being read from the device DMA. */ - R3PTRTYPE(PPDMAUDIOFILE) pFileDMA; + R3PTRTYPE(PAUDIOHLPFILE) pFileDMA; } AC97STREAMDEBUGRT; /** @@ -419,13 +443,27 @@ */ typedef struct AC97STREAM { + /** Bus master registers of this stream. */ + AC97BMREGS Regs; /** Stream number (SDn). */ uint8_t u8SD; uint8_t abPadding0[7]; - /** Bus master registers of this stream. */ - AC97BMREGS Regs; + /** The timer for pumping data thru the attached LUN drivers. */ TMTIMERHANDLE hTimer; + /** When the timer was armed (timer clock). */ + uint64_t uArmedTs; + /** (Virtual) clock ticks per transfer. */ + uint64_t cDmaPeriodTicks; + /** Transfer chunk size (in bytes) of a transfer period. */ + uint32_t cbDmaPeriod; + /** DMA period counter (for logging). */ + uint32_t uDmaPeriod; + + STAMCOUNTER StatWriteLvi; + STAMCOUNTER StatWriteSr1; + STAMCOUNTER StatWriteSr2; + STAMCOUNTER StatWriteBdBar; } AC97STREAM; AssertCompileSizeAlignment(AC97STREAM, 8); /** Pointer to a shared AC'97 stream state. */ @@ -450,19 +488,6 @@ typedef AC97STREAMR3 *PAC97STREAMR3; -#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO -/** - * Asynchronous I/O thread context (arguments). - */ -typedef struct AC97STREAMTHREADCTX -{ - PAC97STATE pThis; - PAC97STREAM pStream; -} AC97STREAMTHREADCTX; -/** Pointer to the context for an async I/O thread. */ -typedef AC97STREAMTHREADCTX *PAC97STREAMTHREADCTX; -#endif - /** * A driver stream (host backend). * @@ -484,15 +509,11 @@ { /** Node for storing this driver in our device driver list of AC97STATE. */ RTLISTNODER3 Node; - /** Driver flags. */ - PDMAUDIODRVFLAGS fFlags; /** LUN # to which this driver has been assigned. */ uint8_t uLUN; /** Whether this driver is in an attached state or not. */ bool fAttached; - uint8_t abPadding[2]; - /** Pointer to the description string passed to PDMDevHlpDriverAttach(). */ - R3PTRTYPE(char *) pszDesc; + uint8_t abPadding[6]; /** Pointer to attached driver base interface. */ R3PTRTYPE(PPDMIBASE) pDrvBase; /** Audio connector interface to the underlying host backend. */ @@ -503,6 +524,8 @@ AC97DRIVERSTREAM MicIn; /** Driver stream for output. */ AC97DRIVERSTREAM Out; + /** The LUN description. */ + char szDesc[48 - 2]; } AC97DRIVER; /** Pointer to a host backend driver (LUN). */ typedef AC97DRIVER *PAC97DRIVER; @@ -516,7 +539,7 @@ bool fEnabled; bool afAlignment[7]; /** Path where to dump the debug output to. - * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH. */ + * Can be NULL, in which the system's temporary directory will be used then. */ R3PTRTYPE(char *) pszOutPath; } AC97STATEDEBUG; @@ -551,7 +574,15 @@ AC97STREAM aStreams[AC97_MAX_STREAMS]; /** The device timer Hz rate. Defaults to AC97_TIMER_HZ_DEFAULT_DEFAULT. */ uint16_t uTimerHz; - uint16_t au16Padding1[3]; + /** Config: Internal input DMA buffer size override, specified in milliseconds. + * Zero means default size according to buffer and stream config. + * @sa BufSizeInMs config value. */ + uint16_t cMsCircBufIn; + /** Config: Internal output DMA buffer size override, specified in milliseconds. + * Zero means default size according to buffer and stream config. + * @sa BufSizeOutMs config value. */ + uint16_t cMsCircBufOut; + uint16_t au16Padding1[1]; uint8_t silence[128]; uint32_t bup_flag; /** Codec model. */ @@ -564,21 +595,14 @@ STAMCOUNTER StatUnimplementedNabmReads; STAMCOUNTER StatUnimplementedNabmWrites; + STAMCOUNTER StatUnimplementedNamReads; + STAMCOUNTER StatUnimplementedNamWrites; #ifdef VBOX_WITH_STATISTICS STAMPROFILE StatTimer; - STAMPROFILE StatIn; - STAMPROFILE StatOut; - STAMCOUNTER StatBytesRead; - STAMCOUNTER StatBytesWritten; #endif } AC97STATE; AssertCompileMemberAlignment(AC97STATE, aStreams, 8); AssertCompileMemberAlignment(AC97STATE, StatUnimplementedNabmReads, 8); -#ifdef VBOX_WITH_STATISTICS -AssertCompileMemberAlignment(AC97STATE, StatTimer, 8); -AssertCompileMemberAlignment(AC97STATE, StatBytesRead, 8); -AssertCompileMemberAlignment(AC97STATE, StatBytesWritten, 8); -#endif /** @@ -631,35 +655,12 @@ return rcLock; \ } while (0) -/** Retrieves an attribute from a specific audio stream in RC. */ -#define DEVAC97_CTX_SUFF_SD(a_Var, a_SD) CTX_SUFF(a_Var)[a_SD] - /** * Releases the AC'97 lock. */ #define DEVAC97_UNLOCK(a_pDevIns, a_pThis) \ do { PDMDevHlpCritSectLeave((a_pDevIns), &(a_pThis)->CritSect); } while (0) -/** - * Acquires the TM lock and AC'97 lock, returns on failure. - */ -#define DEVAC97_LOCK_BOTH_RETURN(a_pDevIns, a_pThis, a_pStream, a_rcBusy) \ - do { \ - VBOXSTRICTRC rcLock = PDMDevHlpTimerLockClock2((a_pDevIns), (a_pStream)->hTimer, &(a_pThis)->CritSect, (a_rcBusy)); \ - if (RT_LIKELY(rcLock == VINF_SUCCESS)) \ - { /* likely */ } \ - else \ - { \ - AssertRC(VBOXSTRICTRC_VAL(rcLock)); \ - return rcLock; \ - } \ - } while (0) - -/** - * Releases the AC'97 lock and TM lock. - */ -#define DEVAC97_UNLOCK_BOTH(a_pDevIns, a_pThis, a_pStream) \ - PDMDevHlpTimerUnlockClock2((a_pDevIns), (a_pStream)->hTimer, &(a_pThis)->CritSect) #ifndef VBOX_DEVICE_STRUCT_TESTCASE @@ -667,36 +668,15 @@ /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ +static void ichac97StreamUpdateSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr); +static uint16_t ichac97MixerGet(PAC97STATE pThis, uint32_t uMixerIdx); #ifdef IN_RING3 -static int ichac97R3StreamOpen(PAC97STATE pThis, PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fForce); -static int ichac97R3StreamClose(PAC97STREAM pStream); -static void ichac97R3StreamLock(PAC97STREAMR3 pStreamCC); -static void ichac97R3StreamUnlock(PAC97STREAMR3 pStreamCC); -static uint32_t ichac97R3StreamGetUsed(PAC97STREAMR3 pStreamCC); -static uint32_t ichac97R3StreamGetFree(PAC97STREAMR3 pStreamCC); -static int ichac97R3StreamTransfer(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, - PAC97STREAMR3 pStreamCC, uint32_t cbToProcessMax); -static void ichac97R3StreamUpdate(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC, PAC97STREAM pStream, - PAC97STREAMR3 pStreamCC, bool fInTimer); - -static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns); - -static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser); - -static void ichac97R3MixerRemoveDrvStreams(PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir, - PDMAUDIODSTSRCUNION dstSrc); - -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO -static int ichac97R3StreamAsyncIOCreate(PAC97STATE pThis, PAC97STREAM pStream); -static int ichac97R3StreamAsyncIODestroy(PAC97STATE pThis, PAC97STREAM pStream); -static void ichac97R3StreamAsyncIOLock(PAC97STREAM pStream); -static void ichac97R3StreamAsyncIOUnlock(PAC97STREAM pStream); -/*static void ichac97R3StreamAsyncIOEnable(PAC97STREAM pStream, bool fEnable); Unused */ -# endif - -DECLINLINE(PDMAUDIODIR) ichac97GetDirFromSD(uint8_t uSD); -DECLINLINE(void) ichac97R3TimerSet(PPDMDEVINS pDevIns, PAC97STREAM pStream, uint64_t cTicksToDeadline); -#endif /* IN_RING3 */ +DECLINLINE(void) ichac97R3StreamLock(PAC97STREAMR3 pStreamCC); +DECLINLINE(void) ichac97R3StreamUnlock(PAC97STREAMR3 pStreamCC); +static void ichac97R3DbgPrintBdl(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, + PCDBGFINFOHLP pHlp, const char *pszPrefix); +static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns); +#endif /********************************************************************************************************************************* @@ -765,29 +745,32 @@ { NULL, NULL, NULL, NULL }, }; -#define AC97SOUNDSOURCE_PI_INDEX 0 /**< PCM in */ -#define AC97SOUNDSOURCE_PO_INDEX 1 /**< PCM out */ -#define AC97SOUNDSOURCE_MC_INDEX 2 /**< Mic in */ -#define AC97SOUNDSOURCE_MAX 3 /**< Max sound sources. */ +/** @name Source indices + * @{ */ +# define AC97SOUNDSOURCE_PI_INDEX 0 /**< PCM in */ +# define AC97SOUNDSOURCE_PO_INDEX 1 /**< PCM out */ +# define AC97SOUNDSOURCE_MC_INDEX 2 /**< Mic in */ +# define AC97SOUNDSOURCE_MAX 3 /**< Max sound sources. */ /** @} */ /** Port number (offset into NABM BAR) to stream index. */ -#define AC97_PORT2IDX(a_idx) ( ((a_idx) >> 4) & 3 ) +# define AC97_PORT2IDX(a_idx) ( ((a_idx) >> 4) & 3 ) /** Port number (offset into NABM BAR) to stream index, but no masking. */ -#define AC97_PORT2IDX_UNMASKED(a_idx) ( ((a_idx) >> 4) ) +# define AC97_PORT2IDX_UNMASKED(a_idx) ( ((a_idx) >> 4) ) /** @name Stream offsets * @{ */ -#define AC97_NABM_OFF_BDBAR 0x0 /**< Buffer Descriptor Base Address */ -#define AC97_NABM_OFF_CIV 0x4 /**< Current Index Value */ -#define AC97_NABM_OFF_LVI 0x5 /**< Last Valid Index */ -#define AC97_NABM_OFF_SR 0x6 /**< Status Register */ -#define AC97_NABM_OFF_PICB 0x8 /**< Position in Current Buffer */ -#define AC97_NABM_OFF_PIV 0xa /**< Prefetched Index Value */ -#define AC97_NABM_OFF_CR 0xb /**< Control Register */ -#define AC97_NABM_OFF_MASK 0xf /**< Mask for getting the the per-stream register. */ +# define AC97_NABM_OFF_BDBAR 0x0 /**< Buffer Descriptor Base Address */ +# define AC97_NABM_OFF_CIV 0x4 /**< Current Index Value */ +# define AC97_NABM_OFF_LVI 0x5 /**< Last Valid Index */ +# define AC97_NABM_OFF_SR 0x6 /**< Status Register */ +# define AC97_NABM_OFF_PICB 0x8 /**< Position in Current Buffer */ +# define AC97_NABM_OFF_PIV 0xa /**< Prefetched Index Value */ +# define AC97_NABM_OFF_CR 0xb /**< Control Register */ +# define AC97_NABM_OFF_MASK 0xf /**< Mask for getting the the per-stream register. */ +/** @} */ -#endif +#endif /* IN_RING3 */ @@ -805,6 +788,25 @@ #ifdef IN_RING3 /** + * Returns the audio direction of a specified stream descriptor. + * + * @return Audio direction. + */ +DECLINLINE(PDMAUDIODIR) ichac97R3GetDirFromSD(uint8_t uSD) +{ + switch (uSD) + { + case AC97SOUNDSOURCE_PI_INDEX: return PDMAUDIODIR_IN; + case AC97SOUNDSOURCE_PO_INDEX: return PDMAUDIODIR_OUT; + case AC97SOUNDSOURCE_MC_INDEX: return PDMAUDIODIR_IN; + } + + AssertFailed(); + return PDMAUDIODIR_UNKNOWN; +} + + +/** * Retrieves the audio mixer sink of a corresponding AC'97 stream index. * * @returns Pointer to audio mixer sink if found, or NULL if not found / invalid. @@ -823,1211 +825,1054 @@ } } + +/********************************************************************************************************************************* +* Stream DMA * +*********************************************************************************************************************************/ + /** - * Fetches the current BDLE (Buffer Descriptor List Entry) of an AC'97 audio stream. - * - * @returns IPRT status code. - * @param pDevIns The device instance. - * @param pStream AC'97 stream to fetch BDLE for. + * Retrieves the available size of (buffered) audio data (in bytes) of a given AC'97 stream. * - * @remark Uses CIV as BDLE index. + * @returns Available data (in bytes). + * @param pStreamCC The AC'97 stream to retrieve size for (ring-3). */ -static void ichac97R3StreamFetchBDLE(PPDMDEVINS pDevIns, PAC97STREAM pStream) +DECLINLINE(uint32_t) ichac97R3StreamGetUsed(PAC97STREAMR3 pStreamCC) { - PAC97BMREGS pRegs = &pStream->Regs; - - AC97BDLE BDLE; - PDMDevHlpPhysRead(pDevIns, pRegs->bdbar + pRegs->civ * sizeof(AC97BDLE), &BDLE, sizeof(AC97BDLE)); - pRegs->bd_valid = 1; -# ifndef RT_LITTLE_ENDIAN -# error "Please adapt the code (audio buffers are little endian)!" -# else - pRegs->bd.addr = RT_H2LE_U32(BDLE.addr & ~3); - pRegs->bd.ctl_len = RT_H2LE_U32(BDLE.ctl_len); -# endif - pRegs->picb = pRegs->bd.ctl_len & AC97_BD_LEN_MASK; - LogFlowFunc(("bd %2d addr=%#x ctl=%#06x len=%#x(%d bytes), bup=%RTbool, ioc=%RTbool\n", - pRegs->civ, pRegs->bd.addr, pRegs->bd.ctl_len >> 16, - pRegs->bd.ctl_len & AC97_BD_LEN_MASK, - (pRegs->bd.ctl_len & AC97_BD_LEN_MASK) << 1, /** @todo r=andy Assumes 16bit samples. */ - RT_BOOL(pRegs->bd.ctl_len & AC97_BD_BUP), - RT_BOOL(pRegs->bd.ctl_len & AC97_BD_IOC))); + PRTCIRCBUF const pCircBuf = pStreamCC->State.pCircBuf; + if (pCircBuf) + return (uint32_t)RTCircBufUsed(pCircBuf); + return 0; } -#endif /* IN_RING3 */ /** - * Updates the status register (SR) of an AC'97 audio stream. + * Retrieves the free size of audio data (in bytes) of a given AC'97 stream. * - * @param pDevIns The device instance. - * @param pThis The shared AC'97 state. - * @param pStream AC'97 stream to update SR for. - * @param new_sr New value for status register (SR). + * @returns Free data (in bytes). + * @param pStreamCC AC'97 stream to retrieve size for (ring-3). */ -static void ichac97StreamUpdateSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr) +DECLINLINE(uint32_t) ichac97R3StreamGetFree(PAC97STREAMR3 pStreamCC) { - PAC97BMREGS pRegs = &pStream->Regs; + PRTCIRCBUF const pCircBuf = pStreamCC->State.pCircBuf; + if (pCircBuf) + return (uint32_t)RTCircBufFree(pCircBuf); + return 0; +} - bool fSignal = false; - int iIRQL = 0; - uint32_t new_mask = new_sr & AC97_SR_INT_MASK; - uint32_t old_mask = pRegs->sr & AC97_SR_INT_MASK; +# if 0 /* Unused */ +static void ichac97R3WriteBUP(PAC97STATE pThis, uint32_t cbElapsed) +{ + LogFlowFunc(("cbElapsed=%RU32\n", cbElapsed)); - if (new_mask ^ old_mask) + if (!(pThis->bup_flag & BUP_SET)) { - /** @todo Is IRQ deasserted when only one of status bits is cleared? */ - if (!new_mask) - { - fSignal = true; - iIRQL = 0; - } - else if ((new_mask & AC97_SR_LVBCI) && (pRegs->cr & AC97_CR_LVBIE)) - { - fSignal = true; - iIRQL = 1; - } - else if ((new_mask & AC97_SR_BCIS) && (pRegs->cr & AC97_CR_IOCE)) + if (pThis->bup_flag & BUP_LAST) { - fSignal = true; - iIRQL = 1; + unsigned int i; + uint32_t *p = (uint32_t*)pThis->silence; + for (i = 0; i < sizeof(pThis->silence) / 4; i++) /** @todo r=andy Assumes 16-bit samples, stereo. */ + *p++ = pThis->last_samp; } - } - - pRegs->sr = new_sr; + else + RT_ZERO(pThis->silence); - LogFlowFunc(("IOC%d, LVB%d, sr=%#x, fSignal=%RTbool, IRQL=%d\n", - pRegs->sr & AC97_SR_BCIS, pRegs->sr & AC97_SR_LVBCI, pRegs->sr, fSignal, iIRQL)); + pThis->bup_flag |= BUP_SET; + } - if (fSignal) + while (cbElapsed) { - static uint32_t const s_aMasks[] = { AC97_GS_PIINT, AC97_GS_POINT, AC97_GS_MINT }; - Assert(pStream->u8SD < AC97_MAX_STREAMS); - if (iIRQL) - pThis->glob_sta |= s_aMasks[pStream->u8SD]; - else - pThis->glob_sta &= ~s_aMasks[pStream->u8SD]; + uint32_t cbToWrite = RT_MIN(cbElapsed, (uint32_t)sizeof(pThis->silence)); + uint32_t cbWrittenToStream; - LogFlowFunc(("Setting IRQ level=%d\n", iIRQL)); - PDMDevHlpPCISetIrq(pDevIns, 0, iIRQL); + int rc2 = AudioMixerSinkWrite(pThisCC->pSinkOut, AUDMIXOP_COPY, + pThis->silence, cbToWrite, &cbWrittenToStream); + if (RT_SUCCESS(rc2)) + { + if (cbWrittenToStream < cbToWrite) /* Lagging behind? */ + LogFlowFunc(("Warning: Only written %RU32 / %RU32 bytes, expect lags\n", cbWrittenToStream, cbToWrite)); + } + + /* Always report all data as being written; + * backends who were not able to catch up have to deal with it themselves. */ + Assert(cbElapsed >= cbToWrite); + cbElapsed -= cbToWrite; } } +# endif /* Unused */ + /** - * Writes a new value to a stream's status register (SR). + * Fetches the next buffer descriptor (BDLE) updating the stream registers. + * + * This will skip zero length descriptors. * + * @returns Zero, or AC97_SR_BCIS if skipped zero length buffer with IOC set. * @param pDevIns The device instance. - * @param pThis The shared AC'97 device state. - * @param pStream Stream to update SR for. - * @param u32Val New value to set the stream's SR to. + * @param pStream AC'97 stream to fetch BDLE for. + * @param pStreamCC The AC'97 stream, ring-3 state. + * + * @remarks Updates CIV, PIV, BD and PICB. + * + * @note Both PIV and CIV will be zero after a stream reset, so the first + * time we advance the buffer position afterwards, CIV will remain zero + * and PIV becomes 1. Thus we will start processing from BDLE00 and + * not BDLE01 as CIV=0 may lead you to think. */ -static void ichac97StreamWriteSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t u32Val) +static uint32_t ichac97R3StreamFetchNextBdle(PPDMDEVINS pDevIns, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC) { - PAC97BMREGS pRegs = &pStream->Regs; + RT_NOREF(pStreamCC); + uint32_t fSrBcis = 0; - Log3Func(("[SD%RU8] SR <- %#x (sr %#x)\n", pStream->u8SD, u32Val, pRegs->sr)); + /* + * Loop for skipping zero length entries. + */ + for (;;) + { + /* Advance the buffer. */ + pStream->Regs.civ = pStream->Regs.piv % AC97_MAX_BDLE /* (paranoia) */; + pStream->Regs.piv = (pStream->Regs.piv + 1) % AC97_MAX_BDLE; + + /* Load it. */ + AC97BDLE Bdle = { 0, 0 }; + PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bdbar + pStream->Regs.civ * sizeof(AC97BDLE), &Bdle, sizeof(AC97BDLE)); + pStream->Regs.bd_valid = 1; + pStream->Regs.bd.addr = RT_H2LE_U32(Bdle.addr) & ~3; + pStream->Regs.bd.ctl_len = RT_H2LE_U32(Bdle.ctl_len); + pStream->Regs.picb = pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK; + + LogFlowFunc(("BDLE%02u: %#RX32 L %#x / LB %#x, ctl=%#06x%s%s\n", + pStream->Regs.civ, pStream->Regs.bd.addr, pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK, + (pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK) * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props), + pStream->Regs.bd.ctl_len >> 16, + pStream->Regs.bd.ctl_len & AC97_BD_IOC ? " ioc" : "", + pStream->Regs.bd.ctl_len & AC97_BD_BUP ? " bup" : "")); + + /* Complain about any reserved bits set in CTL and ADDR: */ + ASSERT_GUEST_MSG(!(pStream->Regs.bd.ctl_len & AC97_BD_LEN_CTL_MBZ), + ("Reserved bits set: %#RX32\n", pStream->Regs.bd.ctl_len)); + ASSERT_GUEST_MSG(!(RT_H2LE_U32(Bdle.addr) & 3), + ("Reserved addr bits set: %#RX32\n", RT_H2LE_U32(Bdle.addr) )); + + /* If the length is non-zero or if we've reached LVI, we're done regardless + of what's been loaded. Otherwise, we skip zero length buffers. */ + if (pStream->Regs.picb) + break; + if (pStream->Regs.civ == (pStream->Regs.lvi % AC97_MAX_BDLE /* (paranoia) */)) + { + LogFunc(("BDLE%02u is zero length! Can't skip (CIV=LVI). %#RX32 %#RX32\n", pStream->Regs.civ, Bdle.addr, Bdle.ctl_len)); + break; + } + LogFunc(("BDLE%02u is zero length! Skipping. %#RX32 %#RX32\n", pStream->Regs.civ, Bdle.addr, Bdle.ctl_len)); - pRegs->sr |= u32Val & ~(AC97_SR_RO_MASK | AC97_SR_WCLEAR_MASK); - ichac97StreamUpdateSR(pDevIns, pThis, pStream, pRegs->sr & ~(u32Val & AC97_SR_WCLEAR_MASK)); -} + /* If the buffer has IOC set, make sure it's triggered by the caller. */ + if (pStream->Regs.bd.ctl_len & AC97_BD_IOC) + fSrBcis |= AC97_SR_BCIS; + } -#ifdef IN_RING3 + /* 1.2.4.2 PCM Buffer Restrictions (in 302349-003) - #1 */ + ASSERT_GUEST_MSG(!(pStream->Regs.picb & 1), + ("Odd lengths buffers are not allowed: %#x (%d) samples\n", pStream->Regs.picb, pStream->Regs.picb)); -/** - * Returns whether an AC'97 stream is enabled or not. - * - * @returns IPRT status code. - * @param pThisCC The ring-3 AC'97 device state. - * @param pStream Stream to return status for. - */ -static bool ichac97R3StreamIsEnabled(PAC97STATER3 pThisCC, PAC97STREAM pStream) -{ - PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD); - bool fIsEnabled = RT_BOOL(AudioMixerSinkGetStatus(pSink) & AUDMIXSINK_STS_RUNNING); + /* 1.2.4.2 PCM Buffer Restrictions (in 302349-003) - #2 */ + ASSERT_GUEST_MSG(pStream->Regs.picb > 0, ("Zero length buffers not allowed to terminate list (LVI=%u CIV=%u)\n", + pStream->Regs.lvi, pStream->Regs.civ)); - LogFunc(("[SD%RU8] fIsEnabled=%RTbool\n", pStream->u8SD, fIsEnabled)); - return fIsEnabled; + return fSrBcis; } + /** - * Enables or disables an AC'97 audio stream. + * Transfers data of an AC'97 stream according to its usage (input / output). * - * @returns IPRT status code. - * @param pThis The shared AC'97 state. - * @param pThisCC The ring-3 AC'97 state. - * @param pStream The AC'97 stream to enable or disable (shared - * state). - * @param pStreamCC The ring-3 stream state (matching to @a pStream). - * @param fEnable Whether to enable or disable the stream. + * For an SDO (output) stream this means reading DMA data from the device to + * the AC'97 stream's internal FIFO buffer. + * + * For an SDI (input) stream this is reading audio data from the AC'97 stream's + * internal FIFO buffer and writing it as DMA data to the device. * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The shared AC'97 state. + * @param pStream The AC'97 stream to update (shared). + * @param pStreamCC The AC'97 stream to update (ring-3). + * @param cbToProcess The max amount of data to process (i.e. + * put into / remove from the circular buffer). + * Unless something is going seriously wrong, this + * will always be transfer size for the current + * period. The current period will never be + * larger than what can be stored in the current + * buffer (i.e. what PICB indicates). + * @param fWriteSilence Whether to write silence if this is an input + * stream (done while waiting for backend to get + * going). + * @param fInput Set if input, clear if output. */ -static int ichac97R3StreamEnable(PAC97STATE pThis, PAC97STATER3 pThisCC, - PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fEnable) +static int ichac97R3StreamTransfer(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, + PAC97STREAMR3 pStreamCC, uint32_t cbToProcess, bool fWriteSilence, bool fInput) { + if (RT_LIKELY(cbToProcess > 0)) + Assert(PDMAudioPropsIsSizeAligned(&pStreamCC->State.Cfg.Props, cbToProcess)); + else + return VINF_SUCCESS; + ichac97R3StreamLock(pStreamCC); - int rc = VINF_SUCCESS; + /* + * Check that the controller is not halted (DCH) and that the buffer + * completion interrupt isn't pending. + */ + /** @todo r=bird: Why do we not just barge ahead even when BCIS is set? Can't + * find anything in spec indicating that we shouldn't. Linux shouldn't + * care if be bundle IOCs, as it checks how many steps we've taken using + * CIV. The Windows AC'97 sample driver doesn't care at all, since it + * just sets LIV to CIV-1 (thought that's probably not what the real + * windows driver does)... + * + * This is not going to sound good if it happens often enough, because + * each time we'll lose one DMA period (exact length depends on the + * buffer here). + * + * If we're going to keep this hack, there should be a + * PDMDevHlpTimerSetRelative call arm-ing the DMA timer to fire shortly + * after BCIS is cleared. Otherwise, we might lag behind even more + * before we get stuff going again. + * + * I just wish there was some clear reasoning in the source code for + * weird shit like this. This is just random voodoo. Sigh^3! */ + if (!(pStream->Regs.sr & (AC97_SR_DCH | AC97_SR_BCIS))) /* Controller halted? */ + { /* not halted nor does it have pending interrupt - likely */ } + else + { + /** @todo Stop DMA timer when DCH is set. */ + if (pStream->Regs.sr & AC97_SR_DCH) + { + STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaSkippedDch); + LogFunc(("[SD%RU8] DCH set\n", pStream->u8SD)); + } + if (pStream->Regs.sr & AC97_SR_BCIS) + { + STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaSkippedPendingBcis); + LogFunc(("[SD%RU8] BCIS set\n", pStream->u8SD)); + } + if ((pStream->Regs.cr & AC97_CR_RPBM) /* Bus master operation started. */ && !fInput) + { + /*ichac97R3WriteBUP(pThis, cbToProcess);*/ + } -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - if (fEnable) - rc = ichac97R3StreamAsyncIOCreate(pThis, pStream); - if (RT_SUCCESS(rc)) - ichac97R3StreamAsyncIOLock(pStream); -# endif + ichac97R3StreamUnlock(pStreamCC); + return VINF_SUCCESS; + } - if (fEnable) - { - if (pStreamCC->State.pCircBuf) - RTCircBufReset(pStreamCC->State.pCircBuf); + /* 0x1ba*2 = 0x374 (884) 0x3c0 + * Transfer loop. + */ +#ifdef LOG_ENABLED + uint32_t cbProcessedTotal = 0; +#endif + int rc = VINF_SUCCESS; + PRTCIRCBUF pCircBuf = pStreamCC->State.pCircBuf; + AssertReturnStmt(pCircBuf, ichac97R3StreamUnlock(pStreamCC), VINF_SUCCESS); + Assert((uint32_t)pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props) >= cbToProcess); + Log3Func(("[SD%RU8] cbToProcess=%#x PICB=%#x/%#x\n", pStream->u8SD, cbToProcess, + pStream->Regs.picb, pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props))); - rc = ichac97R3StreamOpen(pThis, pThisCC, pStream, pStreamCC, false /* fForce */); + while (cbToProcess > 0) + { + uint32_t cbChunk = cbToProcess; - if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled)) - { /* likely */ } - else + /* + * Output. + */ + if (!fInput) { - if (!DrvAudioHlpFileIsOpen(pStreamCC->Dbg.Runtime.pFileStream)) - { - int rc2 = DrvAudioHlpFileOpen(pStreamCC->Dbg.Runtime.pFileStream, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStreamCC->State.Cfg.Props); - AssertRC(rc2); - } + void *pvDst = NULL; + size_t cbDst = 0; + RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvDst, &cbDst); - if (!DrvAudioHlpFileIsOpen(pStreamCC->Dbg.Runtime.pFileDMA)) + if (cbDst) { - int rc2 = DrvAudioHlpFileOpen(pStreamCC->Dbg.Runtime.pFileDMA, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStreamCC->State.Cfg.Props); + int rc2 = PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bd.addr, pvDst, cbDst); AssertRC(rc2); + + if (RT_LIKELY(!pStreamCC->Dbg.Runtime.pFileDMA)) + { /* likely */ } + else + AudioHlpFileWrite(pStreamCC->Dbg.Runtime.pFileDMA, pvDst, cbDst); } + + RTCircBufReleaseWriteBlock(pCircBuf, cbDst); + + cbChunk = (uint32_t)cbDst; /* Update the current chunk size to what really has been written. */ } - } - else - rc = ichac97R3StreamClose(pStream); + /* + * Input. + */ + else if (!fWriteSilence) + { + void *pvSrc = NULL; + size_t cbSrc = 0; + RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvSrc, &cbSrc); - if (RT_SUCCESS(rc)) - { - /* First, enable or disable the stream and the stream's sink, if any. */ - rc = AudioMixerSinkCtl(ichac97R3IndexToSink(pThisCC, pStream->u8SD), - fEnable ? AUDMIXSINKCMD_ENABLE : AUDMIXSINKCMD_DISABLE); - } + if (cbSrc) + { + int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, pStream->Regs.bd.addr, pvSrc, cbSrc); + AssertRC(rc2); -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - ichac97R3StreamAsyncIOUnlock(pStream); -# endif + if (RT_LIKELY(!pStreamCC->Dbg.Runtime.pFileDMA)) + { /* likely */ } + else + AudioHlpFileWrite(pStreamCC->Dbg.Runtime.pFileDMA, pvSrc, cbSrc); + } - /* Make sure to leave the lock before (eventually) starting the timer. */ - ichac97R3StreamUnlock(pStreamCC); + RTCircBufReleaseReadBlock(pCircBuf, cbSrc); - LogFunc(("[SD%RU8] fEnable=%RTbool, rc=%Rrc\n", pStream->u8SD, fEnable, rc)); - return rc; -} + cbChunk = (uint32_t)cbSrc; /* Update the current chunk size to what really has been read. */ + } + else + { + /* Since the format is signed 16-bit or 32-bit integer samples, we can + use g_abRTZero64K as source and avoid some unnecessary bzero() work. */ + cbChunk = RT_MIN(cbChunk, sizeof(g_abRTZero64K)); + cbChunk = PDMAudioPropsFloorBytesToFrame(&pStreamCC->State.Cfg.Props, cbChunk); -/** - * Resets an AC'97 stream. - * - * @param pThis The shared AC'97 state. - * @param pStream The AC'97 stream to reset (shared). - * @param pStreamCC The AC'97 stream to reset (ring-3). - */ -static void ichac97R3StreamReset(PAC97STATE pThis, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC) -{ - ichac97R3StreamLock(pStreamCC); + int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, pStream->Regs.bd.addr, g_abRTZero64K, cbChunk); + AssertRC(rc2); + } - LogFunc(("[SD%RU8]\n", pStream->u8SD)); + Assert(PDMAudioPropsIsSizeAligned(&pStreamCC->State.Cfg.Props, cbChunk)); + Assert(cbChunk <= cbToProcess); - if (pStreamCC->State.pCircBuf) - RTCircBufReset(pStreamCC->State.pCircBuf); + /* + * Advance. + */ + pStream->Regs.picb -= cbChunk / PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props); + pStream->Regs.bd.addr += cbChunk; + cbToProcess -= cbChunk; +#ifdef LOG_ENABLED + cbProcessedTotal += cbChunk; +#endif + LogFlowFunc(("[SD%RU8] cbChunk=%#x, cbToProcess=%#x, cbTotal=%#x picb=%#x\n", + pStream->u8SD, cbChunk, cbToProcess, cbProcessedTotal, pStream->Regs.picb)); + } - PAC97BMREGS pRegs = &pStream->Regs; + /* + * Fetch a new buffer descriptor if we've exhausted the current one. + */ + if (!pStream->Regs.picb) + { + uint32_t fNewSr = pStream->Regs.sr & ~AC97_SR_CELV; - pRegs->bdbar = 0; - pRegs->civ = 0; - pRegs->lvi = 0; - - pRegs->picb = 0; - pRegs->piv = 0; - pRegs->cr = pRegs->cr & AC97_CR_DONT_CLEAR_MASK; - pRegs->bd_valid = 0; + if (pStream->Regs.bd.ctl_len & AC97_BD_IOC) + fNewSr |= AC97_SR_BCIS; - RT_ZERO(pThis->silence); + if (pStream->Regs.civ != pStream->Regs.lvi) + fNewSr |= ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC); + else + { + LogFunc(("Underrun CIV (%RU8) == LVI (%RU8)\n", pStream->Regs.civ, pStream->Regs.lvi)); + fNewSr |= AC97_SR_LVBCI | AC97_SR_DCH | AC97_SR_CELV; + pThis->bup_flag = (pStream->Regs.bd.ctl_len & AC97_BD_BUP) ? BUP_LAST : 0; + /** @todo r=bird: The bup_flag isn't cleared anywhere else. We should probably + * do what the spec says, and keep writing zeros (silence). + * Alternatively, we could hope the guest will pause the DMA engine + * immediately after seeing this condition, in which case we should + * stop the DMA timer from being re-armed. */ + } + + ichac97StreamUpdateSR(pDevIns, pThis, pStream, fNewSr); + } ichac97R3StreamUnlock(pStreamCC); + LogFlowFuncLeaveRC(rc); + return rc; } + /** - * Creates an AC'97 audio stream. + * Input streams: Pulls data from the mixer, putting it in the internal DMA + * buffer. * - * @returns IPRT status code. - * @param pThisCC The ring-3 AC'97 state. - * @param pStream The AC'97 stream to create (shared). - * @param pStreamCC The AC'97 stream to create (ring-3). - * @param u8SD Stream descriptor number to assign. + * @param pStreamR3 The AC'97 stream (ring-3 bits). + * @param pSink The mixer sink to pull from. */ -static int ichac97R3StreamCreate(PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, uint8_t u8SD) +static void ichac97R3StreamPullFromMixer(PAC97STREAMR3 pStreamR3, PAUDMIXSINK pSink) { - LogFunc(("[SD%RU8] pStream=%p\n", u8SD, pStream)); - - AssertReturn(u8SD < AC97_MAX_STREAMS, VERR_INVALID_PARAMETER); - pStream->u8SD = u8SD; - pStreamCC->u8SD = u8SD; - - int rc = RTCritSectInit(&pStreamCC->State.CritSect); - AssertRCReturn(rc, rc); - - pStreamCC->Dbg.Runtime.fEnabled = pThisCC->Dbg.fEnabled; - - if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - { - char szFile[64]; - - if (ichac97GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) - RTStrPrintf(szFile, sizeof(szFile), "ac97StreamWriteSD%RU8", pStream->u8SD); - else - RTStrPrintf(szFile, sizeof(szFile), "ac97StreamReadSD%RU8", pStream->u8SD); - - char szPath[RTPATH_MAX]; - int rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThisCC->Dbg.pszOutPath, szFile, - 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - AssertRC(rc2); - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAGS_NONE, &pStreamCC->Dbg.Runtime.pFileStream); - AssertRC(rc2); - - if (ichac97GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) - RTStrPrintf(szFile, sizeof(szFile), "ac97DMAWriteSD%RU8", pStream->u8SD); - else - RTStrPrintf(szFile, sizeof(szFile), "ac97DMAReadSD%RU8", pStream->u8SD); - - rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThisCC->Dbg.pszOutPath, szFile, - 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - AssertRC(rc2); - - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAGS_NONE, &pStreamCC->Dbg.Runtime.pFileDMA); - AssertRC(rc2); +# ifdef LOG_ENABLED + uint64_t const offWriteOld = pStreamR3->State.offWrite; +# endif + pStreamR3->State.offWrite = AudioMixerSinkTransferToCircBuf(pSink, + pStreamR3->State.pCircBuf, + pStreamR3->State.offWrite, + pStreamR3->u8SD, + pStreamR3->Dbg.Runtime.fEnabled + ? pStreamR3->Dbg.Runtime.pFileStream : NULL); - /* Delete stale debugging files from a former run. */ - DrvAudioHlpFileDelete(pStreamCC->Dbg.Runtime.pFileStream); - DrvAudioHlpFileDelete(pStreamCC->Dbg.Runtime.pFileDMA); - } + Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD, + pStreamR3->State.offWrite - offWriteOld, pStreamR3->State.offWrite)); - return rc; + /* Update buffer stats. */ + pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); } + /** - * Destroys an AC'97 audio stream. + * Output streams: Pushes data to the mixer. * - * @returns IPRT status code. - * @param pThis The shared AC'97 state. - * @param pStream The AC'97 stream to destroy (shared). - * @param pStreamCC The AC'97 stream to destroy (ring-3). + * @param pStreamR3 The AC'97 stream (ring-3 bits). + * @param pSink The mixer sink to push to. */ -static void ichac97R3StreamDestroy(PAC97STATE pThis, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC) +static void ichac97R3StreamPushToMixer(PAC97STREAMR3 pStreamR3, PAUDMIXSINK pSink) { - LogFlowFunc(("[SD%RU8]\n", pStream->u8SD)); - - ichac97R3StreamClose(pStream); - - int rc2 = RTCritSectDelete(&pStreamCC->State.CritSect); - AssertRC(rc2); - -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - rc2 = ichac97R3StreamAsyncIODestroy(pThis, pStream); - AssertRC(rc2); -# else - RT_NOREF(pThis); +# ifdef LOG_ENABLED + uint64_t const offReadOld = pStreamR3->State.offRead; # endif + pStreamR3->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink, + pStreamR3->State.pCircBuf, + pStreamR3->State.offRead, + pStreamR3->u8SD, + pStreamR3->Dbg.Runtime.fEnabled + ? pStreamR3->Dbg.Runtime.pFileStream : NULL); - if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - { - DrvAudioHlpFileDestroy(pStreamCC->Dbg.Runtime.pFileStream); - pStreamCC->Dbg.Runtime.pFileStream = NULL; - - DrvAudioHlpFileDestroy(pStreamCC->Dbg.Runtime.pFileDMA); - pStreamCC->Dbg.Runtime.pFileDMA = NULL; - } - - if (pStreamCC->State.pCircBuf) - { - RTCircBufDestroy(pStreamCC->State.pCircBuf); - pStreamCC->State.pCircBuf = NULL; - } + Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD, + pStreamR3->State.offRead - offReadOld, pStreamR3->State.offRead)); - LogFlowFuncLeave(); + /* Update buffer stats. */ + pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); } + /** - * Destroys all AC'97 audio streams of the device. + * Updates an AC'97 stream by doing its DMA transfers. * + * The host sink(s) set the overall pace (bird: no it doesn't, the DMA timer + * does - we just hope like heck it matches the speed at which the *backend* + * host audio driver processes samples). + * + * @param pDevIns The device instance. * @param pThis The shared AC'97 state. * @param pThisCC The ring-3 AC'97 state. + * @param pStream The AC'97 stream to update (shared). + * @param pStreamCC The AC'97 stream to update (ring-3). + * @param pSink The sink being updated. */ -static void ichac97R3StreamsDestroy(PAC97STATE pThis, PAC97STATER3 pThisCC) +static void ichac97R3StreamUpdateDma(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC, + PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, PAUDMIXSINK pSink) { - LogFlowFuncEnter(); + RT_NOREF(pThisCC); + int rc2; - /* - * Destroy all AC'97 streams. - */ - for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) - ichac97R3StreamDestroy(pThis, &pThis->aStreams[i], &pThisCC->aStreams[i]); + /* The amount we're supposed to be transfering in this DMA period. */ + uint32_t cbPeriod = pStream->cbDmaPeriod; /* - * Destroy all sinks. + * Output streams (SDO). */ - - PDMAUDIODSTSRCUNION dstSrc; - if (pThisCC->pSinkLineIn) + if (pStreamCC->State.Cfg.enmDir == PDMAUDIODIR_OUT) { - dstSrc.enmSrc = PDMAUDIORECSRC_LINE; - ichac97R3MixerRemoveDrvStreams(pThisCC, pThisCC->pSinkLineIn, PDMAUDIODIR_IN, dstSrc); + /* + * Check how much room we have in our DMA buffer. There should be at + * least one period worth of space there or we're in an overflow situation. + */ + uint32_t cbStreamFree = ichac97R3StreamGetFree(pStreamCC); + if (cbStreamFree >= cbPeriod) + { /* likely */ } + else + { + STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowProblems); + LogFunc(("Warning! Stream #%u has insufficient space free: %u bytes, need %u. Will try move data out of the buffer...\n", + pStreamCC->u8SD, cbStreamFree, cbPeriod)); + int rc = AudioMixerSinkTryLock(pSink); + if (RT_SUCCESS(rc)) + { + ichac97R3StreamPushToMixer(pStreamCC, pSink); + AudioMixerSinkUpdate(pSink, 0, 0); + AudioMixerSinkUnlock(pSink); + } + else + RTThreadYield(); + LogFunc(("Gained %u bytes.\n", ichac97R3StreamGetFree(pStreamCC) - cbStreamFree)); - AudioMixerSinkDestroy(pThisCC->pSinkLineIn); - pThisCC->pSinkLineIn = NULL; - } + cbStreamFree = ichac97R3StreamGetFree(pStreamCC); + if (cbStreamFree < cbPeriod) + { + /* Unable to make sufficient space. Drop the whole buffer content. + * This is needed in order to keep the device emulation running at a constant rate, + * at the cost of losing valid (but too much) data. */ + STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowErrors); + LogRel2(("AC97: Warning: Hit stream #%RU8 overflow, dropping %u bytes of audio data\n", + pStreamCC->u8SD, ichac97R3StreamGetUsed(pStreamCC))); +# ifdef AC97_STRICT + AssertMsgFailed(("Hit stream #%RU8 overflow -- timing bug?\n", pStreamCC->u8SD)); +# endif + RTCircBufReset(pStreamCC->State.pCircBuf); + pStreamCC->State.offWrite = 0; + pStreamCC->State.offRead = 0; + cbStreamFree = ichac97R3StreamGetFree(pStreamCC); + Assert(cbStreamFree >= cbPeriod); + } + } - if (pThisCC->pSinkMicIn) - { - dstSrc.enmSrc = PDMAUDIORECSRC_MIC; - ichac97R3MixerRemoveDrvStreams(pThisCC, pThisCC->pSinkMicIn, PDMAUDIODIR_IN, dstSrc); + /* + * Do the DMA transfer. + */ + Log3Func(("[SD%RU8] PICB=%#x samples / %RU64 ms, cbFree=%#x / %RU64 ms, cbTransferChunk=%#x / %RU64 ms\n", pStream->u8SD, + pStream->Regs.picb, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props, + PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props) + * pStream->Regs.picb), + cbStreamFree, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props, cbStreamFree), + cbPeriod, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props, cbPeriod))); - AudioMixerSinkDestroy(pThisCC->pSinkMicIn); - pThisCC->pSinkMicIn = NULL; - } + rc2 = ichac97R3StreamTransfer(pDevIns, pThis, pStream, pStreamCC, RT_MIN(cbStreamFree, cbPeriod), + false /*fWriteSilence*/, false /*fInput*/); + AssertRC(rc2); - if (pThisCC->pSinkOut) - { - dstSrc.enmDst = PDMAUDIOPLAYBACKDST_FRONT; - ichac97R3MixerRemoveDrvStreams(pThisCC, pThisCC->pSinkOut, PDMAUDIODIR_OUT, dstSrc); + pStreamCC->State.tsLastUpdateNs = RTTimeNanoTS(); - AudioMixerSinkDestroy(pThisCC->pSinkOut); - pThisCC->pSinkOut = NULL; + + /* + * Notify the AIO thread. + */ + rc2 = AudioMixerSinkSignalUpdateJob(pSink); + AssertRC(rc2); } -} + /* + * Input stream (SDI). + */ + else + { + /* + * See how much data we've got buffered... + */ + bool fWriteSilence = false; + uint32_t cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC); + if (pStreamCC->State.fInputPreBuffered && cbStreamUsed >= cbPeriod) + { /*likely*/ } + /* + * Because it may take a while for the input stream to get going (at least + * with pulseaudio), we feed the guest silence till we've pre-buffer a + * couple of timer Hz periods. (This avoid lots of bogus buffer underruns + * when starting an input stream and hogging the timer EMT.) + */ + else if (!pStreamCC->State.fInputPreBuffered) + { + uint32_t const cbPreBuffer = PDMAudioPropsNanoToBytes(&pStreamCC->State.Cfg.Props, + RT_NS_1SEC / pStreamCC->State.uTimerHz); + if (cbStreamUsed < cbPreBuffer) + { + Log3Func(("Pre-buffering (got %#x out of %#x bytes)...\n", cbStreamUsed, cbPreBuffer)); + fWriteSilence = true; + cbStreamUsed = cbPeriod; + } + else + { + Log3Func(("Completed pre-buffering (got %#x, needed %#x bytes).\n", cbStreamUsed, cbPreBuffer)); + pStreamCC->State.fInputPreBuffered = true; + fWriteSilence = ichac97R3StreamGetFree(pStreamCC) >= cbPreBuffer + cbPreBuffer / 2; + if (fWriteSilence) + cbStreamUsed = cbPeriod; + } + } + /* + * When we're low on data, we must really try fetch some ourselves + * as buffer underruns must not happen. + */ + else + { + STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowProblems); + LogFunc(("Warning! Stream #%u has insufficient data available: %u bytes, need %u. Will try move pull more data into the buffer...\n", + pStreamCC->u8SD, cbStreamUsed, cbPeriod)); + int rc = AudioMixerSinkTryLock(pSink); + if (RT_SUCCESS(rc)) + { + AudioMixerSinkUpdate(pSink, cbStreamUsed, cbPeriod); + ichac97R3StreamPullFromMixer(pStreamCC, pSink); + AudioMixerSinkUnlock(pSink); + } + else + RTThreadYield(); + LogFunc(("Gained %u bytes.\n", ichac97R3StreamGetUsed(pStreamCC) - cbStreamUsed)); + cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC); + if (cbStreamUsed < cbPeriod) + { + /* Unable to find sufficient input data by simple prodding. + In order to keep a constant byte stream following thru the DMA + engine into the guest, we will try again and then fall back on + filling the gap with silence. */ + uint32_t cbSilence = 0; + do + { + AudioMixerSinkLock(pSink); -/** - * Writes audio data from a mixer sink into an AC'97 stream's DMA buffer. - * - * @returns IPRT status code. - * @param pDstStreamCC The AC'97 stream to write to (ring-3). - * @param pSrcMixSink Mixer sink to get audio data to write from. - * @param cbToWrite Number of bytes to write. - * @param pcbWritten Number of bytes written. Optional. - */ -static int ichac97R3StreamWrite(PAC97STREAMR3 pDstStreamCC, PAUDMIXSINK pSrcMixSink, uint32_t cbToWrite, uint32_t *pcbWritten) -{ - AssertPtrReturn(pSrcMixSink, VERR_INVALID_POINTER); - AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER); - /* pcbWritten is optional. */ + cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC); + if (cbStreamUsed < cbPeriod) + { + ichac97R3StreamPullFromMixer(pStreamCC, pSink); + cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC); + while (cbStreamUsed < cbPeriod) + { + void *pvDstBuf; + size_t cbDstBuf; + RTCircBufAcquireWriteBlock(pStreamCC->State.pCircBuf, cbPeriod - cbStreamUsed, + &pvDstBuf, &cbDstBuf); + RT_BZERO(pvDstBuf, cbDstBuf); + RTCircBufReleaseWriteBlock(pStreamCC->State.pCircBuf, cbDstBuf); + cbSilence += (uint32_t)cbDstBuf; + cbStreamUsed += (uint32_t)cbDstBuf; + } + } - PRTCIRCBUF pCircBuf = pDstStreamCC->State.pCircBuf; - AssertPtr(pCircBuf); + AudioMixerSinkUnlock(pSink); + } while (cbStreamUsed < cbPeriod); + if (cbSilence > 0) + { + STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowErrors); + STAM_REL_COUNTER_ADD(&pStreamCC->State.StatDmaFlowErrorBytes, cbSilence); + LogRel2(("AC97: Warning: Stream #%RU8 underrun, added %u bytes of silence (%u us)\n", pStreamCC->u8SD, + cbSilence, PDMAudioPropsBytesToMicro(&pStreamCC->State.Cfg.Props, cbSilence))); + } + } + } - uint32_t cbRead = 0; + /* + * Do the DMA'ing. + */ + if (cbStreamUsed) + { + rc2 = ichac97R3StreamTransfer(pDevIns, pThis, pStream, pStreamCC, RT_MIN(cbPeriod, cbStreamUsed), + fWriteSilence, true /*fInput*/); + AssertRC(rc2); - void *pvDst; - size_t cbDst; - RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvDst, &cbDst); + pStreamCC->State.tsLastUpdateNs = RTTimeNanoTS(); + } - if (cbDst) - { - int rc2 = AudioMixerSinkRead(pSrcMixSink, AUDMIXOP_COPY, pvDst, (uint32_t)cbDst, &cbRead); + /* + * We should always kick the AIO thread. + */ + /** @todo This isn't entirely ideal. If we get into an underrun situation, + * we ideally want the AIO thread to run right before the DMA timer + * rather than right after it ran. */ + Log5Func(("Notifying AIO thread\n")); + rc2 = AudioMixerSinkSignalUpdateJob(pSink); AssertRC(rc2); - - if (RT_LIKELY(!pDstStreamCC->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - DrvAudioHlpFileWrite(pDstStreamCC->Dbg.Runtime.pFileStream, pvDst, cbRead, 0 /* fFlags */); } +} - RTCircBufReleaseWriteBlock(pCircBuf, cbRead); - if (pcbWritten) - *pcbWritten = cbRead; +/** + * @callback_method_impl{FNAUDMIXSINKUPDATE} + * + * For output streams this moves data from the internal DMA buffer (in which + * ichac97R3StreamUpdateDma put it), thru the mixer and to the various backend + * audio devices. + * + * For input streams this pulls data from the backend audio device(s), thru the + * mixer and puts it in the internal DMA buffer ready for + * ichac97R3StreamUpdateDma to pump into guest memory. + */ +static DECLCALLBACK(void) ichac97R3StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser) +{ + PAC97STATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); + PAC97STREAMR3 const pStreamCC = (PAC97STREAMR3)pvUser; + Assert(pStreamCC->u8SD == (uintptr_t)(pStreamCC - &pThisCC->aStreams[0])); + Assert(pSink == ichac97R3IndexToSink(pThisCC, pStreamCC->u8SD)); + RT_NOREF(pThisCC); - return VINF_SUCCESS; + /* + * Output (SDO). + */ + if (pStreamCC->State.Cfg.enmDir == PDMAUDIODIR_OUT) + ichac97R3StreamPushToMixer(pStreamCC, pSink); + /* + * Input (SDI). + */ + else + ichac97R3StreamPullFromMixer(pStreamCC, pSink); } + /** - * Reads audio data from an AC'97 stream's DMA buffer and writes into a specified mixer sink. + * Updates the next transfer based on a specific amount of bytes. * - * @returns IPRT status code. - * @param pSrcStreamCC AC'97 stream to read audio data from (ring-3). - * @param pDstMixSink Mixer sink to write audio data to. - * @param cbToRead Number of bytes to read. - * @param pcbRead Number of bytes read. Optional. + * @param pDevIns The device instance. + * @param pStream The AC'97 stream to update (shared). + * @param pStreamCC The AC'97 stream to update (ring-3). */ -static int ichac97R3StreamRead(PAC97STREAMR3 pSrcStreamCC, PAUDMIXSINK pDstMixSink, uint32_t cbToRead, uint32_t *pcbRead) +static void ichac97R3StreamTransferUpdate(PPDMDEVINS pDevIns, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC) { - AssertPtrReturn(pDstMixSink, VERR_INVALID_POINTER); - AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER); - /* pcbRead is optional. */ - - PRTCIRCBUF pCircBuf = pSrcStreamCC->State.pCircBuf; - AssertPtr(pCircBuf); - - void *pvSrc; - size_t cbSrc; - - int rc = VINF_SUCCESS; - - uint32_t cbReadTotal = 0; - uint32_t cbLeft = RT_MIN(cbToRead, (uint32_t)RTCircBufUsed(pCircBuf)); - - while (cbLeft) + /* + * Get the number of bytes left in the current buffer. + * + * This isn't entirely optimal iff the current entry doesn't have IOC set, in + * that case we should use the number of bytes to the next IOC. Unfortuantely, + * it seems the spec doesn't allow us to prefetch more than one BDLE, so we + * probably cannot look ahead without violating that restriction. This is + * probably a purely theoretical problem at this point. + */ + uint32_t const cbLeftInBdle = pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props); + if (cbLeftInBdle > 0) /** @todo r=bird: see todo about this in ichac97R3StreamFetchBDLE. */ { - uint32_t cbWritten = 0; - - RTCircBufAcquireReadBlock(pCircBuf, cbLeft, &pvSrc, &cbSrc); - - if (cbSrc) - { - if (RT_LIKELY(!pSrcStreamCC->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - DrvAudioHlpFileWrite(pSrcStreamCC->Dbg.Runtime.pFileStream, pvSrc, cbSrc, 0 /* fFlags */); - - rc = AudioMixerSinkWrite(pDstMixSink, AUDMIXOP_COPY, pvSrc, (uint32_t)cbSrc, &cbWritten); - AssertRC(rc); - - Assert(cbSrc >= cbWritten); - Log3Func(("[SD%RU8] %RU32/%zu bytes read\n", pSrcStreamCC->u8SD, cbWritten, cbSrc)); - } - - RTCircBufReleaseReadBlock(pCircBuf, cbWritten); + /* + * Since the buffer can be up to 0xfffe samples long (frame aligning stereo + * prevents 0xffff), which translates to 743ms at a 44.1kHz rate, we must + * also take the nominal timer frequency into account here so we keep + * moving data at a steady rate. (In theory, I think the guest can even + * set up just one buffer and anticipate where we are in the buffer + * processing when it writes/reads from it. Linux seems to be doing such + * configs when not playing or something.) + */ + uint32_t const cbMaxPerHz = PDMAudioPropsNanoToBytes(&pStreamCC->State.Cfg.Props, RT_NS_1SEC / pStreamCC->State.uTimerHz); - if ( !cbWritten /* Nothing written? */ - || RT_FAILURE(rc)) - break; + if (cbLeftInBdle <= cbMaxPerHz) + pStream->cbDmaPeriod = cbLeftInBdle; + /* Try avoid leaving a very short period at the end of a buffer. */ + else if (cbLeftInBdle >= cbMaxPerHz + cbMaxPerHz / 2) + pStream->cbDmaPeriod = cbMaxPerHz; + else + pStream->cbDmaPeriod = PDMAudioPropsFloorBytesToFrame(&pStreamCC->State.Cfg.Props, cbLeftInBdle / 2); - Assert(cbLeft >= cbWritten); - cbLeft -= cbWritten; + /* + * Translate the chunk size to timer ticks. + */ + uint64_t const cNsXferChunk = PDMAudioPropsBytesToNano(&pStreamCC->State.Cfg.Props, pStream->cbDmaPeriod); + pStream->cDmaPeriodTicks = PDMDevHlpTimerFromNano(pDevIns, pStream->hTimer, cNsXferChunk); + Assert(pStream->cDmaPeriodTicks > 0); - cbReadTotal += cbWritten; + Log3Func(("[SD%RU8] cbLeftInBdle=%#RX32 cbMaxPerHz=%#RX32 (%RU16Hz) -> cbDmaPeriod=%#RX32 cDmaPeriodTicks=%RX64\n", + pStream->u8SD, cbLeftInBdle, cbMaxPerHz, pStreamCC->State.uTimerHz, pStream->cbDmaPeriod, pStream->cDmaPeriodTicks)); } - - if (pcbRead) - *pcbRead = cbReadTotal; - - return rc; } -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO /** - * Asynchronous I/O thread for an AC'97 stream. - * This will do the heavy lifting work for us as soon as it's getting notified by another thread. + * Sets the virtual device timer to a new expiration time. + * + * @param pDevIns The device instance. + * @param pStream AC'97 stream to set timer for. + * @param cTicksToDeadline The number of ticks to the new deadline. * - * @returns IPRT status code. - * @param hThreadSelf Thread handle. - * @param pvUser User argument. Must be of type PAC97STREAMTHREADCTX. + * @remarks This used to be more complicated a long time ago... */ -static DECLCALLBACK(int) ichac97R3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser) +DECLINLINE(void) ichac97R3TimerSet(PPDMDEVINS pDevIns, PAC97STREAM pStream, uint64_t cTicksToDeadline) { - PAC97STREAMTHREADCTX pCtx = (PAC97STREAMTHREADCTX)pvUser; - AssertPtr(pCtx); - - PAC97STATE pThis = pCtx->pThis; - AssertPtr(pThis); - - PAC97STREAM pStream = pCtx->pStream; - AssertPtr(pStream); - - PAC97STREAMSTATEAIO pAIO = &pCtx->pStreamCC->State.AIO; - - ASMAtomicXchgBool(&pAIO->fStarted, true); - - RTThreadUserSignal(hThreadSelf); - - /** @todo r=bird: What wasn't mentioned by the original author of this - * code, is that pCtx is now invalid as it must be assumed to be out - * of scope in the parent thread. It is a 'ing stack object! */ - - LogFunc(("[SD%RU8] Started\n", pStream->u8SD)); - - for (;;) - { - Log2Func(("[SD%RU8] Waiting ...\n", pStream->u8SD)); - - int rc2 = RTSemEventWait(pAIO->Event, RT_INDEFINITE_WAIT); - if (RT_FAILURE(rc2)) - break; - - if (ASMAtomicReadBool(&pAIO->fShutdown)) - break; - - rc2 = RTCritSectEnter(&pAIO->CritSect); - if (RT_SUCCESS(rc2)) - { - if (!pAIO->fEnabled) - { - RTCritSectLeave(&pAIO->CritSect); - continue; - } - - ichac97R3StreamUpdate(pDevIns, pThis, pThisCC, pStream, pStreamCC, false /* fInTimer */); - - int rc3 = RTCritSectLeave(&pAIO->CritSect); - AssertRC(rc3); - } - - AssertRC(rc2); - } - - LogFunc(("[SD%RU8] Ended\n", pStream->u8SD)); - - ASMAtomicXchgBool(&pAIO->fStarted, false); - - return VINF_SUCCESS; -} - -/** - * Creates the async I/O thread for a specific AC'97 audio stream. - * - * @returns IPRT status code. - * @param pThis The shared AC'97 state. - * @param pStream AC'97 audio stream to create the async I/O thread for. - */ -static int ichac97R3StreamAsyncIOCreate(PAC97STATE pThis, PAC97STREAM pStream) -{ - PAC97STREAMSTATEAIO pAIO = &pStreamCC->State.AIO; - - int rc; - - if (!ASMAtomicReadBool(&pAIO->fStarted)) - { - pAIO->fShutdown = false; - pAIO->fEnabled = true; /* Enabled by default. */ - - rc = RTSemEventCreate(&pAIO->Event); - if (RT_SUCCESS(rc)) - { - rc = RTCritSectInit(&pAIO->CritSect); - if (RT_SUCCESS(rc)) - { -/** @todo r=bird: Why is Ctx on the stack? There is no mention of this in - * the thread structure. Besides, you only wait 10seconds, if the - * host is totally overloaded, it may go out of scope before the new - * thread has finished with it and it will like crash and burn. - * - * Also, there is RTThreadCreateF for giving threads complicated - * names. - * - * Why aren't this code using the PDM threads (PDMDevHlpThreadCreate)? - * They would help you with managing stuff like VM suspending, resuming - * and powering off. - * - * Finally, just create the threads at construction time. */ - AC97STREAMTHREADCTX Ctx = { pThis, pStream }; -# error "Busted code! Do not pass a structure living on the parent stack to the poor thread!" - - char szThreadName[64]; - RTStrPrintf2(szThreadName, sizeof(szThreadName), "ac97AIO%RU8", pStream->u8SD); - - rc = RTThreadCreate(&pAIO->Thread, ichac97R3StreamAsyncIOThread, &Ctx, - 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, szThreadName); - if (RT_SUCCESS(rc)) - rc = RTThreadUserWait(pAIO->Thread, 10 * 1000 /* 10s timeout */); - } - } - } - else - rc = VINF_SUCCESS; - - LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc)); - return rc; + int rc = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs); + AssertRC(rc); } -/** - * Lets the stream's async I/O thread know that there is some data to process. - * - * @returns IPRT status code. - * @param pStreamCC The AC'97 stream to notify async I/O thread - * for (ring-3). - */ -static int ichac97R3StreamAsyncIONotify(PAC97STREAM pStreamCC) -{ - LogFunc(("[SD%RU8]\n", pStreamCC->u8SD)); - return RTSemEventSignal(pStreamCC->State.AIO.Event); -} /** - * Destroys the async I/O thread of a specific AC'97 audio stream. - * - * @returns IPRT status code. - * @param pThis The shared AC'97 state. - * @param pStream AC'97 audio stream to destroy the async I/O thread for. + * @callback_method_impl{FNTMTIMERDEV, + * Timer callback which handles the audio data transfers on a periodic basis.} */ -static int ichac97R3StreamAsyncIODestroy(PAC97STATE pThis, PAC97STREAM pStream) +static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { - PAC97STREAMSTATEAIO pAIO = &pStreamCC->State.AIO; - - if (!ASMAtomicReadBool(&pAIO->fStarted)) - return VINF_SUCCESS; - - ASMAtomicWriteBool(&pAIO->fShutdown, true); - - int rc = ichac97R3StreamAsyncIONotify(pStreamCC); - AssertRC(rc); + PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); + STAM_PROFILE_START(&pThis->StatTimer, a); + PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); + PAC97STREAM pStream = (PAC97STREAM)pvUser; + PAC97STREAMR3 pStreamCC = &RT_SAFE_SUBSCRIPT8(pThisCC->aStreams, pStream->u8SD); + RT_NOREF(pTimer); - int rcThread; - rc = RTThreadWait(pAIO->Thread, 30 * 1000 /* 30s timeout */, &rcThread); - LogFunc(("Async I/O thread ended with %Rrc (%Rrc)\n", rc, rcThread)); + Assert(pStream - &pThis->aStreams[0] == pStream->u8SD); + Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + Assert(PDMDevHlpTimerIsLockOwner(pDevIns, pStream->hTimer)); - if (RT_SUCCESS(rc)) + PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD); + if (pSink && AudioMixerSinkIsActive(pSink)) { - rc = RTCritSectDelete(&pAIO->CritSect); - AssertRC(rc); + ichac97R3StreamUpdateDma(pDevIns, pThis, pThisCC, pStream, pStreamCC, pSink); - rc = RTSemEventDestroy(pAIO->Event); - AssertRC(rc); - - pAIO->fStarted = false; - pAIO->fShutdown = false; - pAIO->fEnabled = false; + pStream->uDmaPeriod++; + ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC); + ichac97R3TimerSet(pDevIns, pStream, pStream->cDmaPeriodTicks); } - LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc)); - return rc; + STAM_PROFILE_STOP(&pThis->StatTimer, a); } -/** - * Locks the async I/O thread of a specific AC'97 audio stream. - * - * @param pStream AC'97 stream to lock async I/O thread for. - */ -static void ichac97R3StreamAsyncIOLock(PAC97STREAM pStream) -{ - PAC97STREAMSTATEAIO pAIO = &pStreamCC->State.AIO; +#endif /* IN_RING3 */ - if (!ASMAtomicReadBool(&pAIO->fStarted)) - return; - int rc2 = RTCritSectEnter(&pAIO->CritSect); - AssertRC(rc2); -} +/********************************************************************************************************************************* +* AC'97 Stream Management * +*********************************************************************************************************************************/ +#ifdef IN_RING3 /** - * Unlocks the async I/O thread of a specific AC'97 audio stream. + * Locks an AC'97 stream for serialized access. * - * @param pStream AC'97 stream to unlock async I/O thread for. + * @returns VBox status code. + * @param pStreamCC The AC'97 stream to lock (ring-3). */ -static void ichac97R3StreamAsyncIOUnlock(PAC97STREAM pStream) +DECLINLINE(void) ichac97R3StreamLock(PAC97STREAMR3 pStreamCC) { - PAC97STREAMSTATEAIO pAIO = &pStreamCC->State.AIO; - - if (!ASMAtomicReadBool(&pAIO->fStarted)) - return; - - int rc2 = RTCritSectLeave(&pAIO->CritSect); + int rc2 = RTCritSectEnter(&pStreamCC->State.CritSect); AssertRC(rc2); } -#if 0 /* Unused */ /** - * Enables (resumes) or disables (pauses) the async I/O thread. - * - * @param pStream AC'97 stream to enable/disable async I/O thread for. - * @param fEnable Whether to enable or disable the I/O thread. + * Unlocks a formerly locked AC'97 stream. * - * @remarks Does not do locking. + * @returns VBox status code. + * @param pStreamCC The AC'97 stream to unlock (ring-3). */ -static void ichac97R3StreamAsyncIOEnable(PAC97STREAM pStream, bool fEnable) +DECLINLINE(void) ichac97R3StreamUnlock(PAC97STREAMR3 pStreamCC) { - PAC97STREAMSTATEAIO pAIO = &pStreamCC->State.AIO; - ASMAtomicXchgBool(&pAIO->fEnabled, fEnable); + int rc2 = RTCritSectLeave(&pStreamCC->State.CritSect); + AssertRC(rc2); } -#endif -# endif /* VBOX_WITH_AUDIO_AC97_ASYNC_IO */ - -# ifdef LOG_ENABLED -static void ichac97R3BDLEDumpAll(PPDMDEVINS pDevIns, uint64_t u64BDLBase, uint16_t cBDLE) -{ - LogFlowFunc(("BDLEs @ 0x%x (%RU16):\n", u64BDLBase, cBDLE)); - if (!u64BDLBase) - return; - - uint32_t cbBDLE = 0; - for (uint16_t i = 0; i < cBDLE; i++) - { - AC97BDLE BDLE; - PDMDevHlpPhysRead(pDevIns, u64BDLBase + i * sizeof(AC97BDLE), &BDLE, sizeof(AC97BDLE)); - -# ifndef RT_LITTLE_ENDIAN -# error "Please adapt the code (audio buffers are little endian)!" -# else - BDLE.addr = RT_H2LE_U32(BDLE.addr & ~3); - BDLE.ctl_len = RT_H2LE_U32(BDLE.ctl_len); -#endif - LogFunc(("\t#%03d BDLE(adr:0x%llx, size:%RU32 [%RU32 bytes], bup:%RTbool, ioc:%RTbool)\n", - i, BDLE.addr, - BDLE.ctl_len & AC97_BD_LEN_MASK, - (BDLE.ctl_len & AC97_BD_LEN_MASK) << 1, /** @todo r=andy Assumes 16bit samples. */ - RT_BOOL(BDLE.ctl_len & AC97_BD_BUP), - RT_BOOL(BDLE.ctl_len & AC97_BD_IOC))); - - cbBDLE += (BDLE.ctl_len & AC97_BD_LEN_MASK) << 1; /** @todo r=andy Ditto. */ - } - LogFlowFunc(("Total: %RU32 bytes\n", cbBDLE)); -} -# endif /* LOG_ENABLED */ +#endif /* IN_RING3 */ /** - * Updates an AC'97 stream by doing its required data transfers. - * The host sink(s) set the overall pace. - * - * This routine is called by both, the synchronous and the asynchronous - * (VBOX_WITH_AUDIO_AC97_ASYNC_IO), implementations. - * - * When running synchronously, the device DMA transfers *and* the mixer sink - * processing is within the device timer. - * - * When running asynchronously, only the device DMA transfers are done in the - * device timer, whereas the mixer sink processing then is done in the stream's - * own async I/O thread. This thread also will call this function - * (with fInTimer set to @c false). + * Updates the status register (SR) of an AC'97 audio stream. * * @param pDevIns The device instance. * @param pThis The shared AC'97 state. - * @param pThisCC The ring-3 AC'97 state. - * @param pStream The AC'97 stream to update (shared). - * @param pStreamCC The AC'97 stream to update (ring-3). - * @param fInTimer Whether to this function was called from the timer - * context or an asynchronous I/O stream thread (if supported). + * @param pStream AC'97 stream to update SR for. + * @param new_sr New value for status register (SR). */ -static void ichac97R3StreamUpdate(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC, - PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fInTimer) +static void ichac97StreamUpdateSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr) { - RT_NOREF(fInTimer); - - PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD); - AssertPtr(pSink); - - if (!AudioMixerSinkIsActive(pSink)) /* No sink available? Bail out. */ - return; + bool fSignal = false; + int iIRQL = 0; - int rc2; + uint32_t new_mask = new_sr & AC97_SR_INT_MASK; + uint32_t old_mask = pStream->Regs.sr & AC97_SR_INT_MASK; - if (pStreamCC->State.Cfg.enmDir == PDMAUDIODIR_OUT) /* Output (SDO). */ + if (new_mask ^ old_mask) { -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - if (fInTimer) -# endif + /** @todo Is IRQ deasserted when only one of status bits is cleared? */ + if (!new_mask) { - const uint32_t cbStreamFree = ichac97R3StreamGetFree(pStreamCC); - if (cbStreamFree) - { - Log3Func(("[SD%RU8] PICB=%zu (%RU64ms), cbFree=%zu (%RU64ms), cbTransferChunk=%zu (%RU64ms)\n", - pStream->u8SD, - (pStream->Regs.picb << 1), DrvAudioHlpBytesToMilli((pStream->Regs.picb << 1), &pStreamCC->State.Cfg.Props), - cbStreamFree, DrvAudioHlpBytesToMilli(cbStreamFree, &pStreamCC->State.Cfg.Props), - pStreamCC->State.cbTransferChunk, DrvAudioHlpBytesToMilli(pStreamCC->State.cbTransferChunk, &pStreamCC->State.Cfg.Props))); - - /* Do the DMA transfer. */ - rc2 = ichac97R3StreamTransfer(pDevIns, pThis, pStream, pStreamCC, - RT_MIN(pStreamCC->State.cbTransferChunk, cbStreamFree)); - AssertRC(rc2); - - pStreamCC->State.tsLastUpdateNs = RTTimeNanoTS(); - } + fSignal = true; + iIRQL = 0; } - - Log3Func(("[SD%RU8] fInTimer=%RTbool\n", pStream->u8SD, fInTimer)); - -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - rc2 = ichac97R3StreamAsyncIONotify(pStreamCC); - AssertRC(rc2); -# endif - -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - if (!fInTimer) /* In async I/O thread */ + else if ((new_mask & AC97_SR_LVBCI) && (pStream->Regs.cr & AC97_CR_LVBIE)) { -# endif - const uint32_t cbSinkWritable = AudioMixerSinkGetWritable(pSink); - const uint32_t cbStreamReadable = ichac97R3StreamGetUsed(pStreamCC); - const uint32_t cbToReadFromStream = RT_MIN(cbStreamReadable, cbSinkWritable); - - Log3Func(("[SD%RU8] cbSinkWritable=%RU32, cbStreamReadable=%RU32\n", pStream->u8SD, cbSinkWritable, cbStreamReadable)); - - if (cbToReadFromStream) - { - /* Read (guest output) data and write it to the stream's sink. */ - rc2 = ichac97R3StreamRead(pStreamCC, pSink, cbToReadFromStream, NULL /* pcbRead */); - AssertRC(rc2); - } -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + fSignal = true; + iIRQL = 1; } -#endif - /* When running synchronously, update the associated sink here. - * Otherwise this will be done in the async I/O thread. */ - rc2 = AudioMixerSinkUpdate(pSink); - AssertRC(rc2); - } - else /* Input (SDI). */ - { -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - if (!fInTimer) + else if ((new_mask & AC97_SR_BCIS) && (pStream->Regs.cr & AC97_CR_IOCE)) { -# endif - rc2 = AudioMixerSinkUpdate(pSink); - AssertRC(rc2); - - /* Is the sink ready to be read (host input data) from? If so, by how much? */ - uint32_t cbSinkReadable = AudioMixerSinkGetReadable(pSink); - - /* How much (guest input) data is available for writing at the moment for the AC'97 stream? */ - uint32_t cbStreamFree = ichac97R3StreamGetFree(pStreamCC); - - Log3Func(("[SD%RU8] cbSinkReadable=%RU32, cbStreamFree=%RU32\n", pStream->u8SD, cbSinkReadable, cbStreamFree)); - - /* Do not read more than the sink can provide at the moment. - * The host sets the overall pace. */ - if (cbSinkReadable > cbStreamFree) - cbSinkReadable = cbStreamFree; - - if (cbSinkReadable) - { - /* Write (guest input) data to the stream which was read from stream's sink before. */ - rc2 = ichac97R3StreamWrite(pStreamCC, pSink, cbSinkReadable, NULL /* pcbWritten */); - AssertRC(rc2); - } -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + fSignal = true; + iIRQL = 1; } - else /* fInTimer */ - { -# endif + } -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - const uint64_t tsNowNs = RTTimeNanoTS(); - if (tsNowNs - pStreamCC->State.tsLastUpdateNs >= pStreamCC->State.Cfg.Device.cMsSchedulingHint * RT_NS_1MS) - { - rc2 = ichac97R3StreamAsyncIONotify(pStreamCC); - AssertRC(rc2); + pStream->Regs.sr = new_sr; - pStreamCC->State.tsLastUpdateNs = tsNowNs; - } -# endif + LogFlowFunc(("IOC%d, LVB%d, sr=%#x, fSignal=%RTbool, IRQL=%d\n", + pStream->Regs.sr & AC97_SR_BCIS, pStream->Regs.sr & AC97_SR_LVBCI, pStream->Regs.sr, fSignal, iIRQL)); - const uint32_t cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC); - if (cbStreamUsed) - { - /* When running synchronously, do the DMA data transfers here. - * Otherwise this will be done in the stream's async I/O thread. */ - rc2 = ichac97R3StreamTransfer(pDevIns, pThis, pStream, pStreamCC, cbStreamUsed); - AssertRC(rc2); - } -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - } -# endif + if (fSignal) + { + static uint32_t const s_aMasks[] = { AC97_GS_PIINT, AC97_GS_POINT, AC97_GS_MINT }; + Assert(pStream->u8SD < AC97_MAX_STREAMS); + if (iIRQL) + pThis->glob_sta |= s_aMasks[pStream->u8SD]; + else + pThis->glob_sta &= ~s_aMasks[pStream->u8SD]; + + LogFlowFunc(("Setting IRQ level=%d\n", iIRQL)); + PDMDevHlpPCISetIrq(pDevIns, 0, iIRQL); } } -#endif /* IN_RING3 */ - /** - * Sets a AC'97 mixer control to a specific value. + * Writes a new value to a stream's status register (SR). * - * @returns IPRT status code. - * @param pThis The shared AC'97 state. - * @param uMixerIdx Mixer control to set value for. - * @param uVal Value to set. + * @param pDevIns The device instance. + * @param pThis The shared AC'97 device state. + * @param pStream Stream to update SR for. + * @param u32Val New value to set the stream's SR to. */ -static void ichac97MixerSet(PAC97STATE pThis, uint8_t uMixerIdx, uint16_t uVal) +static void ichac97StreamWriteSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t u32Val) { - AssertMsgReturnVoid(uMixerIdx + 2U <= sizeof(pThis->mixer_data), - ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data))); - - LogRel2(("AC97: Setting mixer index #%RU8 to %RU16 (%RU8 %RU8)\n", - uMixerIdx, uVal, RT_HI_U8(uVal), RT_LO_U8(uVal))); + Log3Func(("[SD%RU8] SR <- %#x (sr %#x)\n", pStream->u8SD, u32Val, pStream->Regs.sr)); - pThis->mixer_data[uMixerIdx + 0] = RT_LO_U8(uVal); - pThis->mixer_data[uMixerIdx + 1] = RT_HI_U8(uVal); + pStream->Regs.sr |= u32Val & ~(AC97_SR_RO_MASK | AC97_SR_WCLEAR_MASK); + ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr & ~(u32Val & AC97_SR_WCLEAR_MASK)); } +#ifdef IN_RING3 + /** - * Gets a value from a specific AC'97 mixer control. + * Resets an AC'97 stream. * - * @returns Retrieved mixer control value. * @param pThis The shared AC'97 state. - * @param uMixerIdx Mixer control to get value for. + * @param pStream The AC'97 stream to reset (shared). + * @param pStreamCC The AC'97 stream to reset (ring-3). */ -static uint16_t ichac97MixerGet(PAC97STATE pThis, uint32_t uMixerIdx) +static void ichac97R3StreamReset(PAC97STATE pThis, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC) { - AssertMsgReturn(uMixerIdx + 2U <= sizeof(pThis->mixer_data), - ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data)), - UINT16_MAX); - return RT_MAKE_U16(pThis->mixer_data[uMixerIdx + 0], pThis->mixer_data[uMixerIdx + 1]); -} + ichac97R3StreamLock(pStreamCC); -#ifdef IN_RING3 + LogFunc(("[SD%RU8]\n", pStream->u8SD)); + + if (pStreamCC->State.pCircBuf) + RTCircBufReset(pStreamCC->State.pCircBuf); + + pStream->Regs.bdbar = 0; + pStream->Regs.civ = 0; + pStream->Regs.lvi = 0; + + pStream->Regs.picb = 0; + pStream->Regs.piv = 0; /* Note! Because this is also zero, we will actually start transferring with BDLE00. */ + pStream->Regs.cr &= AC97_CR_DONT_CLEAR_MASK; + pStream->Regs.bd_valid = 0; + + RT_ZERO(pThis->silence); + + ichac97R3StreamUnlock(pStreamCC); +} /** * Retrieves a specific driver stream of a AC'97 driver. * * @returns Pointer to driver stream if found, or NULL if not found. - * @param pDrv Driver to retrieve driver stream for. - * @param enmDir Stream direction to retrieve. - * @param dstSrc Stream destination / source to retrieve. + * @param pDrv Driver to retrieve driver stream for. + * @param enmDir Stream direction to retrieve. + * @param enmPath Stream destination / source to retrieve. */ -static PAC97DRIVERSTREAM ichac97R3MixerGetDrvStream(PAC97DRIVER pDrv, PDMAUDIODIR enmDir, PDMAUDIODSTSRCUNION dstSrc) +static PAC97DRIVERSTREAM ichac97R3MixerGetDrvStream(PAC97DRIVER pDrv, PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath) { - PAC97DRIVERSTREAM pDrvStream = NULL; - if (enmDir == PDMAUDIODIR_IN) { - LogFunc(("enmRecSource=%d\n", dstSrc.enmSrc)); - - switch (dstSrc.enmSrc) + LogFunc(("enmRecSource=%d\n", enmPath)); + switch (enmPath) { - case PDMAUDIORECSRC_LINE: - pDrvStream = &pDrv->LineIn; - break; - case PDMAUDIORECSRC_MIC: - pDrvStream = &pDrv->MicIn; - break; + case PDMAUDIOPATH_IN_LINE: + return &pDrv->LineIn; + case PDMAUDIOPATH_IN_MIC: + return &pDrv->MicIn; default: - AssertFailed(); - break; + AssertFailedBreak(); } } else if (enmDir == PDMAUDIODIR_OUT) { - LogFunc(("enmPlaybackDest=%d\n", dstSrc.enmDst)); - - switch (dstSrc.enmDst) + LogFunc(("enmPlaybackDst=%d\n", enmPath)); + switch (enmPath) { - case PDMAUDIOPLAYBACKDST_FRONT: - pDrvStream = &pDrv->Out; - break; + case PDMAUDIOPATH_OUT_FRONT: + return &pDrv->Out; default: - AssertFailed(); - break; + AssertFailedBreak(); } } else AssertFailed(); - return pDrvStream; + return NULL; } /** * Adds a driver stream to a specific mixer sink. * - * @returns IPRT status code. - * @param pMixSink Mixer sink to add driver stream to. - * @param pCfg Stream configuration to use. - * @param pDrv Driver stream to add. + * Called by ichac97R3MixerAddDrvStreams() and ichac97R3MixerAddDrv(). + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pMixSink Mixer sink to add driver stream to. + * @param pCfg Stream configuration to use. + * @param pDrv Driver stream to add. */ -static int ichac97R3MixerAddDrvStream(PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PAC97DRIVER pDrv) +static int ichac97R3MixerAddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PAC97DRIVER pDrv) { AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); + LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfg->szName)); - PPDMAUDIOSTREAMCFG pStreamCfg = DrvAudioHlpStreamCfgDup(pCfg); - if (!pStreamCfg) - return VERR_NO_MEMORY; - - if (!RTStrPrintf(pStreamCfg->szName, sizeof(pStreamCfg->szName), "%s", pCfg->szName)) - { - DrvAudioHlpStreamCfgFree(pStreamCfg); - return VERR_BUFFER_OVERFLOW; - } - - LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pStreamCfg->szName)); - - int rc; - - PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pDrv, pStreamCfg->enmDir, pStreamCfg->u); - if (pDrvStream) + int rc; + PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pDrv, pCfg->enmDir, pCfg->enmPath); + if (pDrvStream) { AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN)); PAUDMIXSTREAM pMixStrm; - rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pStreamCfg, 0 /* fFlags */, &pMixStrm); - LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc)); + rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pCfg, pDevIns, &pMixStrm); + LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc)); if (RT_SUCCESS(rc)) { rc = AudioMixerSinkAddStream(pMixSink, pMixStrm); - LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc)); + LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc)); if (RT_SUCCESS(rc)) - { - /* If this is an input stream, always set the latest (added) stream - * as the recording source. */ - /** @todo Make the recording source dynamic (CFGM?). */ - if (pStreamCfg->enmDir == PDMAUDIODIR_IN) - { - PDMAUDIOBACKENDCFG Cfg; - rc = pDrv->pConnector->pfnGetConfig(pDrv->pConnector, &Cfg); - if (RT_SUCCESS(rc)) - { - if (Cfg.cMaxStreamsIn) /* At least one input source available? */ - { - rc = AudioMixerSinkSetRecordingSource(pMixSink, pMixStrm); - LogFlowFunc(("LUN#%RU8: Recording source for '%s' -> '%s', rc=%Rrc\n", - pDrv->uLUN, pStreamCfg->szName, Cfg.szName, rc)); - - if (RT_SUCCESS(rc)) - LogRel2(("AC97: Set recording source for '%s' to '%s'\n", pStreamCfg->szName, Cfg.szName)); - } - else - LogRel(("AC97: Backend '%s' currently is not offering any recording source for '%s'\n", - Cfg.szName, pStreamCfg->szName)); - } - else if (RT_FAILURE(rc)) - LogFunc(("LUN#%RU8: Unable to retrieve backend configuratio for '%s', rc=%Rrc\n", - pDrv->uLUN, pStreamCfg->szName, rc)); - } - /** @todo r=bird: see below. */ - if (RT_FAILURE(rc)) - AudioMixerSinkRemoveStream(pMixSink, pMixStrm); - } - /** @todo r=bird: I've added this destroy stuff here, because if it looks as if - * you just drop the stream if the AudioMixerSinkAddStream fails for some - * reason. This is definitely true if AudioMixerSinkSetRecordingSource fails - * above, because it leads to duplicate statistics when starting XP with ICH97 - * and VRDP enabled. Looks like the VRDP line-in fails with - * VERR_AUDIO_STREAM_NOT_READY when configured for 8000HZ, then it asserts in - * STAM when 48000Hz is configured right afterwards. */ - if (RT_FAILURE(rc)) - AudioMixerStreamDestroy(pMixStrm); + pDrvStream->pMixStrm = pMixStrm; + else + AudioMixerStreamDestroy(pMixStrm, pDevIns, true /*fImmediate*/); } - - if (RT_SUCCESS(rc)) - pDrvStream->pMixStrm = pMixStrm; } else rc = VERR_INVALID_PARAMETER; - DrvAudioHlpStreamCfgFree(pStreamCfg); - LogFlowFuncLeaveRC(rc); return rc; } + /** * Adds all current driver streams to a specific mixer sink. * - * @returns IPRT status code. - * @param pThisCC The ring-3 AC'97 state. - * @param pMixSink Mixer sink to add stream to. - * @param pCfg Stream configuration to use. - */ -static int ichac97R3MixerAddDrvStreams(PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); - - if (!DrvAudioHlpStreamCfgIsValid(pCfg)) - return VERR_INVALID_PARAMETER; - - int rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props); - if (RT_FAILURE(rc)) - return rc; - - PAC97DRIVER pDrv; - RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node) - { - int rc2 = ichac97R3MixerAddDrvStream(pMixSink, pCfg, pDrv); - if (RT_FAILURE(rc2)) - LogFunc(("Attaching stream failed with %Rrc\n", rc2)); - - /* Do not pass failure to rc here, as there might be drivers which aren't - * configured / ready yet. */ - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Adds a specific AC'97 driver to the driver chain. + * Called by ichac97R3StreamSetUp(). * - * @return IPRT status code. - * @param pThisCC The ring-3 AC'97 device state. - * @param pDrv The AC'97 driver to add. + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThisCC The ring-3 AC'97 state. + * @param pMixSink Mixer sink to add stream to. + * @param pCfg Stream configuration to use. */ -static int ichac97R3MixerAddDrv(PAC97STATER3 pThisCC, PAC97DRIVER pDrv) +static int ichac97R3MixerAddDrvStreams(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg) { - int rc = VINF_SUCCESS; - - if (DrvAudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg)) - rc = ichac97R3MixerAddDrvStream(pThisCC->pSinkLineIn, &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg, pDrv); + AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); - if (DrvAudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg)) + int rc; + if (AudioHlpStreamCfgIsValid(pCfg)) { - int rc2 = ichac97R3MixerAddDrvStream(pThisCC->pSinkOut, &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg, pDrv); + rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props, pCfg->Device.cMsSchedulingHint); if (RT_SUCCESS(rc)) - rc = rc2; - } + { + PAC97DRIVER pDrv; + RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node) + { + int rc2 = ichac97R3MixerAddDrvStream(pDevIns, pMixSink, pCfg, pDrv); + if (RT_FAILURE(rc2)) + LogFunc(("Attaching stream failed with %Rrc\n", rc2)); - if (DrvAudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg)) - { - int rc2 = ichac97R3MixerAddDrvStream(pThisCC->pSinkMicIn, &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg, pDrv); - if (RT_SUCCESS(rc)) - rc = rc2; + /* Do not pass failure to rc here, as there might be drivers which aren't + configured / ready yet. */ + } + } } + else + rc = VERR_INVALID_PARAMETER; + LogFlowFuncLeaveRC(rc); return rc; } -/** - * Removes a specific AC'97 driver from the driver chain and destroys its - * associated streams. - * - * @param pThisCC The ring-3 AC'97 device state. - * @param pDrv AC'97 driver to remove. - */ -static void ichac97R3MixerRemoveDrv(PAC97STATER3 pThisCC, PAC97DRIVER pDrv) -{ - if (pDrv->MicIn.pMixStrm) - { - if (AudioMixerSinkGetRecordingSource(pThisCC->pSinkMicIn) == pDrv->MicIn.pMixStrm) - AudioMixerSinkSetRecordingSource(pThisCC->pSinkMicIn, NULL); - - AudioMixerSinkRemoveStream(pThisCC->pSinkMicIn, pDrv->MicIn.pMixStrm); - AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm); - pDrv->MicIn.pMixStrm = NULL; - } - - if (pDrv->LineIn.pMixStrm) - { - if (AudioMixerSinkGetRecordingSource(pThisCC->pSinkLineIn) == pDrv->LineIn.pMixStrm) - AudioMixerSinkSetRecordingSource(pThisCC->pSinkLineIn, NULL); - - AudioMixerSinkRemoveStream(pThisCC->pSinkLineIn, pDrv->LineIn.pMixStrm); - AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm); - pDrv->LineIn.pMixStrm = NULL; - } - - if (pDrv->Out.pMixStrm) - { - AudioMixerSinkRemoveStream(pThisCC->pSinkOut, pDrv->Out.pMixStrm); - AudioMixerStreamDestroy(pDrv->Out.pMixStrm); - pDrv->Out.pMixStrm = NULL; - } - - RTListNodeRemove(&pDrv->Node); -} /** * Removes a driver stream from a specific mixer sink. * - * @param pMixSink Mixer sink to remove audio streams from. - * @param enmDir Stream direction to remove. - * @param dstSrc Stream destination / source to remove. - * @param pDrv Driver stream to remove. + * Worker for ichac97R3MixerRemoveDrvStreams. + * + * @param pDevIns The device instance. + * @param pMixSink Mixer sink to remove audio streams from. + * @param enmDir Stream direction to remove. + * @param enmPath Stream destination / source to remove. + * @param pDrv Driver stream to remove. */ -static void ichac97R3MixerRemoveDrvStream(PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir, PDMAUDIODSTSRCUNION dstSrc, PAC97DRIVER pDrv) +static void ichac97R3MixerRemoveDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir, + PDMAUDIOPATH enmPath, PAC97DRIVER pDrv) { - PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pDrv, enmDir, dstSrc); + PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pDrv, enmDir, enmPath); if (pDrvStream) { if (pDrvStream->pMixStrm) { AudioMixerSinkRemoveStream(pMixSink, pDrvStream->pMixStrm); - AudioMixerStreamDestroy(pDrvStream->pMixStrm); + AudioMixerStreamDestroy(pDrvStream->pMixStrm, pDevIns, false /*fImmediate*/); pDrvStream->pMixStrm = NULL; } } @@ -2036,1337 +1881,1485 @@ /** * Removes all driver streams from a specific mixer sink. * - * @param pThisCC The ring-3 AC'97 state. - * @param pMixSink Mixer sink to remove audio streams from. - * @param enmDir Stream direction to remove. - * @param dstSrc Stream destination / source to remove. + * Called by ichac97R3StreamSetUp() and ichac97R3StreamsDestroy(). + * + * @param pDevIns The device instance. + * @param pThisCC The ring-3 AC'97 state. + * @param pMixSink Mixer sink to remove audio streams from. + * @param enmDir Stream direction to remove. + * @param enmPath Stream destination / source to remove. */ -static void ichac97R3MixerRemoveDrvStreams(PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink, - PDMAUDIODIR enmDir, PDMAUDIODSTSRCUNION dstSrc) +static void ichac97R3MixerRemoveDrvStreams(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink, + PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath) { AssertPtrReturnVoid(pMixSink); PAC97DRIVER pDrv; RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node) { - ichac97R3MixerRemoveDrvStream(pMixSink, enmDir, dstSrc, pDrv); + ichac97R3MixerRemoveDrvStream(pDevIns, pMixSink, enmDir, enmPath, pDrv); } } + /** - * Calculates and returns the ticks for a specified amount of bytes. + * Gets the frequency of a given stream. * - * @returns Calculated ticks - * @param pDevIns The device instance. - * @param pStream AC'97 stream to calculate ticks for (shared). - * @param pStreamCC AC'97 stream to calculate ticks for (ring-3). - * @param cbBytes Bytes to calculate ticks for. + * @returns The frequency. Zero if invalid stream index. + * @param pThis The shared AC'97 device state. + * @param idxStream The stream. */ -static uint64_t ichac97R3StreamTransferCalcNext(PPDMDEVINS pDevIns, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, uint32_t cbBytes) +DECLINLINE(uint32_t) ichach97R3CalcStreamHz(PAC97STATE pThis, uint8_t idxStream) { - if (!cbBytes) - return 0; + switch (idxStream) + { + case AC97SOUNDSOURCE_PI_INDEX: + return ichac97MixerGet(pThis, AC97_PCM_LR_ADC_Rate); - const uint64_t usBytes = DrvAudioHlpBytesToMicro(cbBytes, &pStreamCC->State.Cfg.Props); - const uint64_t cTransferTicks = PDMDevHlpTimerFromMicro(pDevIns, pStream->hTimer, usBytes); + case AC97SOUNDSOURCE_MC_INDEX: + return ichac97MixerGet(pThis, AC97_MIC_ADC_Rate); - Log3Func(("[SD%RU8] Timer %uHz, cbBytes=%RU32 -> usBytes=%RU64, cTransferTicks=%RU64\n", - pStream->u8SD, pStreamCC->State.uTimerHz, cbBytes, usBytes, cTransferTicks)); + case AC97SOUNDSOURCE_PO_INDEX: + return ichac97MixerGet(pThis, AC97_PCM_Front_DAC_Rate); - return cTransferTicks; + default: + AssertMsgFailedReturn(("%d\n", idxStream), 0); + } } + /** - * Updates the next transfer based on a specific amount of bytes. + * Gets the PCM properties for a given stream. * - * @param pDevIns The device instance. - * @param pStream The AC'97 stream to update (shared). - * @param pStreamCC The AC'97 stream to update (ring-3). - * @param cbBytes Bytes to update next transfer for. + * @returns pProps. + * @param pThis The shared AC'97 device state. + * @param idxStream Which stream + * @param pProps Where to return the stream properties. */ -static void ichac97R3StreamTransferUpdate(PPDMDEVINS pDevIns, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, uint32_t cbBytes) +DECLINLINE(PPDMAUDIOPCMPROPS) ichach97R3CalcStreamProps(PAC97STATE pThis, uint8_t idxStream, PPDMAUDIOPCMPROPS pProps) { - if (!cbBytes) - return; - - /* Calculate the bytes we need to transfer to / from the stream's DMA per iteration. - * This is bound to the device's Hz rate and thus to the (virtual) timing the device expects. */ - pStreamCC->State.cbTransferChunk = cbBytes; - - /* Update the transfer ticks. */ - pStreamCC->State.cTransferTicks = ichac97R3StreamTransferCalcNext(pDevIns, pStream, pStreamCC, - pStreamCC->State.cbTransferChunk); - Assert(pStreamCC->State.cTransferTicks); /* Paranoia. */ + PDMAudioPropsInit(pProps, 2 /*16-bit*/, true /*signed*/, 2 /*stereo*/, ichach97R3CalcStreamHz(pThis, idxStream)); + return pProps; } + /** - * Opens an AC'97 stream with its current mixer settings. + * Sets up an AC'97 stream with its current mixer settings. * - * This will open an AC'97 stream with 2 (stereo) channels, 16-bit samples and + * This will set up an AC'97 stream with 2 (stereo) channels, 16-bit samples and * the last set sample rate in the AC'97 mixer for this stream. * - * @returns IPRT status code. - * @param pThis The shared AC'97 device state (shared). - * @param pThisCC The shared AC'97 device state (ring-3). - * @param pStream The AC'97 stream to open (shared). - * @param pStreamCC The AC'97 stream to open (ring-3). - * @param fForce Whether to force re-opening the stream or not. - * Otherwise re-opening only will happen if the PCM properties have changed. - */ -static int ichac97R3StreamOpen(PAC97STATE pThis, PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fForce) -{ - PDMAUDIOSTREAMCFG Cfg; - RT_ZERO(Cfg); - Cfg.Props.cChannels = 2; - Cfg.Props.cbSample = 2 /* 16-bit */; - Cfg.Props.fSigned = true; - Cfg.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Cfg.Props.cbSample, Cfg.Props.cChannels); + * @returns VBox status code. + * @retval VINF_NO_CHANGE if the streams weren't re-created. + * + * @param pDevIns The device instance. + * @param pThis The shared AC'97 device state (shared). + * @param pThisCC The shared AC'97 device state (ring-3). + * @param pStream The AC'97 stream to open (shared). + * @param pStreamCC The AC'97 stream to open (ring-3). + * @param fForce Whether to force re-opening the stream or not. + * Otherwise re-opening only will happen if the PCM properties have changed. + * + * @remarks This is called holding: + * -# The AC'97 device lock. + * -# The AC'97 stream lock. + * -# The mixer sink lock (to prevent racing AIO thread). + */ +static int ichac97R3StreamSetUp(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC, PAC97STREAM pStream, + PAC97STREAMR3 pStreamCC, bool fForce) +{ + /* + * Assemble the stream config and get the associated mixer sink. + */ + PDMAUDIOPCMPROPS PropsTmp; + PDMAUDIOSTREAMCFG Cfg; + PDMAudioStrmCfgInitWithProps(&Cfg, ichach97R3CalcStreamProps(pThis, pStream->u8SD, &PropsTmp)); + Assert(Cfg.enmDir != PDMAUDIODIR_UNKNOWN); - int rc = VINF_SUCCESS; - PAUDMIXSINK pMixSink; + PAUDMIXSINK pMixSink; switch (pStream->u8SD) { case AC97SOUNDSOURCE_PI_INDEX: - { - Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_PCM_LR_ADC_Rate); Cfg.enmDir = PDMAUDIODIR_IN; - Cfg.u.enmSrc = PDMAUDIORECSRC_LINE; - Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + Cfg.enmPath = PDMAUDIOPATH_IN_LINE; RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Line-In"); pMixSink = pThisCC->pSinkLineIn; break; - } case AC97SOUNDSOURCE_MC_INDEX: - { - Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_MIC_ADC_Rate); Cfg.enmDir = PDMAUDIODIR_IN; - Cfg.u.enmSrc = PDMAUDIORECSRC_MIC; - Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + Cfg.enmPath = PDMAUDIOPATH_IN_MIC; RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Mic-In"); pMixSink = pThisCC->pSinkMicIn; break; - } case AC97SOUNDSOURCE_PO_INDEX: - { - Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_PCM_Front_DAC_Rate); Cfg.enmDir = PDMAUDIODIR_OUT; - Cfg.u.enmDst = PDMAUDIOPLAYBACKDST_FRONT; - Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT; RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Output"); pMixSink = pThisCC->pSinkOut; break; - } default: - rc = VERR_NOT_SUPPORTED; - pMixSink = NULL; + AssertMsgFailedReturn(("u8SD=%d\n", pStream->u8SD), VERR_INTERNAL_ERROR_3); + } + + /* + * Don't continue if the frequency is out of range (the rest of the + * properties should be okay). + * Note! Don't assert on this as we may easily end up here with Hz=0. + */ + char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX]; + if (AudioHlpStreamCfgIsValid(&Cfg)) + { } + else + { + LogFunc(("Invalid stream #%u rate: %s\n", pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp)) )); + return VERR_OUT_OF_RANGE; + } + + /* + * Read the buffer descriptors and check what the max distance between + * interrupts are, so we can more correctly size the internal DMA buffer. + * + * Note! The buffer list are not fixed once the stream starts running as + * with HDA, so this is just a general idea of what the guest is + * up to and we cannot really make much of a plan out of it. + */ + uint8_t const bLvi = pStream->Regs.lvi % AC97_MAX_BDLE /* paranoia */; + uint8_t const bCiv = pStream->Regs.civ % AC97_MAX_BDLE /* paranoia */; + uint32_t const uAddrBdl = pStream->Regs.bdbar; + + /* Linux does this a number of times while probing/whatever the device. The + IOMMU usually does allow us to read address zero, so let's skip and hope + for a better config before the guest actually wants to play/record. + (Note that bLvi and bCiv are also zero then, but I'm not entirely sure if + that can be taken to mean anything as such, as it still indicates that + BDLE00 is valid (LVI == last valid index).) */ + /** @todo Instead of refusing to read address zero, we should probably allow + * reading address zero if explicitly programmed. But, too much work now. */ + if (uAddrBdl != 0) + LogFlowFunc(("bdbar=%#x bLvi=%#x bCiv=%#x\n", uAddrBdl, bLvi, bCiv)); + else + { + LogFunc(("Invalid stream #%u: bdbar=%#x bLvi=%#x bCiv=%#x (%s)\n", pStreamCC->u8SD, uAddrBdl, bLvi, bCiv, + PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp)))); + return VERR_OUT_OF_RANGE; + } + + AC97BDLE aBdl[AC97_MAX_BDLE]; + RT_ZERO(aBdl); + PDMDevHlpPCIPhysRead(pDevIns, uAddrBdl, aBdl, sizeof(aBdl)); + + uint32_t cSamplesMax = 0; + uint32_t cSamplesMin = UINT32_MAX; + uint32_t cSamplesCur = 0; + uint32_t cSamplesTotal = 0; + uint32_t cBuffers = 1; + for (uintptr_t i = bCiv; ; cBuffers++) + { + Log2Func(("BDLE%02u: %#x LB %#x; %#x\n", i, aBdl[i].addr, aBdl[i].ctl_len & AC97_BD_LEN_MASK, aBdl[i].ctl_len >> 16)); + cSamplesTotal += aBdl[i].ctl_len & AC97_BD_LEN_MASK; + cSamplesCur += aBdl[i].ctl_len & AC97_BD_LEN_MASK; + if (aBdl[i].ctl_len & AC97_BD_IOC) + { + if (cSamplesCur > cSamplesMax) + cSamplesMax = cSamplesCur; + if (cSamplesCur < cSamplesMin) + cSamplesMin = cSamplesCur; + cSamplesCur = 0; + } + + /* Advance. */ + if (i != bLvi) + i = (i + 1) % RT_ELEMENTS(aBdl); + else break; } + if (!cSamplesCur) + { /* likely */ } + else if (!cSamplesMax) + { + LogFlowFunc(("%u buffers without IOC set, assuming %#x samples as the IOC period.\n", cBuffers, cSamplesMax)); + cSamplesMin = cSamplesMax = cSamplesCur; + } + else if (cSamplesCur > cSamplesMax) + { + LogFlowFunc(("final buffer is without IOC, using open period as max (%#x vs current max %#x).\n", cSamplesCur, cSamplesMax)); + cSamplesMax = cSamplesCur; + } + else + LogFlowFunc(("final buffer is without IOC, ignoring (%#x vs current max %#x).\n", cSamplesCur, cSamplesMax)); - if (RT_SUCCESS(rc)) + uint32_t const cbDmaMinBuf = cSamplesMax * PDMAudioPropsSampleSize(&Cfg.Props) * 3; /* see further down */ + uint32_t const cMsDmaMinBuf = PDMAudioPropsBytesToMilli(&Cfg.Props, cbDmaMinBuf); + LogRel3(("AC97: [SD%RU8] buffer length stats: total=%#x in %u buffers, min=%#x, max=%#x => min DMA buffer %u ms / %#x bytes\n", + pStream->u8SD, cSamplesTotal, cBuffers, cSamplesMin, cSamplesMax, cMsDmaMinBuf, cbDmaMinBuf)); + + /* + * Calculate the timer Hz / scheduling hint based on the stream frame rate. + */ + uint32_t uTimerHz; + if (pThis->uTimerHz == AC97_TIMER_HZ_DEFAULT) /* Make sure that we don't have any custom Hz rate set we want to enforce */ { - /* Only (re-)create the stream (and driver chain) if we really have to. - * Otherwise avoid this and just reuse it, as this costs performance. */ - if ( !DrvAudioHlpPCMPropsAreEqual(&Cfg.Props, &pStreamCC->State.Cfg.Props) - || fForce) - { - LogRel2(("AC97: (Re-)Opening stream '%s' (%RU32Hz, %RU8 channels, %s%RU8)\n", - Cfg.szName, Cfg.Props.uHz, Cfg.Props.cChannels, Cfg.Props.fSigned ? "S" : "U", Cfg.Props.cbSample * 8)); + if (Cfg.Props.uHz > 44100) /* E.g. 48000 Hz. */ + uTimerHz = 200; + else + uTimerHz = AC97_TIMER_HZ_DEFAULT; + } + else + uTimerHz = pThis->uTimerHz; - LogFlowFunc(("[SD%RU8] uHz=%RU32\n", pStream->u8SD, Cfg.Props.uHz)); + if ( uTimerHz >= 10 + && uTimerHz <= 500) + { /* likely */ } + else + { + LogFunc(("[SD%RU8] Adjusting uTimerHz=%u to %u\n", pStream->u8SD, uTimerHz, + Cfg.Props.uHz > 44100 ? 200 : AC97_TIMER_HZ_DEFAULT)); + uTimerHz = Cfg.Props.uHz > 44100 ? 200 : AC97_TIMER_HZ_DEFAULT; + } - if (Cfg.Props.uHz) - { - Assert(Cfg.enmDir != PDMAUDIODIR_UNKNOWN); + /* Translate it to a scheduling hint. */ + uint32_t const cMsSchedulingHint = RT_MS_1SEC / uTimerHz; - /* - * Set the stream's timer Hz rate, based on the PCM properties Hz rate. - */ - if (pThis->uTimerHz == AC97_TIMER_HZ_DEFAULT) /* Make sure that we don't have any custom Hz rate set we want to enforce */ - { - if (Cfg.Props.uHz > 44100) /* E.g. 48000 Hz. */ - pStreamCC->State.uTimerHz = 200; - else /* Just take the global Hz rate otherwise. */ - pStreamCC->State.uTimerHz = pThis->uTimerHz; - } - else - pStreamCC->State.uTimerHz = pThis->uTimerHz; + /* + * Calculate the circular buffer size so we can decide whether to recreate + * the stream or not. + * + * As mentioned in the HDA code, this should be at least able to hold the + * data transferred in three DMA periods and in three AIO period (whichever + * is higher). However, if we assume that the DMA code will engage the DMA + * timer thread (currently EMT) if the AIO thread isn't getting schduled to + * transfer data thru the stack, we don't need to go overboard and double + * the minimums here. The less buffer the less possible delay can build when + * TM is doing catch up. + */ + uint32_t cMsCircBuf = Cfg.enmDir == PDMAUDIODIR_IN ? pThis->cMsCircBufIn : pThis->cMsCircBufOut; + cMsCircBuf = RT_MAX(cMsCircBuf, cMsDmaMinBuf); + cMsCircBuf = RT_MAX(cMsCircBuf, cMsSchedulingHint * 3); + cMsCircBuf = RT_MIN(cMsCircBuf, RT_MS_1SEC * 2); + uint32_t const cbCircBuf = PDMAudioPropsMilliToBytes(&Cfg.Props, cMsCircBuf); + + LogFlowFunc(("Stream %u: uTimerHz: %u -> %u; cMsSchedulingHint: %u -> %u; cbCircBuf: %#zx -> %#x (%u ms, cMsDmaMinBuf=%u)%s\n", + pStreamCC->u8SD, pStreamCC->State.uTimerHz, uTimerHz, + pStreamCC->State.Cfg.Device.cMsSchedulingHint, cMsSchedulingHint, + pStreamCC->State.pCircBuf ? RTCircBufSize(pStreamCC->State.pCircBuf) : 0, cbCircBuf, cMsCircBuf, cMsDmaMinBuf, + !pStreamCC->State.pCircBuf || RTCircBufSize(pStreamCC->State.pCircBuf) != cbCircBuf ? " - re-creating DMA buffer" : "")); - /* Set scheduling hint (if available). */ - if (pStreamCC->State.uTimerHz) - Cfg.Device.cMsSchedulingHint = 1000 /* ms */ / pStreamCC->State.uTimerHz; + /* + * Update the stream's timer rate and scheduling hint, re-registering the AIO + * update job if necessary. + */ + if ( pStreamCC->State.Cfg.Device.cMsSchedulingHint != cMsSchedulingHint + || !pStreamCC->State.fRegisteredAsyncUpdateJob) + { + if (pStreamCC->State.fRegisteredAsyncUpdateJob) + AudioMixerSinkRemoveUpdateJob(pMixSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC); + int rc2 = AudioMixerSinkAddUpdateJob(pMixSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC, + pStreamCC->State.Cfg.Device.cMsSchedulingHint); + AssertRC(rc2); + pStreamCC->State.fRegisteredAsyncUpdateJob = RT_SUCCESS(rc2) || rc2 == VERR_ALREADY_EXISTS; + } - if (pStreamCC->State.pCircBuf) - { - RTCircBufDestroy(pStreamCC->State.pCircBuf); - pStreamCC->State.pCircBuf = NULL; - } + pStreamCC->State.uTimerHz = uTimerHz; + Cfg.Device.cMsSchedulingHint = cMsSchedulingHint; - rc = RTCircBufCreate(&pStreamCC->State.pCircBuf, DrvAudioHlpMilliToBytes(100 /* ms */, &Cfg.Props)); /** @todo Make this configurable. */ - if (RT_SUCCESS(rc)) - { - ichac97R3MixerRemoveDrvStreams(pThisCC, pMixSink, Cfg.enmDir, Cfg.u); + /* + * Re-create the circular buffer if necessary, resetting if not. + */ + if ( pStreamCC->State.pCircBuf + && RTCircBufSize(pStreamCC->State.pCircBuf) == cbCircBuf) + RTCircBufReset(pStreamCC->State.pCircBuf); + else + { + if (pStreamCC->State.pCircBuf) + RTCircBufDestroy(pStreamCC->State.pCircBuf); - rc = ichac97R3MixerAddDrvStreams(pThisCC, pMixSink, &Cfg); - if (RT_SUCCESS(rc)) - rc = DrvAudioHlpStreamCfgCopy(&pStreamCC->State.Cfg, &Cfg); - } - } + int rc = RTCircBufCreate(&pStreamCC->State.pCircBuf, cbCircBuf); + AssertRCReturnStmt(rc, pStreamCC->State.pCircBuf = NULL, rc); + + pStreamCC->State.StatDmaBufSize = (uint32_t)RTCircBufSize(pStreamCC->State.pCircBuf); + } + Assert(pStreamCC->State.StatDmaBufSize == cbCircBuf); + + /* + * Only (re-)create the stream (and driver chain) if we really have to. + * Otherwise avoid this and just reuse it, as this costs performance. + */ + int rc = VINF_SUCCESS; + if ( fForce + || !PDMAudioStrmCfgMatchesProps(&Cfg, &pStreamCC->State.Cfg.Props) + || (pStreamCC->State.nsRetrySetup && RTTimeNanoTS() >= pStreamCC->State.nsRetrySetup)) + { + LogRel2(("AC97: Setting up stream #%u: %s\n", pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp)) )); + + ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pMixSink, Cfg.enmDir, Cfg.enmPath); + + rc = ichac97R3MixerAddDrvStreams(pDevIns, pThisCC, pMixSink, &Cfg); + if (RT_SUCCESS(rc)) + { + PDMAudioStrmCfgCopy(&pStreamCC->State.Cfg, &Cfg); + pStreamCC->State.nsRetrySetup = 0; + LogFlowFunc(("[SD%RU8] success (uHz=%u)\n", pStreamCC->u8SD, PDMAudioPropsHz(&Cfg.Props))); } else - LogFlowFunc(("[SD%RU8] Skipping (re-)creation\n", pStream->u8SD)); + { + LogFunc(("[SD%RU8] ichac97R3MixerAddDrvStreams failed: %Rrc (uHz=%u)\n", + pStreamCC->u8SD, rc, PDMAudioPropsHz(&Cfg.Props))); + pStreamCC->State.nsRetrySetup = RTTimeNanoTS() + 5*RT_NS_1SEC_64; /* retry in 5 seconds, unless config changes. */ + } + } + else + { + LogFlowFunc(("[SD%RU8] Skipping set-up (unchanged: %s)\n", + pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp)))); + rc = VINF_NO_CHANGE; } - - LogFlowFunc(("[SD%RU8] rc=%Rrc\n", pStream->u8SD, rc)); return rc; } + /** - * Closes an AC'97 stream. + * Tears down an AC'97 stream (counter part to ichac97R3StreamSetUp). + * + * Empty stub at present, nothing to do here as we reuse streams and only really + * re-open them if parameters changed (seldom). * - * @returns IPRT status code. * @param pStream The AC'97 stream to close (shared). */ -static int ichac97R3StreamClose(PAC97STREAM pStream) +static void ichac97R3StreamTearDown(PAC97STREAM pStream) { RT_NOREF(pStream); LogFlowFunc(("[SD%RU8]\n", pStream->u8SD)); - return VINF_SUCCESS; } + /** - * Re-opens (that is, closes and opens again) an AC'97 stream on the backend - * side with the current AC'97 mixer settings for this stream. + * Tears down and sets up an AC'97 stream on the backend side with the current + * AC'97 mixer settings for this stream. * - * @returns IPRT status code. - * @param pThis The shared AC'97 device state. - * @param pThisCC The ring-3 AC'97 device state. - * @param pStream The AC'97 stream to re-open (shared). - * @param pStreamCC The AC'97 stream to re-open (ring-3). - * @param fForce Whether to force re-opening the stream or not. - * Otherwise re-opening only will happen if the PCM properties have changed. + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The shared AC'97 device state. + * @param pThisCC The ring-3 AC'97 device state. + * @param pStream The AC'97 stream to re-open (shared). + * @param pStreamCC The AC'97 stream to re-open (ring-3). + * @param fForce Whether to force re-opening the stream or not. + * Otherwise re-opening only will happen if the PCM properties have changed. */ -static int ichac97R3StreamReOpen(PAC97STATE pThis, PAC97STATER3 pThisCC, - PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fForce) +static int ichac97R3StreamReSetUp(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC, + PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fForce) { + STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatReSetUpChanged, r); LogFlowFunc(("[SD%RU8]\n", pStream->u8SD)); Assert(pStream->u8SD == pStreamCC->u8SD); Assert(pStream - &pThis->aStreams[0] == pStream->u8SD); Assert(pStreamCC - &pThisCC->aStreams[0] == pStream->u8SD); - int rc = ichac97R3StreamClose(pStream); - if (RT_SUCCESS(rc)) - rc = ichac97R3StreamOpen(pThis, pThisCC, pStream, pStreamCC, fForce); - + ichac97R3StreamTearDown(pStream); + int rc = ichac97R3StreamSetUp(pDevIns, pThis, pThisCC, pStream, pStreamCC, fForce); + if (rc == VINF_NO_CHANGE) + STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReSetUpSame, r); + else + STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReSetUpChanged, r); return rc; } + /** - * Locks an AC'97 stream for serialized access. + * Enables or disables an AC'97 audio stream. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The shared AC'97 state. + * @param pThisCC The ring-3 AC'97 state. + * @param pStream The AC'97 stream to enable or disable (shared state). + * @param pStreamCC The ring-3 stream state (matching to @a pStream). + * @param fEnable Whether to enable or disable the stream. * - * @returns IPRT status code. - * @param pStreamCC The AC'97 stream to lock (ring-3). */ -static void ichac97R3StreamLock(PAC97STREAMR3 pStreamCC) +static int ichac97R3StreamEnable(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC, + PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fEnable) { - int rc2 = RTCritSectEnter(&pStreamCC->State.CritSect); - AssertRC(rc2); + ichac97R3StreamLock(pStreamCC); + PAUDMIXSINK const pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD); + AudioMixerSinkLock(pSink); + + int rc = VINF_SUCCESS; + /* + * Enable. + */ + if (fEnable) + { + /* Reset the input pre-buffering state and DMA period counter. */ + pStreamCC->State.fInputPreBuffered = false; + pStream->uDmaPeriod = 0; + + /* Set up (update) the AC'97 stream as needed. */ + rc = ichac97R3StreamSetUp(pDevIns, pThis, pThisCC, pStream, pStreamCC, false /* fForce */); + if (RT_SUCCESS(rc)) + { + /* Open debug files. */ + if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled)) + { /* likely */ } + else + { + if (!AudioHlpFileIsOpen(pStreamCC->Dbg.Runtime.pFileStream)) + AudioHlpFileOpen(pStreamCC->Dbg.Runtime.pFileStream, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS, + &pStreamCC->State.Cfg.Props); + if (!AudioHlpFileIsOpen(pStreamCC->Dbg.Runtime.pFileDMA)) + AudioHlpFileOpen(pStreamCC->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS, + &pStreamCC->State.Cfg.Props); + } + + /* Do the actual enabling (won't fail as long as pSink is valid). */ + rc = AudioMixerSinkStart(pSink); + } + } + /* + * Disable + */ + else + { + rc = AudioMixerSinkDrainAndStop(pSink, pStreamCC->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStreamCC->State.pCircBuf) : 0); + ichac97R3StreamTearDown(pStream); + } + + /* Make sure to leave the lock before (eventually) starting the timer. */ + AudioMixerSinkUnlock(pSink); + ichac97R3StreamUnlock(pStreamCC); + LogFunc(("[SD%RU8] fEnable=%RTbool, rc=%Rrc\n", pStream->u8SD, fEnable, rc)); + return rc; } + /** - * Unlocks a formerly locked AC'97 stream. + * Returns whether an AC'97 stream is enabled or not. * - * @returns IPRT status code. - * @param pStreamCC The AC'97 stream to unlock (ring-3). + * Only used by ichac97R3SaveExec(). + * + * @returns VBox status code. + * @param pThisCC The ring-3 AC'97 device state. + * @param pStream Stream to return status for. */ -static void ichac97R3StreamUnlock(PAC97STREAMR3 pStreamCC) +static bool ichac97R3StreamIsEnabled(PAC97STATER3 pThisCC, PAC97STREAM pStream) { - int rc2 = RTCritSectLeave(&pStreamCC->State.CritSect); - AssertRC(rc2); -} - -/** - * Retrieves the available size of (buffered) audio data (in bytes) of a given AC'97 stream. - * - * @returns Available data (in bytes). - * @param pStreamCC The AC'97 stream to retrieve size for (ring-3). - */ -static uint32_t ichac97R3StreamGetUsed(PAC97STREAMR3 pStreamCC) -{ - if (!pStreamCC->State.pCircBuf) - return 0; + PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD); + bool fIsEnabled = pSink && (AudioMixerSinkGetStatus(pSink) & AUDMIXSINK_STS_RUNNING); - return (uint32_t)RTCircBufUsed(pStreamCC->State.pCircBuf); + LogFunc(("[SD%RU8] fIsEnabled=%RTbool\n", pStream->u8SD, fIsEnabled)); + return fIsEnabled; } -/** - * Retrieves the free size of audio data (in bytes) of a given AC'97 stream. - * - * @returns Free data (in bytes). - * @param pStreamCC AC'97 stream to retrieve size for (ring-3). - */ -static uint32_t ichac97R3StreamGetFree(PAC97STREAMR3 pStreamCC) -{ - if (!pStreamCC->State.pCircBuf) - return 0; - - return (uint32_t)RTCircBufFree(pStreamCC->State.pCircBuf); -} /** - * Sets the volume of a specific AC'97 mixer control. + * Terminates an AC'97 audio stream (VM destroy). * - * This currently only supports attenuation -- gain support is currently not implemented. + * This is called by ichac97R3StreamsDestroy during VM poweroff & destruction. * - * @returns IPRT status code. - * @param pThis The shared AC'97 state. + * @returns VBox status code. * @param pThisCC The ring-3 AC'97 state. - * @param index AC'97 mixer index to set volume for. - * @param enmMixerCtl Corresponding audio mixer sink. - * @param uVal Volume value to set. + * @param pStream The AC'97 stream to destroy (shared). + * @param pStreamCC The AC'97 stream to destroy (ring-3). + * @sa ichac97R3StreamConstruct */ -static int ichac97R3MixerSetVolume(PAC97STATE pThis, PAC97STATER3 pThisCC, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal) +static void ichac97R3StreamDestroy(PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC) { - /* - * From AC'97 SoundMax Codec AD1981A/AD1981B: - * "Because AC '97 defines 6-bit volume registers, to maintain compatibility whenever the - * D5 or D13 bits are set to 1, their respective lower five volume bits are automatically - * set to 1 by the Codec logic. On readback, all lower 5 bits will read ones whenever - * these bits are set to 1." - * - * Linux ALSA depends on this behavior to detect that only 5 bits are used for volume - * control and the optional 6th bit is not used. Note that this logic only applies to the - * master volume controls. - */ - if (index == AC97_Master_Volume_Mute || index == AC97_Headphone_Volume_Mute || index == AC97_Master_Volume_Mono_Mute) - { - if (uVal & RT_BIT(5)) /* D5 bit set? */ - uVal |= RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0); - if (uVal & RT_BIT(13)) /* D13 bit set? */ - uVal |= RT_BIT(12) | RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8); - } + LogFlowFunc(("[SD%RU8]\n", pStream->u8SD)); - const bool fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1; - uint8_t uCtlAttLeft = (uVal >> 8) & AC97_BARS_VOL_MASK; - uint8_t uCtlAttRight = uVal & AC97_BARS_VOL_MASK; + ichac97R3StreamTearDown(pStream); - /* For the master and headphone volume, 0 corresponds to 0dB attenuation. For the other - * volume controls, 0 means 12dB gain and 8 means unity gain. - */ - if (index != AC97_Master_Volume_Mute && index != AC97_Headphone_Volume_Mute) + int rc2 = RTCritSectDelete(&pStreamCC->State.CritSect); + AssertRC(rc2); + + if (pStreamCC->State.fRegisteredAsyncUpdateJob) { -# ifndef VBOX_WITH_AC97_GAIN_SUPPORT - /* NB: Currently there is no gain support, only attenuation. */ - uCtlAttLeft = uCtlAttLeft < 8 ? 0 : uCtlAttLeft - 8; - uCtlAttRight = uCtlAttRight < 8 ? 0 : uCtlAttRight - 8; -# endif + PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD); + if (pSink) + AudioMixerSinkRemoveUpdateJob(pSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC); + pStreamCC->State.fRegisteredAsyncUpdateJob = false; } - Assert(uCtlAttLeft <= 255 / AC97_DB_FACTOR); - Assert(uCtlAttRight <= 255 / AC97_DB_FACTOR); - - LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl)); - LogFunc(("uCtlAttLeft=%RU8, uCtlAttRight=%RU8 ", uCtlAttLeft, uCtlAttRight)); - - /* - * For AC'97 volume controls, each additional step means -1.5dB attenuation with - * zero being maximum. In contrast, we're internally using 255 (PDMAUDIO_VOLUME_MAX) - * steps, each -0.375dB, where 0 corresponds to -96dB and 255 corresponds to 0dB. - */ - uint8_t lVol = PDMAUDIO_VOLUME_MAX - uCtlAttLeft * AC97_DB_FACTOR; - uint8_t rVol = PDMAUDIO_VOLUME_MAX - uCtlAttRight * AC97_DB_FACTOR; - - Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol)); - - int rc = VINF_SUCCESS; - if (pThisCC->pMixer) /* Device can be in reset state, so no mixer available. */ + if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled)) + { /* likely */ } + else { - PDMAUDIOVOLUME Vol = { fCtlMuted, lVol, rVol }; - PAUDMIXSINK pSink = NULL; - - switch (enmMixerCtl) - { - case PDMAUDIOMIXERCTL_VOLUME_MASTER: - rc = AudioMixerSetMasterVolume(pThisCC->pMixer, &Vol); - break; - - case PDMAUDIOMIXERCTL_FRONT: - pSink = pThisCC->pSinkOut; - break; - - case PDMAUDIOMIXERCTL_MIC_IN: - case PDMAUDIOMIXERCTL_LINE_IN: - /* These are recognized but do nothing. */ - break; - - default: - AssertFailed(); - rc = VERR_NOT_SUPPORTED; - break; - } + AudioHlpFileDestroy(pStreamCC->Dbg.Runtime.pFileStream); + pStreamCC->Dbg.Runtime.pFileStream = NULL; - if (pSink) - rc = AudioMixerSinkSetVolume(pSink, &Vol); + AudioHlpFileDestroy(pStreamCC->Dbg.Runtime.pFileDMA); + pStreamCC->Dbg.Runtime.pFileDMA = NULL; } - ichac97MixerSet(pThis, index, uVal); - - if (RT_FAILURE(rc)) - LogFlowFunc(("Failed with %Rrc\n", rc)); + if (pStreamCC->State.pCircBuf) + { + RTCircBufDestroy(pStreamCC->State.pCircBuf); + pStreamCC->State.pCircBuf = NULL; + } - return rc; + LogFlowFuncLeave(); } + /** - * Sets the gain of a specific AC'97 recording control. + * Initializes an AC'97 audio stream (VM construct). * - * NB: gain support is currently not implemented in PDM audio. + * This is only called by ichac97R3Construct. * - * @returns IPRT status code. - * @param pThis The shared AC'97 state. + * @returns VBox status code. * @param pThisCC The ring-3 AC'97 state. - * @param index AC'97 mixer index to set volume for. - * @param enmMixerCtl Corresponding audio mixer sink. - * @param uVal Volume value to set. + * @param pStream The AC'97 stream to create (shared). + * @param pStreamCC The AC'97 stream to create (ring-3). + * @param u8SD Stream descriptor number to assign. + * @sa ichac97R3StreamDestroy */ -static int ichac97R3MixerSetGain(PAC97STATE pThis, PAC97STATER3 pThisCC, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal) +static int ichac97R3StreamConstruct(PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, uint8_t u8SD) { - /* - * For AC'97 recording controls, each additional step means +1.5dB gain with - * zero being 0dB gain and 15 being +22.5dB gain. - */ - const bool fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1; - uint8_t uCtlGainLeft = (uVal >> 8) & AC97_BARS_GAIN_MASK; - uint8_t uCtlGainRight = uVal & AC97_BARS_GAIN_MASK; - - Assert(uCtlGainLeft <= 255 / AC97_DB_FACTOR); - Assert(uCtlGainRight <= 255 / AC97_DB_FACTOR); - - LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl)); - LogFunc(("uCtlGainLeft=%RU8, uCtlGainRight=%RU8 ", uCtlGainLeft, uCtlGainRight)); - - uint8_t lVol = PDMAUDIO_VOLUME_MAX + uCtlGainLeft * AC97_DB_FACTOR; - uint8_t rVol = PDMAUDIO_VOLUME_MAX + uCtlGainRight * AC97_DB_FACTOR; + LogFunc(("[SD%RU8] pStream=%p\n", u8SD, pStream)); - /* We do not currently support gain. Since AC'97 does not support attenuation - * for the recording input, the best we can do is set the maximum volume. - */ -# ifndef VBOX_WITH_AC97_GAIN_SUPPORT - /* NB: Currently there is no gain support, only attenuation. Since AC'97 does not - * support attenuation for the recording inputs, the best we can do is set the - * maximum volume. - */ - lVol = rVol = PDMAUDIO_VOLUME_MAX; -# endif + AssertReturn(u8SD < AC97_MAX_STREAMS, VERR_INVALID_PARAMETER); + pStream->u8SD = u8SD; + pStreamCC->u8SD = u8SD; - Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol)); + int rc = RTCritSectInit(&pStreamCC->State.CritSect); + AssertRCReturn(rc, rc); - int rc = VINF_SUCCESS; + pStreamCC->Dbg.Runtime.fEnabled = pThisCC->Dbg.fEnabled; - if (pThisCC->pMixer) /* Device can be in reset state, so no mixer available. */ + if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled)) + { /* likely */ } + else { - PDMAUDIOVOLUME Vol = { fCtlMuted, lVol, rVol }; - PAUDMIXSINK pSink = NULL; - - switch (enmMixerCtl) - { - case PDMAUDIOMIXERCTL_MIC_IN: - pSink = pThisCC->pSinkMicIn; - break; - - case PDMAUDIOMIXERCTL_LINE_IN: - pSink = pThisCC->pSinkLineIn; - break; + int rc2 = AudioHlpFileCreateF(&pStreamCC->Dbg.Runtime.pFileStream, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV, + pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/, + ichac97R3GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN + ? "ac97StreamWriteSD%RU8" : "ac97StreamReadSD%RU8", pStream->u8SD); + AssertRC(rc2); - default: - AssertFailed(); - rc = VERR_NOT_SUPPORTED; - break; - } + rc2 = AudioHlpFileCreateF(&pStreamCC->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV, + pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/, + ichac97R3GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN + ? "ac97DMAWriteSD%RU8" : "ac97DMAReadSD%RU8", pStream->u8SD); + AssertRC(rc2); - if (pSink) { - rc = AudioMixerSinkSetVolume(pSink, &Vol); - /* There is only one AC'97 recording gain control. If line in - * is changed, also update the microphone. If the optional dedicated - * microphone is changed, only change that. - * NB: The codecs we support do not have the dedicated microphone control. - */ - if ((pSink == pThisCC->pSinkLineIn) && pThisCC->pSinkMicIn) - rc = AudioMixerSinkSetVolume(pSink, &Vol); - } + /* Delete stale debugging files from a former run. */ + AudioHlpFileDelete(pStreamCC->Dbg.Runtime.pFileStream); + AudioHlpFileDelete(pStreamCC->Dbg.Runtime.pFileDMA); } - ichac97MixerSet(pThis, index, uVal); - - if (RT_FAILURE(rc)) - LogFlowFunc(("Failed with %Rrc\n", rc)); - return rc; } -/** - * Converts an AC'97 recording source index to a PDM audio recording source. - * - * @returns PDM audio recording source. - * @param uIdx AC'97 index to convert. - */ -static PDMAUDIORECSRC ichac97R3IdxToRecSource(uint8_t uIdx) -{ - switch (uIdx) - { - case AC97_REC_MIC: return PDMAUDIORECSRC_MIC; - case AC97_REC_CD: return PDMAUDIORECSRC_CD; - case AC97_REC_VIDEO: return PDMAUDIORECSRC_VIDEO; - case AC97_REC_AUX: return PDMAUDIORECSRC_AUX; - case AC97_REC_LINE_IN: return PDMAUDIORECSRC_LINE; - case AC97_REC_PHONE: return PDMAUDIORECSRC_PHONE; - default: - break; - } +#endif /* IN_RING3 */ - LogFlowFunc(("Unknown record source %d, using MIC\n", uIdx)); - return PDMAUDIORECSRC_MIC; -} + +/********************************************************************************************************************************* +* NABM I/O Port Handlers (Global + Stream) * +*********************************************************************************************************************************/ /** - * Converts a PDM audio recording source to an AC'97 recording source index. - * - * @returns AC'97 recording source index. - * @param enmRecSrc PDM audio recording source to convert. + * @callback_method_impl{FNIOMIOPORTNEWIN} */ -static uint8_t ichac97R3RecSourceToIdx(PDMAUDIORECSRC enmRecSrc) +static DECLCALLBACK(VBOXSTRICTRC) +ichac97IoPortNabmRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) { - switch (enmRecSrc) - { - case PDMAUDIORECSRC_MIC: return AC97_REC_MIC; - case PDMAUDIORECSRC_CD: return AC97_REC_CD; - case PDMAUDIORECSRC_VIDEO: return AC97_REC_VIDEO; - case PDMAUDIORECSRC_AUX: return AC97_REC_AUX; - case PDMAUDIORECSRC_LINE: return AC97_REC_LINE_IN; - case PDMAUDIORECSRC_PHONE: return AC97_REC_PHONE; - default: - break; - } + PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); + RT_NOREF(pvUser); - LogFlowFunc(("Unknown audio recording source %d using MIC\n", enmRecSrc)); - return AC97_REC_MIC; -} + DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_READ); -/** - * Returns the audio direction of a specified stream descriptor. - * - * @return Audio direction. - */ -DECLINLINE(PDMAUDIODIR) ichac97GetDirFromSD(uint8_t uSD) -{ - switch (uSD) + /* Get the index of the NABMBAR port. */ + if ( AC97_PORT2IDX_UNMASKED(offPort) < AC97_MAX_STREAMS + && offPort != AC97_GLOB_CNT) { - case AC97SOUNDSOURCE_PI_INDEX: return PDMAUDIODIR_IN; - case AC97SOUNDSOURCE_PO_INDEX: return PDMAUDIODIR_OUT; - case AC97SOUNDSOURCE_MC_INDEX: return PDMAUDIODIR_IN; - } + PAC97STREAM pStream = &pThis->aStreams[AC97_PORT2IDX(offPort)]; - AssertFailed(); - return PDMAUDIODIR_UNKNOWN; -} + switch (cb) + { + case 1: + switch (offPort & AC97_NABM_OFF_MASK) + { + case AC97_NABM_OFF_CIV: + /* Current Index Value Register */ + *pu32 = pStream->Regs.civ; + Log3Func(("CIV[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); + break; + case AC97_NABM_OFF_LVI: + /* Last Valid Index Register */ + *pu32 = pStream->Regs.lvi; + Log3Func(("LVI[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); + break; + case AC97_NABM_OFF_PIV: + /* Prefetched Index Value Register */ + *pu32 = pStream->Regs.piv; + Log3Func(("PIV[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); + break; + case AC97_NABM_OFF_CR: + /* Control Register */ + *pu32 = pStream->Regs.cr; + Log3Func(("CR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); + break; + case AC97_NABM_OFF_SR: + /* Status Register (lower part) */ + *pu32 = RT_LO_U8(pStream->Regs.sr); + Log3Func(("SRb[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); + break; + default: + *pu32 = UINT32_MAX; + LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); + break; + } + break; -#endif /* IN_RING3 */ + case 2: + switch (offPort & AC97_NABM_OFF_MASK) + { + case AC97_NABM_OFF_SR: + /* Status Register */ + *pu32 = pStream->Regs.sr; + Log3Func(("SR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); + break; + case AC97_NABM_OFF_PICB: + /* Position in Current Buffer + * --- + * We can do DMA work here if we want to give the guest a better impression of + * the DMA engine of a real device. For ring-0 we'd have to add some buffering + * to AC97STREAM (4K or so), only going to ring-3 if full. Ring-3 would commit + * that buffer and write directly to the internal DMA pCircBuf. + * + * Checking a Linux guest (knoppix 8.6.2), I see some PIC reads each DMA cycle, + * however most of these happen very very early, 1-10% into the buffer. So, I'm + * not sure if it's worth it, as it'll be a big complication... */ +#if 1 + *pu32 = pStream->Regs.picb; +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + uint64_t offPeriod = PDMDevHlpTimerGet(pDevIns, pStream->hTimer) - pStream->uArmedTs; + Log3Func(("PICB[%d] -> %#x (%RU64 of %RU64 ticks / %RU64%% into DMA period #%RU32)\n", + AC97_PORT2IDX(offPort), *pu32, offPeriod, pStream->cDmaPeriodTicks, + pStream->cDmaPeriodTicks ? offPeriod * 100 / pStream->cDmaPeriodTicks : 0, + pStream->uDmaPeriod)); + } +# endif +#else /* For trying out sub-buffer PICB. Will cause distortions, but can be helpful to see if it help eliminate other issues. */ + if ( (pStream->Regs.cr & AC97_CR_RPBM) + && !(pStream->Regs.sr & AC97_SR_DCH) + && pStream->uArmedTs > 0 + && pStream->cDmaPeriodTicks > 0) + { + uint64_t const offPeriod = PDMDevHlpTimerGet(pDevIns, pStream->hTimer) - pStream->uArmedTs; + uint32_t cSamples; + if (offPeriod < pStream->cDmaPeriodTicks) + cSamples = pStream->Regs.picb * offPeriod / pStream->cDmaPeriodTicks; + else + cSamples = pStream->Regs.picb; + if (cSamples + 8 < pStream->Regs.picb) + { /* likely */ } + else if (pStream->Regs.picb > 8) + cSamples = pStream->Regs.picb - 8; + else + cSamples = 0; + *pu32 = pStream->Regs.picb - cSamples; + Log3Func(("PICB[%d] -> %#x (PICB=%#x cSamples=%#x offPeriod=%RU64 of %RU64 / %RU64%%)\n", + AC97_PORT2IDX(offPort), *pu32, pStream->Regs.picb, cSamples, offPeriod, + pStream->cDmaPeriodTicks, offPeriod * 100 / pStream->cDmaPeriodTicks)); + } + else + { + *pu32 = pStream->Regs.picb; + Log3Func(("PICB[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); + } +#endif + break; + default: + *pu32 = UINT32_MAX; + LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); + break; + } + break; -#ifdef IN_RING3 + case 4: + switch (offPort & AC97_NABM_OFF_MASK) + { + case AC97_NABM_OFF_BDBAR: + /* Buffer Descriptor Base Address Register */ + *pu32 = pStream->Regs.bdbar; + Log3Func(("BMADDR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); + break; + case AC97_NABM_OFF_CIV: + /* 32-bit access: Current Index Value Register + + * Last Valid Index Register + + * Status Register */ + *pu32 = pStream->Regs.civ | ((uint32_t)pStream->Regs.lvi << 8) | ((uint32_t)pStream->Regs.sr << 16); + Log3Func(("CIV LVI SR[%d] -> %#x, %#x, %#x\n", + AC97_PORT2IDX(offPort), pStream->Regs.civ, pStream->Regs.lvi, pStream->Regs.sr)); + break; + case AC97_NABM_OFF_PICB: + /* 32-bit access: Position in Current Buffer Register + + * Prefetched Index Value Register + + * Control Register */ + *pu32 = pStream->Regs.picb | ((uint32_t)pStream->Regs.piv << 16) | ((uint32_t)pStream->Regs.cr << 24); + Log3Func(("PICB PIV CR[%d] -> %#x %#x %#x %#x\n", + AC97_PORT2IDX(offPort), *pu32, pStream->Regs.picb, pStream->Regs.piv, pStream->Regs.cr)); + break; -/** - * Performs an AC'97 mixer record select to switch to a different recording - * source. - * - * @param pThis The shared AC'97 state. - * @param val AC'97 recording source index to set. - */ -static void ichac97R3MixerRecordSelect(PAC97STATE pThis, uint32_t val) -{ - uint8_t rs = val & AC97_REC_MASK; - uint8_t ls = (val >> 8) & AC97_REC_MASK; + default: + *pu32 = UINT32_MAX; + LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); + break; + } + break; - const PDMAUDIORECSRC ars = ichac97R3IdxToRecSource(rs); - const PDMAUDIORECSRC als = ichac97R3IdxToRecSource(ls); + default: + DEVAC97_UNLOCK(pDevIns, pThis); + AssertFailed(); + return VERR_IOM_IOPORT_UNUSED; + } + } + else + { + switch (cb) + { + case 1: + switch (offPort) + { + case AC97_CAS: + /* Codec Access Semaphore Register */ + Log3Func(("CAS %d\n", pThis->cas)); + *pu32 = pThis->cas; + pThis->cas = 1; + break; + default: + *pu32 = UINT32_MAX; + LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); + break; + } + break; - rs = ichac97R3RecSourceToIdx(ars); - ls = ichac97R3RecSourceToIdx(als); + case 2: + *pu32 = UINT32_MAX; + LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); + break; - LogRel(("AC97: Record select to left=%s, right=%s\n", DrvAudioHlpRecSrcToStr(ars), DrvAudioHlpRecSrcToStr(als))); + case 4: + switch (offPort) + { + case AC97_GLOB_CNT: + /* Global Control */ + *pu32 = pThis->glob_cnt; + Log3Func(("glob_cnt -> %#x\n", *pu32)); + break; + case AC97_GLOB_STA: + /* Global Status */ + *pu32 = pThis->glob_sta | AC97_GS_S0CR; + Log3Func(("glob_sta -> %#x\n", *pu32)); + break; + default: + *pu32 = UINT32_MAX; + LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); + break; + } + break; - ichac97MixerSet(pThis, AC97_Record_Select, rs | (ls << 8)); + default: + DEVAC97_UNLOCK(pDevIns, pThis); + AssertFailed(); + return VERR_IOM_IOPORT_UNUSED; + } + } + + DEVAC97_UNLOCK(pDevIns, pThis); + return VINF_SUCCESS; } + /** - * Resets the AC'97 mixer. - * - * @returns IPRT status code. - * @param pThis The shared AC'97 state. - * @param pThisCC The ring-3 AC'97 state. + * @callback_method_impl{FNIOMIOPORTNEWOUT} */ -static int ichac97R3MixerReset(PAC97STATE pThis, PAC97STATER3 pThisCC) +static DECLCALLBACK(VBOXSTRICTRC) +ichac97IoPortNabmWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) { - LogFlowFuncEnter(); + PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); +#ifdef IN_RING3 + PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); +#endif + RT_NOREF(pvUser); - RT_ZERO(pThis->mixer_data); + VBOXSTRICTRC rc = VINF_SUCCESS; + if ( AC97_PORT2IDX_UNMASKED(offPort) < AC97_MAX_STREAMS + && offPort != AC97_GLOB_CNT) + { +#ifdef IN_RING3 + PAC97STREAMR3 pStreamCC = &pThisCC->aStreams[AC97_PORT2IDX(offPort)]; +#endif + PAC97STREAM pStream = &pThis->aStreams[AC97_PORT2IDX(offPort)]; - /* Note: Make sure to reset all registers first before bailing out on error. */ + switch (cb) + { + case 1: + switch (offPort & AC97_NABM_OFF_MASK) + { + /* + * Last Valid Index. + */ + case AC97_NABM_OFF_LVI: + DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE); - ichac97MixerSet(pThis, AC97_Reset , 0x0000); /* 6940 */ - ichac97MixerSet(pThis, AC97_Master_Volume_Mono_Mute , 0x8000); - ichac97MixerSet(pThis, AC97_PC_BEEP_Volume_Mute , 0x0000); + if ( !(pStream->Regs.sr & AC97_SR_DCH) + || !(pStream->Regs.cr & AC97_CR_RPBM)) + { + pStream->Regs.lvi = u32 % AC97_MAX_BDLE; + STAM_REL_COUNTER_INC(&pStream->StatWriteLvi); + DEVAC97_UNLOCK(pDevIns, pThis); + Log3Func(("[SD%RU8] LVI <- %#x\n", pStream->u8SD, u32)); + } + else + { +#ifdef IN_RING3 + /* Recover from underflow situation where CIV caught up with LVI + and the DMA processing stopped. We clear the status condition, + update LVI and then try to load the next BDLE. Unfortunately, + we cannot do this from ring-3 as much of the BDLE state is + ring-3 only. */ + pStream->Regs.sr &= ~(AC97_SR_DCH | AC97_SR_CELV); + pStream->Regs.lvi = u32 % AC97_MAX_BDLE; + if (ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC)) + ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr | AC97_SR_BCIS); + + /* We now have to re-arm the DMA timer according to the new BDLE length. + This means leaving the device lock to avoid virtual sync lock order issues. */ + ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC); + uint64_t const cTicksToDeadline = pStream->cDmaPeriodTicks; + + /** @todo Stop the DMA timer when we get into the AC97_SR_CELV situation to + * avoid potential race here. */ + STAM_REL_COUNTER_INC(&pStreamCC->State.StatWriteLviRecover); + DEVAC97_UNLOCK(pDevIns, pThis); - ichac97MixerSet(pThis, AC97_Phone_Volume_Mute , 0x8008); - ichac97MixerSet(pThis, AC97_Mic_Volume_Mute , 0x8008); - ichac97MixerSet(pThis, AC97_CD_Volume_Mute , 0x8808); - ichac97MixerSet(pThis, AC97_Aux_Volume_Mute , 0x8808); - ichac97MixerSet(pThis, AC97_Record_Gain_Mic_Mute , 0x8000); - ichac97MixerSet(pThis, AC97_General_Purpose , 0x0000); - ichac97MixerSet(pThis, AC97_3D_Control , 0x0000); - ichac97MixerSet(pThis, AC97_Powerdown_Ctrl_Stat , 0x000f); + LogFunc(("[SD%RU8] LVI <- %#x; CIV=%#x PIV=%#x SR=%#x cTicksToDeadline=%#RX64 [recovering]\n", + pStream->u8SD, u32, pStream->Regs.civ, pStream->Regs.piv, pStream->Regs.sr, cTicksToDeadline)); - /* Configure Extended Audio ID (EAID) + Control & Status (EACS) registers. */ - const uint16_t fEAID = AC97_EAID_REV1 | AC97_EACS_VRA | AC97_EACS_VRM; /* Our hardware is AC'97 rev2.3 compliant. */ - const uint16_t fEACS = AC97_EACS_VRA | AC97_EACS_VRM; /* Variable Rate PCM Audio (VRA) + Mic-In (VRM) capable. */ + int rc2 = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs); + AssertRC(rc2); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + } + break; - LogRel(("AC97: Mixer reset (EAID=0x%x, EACS=0x%x)\n", fEAID, fEACS)); + /* + * Control Registers. + */ + case AC97_NABM_OFF_CR: + { +#ifdef IN_RING3 + DEVAC97_LOCK(pDevIns, pThis); + STAM_REL_COUNTER_INC(&pStreamCC->State.StatWriteCr); - ichac97MixerSet(pThis, AC97_Extended_Audio_ID, fEAID); - ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, fEACS); - ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate , 0xbb80 /* 48000 Hz by default */); - ichac97MixerSet(pThis, AC97_PCM_Surround_DAC_Rate , 0xbb80 /* 48000 Hz by default */); - ichac97MixerSet(pThis, AC97_PCM_LFE_DAC_Rate , 0xbb80 /* 48000 Hz by default */); - ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate , 0xbb80 /* 48000 Hz by default */); - ichac97MixerSet(pThis, AC97_MIC_ADC_Rate , 0xbb80 /* 48000 Hz by default */); + uint32_t const fCrChanged = pStream->Regs.cr ^ u32; + Log3Func(("[SD%RU8] CR <- %#x (was %#x; changed %#x)\n", pStream->u8SD, u32, pStream->Regs.cr, fCrChanged)); - if (pThis->enmCodecModel == AC97CODEC_AD1980) - { - /* Analog Devices 1980 (AD1980) */ - ichac97MixerSet(pThis, AC97_Reset , 0x0010); /* Headphones. */ - ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144); - ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5370); - ichac97MixerSet(pThis, AC97_Headphone_Volume_Mute , 0x8000); - } - else if (pThis->enmCodecModel == AC97CODEC_AD1981B) - { - /* Analog Devices 1981B (AD1981B) */ - ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144); - ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5374); - } - else - { - /* Sigmatel 9700 (STAC9700) */ - ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x8384); - ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x7600); /* 7608 */ - } - ichac97R3MixerRecordSelect(pThis, 0); + /* + * Busmaster reset. + */ + if (u32 & AC97_CR_RR) + { + STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatReset, r); + LogFunc(("[SD%RU8] Reset\n", pStream->u8SD)); - /* The default value is 8000h, which corresponds to 0 dB attenuation with mute on. */ - ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER, 0x8000); + /* Make sure that Run/Pause Bus Master bit (RPBM) is cleared (0). + 3.2.7 in 302349-003 says RPBM be must be clear when resetting + and that behavior is undefined if it's set. */ + ASSERT_GUEST_STMT((pStream->Regs.cr & AC97_CR_RPBM) == 0, + ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, + pStreamCC, false /* fEnable */)); - /* The default value for stereo registers is 8808h, which corresponds to 0 dB gain with mute on.*/ - ichac97R3MixerSetVolume(pThis, pThisCC, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT, 0x8808); - ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8808); - ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8008); + ichac97R3StreamReset(pThis, pStream, pStreamCC); - /* The default for record controls is 0 dB gain with mute on. */ - ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8000); - ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8000); + ichac97StreamUpdateSR(pDevIns, pThis, pStream, AC97_SR_DCH); /** @todo Do we need to do that? */ - return VINF_SUCCESS; -} + DEVAC97_UNLOCK(pDevIns, pThis); + STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReset, r); + break; + } -# if 0 /* Unused */ -static void ichac97R3WriteBUP(PAC97STATE pThis, uint32_t cbElapsed) -{ - LogFlowFunc(("cbElapsed=%RU32\n", cbElapsed)); + /* + * Write the new value to the register and if RPBM didn't change we're done. + */ + pStream->Regs.cr = u32 & AC97_CR_VALID_MASK; + + if (!(fCrChanged & AC97_CR_RPBM)) + DEVAC97_UNLOCK(pDevIns, pThis); /* Probably not so likely, but avoid one extra intentation level. */ + /* + * Pause busmaster. + */ + else if (!(pStream->Regs.cr & AC97_CR_RPBM)) + { + STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatStop, p); + LogFunc(("[SD%RU8] Pause busmaster (disable stream) SR=%#x -> %#x\n", + pStream->u8SD, pStream->Regs.sr, pStream->Regs.sr | AC97_SR_DCH)); + ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, false /* fEnable */); + pStream->Regs.sr |= AC97_SR_DCH; - if (!(pThis->bup_flag & BUP_SET)) + DEVAC97_UNLOCK(pDevIns, pThis); + STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatStop, p); + } + /* + * Run busmaster. + */ + else + { + STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatStart, r); + LogFunc(("[SD%RU8] Run busmaster (enable stream) SR=%#x -> %#x\n", + pStream->u8SD, pStream->Regs.sr, pStream->Regs.sr & ~AC97_SR_DCH)); + pStream->Regs.sr &= ~AC97_SR_DCH; + + if (ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC)) + ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr | AC97_SR_BCIS); +# ifdef LOG_ENABLED + if (LogIsFlowEnabled()) + ichac97R3DbgPrintBdl(pDevIns, pThis, pStream, DBGFR3InfoLogHlp(), "ichac97IoPortNabmWrite: "); +# endif + ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, true /* fEnable */); + + /* + * Arm the DMA timer. Must drop the AC'97 device lock first as it would + * create a lock order violation with the virtual sync time lock otherwise. + */ + ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC); + uint64_t const cTicksToDeadline = pStream->cDmaPeriodTicks; + + DEVAC97_UNLOCK(pDevIns, pThis); + + /** @todo for output streams we could probably service this a little bit + * earlier if we push it, just to reduce the lag... For HDA we do a + * DMA run immediately after the stream is enabled. */ + int rc2 = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs); + AssertRC(rc2); + + STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatStart, r); + } +#else /* !IN_RING3 */ + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + } + + /* + * Status Registers. + */ + case AC97_NABM_OFF_SR: + DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE); + ichac97StreamWriteSR(pDevIns, pThis, pStream, u32); + STAM_REL_COUNTER_INC(&pStream->StatWriteSr1); + DEVAC97_UNLOCK(pDevIns, pThis); + break; + + default: + /* Linux tries to write CIV. */ + LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x%s <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n", + offPort, (offPort & AC97_NABM_OFF_MASK) == AC97_NABM_OFF_CIV ? " (CIV)" : "" , u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); + break; + } + break; + + case 2: + switch (offPort & AC97_NABM_OFF_MASK) + { + case AC97_NABM_OFF_SR: + DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE); + ichac97StreamWriteSR(pDevIns, pThis, pStream, u32); + STAM_REL_COUNTER_INC(&pStream->StatWriteSr2); + DEVAC97_UNLOCK(pDevIns, pThis); + break; + default: + LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); + break; + } + break; + + case 4: + switch (offPort & AC97_NABM_OFF_MASK) + { + case AC97_NABM_OFF_BDBAR: + DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE); + /* Buffer Descriptor list Base Address Register */ + pStream->Regs.bdbar = u32 & ~(uint32_t)3; + Log3Func(("[SD%RU8] BDBAR <- %#x (bdbar %#x)\n", AC97_PORT2IDX(offPort), u32, pStream->Regs.bdbar)); + STAM_REL_COUNTER_INC(&pStream->StatWriteBdBar); + DEVAC97_UNLOCK(pDevIns, pThis); + break; + default: + LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); + break; + } + break; + + default: + AssertMsgFailed(("offPort=%#x <- %#x LB %u\n", offPort, u32, cb)); + break; + } + } + else { - if (pThis->bup_flag & BUP_LAST) + switch (cb) { - unsigned int i; - uint32_t *p = (uint32_t*)pThis->silence; - for (i = 0; i < sizeof(pThis->silence) / 4; i++) /** @todo r=andy Assumes 16-bit samples, stereo. */ - *p++ = pThis->last_samp; - } - else - RT_ZERO(pThis->silence); + case 1: + LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort, u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); + break; - pThis->bup_flag |= BUP_SET; - } + case 2: + LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); + break; - while (cbElapsed) - { - uint32_t cbToWrite = RT_MIN(cbElapsed, (uint32_t)sizeof(pThis->silence)); - uint32_t cbWrittenToStream; + case 4: + switch (offPort) + { + case AC97_GLOB_CNT: + /* Global Control */ + DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE); + if (u32 & AC97_GC_WR) + ichac97WarmReset(pThis); + if (u32 & AC97_GC_CR) + ichac97ColdReset(pThis); + if (!(u32 & (AC97_GC_WR | AC97_GC_CR))) + pThis->glob_cnt = u32 & AC97_GC_VALID_MASK; + Log3Func(("glob_cnt <- %#x (glob_cnt %#x)\n", u32, pThis->glob_cnt)); + DEVAC97_UNLOCK(pDevIns, pThis); + break; + case AC97_GLOB_STA: + /* Global Status */ + DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE); + pThis->glob_sta &= ~(u32 & AC97_GS_WCLEAR_MASK); + pThis->glob_sta |= (u32 & ~(AC97_GS_WCLEAR_MASK | AC97_GS_RO_MASK)) & AC97_GS_VALID_MASK; + Log3Func(("glob_sta <- %#x (glob_sta %#x)\n", u32, pThis->glob_sta)); + DEVAC97_UNLOCK(pDevIns, pThis); + break; + default: + LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); + break; + } + break; - int rc2 = AudioMixerSinkWrite(pThisCC->pSinkOut, AUDMIXOP_COPY, - pThis->silence, cbToWrite, &cbWrittenToStream); - if (RT_SUCCESS(rc2)) - { - if (cbWrittenToStream < cbToWrite) /* Lagging behind? */ - LogFlowFunc(("Warning: Only written %RU32 / %RU32 bytes, expect lags\n", cbWrittenToStream, cbToWrite)); + default: + AssertMsgFailed(("offPort=%#x <- %#x LB %u\n", offPort, u32, cb)); + break; } - - /* Always report all data as being written; - * backends who were not able to catch up have to deal with it themselves. */ - Assert(cbElapsed >= cbToWrite); - cbElapsed -= cbToWrite; } + + return rc; } -# endif /* Unused */ + + +/********************************************************************************************************************************* +* Mixer & NAM I/O handlers * +*********************************************************************************************************************************/ /** - * @callback_method_impl{FNTMTIMERDEV, - * Timer callback which handles the audio data transfers on a periodic basis.} + * Sets a AC'97 mixer control to a specific value. + * + * @returns VBox status code. + * @param pThis The shared AC'97 state. + * @param uMixerIdx Mixer control to set value for. + * @param uVal Value to set. */ -static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) +static void ichac97MixerSet(PAC97STATE pThis, uint8_t uMixerIdx, uint16_t uVal) { - PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); - STAM_PROFILE_START(&pThis->StatTimer, a); - PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); - PAC97STREAM pStream = (PAC97STREAM)pvUser; - PAC97STREAMR3 pStreamCC = &RT_SAFE_SUBSCRIPT8(pThisCC->aStreams, pStream->u8SD); - RT_NOREF(pTimer); - - Assert(pStream - &pThis->aStreams[0] == pStream->u8SD); - Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); - Assert(PDMDevHlpTimerIsLockOwner(pDevIns, pStream->hTimer)); - - ichac97R3StreamUpdate(pDevIns, pThis, pThisCC, pStream, pStreamCC, true /* fInTimer */); + AssertMsgReturnVoid(uMixerIdx + 2U <= sizeof(pThis->mixer_data), + ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data))); - PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD); - if (pSink && AudioMixerSinkIsActive(pSink)) - { - ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC, pStream->Regs.picb << 1); /** @todo r=andy Assumes 16-bit samples. */ - ichac97R3TimerSet(pDevIns, pStream, pStreamCC->State.cTransferTicks); - } + LogRel2(("AC97: Setting mixer index #%RU8 to %RU16 (%RU8 %RU8)\n", uMixerIdx, uVal, RT_HI_U8(uVal), RT_LO_U8(uVal))); - STAM_PROFILE_STOP(&pThis->StatTimer, a); + pThis->mixer_data[uMixerIdx + 0] = RT_LO_U8(uVal); + pThis->mixer_data[uMixerIdx + 1] = RT_HI_U8(uVal); } /** - * Sets the virtual device timer to a new expiration time. - * - * @param pDevIns The device instance. - * @param pStream AC'97 stream to set timer for. - * @param cTicksToDeadline The number of ticks to the new deadline. + * Gets a value from a specific AC'97 mixer control. * - * @remarks This used to be more complicated a long time ago... + * @returns Retrieved mixer control value. + * @param pThis The shared AC'97 state. + * @param uMixerIdx Mixer control to get value for. */ -DECLINLINE(void) ichac97R3TimerSet(PPDMDEVINS pDevIns, PAC97STREAM pStream, uint64_t cTicksToDeadline) +static uint16_t ichac97MixerGet(PAC97STATE pThis, uint32_t uMixerIdx) { - int rc = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, NULL /*pu64Now*/); - AssertRC(rc); + AssertMsgReturn(uMixerIdx + 2U <= sizeof(pThis->mixer_data), + ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data)), + UINT16_MAX); + return RT_MAKE_U16(pThis->mixer_data[uMixerIdx + 0], pThis->mixer_data[uMixerIdx + 1]); } +#ifdef IN_RING3 /** - * Transfers data of an AC'97 stream according to its usage (input / output). - * - * For an SDO (output) stream this means reading DMA data from the device to - * the AC'97 stream's internal FIFO buffer. + * Sets the volume of a specific AC'97 mixer control. * - * For an SDI (input) stream this is reading audio data from the AC'97 stream's - * internal FIFO buffer and writing it as DMA data to the device. + * This currently only supports attenuation -- gain support is currently not implemented. * - * @returns IPRT status code. - * @param pDevIns The device instance. + * @returns VBox status code. * @param pThis The shared AC'97 state. - * @param pStream The AC'97 stream to update (shared). - * @param pStreamCC The AC'97 stream to update (ring-3). - * @param cbToProcessMax Maximum of data (in bytes) to process. + * @param pThisCC The ring-3 AC'97 state. + * @param index AC'97 mixer index to set volume for. + * @param enmMixerCtl Corresponding audio mixer sink. + * @param uVal Volume value to set. */ -static int ichac97R3StreamTransfer(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, - PAC97STREAMR3 pStreamCC, uint32_t cbToProcessMax) +static int ichac97R3MixerSetVolume(PAC97STATE pThis, PAC97STATER3 pThisCC, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal) { - if (!cbToProcessMax) - return VINF_SUCCESS; - -#ifdef VBOX_STRICT - const unsigned cbFrame = DrvAudioHlpPCMPropsBytesPerFrame(&pStreamCC->State.Cfg.Props); -#endif - - /* Make sure to only process an integer number of audio frames. */ - Assert(cbToProcessMax % cbFrame == 0); - - ichac97R3StreamLock(pStreamCC); - - PAC97BMREGS pRegs = &pStream->Regs; - - if (pRegs->sr & AC97_SR_DCH) /* Controller halted? */ + /* + * From AC'97 SoundMax Codec AD1981A/AD1981B: + * "Because AC '97 defines 6-bit volume registers, to maintain compatibility whenever the + * D5 or D13 bits are set to 1, their respective lower five volume bits are automatically + * set to 1 by the Codec logic. On readback, all lower 5 bits will read ones whenever + * these bits are set to 1." + * + * Linux ALSA depends on this behavior to detect that only 5 bits are used for volume + * control and the optional 6th bit is not used. Note that this logic only applies to the + * master volume controls. + */ + if ( index == AC97_Master_Volume_Mute + || index == AC97_Headphone_Volume_Mute + || index == AC97_Master_Volume_Mono_Mute) { - if (pRegs->cr & AC97_CR_RPBM) /* Bus master operation starts. */ - { - switch (pStream->u8SD) - { - case AC97SOUNDSOURCE_PO_INDEX: - /*ichac97R3WriteBUP(pThis, cbToProcess);*/ - break; - - default: - break; - } - } - - ichac97R3StreamUnlock(pStreamCC); - return VINF_SUCCESS; + if (uVal & RT_BIT(5)) /* D5 bit set? */ + uVal |= RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0); + if (uVal & RT_BIT(13)) /* D13 bit set? */ + uVal |= RT_BIT(12) | RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8); } - /* BCIS flag still set? Skip iteration. */ - if (pRegs->sr & AC97_SR_BCIS) - { - Log3Func(("[SD%RU8] BCIS set\n", pStream->u8SD)); + const bool fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1; + uint8_t uCtlAttLeft = (uVal >> 8) & AC97_BARS_VOL_MASK; + uint8_t uCtlAttRight = uVal & AC97_BARS_VOL_MASK; - ichac97R3StreamUnlock(pStreamCC); - return VINF_SUCCESS; + /* For the master and headphone volume, 0 corresponds to 0dB attenuation. For the other + * volume controls, 0 means 12dB gain and 8 means unity gain. + */ + if (index != AC97_Master_Volume_Mute && index != AC97_Headphone_Volume_Mute) + { +# ifndef VBOX_WITH_AC97_GAIN_SUPPORT + /* NB: Currently there is no gain support, only attenuation. */ + uCtlAttLeft = uCtlAttLeft < 8 ? 0 : uCtlAttLeft - 8; + uCtlAttRight = uCtlAttRight < 8 ? 0 : uCtlAttRight - 8; +# endif } + Assert(uCtlAttLeft <= 255 / AC97_DB_FACTOR); + Assert(uCtlAttRight <= 255 / AC97_DB_FACTOR); + + LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl)); + LogFunc(("uCtlAttLeft=%RU8, uCtlAttRight=%RU8 ", uCtlAttLeft, uCtlAttRight)); - uint32_t cbLeft = RT_MIN((uint32_t)(pRegs->picb << 1), cbToProcessMax); /** @todo r=andy Assumes 16bit samples. */ - uint32_t cbProcessedTotal = 0; + /* + * For AC'97 volume controls, each additional step means -1.5dB attenuation with + * zero being maximum. In contrast, we're internally using 255 (PDMAUDIO_VOLUME_MAX) + * steps, each -0.375dB, where 0 corresponds to -96dB and 255 corresponds to 0dB. + */ + uint8_t lVol = PDMAUDIO_VOLUME_MAX - uCtlAttLeft * AC97_DB_FACTOR; + uint8_t rVol = PDMAUDIO_VOLUME_MAX - uCtlAttRight * AC97_DB_FACTOR; - PRTCIRCBUF pCircBuf = pStreamCC->State.pCircBuf; - AssertPtr(pCircBuf); + Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol)); int rc = VINF_SUCCESS; - Log3Func(("[SD%RU8] cbToProcessMax=%RU32, cbLeft=%RU32\n", pStream->u8SD, cbToProcessMax, cbLeft)); - - while (cbLeft) + if (pThisCC->pMixer) /* Device can be in reset state, so no mixer available. */ { - if (!pRegs->picb) /* Got a new buffer descriptor, that is, the position is 0? */ - { - Log3Func(("Fresh buffer descriptor %RU8 is empty, addr=%#x, len=%#x, skipping\n", - pRegs->civ, pRegs->bd.addr, pRegs->bd.ctl_len)); - if (pRegs->civ == pRegs->lvi) - { - pRegs->sr |= AC97_SR_DCH; /** @todo r=andy Also set CELV? */ - pThis->bup_flag = 0; - - rc = VINF_EOF; - break; - } - - pRegs->sr &= ~AC97_SR_CELV; - pRegs->civ = pRegs->piv; - pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE; - - ichac97R3StreamFetchBDLE(pDevIns, pStream); - continue; - } - - uint32_t cbChunk = cbLeft; + PDMAUDIOVOLUME Vol; + PDMAudioVolumeInitFromStereo(&Vol, fCtlMuted, lVol, rVol); - switch (pStream->u8SD) + PAUDMIXSINK pSink = NULL; + switch (enmMixerCtl) { - case AC97SOUNDSOURCE_PO_INDEX: /* Output */ - { - void *pvDst; - size_t cbDst; - - RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvDst, &cbDst); - - if (cbDst) - { - int rc2 = PDMDevHlpPhysRead(pDevIns, pRegs->bd.addr, (uint8_t *)pvDst, cbDst); - AssertRC(rc2); - - if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - DrvAudioHlpFileWrite(pStreamCC->Dbg.Runtime.pFileDMA, pvDst, cbDst, 0 /* fFlags */); - } - - RTCircBufReleaseWriteBlock(pCircBuf, cbDst); - - cbChunk = (uint32_t)cbDst; /* Update the current chunk size to what really has been written. */ + case PDMAUDIOMIXERCTL_VOLUME_MASTER: + rc = AudioMixerSetMasterVolume(pThisCC->pMixer, &Vol); break; - } - - case AC97SOUNDSOURCE_PI_INDEX: /* Input */ - case AC97SOUNDSOURCE_MC_INDEX: /* Input */ - { - void *pvSrc; - size_t cbSrc; - - RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvSrc, &cbSrc); - if (cbSrc) - { -/** @todo r=bird: Just curious, DevHDA uses PDMDevHlpPCIPhysWrite here. So, - * is AC97 not subject to PCI busmaster enable/disable? */ - int rc2 = PDMDevHlpPhysWrite(pDevIns, pRegs->bd.addr, (uint8_t *)pvSrc, cbSrc); - AssertRC(rc2); - - if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - DrvAudioHlpFileWrite(pStreamCC->Dbg.Runtime.pFileDMA, pvSrc, cbSrc, 0 /* fFlags */); - } - - RTCircBufReleaseReadBlock(pCircBuf, cbSrc); + case PDMAUDIOMIXERCTL_FRONT: + pSink = pThisCC->pSinkOut; + break; - cbChunk = (uint32_t)cbSrc; /* Update the current chunk size to what really has been read. */ + case PDMAUDIOMIXERCTL_MIC_IN: + case PDMAUDIOMIXERCTL_LINE_IN: + /* These are recognized but do nothing. */ break; - } default: - AssertMsgFailed(("Stream #%RU8 not supported\n", pStream->u8SD)); + AssertFailed(); rc = VERR_NOT_SUPPORTED; - break; - } - - if (RT_FAILURE(rc)) - break; - - if (cbChunk) - { - cbProcessedTotal += cbChunk; - Assert(cbProcessedTotal <= cbToProcessMax); - Assert(cbLeft >= cbChunk); - cbLeft -= cbChunk; - Assert((cbChunk & 1) == 0); /* Else the following shift won't work */ - - pRegs->picb -= (cbChunk >> 1); /** @todo r=andy Assumes 16bit samples. */ - pRegs->bd.addr += cbChunk; - } - - LogFlowFunc(("[SD%RU8] cbChunk=%RU32, cbLeft=%RU32, cbTotal=%RU32, rc=%Rrc\n", - pStream->u8SD, cbChunk, cbLeft, cbProcessedTotal, rc)); - - if (!pRegs->picb) - { - uint32_t new_sr = pRegs->sr & ~AC97_SR_CELV; - - if (pRegs->bd.ctl_len & AC97_BD_IOC) - { - new_sr |= AC97_SR_BCIS; - } - - if (pRegs->civ == pRegs->lvi) - { - /* Did we run out of data? */ - LogFunc(("Underrun CIV (%RU8) == LVI (%RU8)\n", pRegs->civ, pRegs->lvi)); - - new_sr |= AC97_SR_LVBCI | AC97_SR_DCH | AC97_SR_CELV; - pThis->bup_flag = (pRegs->bd.ctl_len & AC97_BD_BUP) ? BUP_LAST : 0; - - rc = VINF_EOF; - } - else - { - pRegs->civ = pRegs->piv; - pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE; - ichac97R3StreamFetchBDLE(pDevIns, pStream); - } - - ichac97StreamUpdateSR(pDevIns, pThis, pStream, new_sr); - } - - if (/* All data processed? */ - rc == VINF_EOF - /* ... or an error occurred? */ - || RT_FAILURE(rc)) - { - break; + break; } + + if (pSink) + rc = AudioMixerSinkSetVolume(pSink, &Vol); } - ichac97R3StreamUnlock(pStreamCC); + ichac97MixerSet(pThis, index, uVal); + + if (RT_FAILURE(rc)) + LogFlowFunc(("Failed with %Rrc\n", rc)); - LogFlowFuncLeaveRC(rc); return rc; } -#endif /* IN_RING3 */ - - /** - * @callback_method_impl{FNIOMIOPORTNEWIN} + * Sets the gain of a specific AC'97 recording control. + * + * @note Gain support is currently not implemented in PDM audio. + * + * @returns VBox status code. + * @param pThis The shared AC'97 state. + * @param pThisCC The ring-3 AC'97 state. + * @param index AC'97 mixer index to set volume for. + * @param enmMixerCtl Corresponding audio mixer sink. + * @param uVal Volume value to set. */ -static DECLCALLBACK(VBOXSTRICTRC) -ichac97IoPortNabmRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) +static int ichac97R3MixerSetGain(PAC97STATE pThis, PAC97STATER3 pThisCC, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal) { - PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); - RT_NOREF(pvUser); + /* + * For AC'97 recording controls, each additional step means +1.5dB gain with + * zero being 0dB gain and 15 being +22.5dB gain. + */ + bool const fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1; + uint8_t uCtlGainLeft = (uVal >> 8) & AC97_BARS_GAIN_MASK; + uint8_t uCtlGainRight = uVal & AC97_BARS_GAIN_MASK; - DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_READ); + Assert(uCtlGainLeft <= 255 / AC97_DB_FACTOR); + Assert(uCtlGainRight <= 255 / AC97_DB_FACTOR); - /* Get the index of the NABMBAR port. */ - if ( AC97_PORT2IDX_UNMASKED(offPort) < AC97_MAX_STREAMS - && offPort != AC97_GLOB_CNT) - { - PAC97STREAM pStream = &pThis->aStreams[AC97_PORT2IDX(offPort)]; - PAC97BMREGS pRegs = &pStream->Regs; + LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl)); + LogFunc(("uCtlGainLeft=%RU8, uCtlGainRight=%RU8 ", uCtlGainLeft, uCtlGainRight)); - switch (cb) - { - case 1: - switch (offPort & AC97_NABM_OFF_MASK) - { - case AC97_NABM_OFF_CIV: - /* Current Index Value Register */ - *pu32 = pRegs->civ; - Log3Func(("CIV[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); - break; - case AC97_NABM_OFF_LVI: - /* Last Valid Index Register */ - *pu32 = pRegs->lvi; - Log3Func(("LVI[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); - break; - case AC97_NABM_OFF_PIV: - /* Prefetched Index Value Register */ - *pu32 = pRegs->piv; - Log3Func(("PIV[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); - break; - case AC97_NABM_OFF_CR: - /* Control Register */ - *pu32 = pRegs->cr; - Log3Func(("CR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); - break; - case AC97_NABM_OFF_SR: - /* Status Register (lower part) */ - *pu32 = RT_LO_U8(pRegs->sr); - Log3Func(("SRb[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); - break; - default: - *pu32 = UINT32_MAX; - LogFunc(("U nabm readb %#x -> %#x\n", offPort, UINT32_MAX)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); - break; - } - break; + uint8_t lVol = PDMAUDIO_VOLUME_MAX + uCtlGainLeft * AC97_DB_FACTOR; + uint8_t rVol = PDMAUDIO_VOLUME_MAX + uCtlGainRight * AC97_DB_FACTOR; - case 2: - switch (offPort & AC97_NABM_OFF_MASK) - { - case AC97_NABM_OFF_SR: - /* Status Register */ - *pu32 = pRegs->sr; - Log3Func(("SR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); - break; - case AC97_NABM_OFF_PICB: - /* Position in Current Buffer */ - *pu32 = pRegs->picb; - Log3Func(("PICB[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); - break; - default: - *pu32 = UINT32_MAX; - LogFunc(("U nabm readw %#x -> %#x\n", offPort, UINT32_MAX)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); - break; - } - break; + /* We do not currently support gain. Since AC'97 does not support attenuation + * for the recording input, the best we can do is set the maximum volume. + */ +# ifndef VBOX_WITH_AC97_GAIN_SUPPORT + /* NB: Currently there is no gain support, only attenuation. Since AC'97 does not + * support attenuation for the recording inputs, the best we can do is set the + * maximum volume. + */ + lVol = rVol = PDMAUDIO_VOLUME_MAX; +# endif - case 4: - switch (offPort & AC97_NABM_OFF_MASK) - { - case AC97_NABM_OFF_BDBAR: - /* Buffer Descriptor Base Address Register */ - *pu32 = pRegs->bdbar; - Log3Func(("BMADDR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32)); - break; - case AC97_NABM_OFF_CIV: - /* 32-bit access: Current Index Value Register + - * Last Valid Index Register + - * Status Register */ - *pu32 = pRegs->civ | (pRegs->lvi << 8) | (pRegs->sr << 16); /** @todo r=andy Use RT_MAKE_U32_FROM_U8. */ - Log3Func(("CIV LVI SR[%d] -> %#x, %#x, %#x\n", - AC97_PORT2IDX(offPort), pRegs->civ, pRegs->lvi, pRegs->sr)); - break; - case AC97_NABM_OFF_PICB: - /* 32-bit access: Position in Current Buffer Register + - * Prefetched Index Value Register + - * Control Register */ - *pu32 = pRegs->picb | (pRegs->piv << 16) | (pRegs->cr << 24); /** @todo r=andy Use RT_MAKE_U32_FROM_U8. */ - Log3Func(("PICB PIV CR[%d] -> %#x %#x %#x %#x\n", - AC97_PORT2IDX(offPort), *pu32, pRegs->picb, pRegs->piv, pRegs->cr)); - break; + Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol)); - default: - *pu32 = UINT32_MAX; - LogFunc(("U nabm readl %#x -> %#x\n", offPort, UINT32_MAX)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); - break; - } - break; + int rc = VINF_SUCCESS; - default: - DEVAC97_UNLOCK(pDevIns, pThis); - AssertFailed(); - return VERR_IOM_IOPORT_UNUSED; - } - } - else + if (pThisCC->pMixer) /* Device can be in reset state, so no mixer available. */ { - switch (cb) - { - case 1: - switch (offPort) - { - case AC97_CAS: - /* Codec Access Semaphore Register */ - Log3Func(("CAS %d\n", pThis->cas)); - *pu32 = pThis->cas; - pThis->cas = 1; - break; - default: - *pu32 = UINT32_MAX; - LogFunc(("U nabm readb %#x -> %#x\n", offPort, UINT32_MAX)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); - break; - } - break; + PDMAUDIOVOLUME Vol; + PDMAudioVolumeInitFromStereo(&Vol, fCtlMuted, lVol, rVol); - case 2: - *pu32 = UINT32_MAX; - LogFunc(("U nabm readw %#x -> %#x\n", offPort, UINT32_MAX)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); + PAUDMIXSINK pSink = NULL; + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_MIC_IN: + pSink = pThisCC->pSinkMicIn; break; - case 4: - switch (offPort) - { - case AC97_GLOB_CNT: - /* Global Control */ - *pu32 = pThis->glob_cnt; - Log3Func(("glob_cnt -> %#x\n", *pu32)); - break; - case AC97_GLOB_STA: - /* Global Status */ - *pu32 = pThis->glob_sta | AC97_GS_S0CR; - Log3Func(("glob_sta -> %#x\n", *pu32)); - break; - default: - *pu32 = UINT32_MAX; - LogFunc(("U nabm readl %#x -> %#x\n", offPort, UINT32_MAX)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads); - break; - } + case PDMAUDIOMIXERCTL_LINE_IN: + pSink = pThisCC->pSinkLineIn; break; default: - DEVAC97_UNLOCK(pDevIns, pThis); AssertFailed(); - return VERR_IOM_IOPORT_UNUSED; + rc = VERR_NOT_SUPPORTED; + break; + } + + if (pSink) + { + rc = AudioMixerSinkSetVolume(pSink, &Vol); + /* There is only one AC'97 recording gain control. If line in + * is changed, also update the microphone. If the optional dedicated + * microphone is changed, only change that. + * NB: The codecs we support do not have the dedicated microphone control. + */ + if (pSink == pThisCC->pSinkLineIn && pThisCC->pSinkMicIn) + rc = AudioMixerSinkSetVolume(pSink, &Vol); } } - DEVAC97_UNLOCK(pDevIns, pThis); - return VINF_SUCCESS; + ichac97MixerSet(pThis, index, uVal); + + if (RT_FAILURE(rc)) + LogFlowFunc(("Failed with %Rrc\n", rc)); + + return rc; } + /** - * @callback_method_impl{FNIOMIOPORTNEWOUT} + * Converts an AC'97 recording source index to a PDM audio recording source. + * + * @returns PDM audio recording source. + * @param uIdx AC'97 index to convert. */ -static DECLCALLBACK(VBOXSTRICTRC) -ichac97IoPortNabmWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) +static PDMAUDIOPATH ichac97R3IdxToRecSource(uint8_t uIdx) { - PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); -#ifdef IN_RING3 - PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); -#endif - RT_NOREF(pvUser); - - VBOXSTRICTRC rc = VINF_SUCCESS; - if ( AC97_PORT2IDX_UNMASKED(offPort) < AC97_MAX_STREAMS - && offPort != AC97_GLOB_CNT) + switch (uIdx) { -#ifdef IN_RING3 - PAC97STREAMR3 pStreamCC = &pThisCC->aStreams[AC97_PORT2IDX(offPort)]; -#endif - PAC97STREAM pStream = &pThis->aStreams[AC97_PORT2IDX(offPort)]; - PAC97BMREGS pRegs = &pStream->Regs; + case AC97_REC_MIC: return PDMAUDIOPATH_IN_MIC; + case AC97_REC_CD: return PDMAUDIOPATH_IN_CD; + case AC97_REC_VIDEO: return PDMAUDIOPATH_IN_VIDEO; + case AC97_REC_AUX: return PDMAUDIOPATH_IN_AUX; + case AC97_REC_LINE_IN: return PDMAUDIOPATH_IN_LINE; + case AC97_REC_PHONE: return PDMAUDIOPATH_IN_PHONE; + default: + break; + } - DEVAC97_LOCK_BOTH_RETURN(pDevIns, pThis, pStream, VINF_IOM_R3_IOPORT_WRITE); - switch (cb) - { - case 1: - switch (offPort & AC97_NABM_OFF_MASK) - { - /* - * Last Valid Index. - */ - case AC97_NABM_OFF_LVI: - if ( (pRegs->cr & AC97_CR_RPBM) - && (pRegs->sr & AC97_SR_DCH)) - { -#ifdef IN_RING3 - pRegs->sr &= ~(AC97_SR_DCH | AC97_SR_CELV); - pRegs->civ = pRegs->piv; - pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE; -#else - rc = VINF_IOM_R3_IOPORT_WRITE; -#endif - } - pRegs->lvi = u32 % AC97_MAX_BDLE; - Log3Func(("[SD%RU8] LVI <- %#x\n", pStream->u8SD, u32)); - break; + LogFlowFunc(("Unknown record source %d, using MIC\n", uIdx)); + return PDMAUDIOPATH_IN_MIC; +} + + +/** + * Converts a PDM audio recording source to an AC'97 recording source index. + * + * @returns AC'97 recording source index. + * @param enmRecSrc PDM audio recording source to convert. + */ +static uint8_t ichac97R3RecSourceToIdx(PDMAUDIOPATH enmRecSrc) +{ + switch (enmRecSrc) + { + case PDMAUDIOPATH_IN_MIC: return AC97_REC_MIC; + case PDMAUDIOPATH_IN_CD: return AC97_REC_CD; + case PDMAUDIOPATH_IN_VIDEO: return AC97_REC_VIDEO; + case PDMAUDIOPATH_IN_AUX: return AC97_REC_AUX; + case PDMAUDIOPATH_IN_LINE: return AC97_REC_LINE_IN; + case PDMAUDIOPATH_IN_PHONE: return AC97_REC_PHONE; + default: + AssertMsgFailedBreak(("%d\n", enmRecSrc)); + } - /* - * Control Registers. - */ - case AC97_NABM_OFF_CR: -#ifdef IN_RING3 - Log3Func(("[SD%RU8] CR <- %#x (cr %#x)\n", pStream->u8SD, u32, pRegs->cr)); - if (u32 & AC97_CR_RR) /* Busmaster reset. */ - { - Log3Func(("[SD%RU8] Reset\n", pStream->u8SD)); + LogFlowFunc(("Unknown audio recording source %d using MIC\n", enmRecSrc)); + return AC97_REC_MIC; +} - /* Make sure that Run/Pause Bus Master bit (RPBM) is cleared (0). */ - Assert((pRegs->cr & AC97_CR_RPBM) == 0); - ichac97R3StreamEnable(pThis, pThisCC, pStream, pStreamCC, false /* fEnable */); - ichac97R3StreamReset(pThis, pStream, pStreamCC); +/** + * Performs an AC'97 mixer record select to switch to a different recording + * source. + * + * @param pThis The shared AC'97 state. + * @param val AC'97 recording source index to set. + */ +static void ichac97R3MixerRecordSelect(PAC97STATE pThis, uint32_t val) +{ + uint8_t rs = val & AC97_REC_MASK; + uint8_t ls = (val >> 8) & AC97_REC_MASK; - ichac97StreamUpdateSR(pDevIns, pThis, pStream, AC97_SR_DCH); /** @todo Do we need to do that? */ - } - else - { - pRegs->cr = u32 & AC97_CR_VALID_MASK; + PDMAUDIOPATH const ars = ichac97R3IdxToRecSource(rs); + PDMAUDIOPATH const als = ichac97R3IdxToRecSource(ls); - if (!(pRegs->cr & AC97_CR_RPBM)) - { - Log3Func(("[SD%RU8] Disable\n", pStream->u8SD)); + rs = ichac97R3RecSourceToIdx(ars); + ls = ichac97R3RecSourceToIdx(als); - ichac97R3StreamEnable(pThis, pThisCC, pStream, pStreamCC, false /* fEnable */); + LogRel(("AC97: Record select to left=%s, right=%s\n", PDMAudioPathGetName(ars), PDMAudioPathGetName(als))); - pRegs->sr |= AC97_SR_DCH; - } - else - { - Log3Func(("[SD%RU8] Enable\n", pStream->u8SD)); + ichac97MixerSet(pThis, AC97_Record_Select, rs | (ls << 8)); +} - pRegs->civ = pRegs->piv; - pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE; +/** + * Resets the AC'97 mixer. + * + * @returns VBox status code. + * @param pThis The shared AC'97 state. + * @param pThisCC The ring-3 AC'97 state. + */ +static int ichac97R3MixerReset(PAC97STATE pThis, PAC97STATER3 pThisCC) +{ + LogFlowFuncEnter(); - pRegs->sr &= ~AC97_SR_DCH; + RT_ZERO(pThis->mixer_data); - /* Fetch the initial BDLE descriptor. */ - ichac97R3StreamFetchBDLE(pDevIns, pStream); -# ifdef LOG_ENABLED - ichac97R3BDLEDumpAll(pDevIns, pStream->Regs.bdbar, pStream->Regs.lvi + 1); -# endif - ichac97R3StreamEnable(pThis, pThisCC, pStream, pStreamCC, true /* fEnable */); + /* Note: Make sure to reset all registers first before bailing out on error. */ - /* Arm the timer for this stream. */ - /** @todo r=bird: This function returns bool, not VBox status! */ - ichac97R3TimerSet(pDevIns, pStream, pStreamCC->State.cTransferTicks); - } - } -#else /* !IN_RING3 */ - rc = VINF_IOM_R3_IOPORT_WRITE; -#endif - break; + ichac97MixerSet(pThis, AC97_Reset , 0x0000); /* 6940 */ + ichac97MixerSet(pThis, AC97_Master_Volume_Mono_Mute , 0x8000); + ichac97MixerSet(pThis, AC97_PC_BEEP_Volume_Mute , 0x0000); - /* - * Status Registers. - */ - case AC97_NABM_OFF_SR: - ichac97StreamWriteSR(pDevIns, pThis, pStream, u32); - break; + ichac97MixerSet(pThis, AC97_Phone_Volume_Mute , 0x8008); + ichac97MixerSet(pThis, AC97_Mic_Volume_Mute , 0x8008); + ichac97MixerSet(pThis, AC97_CD_Volume_Mute , 0x8808); + ichac97MixerSet(pThis, AC97_Aux_Volume_Mute , 0x8808); + ichac97MixerSet(pThis, AC97_Record_Gain_Mic_Mute , 0x8000); + ichac97MixerSet(pThis, AC97_General_Purpose , 0x0000); + ichac97MixerSet(pThis, AC97_3D_Control , 0x0000); + ichac97MixerSet(pThis, AC97_Powerdown_Ctrl_Stat , 0x000f); - default: - LogRel2(("AC97: Warning: Unimplemented NABMWrite offPort=%#x <- %#x LB 1\n", offPort, u32)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); - break; - } - break; + /* Configure Extended Audio ID (EAID) + Control & Status (EACS) registers. */ + const uint16_t fEAID = AC97_EAID_REV1 | AC97_EACS_VRA | AC97_EACS_VRM; /* Our hardware is AC'97 rev2.3 compliant. */ + const uint16_t fEACS = AC97_EACS_VRA | AC97_EACS_VRM; /* Variable Rate PCM Audio (VRA) + Mic-In (VRM) capable. */ - case 2: - switch (offPort & AC97_NABM_OFF_MASK) - { - case AC97_NABM_OFF_SR: - ichac97StreamWriteSR(pDevIns, pThis, pStream, u32); - break; - default: - LogRel2(("AC97: Warning: Unimplemented NABMWrite offPort=%#x <- %#x LB 2\n", offPort, u32)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); - break; - } - break; + LogRel(("AC97: Mixer reset (EAID=0x%x, EACS=0x%x)\n", fEAID, fEACS)); - case 4: - switch (offPort & AC97_NABM_OFF_MASK) - { - case AC97_NABM_OFF_BDBAR: - /* Buffer Descriptor list Base Address Register */ - pRegs->bdbar = u32 & ~3; - Log3Func(("[SD%RU8] BDBAR <- %#x (bdbar %#x)\n", AC97_PORT2IDX(offPort), u32, pRegs->bdbar)); - break; - default: - LogRel2(("AC97: Warning: Unimplemented NABMWrite offPort=%#x <- %#x LB 4\n", offPort, u32)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); - break; - } - break; + ichac97MixerSet(pThis, AC97_Extended_Audio_ID, fEAID); + ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, fEACS); + ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate , 0xbb80 /* 48000 Hz by default */); + ichac97MixerSet(pThis, AC97_PCM_Surround_DAC_Rate , 0xbb80 /* 48000 Hz by default */); + ichac97MixerSet(pThis, AC97_PCM_LFE_DAC_Rate , 0xbb80 /* 48000 Hz by default */); + ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate , 0xbb80 /* 48000 Hz by default */); + ichac97MixerSet(pThis, AC97_MIC_ADC_Rate , 0xbb80 /* 48000 Hz by default */); - default: - AssertMsgFailed(("offPort=%#x <- %#x LB %u\n", offPort, u32, cb)); - break; - } - DEVAC97_UNLOCK_BOTH(pDevIns, pThis, pStream); + if (pThis->enmCodecModel == AC97CODEC_AD1980) + { + /* Analog Devices 1980 (AD1980) */ + ichac97MixerSet(pThis, AC97_Reset , 0x0010); /* Headphones. */ + ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144); + ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5370); + ichac97MixerSet(pThis, AC97_Headphone_Volume_Mute , 0x8000); + } + else if (pThis->enmCodecModel == AC97CODEC_AD1981B) + { + /* Analog Devices 1981B (AD1981B) */ + ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144); + ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5374); } else { - switch (cb) - { - case 1: - LogRel2(("AC97: Warning: Unimplemented NABMWrite offPort=%#x <- %#x LB 1\n", offPort, u32)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); - break; + /* Sigmatel 9700 (STAC9700) */ + ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x8384); + ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x7600); /* 7608 */ + } + ichac97R3MixerRecordSelect(pThis, 0); - case 2: - LogRel2(("AC97: Warning: Unimplemented NABMWrite offPort=%#x <- %#x LB 2\n", offPort, u32)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); - break; + /* The default value is 8000h, which corresponds to 0 dB attenuation with mute on. */ + ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER, 0x8000); - case 4: - switch (offPort) - { - case AC97_GLOB_CNT: - /* Global Control */ - DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE); - if (u32 & AC97_GC_WR) - ichac97WarmReset(pThis); - if (u32 & AC97_GC_CR) - ichac97ColdReset(pThis); - if (!(u32 & (AC97_GC_WR | AC97_GC_CR))) - pThis->glob_cnt = u32 & AC97_GC_VALID_MASK; - Log3Func(("glob_cnt <- %#x (glob_cnt %#x)\n", u32, pThis->glob_cnt)); - DEVAC97_UNLOCK(pDevIns, pThis); - break; - case AC97_GLOB_STA: - /* Global Status */ - DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE); - pThis->glob_sta &= ~(u32 & AC97_GS_WCLEAR_MASK); - pThis->glob_sta |= (u32 & ~(AC97_GS_WCLEAR_MASK | AC97_GS_RO_MASK)) & AC97_GS_VALID_MASK; - Log3Func(("glob_sta <- %#x (glob_sta %#x)\n", u32, pThis->glob_sta)); - DEVAC97_UNLOCK(pDevIns, pThis); - break; - default: - LogRel2(("AC97: Warning: Unimplemented NABMWrite offPort=%#x <- %#x LB 4\n", offPort, u32)); - STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites); - break; - } - break; + /* The default value for stereo registers is 8808h, which corresponds to 0 dB gain with mute on.*/ + ichac97R3MixerSetVolume(pThis, pThisCC, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT, 0x8808); + ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8808); + ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8008); - default: - AssertMsgFailed(("offPort=%#x <- %#x LB %u\n", offPort, u32, cb)); - break; - } - } + /* The default for record controls is 0 dB gain with mute on. */ + ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8000); + ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8000); - return rc; + return VINF_SUCCESS; } +#endif /* IN_RING3 */ + /** * @callback_method_impl{FNIOMIOPORTNEWIN} */ @@ -3383,33 +3376,28 @@ switch (cb) { case 1: - { - LogRel2(("AC97: Warning: Unimplemented read (1 byte) offPort=%#x\n", offPort)); + LogRel2(("AC97: Warning: Unimplemented NAM read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamReads); pThis->cas = 0; *pu32 = UINT32_MAX; break; - } case 2: - { pThis->cas = 0; *pu32 = ichac97MixerGet(pThis, offPort); break; - } case 4: - { - LogRel2(("AC97: Warning: Unimplemented read (4 bytes) offPort=%#x\n", offPort)); + LogRel2(("AC97: Warning: Unimplemented NAM read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamReads); pThis->cas = 0; *pu32 = UINT32_MAX; break; - } default: - { AssertFailed(); rc = VERR_IOM_IOPORT_UNUSED; - } + break; } DEVAC97_UNLOCK(pDevIns, pThis); @@ -3434,11 +3422,10 @@ switch (cb) { case 1: - { - LogRel2(("AC97: Warning: Unimplemented NAMWrite (1 byte) offPort=%#x <- %#x\n", offPort, u32)); + LogRel2(("AC97: Warning: Unimplemented NAM write offPort=%#x <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort, u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites); pThis->cas = 0; break; - } case 2: { @@ -3536,11 +3523,15 @@ if (!(u32 & AC97_EACS_VRA)) /* Check if VRA bit is not set. */ { ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate, 0xbb80); /* Set default (48000 Hz). */ - ichac97R3StreamReOpen(pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX], + /** @todo r=bird: Why reopen it now? Can't we put that off till it's + * actually used? */ + ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX], &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX], true /* fForce */); ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate, 0xbb80); /* Set default (48000 Hz). */ - ichac97R3StreamReOpen(pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX], + /** @todo r=bird: Why reopen it now? Can't we put that off till it's + * actually used? */ + ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX], &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX], true /* fForce */); } else @@ -3552,7 +3543,9 @@ if (!(u32 & AC97_EACS_VRM)) /* Check if VRM bit is not set. */ { ichac97MixerSet(pThis, AC97_MIC_ADC_Rate, 0xbb80); /* Set default (48000 Hz). */ - ichac97R3StreamReOpen(pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX], + /** @todo r=bird: Why reopen it now? Can't we put that off till it's + * actually used? */ + ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX], &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX], true /* fForce */); } else @@ -3570,7 +3563,9 @@ { LogRel2(("AC97: Setting front DAC rate to 0x%x\n", u32)); ichac97MixerSet(pThis, offPort, u32); - ichac97R3StreamReOpen(pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX], + /** @todo r=bird: Why reopen it now? Can't we put that off till it's + * actually used? */ + ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX], &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX], true /* fForce */); } else @@ -3585,7 +3580,9 @@ { LogRel2(("AC97: Setting microphone ADC rate to 0x%x\n", u32)); ichac97MixerSet(pThis, offPort, u32); - ichac97R3StreamReOpen(pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX], + /** @todo r=bird: Why reopen it now? Can't we put that off till it's + * actually used? */ + ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX], &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX], true /* fForce */); } else @@ -3600,7 +3597,9 @@ { LogRel2(("AC97: Setting line-in ADC rate to 0x%x\n", u32)); ichac97MixerSet(pThis, offPort, u32); - ichac97R3StreamReOpen(pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX], + /** @todo r=bird: Why reopen it now? Can't we put that off till it's + * actually used? */ + ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX], &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX], true /* fForce */); } else @@ -3610,7 +3609,11 @@ #endif break; default: - LogRel2(("AC97: Warning: Unimplemented NAMWrite (2 bytes) offPort=%#x <- %#x\n", offPort, u32)); + /* Most of these are to register we don't care about like AC97_CD_Volume_Mute + and AC97_Master_Volume_Mono_Mute or things we don't need to handle specially. + Thus this is not a 'warning' but an 'info log message. */ + LogRel2(("AC97: Info: Unimplemented NAM write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites); ichac97MixerSet(pThis, offPort, u32); break; } @@ -3618,14 +3621,13 @@ } case 4: - { - LogRel2(("AC97: Warning: Unimplemented 4 byte NAMWrite: offPort=%#x <- %#x\n", offPort, u32)); + LogRel2(("AC97: Warning: Unimplemented NAM write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32)); + STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites); pThis->cas = 0; break; - } default: - AssertMsgFailed(("Unhandled NAMWrite offPort=%#x, cb=%u u32=%#x\n", offPort, cb, u32)); + AssertMsgFailed(("Unhandled NAM write offPort=%#x, cb=%u u32=%#x\n", offPort, cb, u32)); break; } @@ -3635,6 +3637,11 @@ #ifdef IN_RING3 + +/********************************************************************************************************************************* +* State Saving & Loading * +*********************************************************************************************************************************/ + /** * Saves (serializes) an AC'97 stream using SSM. * @@ -3644,21 +3651,21 @@ */ static void ichac97R3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PAC97STREAM pStream) { - PAC97BMREGS pRegs = &pStream->Regs; PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - pHlp->pfnSSMPutU32(pSSM, pRegs->bdbar); - pHlp->pfnSSMPutU8( pSSM, pRegs->civ); - pHlp->pfnSSMPutU8( pSSM, pRegs->lvi); - pHlp->pfnSSMPutU16(pSSM, pRegs->sr); - pHlp->pfnSSMPutU16(pSSM, pRegs->picb); - pHlp->pfnSSMPutU8( pSSM, pRegs->piv); - pHlp->pfnSSMPutU8( pSSM, pRegs->cr); - pHlp->pfnSSMPutS32(pSSM, pRegs->bd_valid); - pHlp->pfnSSMPutU32(pSSM, pRegs->bd.addr); - pHlp->pfnSSMPutU32(pSSM, pRegs->bd.ctl_len); + pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bdbar); + pHlp->pfnSSMPutU8( pSSM, pStream->Regs.civ); + pHlp->pfnSSMPutU8( pSSM, pStream->Regs.lvi); + pHlp->pfnSSMPutU16(pSSM, pStream->Regs.sr); + pHlp->pfnSSMPutU16(pSSM, pStream->Regs.picb); + pHlp->pfnSSMPutU8( pSSM, pStream->Regs.piv); + pHlp->pfnSSMPutU8( pSSM, pStream->Regs.cr); + pHlp->pfnSSMPutS32(pSSM, pStream->Regs.bd_valid); + pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bd.addr); + pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bd.ctl_len); } + /** * @callback_method_impl{FNSSMDEVSAVEEXEC} */ @@ -3694,31 +3701,32 @@ return VINF_SUCCESS; } + /** * Loads an AC'97 stream from SSM. * - * @returns IPRT status code. + * @returns VBox status code. * @param pDevIns The device instance. * @param pSSM Saved state manager (SSM) handle to use. * @param pStream AC'97 stream to load. */ static int ichac97R3LoadStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PAC97STREAM pStream) { - PAC97BMREGS pRegs = &pStream->Regs; PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - pHlp->pfnSSMGetU32(pSSM, &pRegs->bdbar); - pHlp->pfnSSMGetU8( pSSM, &pRegs->civ); - pHlp->pfnSSMGetU8( pSSM, &pRegs->lvi); - pHlp->pfnSSMGetU16(pSSM, &pRegs->sr); - pHlp->pfnSSMGetU16(pSSM, &pRegs->picb); - pHlp->pfnSSMGetU8( pSSM, &pRegs->piv); - pHlp->pfnSSMGetU8( pSSM, &pRegs->cr); - pHlp->pfnSSMGetS32(pSSM, &pRegs->bd_valid); - pHlp->pfnSSMGetU32(pSSM, &pRegs->bd.addr); - return pHlp->pfnSSMGetU32(pSSM, &pRegs->bd.ctl_len); + pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bdbar); + pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.civ); + pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.lvi); + pHlp->pfnSSMGetU16(pSSM, &pStream->Regs.sr); + pHlp->pfnSSMGetU16(pSSM, &pStream->Regs.picb); + pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.piv); + pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.cr); + pHlp->pfnSSMGetS32(pSSM, &pStream->Regs.bd_valid); + pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bd.addr); + return pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bd.ctl_len); } + /** * @callback_method_impl{FNSSMDEVLOADEXEC} */ @@ -3743,8 +3751,8 @@ */ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) { - int rc2 = ichac97R3LoadStream(pDevIns, pSSM, &pThis->aStreams[i]); - AssertRCReturn(rc2, rc2); + int rc = ichac97R3LoadStream(pDevIns, pSSM, &pThis->aStreams[i]); + AssertRCReturn(rc, rc); } pHlp->pfnSSMGetMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data)); @@ -3771,8 +3779,8 @@ * Again the stream order is set is stone. */ uint8_t afActiveStrms[AC97SOUNDSOURCE_MAX]; - int rc2 = pHlp->pfnSSMGetMem(pSSM, afActiveStrms, sizeof(afActiveStrms)); - AssertRCReturn(rc2, rc2); + int rc = pHlp->pfnSSMGetMem(pSSM, afActiveStrms, sizeof(afActiveStrms)); + AssertRCReturn(rc, rc); for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) { @@ -3780,25 +3788,195 @@ const PAC97STREAM pStream = &pThis->aStreams[i]; const PAC97STREAMR3 pStreamCC = &pThisCC->aStreams[i]; - rc2 = ichac97R3StreamEnable(pThis, pThisCC, pStream, pStreamCC, fEnable); - AssertRC(rc2); + rc = ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, fEnable); + AssertRC(rc); if ( fEnable - && RT_SUCCESS(rc2)) + && RT_SUCCESS(rc)) { + /* + * We need to make sure to update the stream's next transfer (if any) when + * restoring from a saved state. + * + * Otherwise pStream->cDmaPeriodTicks always will be 0 and thus streams won't + * resume when running while the saved state has been taken. + * + * Also see oem2ticketref:52. + */ + ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC); + /* Re-arm the timer for this stream. */ - ichac97R3TimerSet(pDevIns, pStream, pStreamCC->State.cTransferTicks); + /** @todo r=aeichner This causes a VM hang upon saved state resume when NetBSD is used as a guest + * Stopping the timer if cDmaPeriodTicks is 0 is a workaround but needs further investigation, + * see @bugref{9759} for more information. */ + if (pStream->cDmaPeriodTicks) + ichac97R3TimerSet(pDevIns, pStream, pStream->cDmaPeriodTicks); + else + PDMDevHlpTimerStop(pDevIns, pStream->hTimer); + } + + /* Keep going. */ + } + + pThis->bup_flag = 0; + pThis->last_samp = 0; + + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Debug Info Items * +*********************************************************************************************************************************/ + +/** Used by ichac97R3DbgInfoStream and ichac97R3DbgInfoBDL. */ +static int ichac97R3DbgLookupStrmIdx(PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + if (pszArgs && *pszArgs) + { + int32_t idxStream; + int rc = RTStrToInt32Full(pszArgs, 0, &idxStream); + if (RT_SUCCESS(rc) && idxStream >= -1 && idxStream < AC97_MAX_STREAMS) + return idxStream; + pHlp->pfnPrintf(pHlp, "Argument '%s' is not a valid stream number!\n", pszArgs); + } + return -1; +} + + +/** + * Generic buffer descriptor list dumper. + */ +static void ichac97R3DbgPrintBdl(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, + PCDBGFINFOHLP pHlp, const char *pszPrefix) +{ + uint8_t const bLvi = pStream->Regs.lvi; + uint8_t const bCiv = pStream->Regs.civ; + pHlp->pfnPrintf(pHlp, "%sBDL for stream #%u: @ %#RX32 LB 0x100; CIV=%#04x LVI=%#04x:\n", + pszPrefix, pStream->u8SD, pStream->Regs.bdbar, bCiv, bLvi); + if (pStream->Regs.bdbar != 0) + { + /* Read all in one go. */ + AC97BDLE aBdl[AC97_MAX_BDLE]; + RT_ZERO(aBdl); + PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bdbar, aBdl, sizeof(aBdl)); + + /* Get the audio props for the stream so we can translate the sizes correctly. */ + PDMAUDIOPCMPROPS Props; + ichach97R3CalcStreamProps(pThis, pStream->u8SD, &Props); + + /* Dump them. */ + uint64_t cbTotal = 0; + uint64_t cbValid = 0; + for (unsigned i = 0; i < RT_ELEMENTS(aBdl); i++) + { + aBdl[i].addr = RT_LE2H_U32(aBdl[i].addr); + aBdl[i].ctl_len = RT_LE2H_U32(aBdl[i].ctl_len); + + bool const fValid = bCiv <= bLvi + ? i >= bCiv && i <= bLvi + : i >= bCiv || i <= bLvi; + + uint32_t const cb = (aBdl[i].ctl_len & AC97_BD_LEN_MASK) * PDMAudioPropsSampleSize(&Props); /** @todo or frame size? OSDev says frame... */ + cbTotal += cb; + if (fValid) + cbValid += cb; + + char szFlags[64]; + szFlags[0] = '\0'; + if (aBdl[i].ctl_len & ~(AC97_BD_LEN_MASK | AC97_BD_IOC | AC97_BD_BUP)) + RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", aBdl[i].ctl_len & ~AC97_BD_LEN_MASK); + + pHlp->pfnPrintf(pHlp, "%s %cBDLE%02u: %#010RX32 L %#06x / LB %#RX32 / %RU64ms%s%s%s%s\n", + pszPrefix, fValid ? ' ' : '?', i, aBdl[i].addr, + aBdl[i].ctl_len & AC97_BD_LEN_MASK, cb, PDMAudioPropsBytesToMilli(&Props, cb), + aBdl[i].ctl_len & AC97_BD_IOC ? " ioc" : "", + aBdl[i].ctl_len & AC97_BD_BUP ? " bup" : "", + szFlags, !(aBdl[i].addr & 3) ? "" : " !!Addr!!"); } - /* Keep going. */ + pHlp->pfnPrintf(pHlp, "%sTotal: %#RX64 bytes (%RU64), %RU64 ms; Valid: %#RX64 bytes (%RU64), %RU64 ms\n", pszPrefix, + cbTotal, cbTotal, PDMAudioPropsBytesToMilli(&Props, cbTotal), + cbValid, cbValid, PDMAudioPropsBytesToMilli(&Props, cbValid) ); + } +} + + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, ac97bdl} + */ +static DECLCALLBACK(void) ichac97R3DbgInfoBDL(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); + int idxStream = ichac97R3DbgLookupStrmIdx(pHlp, pszArgs); + if (idxStream != -1) + ichac97R3DbgPrintBdl(pDevIns, pThis, &pThis->aStreams[idxStream], pHlp, ""); + else + for (idxStream = 0; idxStream < AC97_MAX_STREAMS; ++idxStream) + ichac97R3DbgPrintBdl(pDevIns, pThis, &pThis->aStreams[idxStream], pHlp, ""); +} + + +/** Worker for ichac97R3DbgInfoStream. */ +static void ichac97R3DbgPrintStream(PCDBGFINFOHLP pHlp, PAC97STREAM pStream, PAC97STREAMR3 pStreamR3) +{ + char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX]; + pHlp->pfnPrintf(pHlp, "Stream #%d: %s\n", pStream->u8SD, + PDMAudioStrmCfgToString(&pStreamR3->State.Cfg, szTmp, sizeof(szTmp))); + pHlp->pfnPrintf(pHlp, " BDBAR %#010RX32\n", pStream->Regs.bdbar); + pHlp->pfnPrintf(pHlp, " CIV %#04RX8\n", pStream->Regs.civ); + pHlp->pfnPrintf(pHlp, " LVI %#04RX8\n", pStream->Regs.lvi); + pHlp->pfnPrintf(pHlp, " SR %#06RX16\n", pStream->Regs.sr); + pHlp->pfnPrintf(pHlp, " PICB %#06RX16\n", pStream->Regs.picb); + pHlp->pfnPrintf(pHlp, " PIV %#04RX8\n", pStream->Regs.piv); + pHlp->pfnPrintf(pHlp, " CR %#04RX8\n", pStream->Regs.cr); + if (pStream->Regs.bd_valid) + { + pHlp->pfnPrintf(pHlp, " BD.ADDR %#010RX32\n", pStream->Regs.bd.addr); + pHlp->pfnPrintf(pHlp, " BD.LEN %#04RX16\n", (uint16_t)pStream->Regs.bd.ctl_len); + pHlp->pfnPrintf(pHlp, " BD.CTL %#04RX16\n", (uint16_t)(pStream->Regs.bd.ctl_len >> 16)); } - pThis->bup_flag = 0; - pThis->last_samp = 0; + pHlp->pfnPrintf(pHlp, " offRead %#RX64\n", pStreamR3->State.offRead); + pHlp->pfnPrintf(pHlp, " offWrite %#RX64\n", pStreamR3->State.offWrite); + pHlp->pfnPrintf(pHlp, " uTimerHz %RU16\n", pStreamR3->State.uTimerHz); + pHlp->pfnPrintf(pHlp, " cDmaPeriodTicks %RU64\n", pStream->cDmaPeriodTicks); + pHlp->pfnPrintf(pHlp, " cbDmaPeriod %#RX32\n", pStream->cbDmaPeriod); +} - return VINF_SUCCESS; + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, ac97stream} + */ +static DECLCALLBACK(void) ichac97R3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); + PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); + int idxStream = ichac97R3DbgLookupStrmIdx(pHlp, pszArgs); + if (idxStream != -1) + ichac97R3DbgPrintStream(pHlp, &pThis->aStreams[idxStream], &pThisCC->aStreams[idxStream]); + else + for (idxStream = 0; idxStream < AC97_MAX_STREAMS; ++idxStream) + ichac97R3DbgPrintStream(pHlp, &pThis->aStreams[idxStream], &pThisCC->aStreams[idxStream]); +} + + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, ac97mixer} + */ +static DECLCALLBACK(void) ichac97R3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); + if (pThisCC->pMixer) + AudioMixerDebug(pThisCC->pMixer, pHlp, pszArgs); + else + pHlp->pfnPrintf(pHlp, "Mixer not available\n"); } +/********************************************************************************************************************************* +* PDMIBASE * +*********************************************************************************************************************************/ + /** * @interface_method_impl{PDMIBASE,pfnQueryInterface} */ @@ -3810,6 +3988,56 @@ } +/********************************************************************************************************************************* +* PDMDEVREG * +*********************************************************************************************************************************/ + +/** + * Destroys all AC'97 audio streams of the device. + * + * @param pDevIns The device AC'97 instance. + * @param pThis The shared AC'97 state. + * @param pThisCC The ring-3 AC'97 state. + */ +static void ichac97R3StreamsDestroy(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC) +{ + LogFlowFuncEnter(); + + /* + * Destroy all AC'97 streams. + */ + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + ichac97R3StreamDestroy(pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i]); + + /* + * Destroy all sinks. + */ + if (pThisCC->pSinkLineIn) + { + ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkLineIn, PDMAUDIODIR_IN, PDMAUDIOPATH_IN_LINE); + + AudioMixerSinkDestroy(pThisCC->pSinkLineIn, pDevIns); + pThisCC->pSinkLineIn = NULL; + } + + if (pThisCC->pSinkMicIn) + { + ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkMicIn, PDMAUDIODIR_IN, PDMAUDIOPATH_IN_MIC); + + AudioMixerSinkDestroy(pThisCC->pSinkMicIn, pDevIns); + pThisCC->pSinkMicIn = NULL; + } + + if (pThisCC->pSinkOut) + { + ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkOut, PDMAUDIODIR_OUT, PDMAUDIOPATH_OUT_FRONT); + + AudioMixerSinkDestroy(pThisCC->pSinkOut, pDevIns); + pThisCC->pSinkOut = NULL; + } +} + + /** * Powers off the device. * @@ -3824,7 +4052,7 @@ /* Note: Involves mixer stream / sink destruction, so also do this here * instead of in ichac97R3Destruct(). */ - ichac97R3StreamsDestroy(pThis, pThisCC); + ichac97R3StreamsDestroy(pDevIns, pThis, pThisCC); /* * Note: Destroy the mixer while powering off and *not* in ichac97R3Destruct, @@ -3833,7 +4061,7 @@ */ if (pThisCC->pMixer) { - AudioMixerDestroy(pThisCC->pMixer); + AudioMixerDestroy(pThisCC->pMixer, pDevIns); pThisCC->pMixer = NULL; } } @@ -3864,7 +4092,7 @@ */ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) { - ichac97R3StreamEnable(pThis, pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i], false /* fEnable */); + ichac97R3StreamEnable(pDevIns, pThis, pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i], false /* fEnable */); ichac97R3StreamReset(pThis, &pThis->aStreams[i], &pThisCC->aStreams[i]); } @@ -3881,51 +4109,71 @@ /** - * Attach command, internal version. + * Adds a specific AC'97 driver to the driver chain. * - * This is called to let the device attach to a driver for a specified LUN - * during runtime. This is not called during VM construction, the device - * constructor has to attach to all the available drivers. + * Only called from ichac97R3Attach(). * * @returns VBox status code. * @param pDevIns The device instance. * @param pThisCC The ring-3 AC'97 device state. - * @param iLun The logical unit which is being attached. - * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. - * @param ppDrv Attached driver instance on success. Optional. + * @param pDrv The AC'97 driver to add. */ -static int ichac97R3AttachInternal(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, unsigned iLun, uint32_t fFlags, PAC97DRIVER *ppDrv) +static int ichac97R3MixerAddDrv(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAC97DRIVER pDrv) { - RT_NOREF(fFlags); + int rc = VINF_SUCCESS; + + if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg)) + rc = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkLineIn, + &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg, pDrv); + + if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg)) + { + int rc2 = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkOut, + &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg)) + { + int rc2 = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkMicIn, + &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + +/** + * Worker for ichac97R3Construct() and ichac97R3Attach(). + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThisCC The ring-3 AC'97 device state. + * @param uLUN The logical unit which is being attached. + * @param ppDrv Attached driver instance on success. Optional. + */ +static int ichac97R3AttachInternal(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, unsigned uLUN, PAC97DRIVER *ppDrv) +{ /* - * Attach driver. + * Allocate a new driver structure and try attach the driver. */ - char *pszDesc; - if (RTStrAPrintf(&pszDesc, "Audio driver port (AC'97) for LUN #%u", iLun) <= 0) - AssertLogRelFailedReturn(VERR_NO_MEMORY); + PAC97DRIVER pDrv = (PAC97DRIVER)RTMemAllocZ(sizeof(AC97DRIVER)); + AssertPtrReturn(pDrv, VERR_NO_MEMORY); + RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (AC'97) for LUN #%u", uLUN); PPDMIBASE pDrvBase; - int rc = PDMDevHlpDriverAttach(pDevIns, iLun, &pThisCC->IBase, &pDrvBase, pszDesc); + int rc = PDMDevHlpDriverAttach(pDevIns, uLUN, &pThisCC->IBase, &pDrvBase, pDrv->szDesc); if (RT_SUCCESS(rc)) { - PAC97DRIVER pDrv = (PAC97DRIVER)RTMemAllocZ(sizeof(AC97DRIVER)); - if (pDrv) + pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR); + AssertPtr(pDrv->pConnector); + if (RT_VALID_PTR(pDrv->pConnector)) { pDrv->pDrvBase = pDrvBase; - pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR); - AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN #%u has no host audio interface, rc=%Rrc\n", iLun, rc)); - pDrv->uLUN = iLun; - pDrv->pszDesc = pszDesc; - - /* - * For now we always set the driver at LUN 0 as our primary - * host backend. This might change in the future. - */ - if (iLun == 0) - pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY; - - LogFunc(("LUN#%u: pCon=%p, drvFlags=0x%x\n", iLun, pDrv->pConnector, pDrv->fFlags)); + pDrv->uLUN = uLUN; /* Attach to driver list if not attached yet. */ if (!pDrv->fAttached) @@ -3936,107 +4184,120 @@ if (ppDrv) *ppDrv = pDrv; + + /* + * While we're here, give the windows backends a hint about our typical playback + * configuration. + */ + if ( pDrv->pConnector + && pDrv->pConnector->pfnStreamConfigHint) + { + /* 48kHz */ + PDMAUDIOSTREAMCFG Cfg; + RT_ZERO(Cfg); + Cfg.enmDir = PDMAUDIODIR_OUT; + Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT; + Cfg.Device.cMsSchedulingHint = 5; + Cfg.Backend.cFramesPreBuffering = UINT32_MAX; + PDMAudioPropsInit(&Cfg.Props, 2, true /*fSigned*/, 2, 48000); + RTStrPrintf(Cfg.szName, sizeof(Cfg.szName), "output 48kHz 2ch S16 (HDA config hint)"); + + pDrv->pConnector->pfnStreamConfigHint(pDrv->pConnector, &Cfg); /* (may trash CfgReq) */ +# if 0 + /* 44.1kHz */ + RT_ZERO(Cfg); + Cfg.enmDir = PDMAUDIODIR_OUT; + Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT; + Cfg.Device.cMsSchedulingHint = 10; + Cfg.Backend.cFramesPreBuffering = UINT32_MAX; + PDMAudioPropsInit(&Cfg.Props, 2, true /*fSigned*/, 2, 44100); + RTStrPrintf(Cfg.szName, sizeof(Cfg.szName), "output 44.1kHz 2ch S16 (HDA config hint)"); + + pDrv->pConnector->pfnStreamConfigHint(pDrv->pConnector, &Cfg); /* (may trash CfgReq) */ +# endif + } + + LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector)); + return VINF_SUCCESS; } - else - rc = VERR_NO_MEMORY; + RTMemFree(pDrv); + rc = VERR_PDM_MISSING_INTERFACE_BELOW; } else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) - LogFunc(("No attached driver for LUN #%u\n", iLun)); - - if (RT_FAILURE(rc)) - { - /* Only free this string on failure; - * must remain valid for the live of the driver instance. */ - RTStrFree(pszDesc); - } + LogFunc(("No attached driver for LUN #%u\n", uLUN)); + else + LogFunc(("Attached driver for LUN #%u failed: %Rrc\n", uLUN, rc)); + RTMemFree(pDrv); - LogFunc(("iLun=%u, fFlags=0x%x, rc=%Rrc\n", iLun, fFlags, rc)); + LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc)); return rc; } + /** - * Detach command, internal version. - * - * This is called to let the device detach from a driver for a specified LUN - * during runtime. - * - * @returns VBox status code. - * @param pThisCC The ring-3 AC'97 device state. - * @param pDrv Driver to detach from device. - * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. + * @interface_method_impl{PDMDEVREGR3,pfnAttach} */ -static int ichac97R3DetachInternal(PAC97STATER3 pThisCC, PAC97DRIVER pDrv, uint32_t fFlags) +static DECLCALLBACK(int) ichac97R3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) { + PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); + PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); RT_NOREF(fFlags); + LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags)); - /* First, remove the driver from our list and destory it's associated streams. - * This also will un-set the driver as a recording source (if associated). */ - ichac97R3MixerRemoveDrv(pThisCC, pDrv); - - /* Next, search backwards for a capable (attached) driver which now will be the - * new recording source. */ - PDMAUDIODSTSRCUNION dstSrc; - PAC97DRIVER pDrvCur; - RTListForEachReverse(&pThisCC->lstDrv, pDrvCur, AC97DRIVER, Node) - { - if (!pDrvCur->pConnector) - continue; + DEVAC97_LOCK(pDevIns, pThis); - PDMAUDIOBACKENDCFG Cfg; - int rc2 = pDrvCur->pConnector->pfnGetConfig(pDrvCur->pConnector, &Cfg); + PAC97DRIVER pDrv; + int rc = ichac97R3AttachInternal(pDevIns, pThisCC, iLUN, &pDrv); + if (RT_SUCCESS(rc)) + { + int rc2 = ichac97R3MixerAddDrv(pDevIns, pThisCC, pDrv); if (RT_FAILURE(rc2)) - continue; - - dstSrc.enmSrc = PDMAUDIORECSRC_MIC; - PAC97DRIVERSTREAM pDrvStrm = ichac97R3MixerGetDrvStream(pDrvCur, PDMAUDIODIR_IN, dstSrc); - if ( pDrvStrm - && pDrvStrm->pMixStrm) - { - rc2 = AudioMixerSinkSetRecordingSource(pThisCC->pSinkMicIn, pDrvStrm->pMixStrm); - if (RT_SUCCESS(rc2)) - LogRel2(("AC97: Set new recording source for 'Mic In' to '%s'\n", Cfg.szName)); - } - - dstSrc.enmSrc = PDMAUDIORECSRC_LINE; - pDrvStrm = ichac97R3MixerGetDrvStream(pDrvCur, PDMAUDIODIR_IN, dstSrc); - if ( pDrvStrm - && pDrvStrm->pMixStrm) - { - rc2 = AudioMixerSinkSetRecordingSource(pThisCC->pSinkLineIn, pDrvStrm->pMixStrm); - if (RT_SUCCESS(rc2)) - LogRel2(("AC97: Set new recording source for 'Line In' to '%s'\n", Cfg.szName)); - } + LogFunc(("ichac97R3MixerAddDrv failed with %Rrc (ignored)\n", rc2)); } - LogFunc(("uLUN=%u, fFlags=0x%x\n", pDrv->uLUN, fFlags)); - return VINF_SUCCESS; + DEVAC97_UNLOCK(pDevIns, pThis); + + return rc; } + /** - * @interface_method_impl{PDMDEVREGR3,pfnAttach} + * Removes a specific AC'97 driver from the driver chain and destroys its + * associated streams. + * + * Only called from ichac97R3Detach(). + * + * @param pDevIns The device instance. + * @param pThisCC The ring-3 AC'97 device state. + * @param pDrv AC'97 driver to remove. */ -static DECLCALLBACK(int) ichac97R3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) +static void ichac97R3MixerRemoveDrv(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAC97DRIVER pDrv) { - PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); - PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); - - LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags)); - - DEVAC97_LOCK(pDevIns, pThis); - - PAC97DRIVER pDrv; - int rc2 = ichac97R3AttachInternal(pDevIns, pThisCC, iLUN, fFlags, &pDrv); - if (RT_SUCCESS(rc2)) - rc2 = ichac97R3MixerAddDrv(pThisCC, pDrv); + if (pDrv->MicIn.pMixStrm) + { + AudioMixerSinkRemoveStream(pThisCC->pSinkMicIn, pDrv->MicIn.pMixStrm); + AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->MicIn.pMixStrm = NULL; + } - if (RT_FAILURE(rc2)) - LogFunc(("Failed with %Rrc\n", rc2)); + if (pDrv->LineIn.pMixStrm) + { + AudioMixerSinkRemoveStream(pThisCC->pSinkLineIn, pDrv->LineIn.pMixStrm); + AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->LineIn.pMixStrm = NULL; + } - DEVAC97_UNLOCK(pDevIns, pThis); + if (pDrv->Out.pMixStrm) + { + AudioMixerSinkRemoveStream(pThisCC->pSinkOut, pDrv->Out.pMixStrm); + AudioMixerStreamDestroy(pDrv->Out.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->Out.pMixStrm = NULL; + } - return VINF_SUCCESS; + RTListNodeRemove(&pDrv->Node); } + /** * @interface_method_impl{PDMDEVREG,pfnDetach} */ @@ -4044,47 +4305,33 @@ { PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE); PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3); + RT_NOREF(fFlags); LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags)); DEVAC97_LOCK(pDevIns, pThis); - PAC97DRIVER pDrv, pDrvNext; - RTListForEachSafe(&pThisCC->lstDrv, pDrv, pDrvNext, AC97DRIVER, Node) + PAC97DRIVER pDrv; + RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node) { if (pDrv->uLUN == iLUN) { - int rc2 = ichac97R3DetachInternal(pThisCC, pDrv, fFlags); - if (RT_SUCCESS(rc2)) - { - RTStrFree(pDrv->pszDesc); - RTMemFree(pDrv); - pDrv = NULL; - } + /* Remove the driver from our list and destory it's associated streams. + This also will un-set the driver as a recording source (if associated). */ + ichac97R3MixerRemoveDrv(pDevIns, pThisCC, pDrv); + LogFunc(("Detached LUN#%u\n", pDrv->uLUN)); - break; + DEVAC97_UNLOCK(pDevIns, pThis); + + RTMemFree(pDrv); + return; } } DEVAC97_UNLOCK(pDevIns, pThis); + LogFunc(("LUN#%u was not found\n", iLUN)); } -/** - * Replaces a driver with a the NullAudio drivers. - * - * @returns VBox status code. - * @param pDevIns The device instance. - * @param pThisCC The ring-3 AC'97 device state. - * @param iLun The logical unit which is being replaced. - */ -static int ichac97R3ReconfigLunWithNullAudio(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, unsigned iLun) -{ - int rc = PDMDevHlpDriverReconfigure2(pDevIns, iLun, "AUDIO", "NullAudio"); - if (RT_SUCCESS(rc)) - rc = ichac97R3AttachInternal(pDevIns, pThisCC, iLun, 0 /* fFlags */, NULL /* ppDrv */); - LogFunc(("pThisCC=%p, iLun=%u, rc=%Rrc\n", pThisCC, iLun, rc)); - return rc; -} /** * @interface_method_impl{PDMDEVREG,pfnDestruct} @@ -4100,16 +4347,23 @@ RTListForEachSafe(&pThisCC->lstDrv, pDrv, pDrvNext, AC97DRIVER, Node) { RTListNodeRemove(&pDrv->Node); - RTMemFree(pDrv->pszDesc); RTMemFree(pDrv); } /* Sanity. */ Assert(RTListIsEmpty(&pThisCC->lstDrv)); + /* We don't always go via PowerOff, so make sure the mixer is destroyed. */ + if (pThisCC->pMixer) + { + AudioMixerDestroy(pThisCC->pMixer, pDevIns); + pThisCC->pMixer = NULL; + } + return VINF_SUCCESS; } + /** * @interface_method_impl{PDMDEVREG,pfnConstruct} */ @@ -4131,28 +4385,50 @@ /* * Validate and read configuration. */ - PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Codec|TimerHz|DebugEnabled|DebugPathOut", ""); + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "BufSizeInMs|BufSizeOutMs|Codec|TimerHz|DebugEnabled|DebugPathOut", ""); - char szCodec[20]; - int rc = pHlp->pfnCFGMQueryStringDef(pCfg, "Codec", &szCodec[0], sizeof(szCodec), "STAC9700"); + /** @devcfgm{ac97,BufSizeInMs,uint16_t,0,2000,0,ms} + * The size of the DMA buffer for input streams expressed in milliseconds. */ + int rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeInMs", &pThis->cMsCircBufIn, 0); if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, - N_("AC'97 configuration error: Querying \"Codec\" as string failed")); - - rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, AC97_TIMER_HZ_DEFAULT /* Default value, if not set. */); + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AC97 configuration error: failed to read 'BufSizeInMs' as 16-bit unsigned integer")); + if (pThis->cMsCircBufIn > 2000) + return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE, + N_("AC97 configuration error: 'BufSizeInMs' is out of bound, max 2000 ms")); + + /** @devcfgm{ac97,BufSizeOutMs,uint16_t,0,2000,0,ms} + * The size of the DMA buffer for output streams expressed in milliseconds. */ + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeOutMs", &pThis->cMsCircBufOut, 0); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AC97 configuration error: failed to read 'BufSizeOutMs' as 16-bit unsigned integer")); + if (pThis->cMsCircBufOut > 2000) + return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE, + N_("AC97 configuration error: 'BufSizeOutMs' is out of bound, max 2000 ms")); + + /** @devcfgm{ac97,TimerHz,uint16_t,10,1000,100,ms} + * Currently the approximate rate at which the asynchronous I/O threads move + * data from/to the DMA buffer, thru the mixer and drivers stack, and + * to/from the host device/whatever. (It does NOT govern any DMA timer rate any + * more as might be hinted at by the name.) */ + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, AC97_TIMER_HZ_DEFAULT); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, - N_("AC'97 configuration error: failed to read Hertz (Hz) rate as unsigned integer")); + N_("AC'97 configuration error: failed to read 'TimerHz' as a 16-bit unsigned integer")); + if (pThis->uTimerHz < 10 || pThis->uTimerHz > 1000) + return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE, + N_("AC'97 configuration error: 'TimerHz' is out of range (10-1000 Hz)")); if (pThis->uTimerHz != AC97_TIMER_HZ_DEFAULT) - LogRel(("AC97: Using custom device timer rate (%RU16Hz)\n", pThis->uTimerHz)); + LogRel(("AC97: Using custom device timer rate: %RU16 Hz\n", pThis->uTimerHz)); rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThisCC->Dbg.fEnabled, false); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("AC97 configuration error: failed to read debugging enabled flag as boolean")); - rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThisCC->Dbg.pszOutPath, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH); + rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThisCC->Dbg.pszOutPath, NULL); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("AC97 configuration error: failed to read debugging output path flag as string")); @@ -4165,6 +4441,11 @@ * in the Linux kernel; Linux makes no attempt to measure the data rate and assumes * 48 kHz rate, which is exactly what we need. Same goes for AD1981B. */ + char szCodec[20]; + rc = pHlp->pfnCFGMQueryStringDef(pCfg, "Codec", &szCodec[0], sizeof(szCodec), "STAC9700"); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, + N_("AC'97 configuration error: Querying \"Codec\" as string failed")); if (!strcmp(szCodec, "STAC9700")) pThis->enmCodecModel = AC97CODEC_STAC9700; else if (!strcmp(szCodec, "AD1980")) @@ -4249,10 +4530,6 @@ if (RT_FAILURE(rc)) return rc; -# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO - LogRel(("AC97: Asynchronous I/O enabled\n")); -# endif - /* * Attach drivers. We ASSUME they are configured consecutively without any * gaps, so we stop when we hit the first LUN w/o a driver configured. @@ -4261,30 +4538,30 @@ { AssertBreak(iLun < UINT8_MAX); LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun)); - rc = ichac97R3AttachInternal(pDevIns, pThisCC, iLun, 0 /* fFlags */, NULL /* ppDrv */); + rc = ichac97R3AttachInternal(pDevIns, pThisCC, iLun, NULL /* ppDrv */); if (rc == VERR_PDM_NO_ATTACHED_DRIVER) { LogFunc(("cLUNs=%u\n", iLun)); break; } - if (rc == VERR_AUDIO_BACKEND_INIT_FAILED) - { - ichac97R3ReconfigLunWithNullAudio(pDevIns, pThisCC, iLun); /* Pretend attaching to the NULL audio backend will never fail. */ - PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", - N_("Host audio backend initialization has failed. " - "Selecting the NULL audio backend with the consequence that no sound is audible")); - } - else - AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc); + AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc); } + uint32_t fMixer = AUDMIXER_FLAGS_NONE; + if (pThisCC->Dbg.fEnabled) + fMixer |= AUDMIXER_FLAGS_DEBUG; + rc = AudioMixerCreate("AC'97 Mixer", 0 /* uFlags */, &pThisCC->pMixer); AssertRCReturn(rc, rc); - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Recording] Line In", AUDMIXSINKDIR_INPUT, &pThisCC->pSinkLineIn); + + rc = AudioMixerCreateSink(pThisCC->pMixer, "Line In", + PDMAUDIODIR_IN, pDevIns, &pThisCC->pSinkLineIn); AssertRCReturn(rc, rc); - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Recording] Microphone In", AUDMIXSINKDIR_INPUT, &pThisCC->pSinkMicIn); + rc = AudioMixerCreateSink(pThisCC->pMixer, "Microphone In", + PDMAUDIODIR_IN, pDevIns, &pThisCC->pSinkMicIn); AssertRCReturn(rc, rc); - rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] PCM Output", AUDMIXSINKDIR_OUTPUT, &pThisCC->pSinkOut); + rc = AudioMixerCreateSink(pThisCC->pMixer, "PCM Output", + PDMAUDIODIR_OUT, pDevIns, &pThisCC->pSinkOut); AssertRCReturn(rc, rc); /* @@ -4293,7 +4570,7 @@ AssertCompile(RT_ELEMENTS(pThis->aStreams) == AC97_MAX_STREAMS); for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) { - rc = ichac97R3StreamCreate(pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i], i /* SD# */); + rc = ichac97R3StreamConstruct(pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i], i /* SD# */); AssertRCReturn(rc, rc); } @@ -4319,116 +4596,87 @@ AssertRCReturn(rc, rc); } - -# ifdef VBOX_WITH_AUDIO_AC97_ONETIME_INIT - PAC97DRIVER pDrv; - RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node) - { - /* - * Only primary drivers are critical for the VM to run. Everything else - * might not worth showing an own error message box in the GUI. - */ - if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY)) - continue; - - PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector; - AssertPtr(pCon); - - bool fValidLineIn = AudioMixerStreamIsValid(pDrv->LineIn.pMixStrm); - bool fValidMicIn = AudioMixerStreamIsValid(pDrv->MicIn.pMixStrm); - bool fValidOut = AudioMixerStreamIsValid(pDrv->Out.pMixStrm); - - if ( !fValidLineIn - && !fValidMicIn - && !fValidOut) - { - LogRel(("AC97: Falling back to NULL backend (no sound audible)\n")); - ichac97R3Reset(pDevIns); - ichac97R3ReconfigLunWithNullAudio(pdEvIns, pThsiCC, iLun); - PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", - N_("No audio devices could be opened. " - "Selecting the NULL audio backend with the consequence that no sound is audible")); - } - else - { - bool fWarn = false; - - PDMAUDIOBACKENDCFG backendCfg; - int rc2 = pCon->pfnGetConfig(pCon, &backendCfg); - if (RT_SUCCESS(rc2)) - { - if (backendCfg.cMaxStreamsIn) - { - /* If the audio backend supports two or more input streams at once, - * warn if one of our two inputs (microphone-in and line-in) failed to initialize. */ - if (backendCfg.cMaxStreamsIn >= 2) - fWarn = !fValidLineIn || !fValidMicIn; - /* If the audio backend only supports one input stream at once (e.g. pure ALSA, and - * *not* ALSA via PulseAudio plugin!), only warn if both of our inputs failed to initialize. - * One of the two simply is not in use then. */ - else if (backendCfg.cMaxStreamsIn == 1) - fWarn = !fValidLineIn && !fValidMicIn; - /* Don't warn if our backend is not able of supporting any input streams at all. */ - } - - if ( !fWarn - && backendCfg.cMaxStreamsOut) - { - fWarn = !fValidOut; - } - } - else - { - LogRel(("AC97: Unable to retrieve audio backend configuration for LUN #%RU8, rc=%Rrc\n", pDrv->uLUN, rc2)); - fWarn = true; - } - - if (fWarn) - { - char szMissingStreams[255] = ""; - size_t len = 0; - if (!fValidLineIn) - { - LogRel(("AC97: WARNING: Unable to open PCM line input for LUN #%RU8!\n", pDrv->uLUN)); - len = RTStrPrintf(szMissingStreams, sizeof(szMissingStreams), "PCM Input"); - } - if (!fValidMicIn) - { - LogRel(("AC97: WARNING: Unable to open PCM microphone input for LUN #%RU8!\n", pDrv->uLUN)); - len += RTStrPrintf(szMissingStreams + len, - sizeof(szMissingStreams) - len, len ? ", PCM Microphone" : "PCM Microphone"); - } - if (!fValidOut) - { - LogRel(("AC97: WARNING: Unable to open PCM output for LUN #%RU8!\n", pDrv->uLUN)); - len += RTStrPrintf(szMissingStreams + len, - sizeof(szMissingStreams) - len, len ? ", PCM Output" : "PCM Output"); - } - - PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", - N_("Some AC'97 audio streams (%s) could not be opened. Guest applications generating audio " - "output or depending on audio input may hang. Make sure your host audio device " - "is working properly. Check the logfile for error messages of the audio " - "subsystem"), szMissingStreams); - } - } - } -# endif /* VBOX_WITH_AUDIO_AC97_ONETIME_INIT */ - ichac97R3Reset(pDevIns); /* + * Info items. + */ + //PDMDevHlpDBGFInfoRegister(pDevIns, "ac97", "AC'97 registers. (ac97 [register case-insensitive])", ichac97R3DbgInfo); + PDMDevHlpDBGFInfoRegister(pDevIns, "ac97bdl", "AC'97 buffer descriptor list (BDL). (ac97bdl [stream number])", + ichac97R3DbgInfoBDL); + PDMDevHlpDBGFInfoRegister(pDevIns, "ac97stream", "AC'97 stream info. (ac97stream [stream number])", ichac97R3DbgInfoStream); + PDMDevHlpDBGFInfoRegister(pDevIns, "ac97mixer", "AC'97 mixer state.", ichac97R3DbgInfoMixer); + + /* * Register statistics. */ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNabmReads, STAMTYPE_COUNTER, "UnimplementedNabmReads", STAMUNIT_OCCURENCES, "Unimplemented NABM register reads."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNabmWrites, STAMTYPE_COUNTER, "UnimplementedNabmWrites", STAMUNIT_OCCURENCES, "Unimplemented NABM register writes."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNamReads, STAMTYPE_COUNTER, "UnimplementedNamReads", STAMUNIT_OCCURENCES, "Unimplemented NAM register reads."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNamWrites, STAMTYPE_COUNTER, "UnimplementedNamWrites", STAMUNIT_OCCURENCES, "Unimplemented NAM register writes."); # ifdef VBOX_WITH_STATISTICS PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimer, STAMTYPE_PROFILE, "Timer", STAMUNIT_TICKS_PER_CALL, "Profiling ichac97Timer."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "Input", STAMUNIT_TICKS_PER_CALL, "Profiling input."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "Output", STAMUNIT_TICKS_PER_CALL, "Profiling output."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead" , STAMUNIT_BYTES, "Bytes read from AC97 emulation."); - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "BytesWritten", STAMUNIT_BYTES, "Bytes written to AC97 emulation."); # endif + for (unsigned idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++) + { + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].cbDmaPeriod, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Bytes to transfer in the current DMA period.", "Stream%u/cbTransferChunk", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].Regs.cr, STAMTYPE_X8, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, + "Control register (CR), bit 0 is the run bit.", "Stream%u/reg-CR", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].Regs.sr, STAMTYPE_X16, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, + "Status register (SR).", "Stream%u/reg-SR", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_HZ, + "The stream frequency.", "Stream%u/Hz", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Cfg.Props.cbFrame, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "The frame size.", "Stream%u/FrameSize", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Virtual internal buffer read position.", "Stream%u/offRead", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowProblems, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Number of internal DMA buffer problems.", "Stream%u/DMABufferProblems", idxStream); + if (ichac97R3GetDirFromSD(idxStream) == PDMAUDIODIR_OUT) + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Number of internal DMA buffer overflows.", "Stream%u/DMABufferOverflows", idxStream); + else + { + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Number of internal DMA buffer underuns.", "Stream%u/DMABufferUnderruns", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrorBytes, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Number of bytes of silence added to cope with underruns.", "Stream%u/DMABufferSilence", idxStream); + } + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaSkippedDch, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "DMA transfer period skipped, controller halted (DCH).", "Stream%u/DMASkippedDch", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaSkippedPendingBcis, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "DMA transfer period skipped because of BCIS pending.", "Stream%u/DMASkippedPendingBCIS", idxStream); + + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStart, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL, + "Starting the stream.", "Stream%u/Start", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStop, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL, + "Stopping the stream.", "Stream%u/Stop", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReset, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL, + "Resetting the stream.", "Stream%u/Reset", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReSetUpChanged, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL, + "ichac97R3StreamReSetUp when recreating the streams.", "Stream%u/ReSetUp-Change", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReSetUpSame, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL, + "ichac97R3StreamReSetUp when no change.", "Stream%u/ReSetUp-NoChange", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatWriteCr, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "CR register writes.", "Stream%u/WriteCr", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatWriteLviRecover, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "LVI register writes recovering from underflow.", "Stream%u/WriteLviRecover", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteLvi, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "LVI register writes (non-recoving).", "Stream%u/WriteLvi", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteSr1, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "SR register 1-byte writes.", "Stream%u/WriteSr-1byte", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteSr2, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "SR register 2-byte writes.", "Stream%u/WriteSr-2byte", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteBdBar, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "BDBAR register writes.", "Stream%u/WriteBdBar", idxStream); + } LogFlowFuncLeaveRC(VINF_SUCCESS); return VINF_SUCCESS; @@ -4465,7 +4713,8 @@ /* .u32Version = */ PDM_DEVREG_VERSION, /* .uReserved0 = */ 0, /* .szName = */ "ichac97", - /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE + | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */, /* .fClass = */ PDM_DEVREG_CLASS_AUDIO, /* .cMaxInstances = */ 1, /* .uSharedVersion = */ 42, diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevSB16.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevSB16.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DevSB16.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DevSB16.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2015-2020 Oracle Corporation + * Copyright (C) 2015-2021 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -55,18 +55,27 @@ #include #include +#include #include #include "VBoxDD.h" #include "AudioMixBuffer.h" #include "AudioMixer.h" -#include "DrvAudio.h" +#include "AudioHlp.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ +/** Default timer frequency (in Hz). */ +#define SB16_TIMER_HZ_DEFAULT 100 +/** The maximum number of separate streams we currently implement. + * Currently we only support one stream only, namely the output stream. */ +#define SB16_MAX_STREAMS 1 +/** The (zero-based) index of the output stream in \a aStreams. */ +#define SB16_IDX_OUT 0 + /** Current saved state version. */ #define SB16_SAVE_STATE_VERSION 2 /** The version used in VirtualBox version 3.0 and earlier. This didn't include the config dump. */ @@ -87,14 +96,38 @@ typedef struct SB16STATE *PSB16STATE; /** + * The internal state of a SB16 stream. + */ +typedef struct SB16STREAMSTATE +{ + /** Flag indicating whether this stream is in enabled state or not. */ + bool fEnabled; + /** Set if we've registered the asynchronous update job. */ + bool fRegisteredAsyncUpdateJob; + /** DMA cache to read data from / write data to. */ + PRTCIRCBUF pCircBuf; + /** Current circular buffer read offset (for tracing & logging). */ + uint64_t offRead; + /** Current circular buffer write offset (for tracing & logging). */ + uint64_t offWrite; + + /** Size of the DMA buffer (pCircBuf) in bytes. */ + uint32_t StatDmaBufSize; + /** Number of used bytes in the DMA buffer (pCircBuf). */ + uint32_t StatDmaBufUsed; +} SB16STREAMSTATE; +/** Pointer to internal state of an SB16 stream. */ +typedef SB16STREAMSTATE *PSB16STREAMSTATE; + +/** * Structure defining a (host backend) driver stream. * Each driver has its own instances of audio mixer streams, which then * can go into the same (or even different) audio mixer sinks. */ typedef struct SB16DRIVERSTREAM { - /** Associated PDM audio stream. */ - R3PTRTYPE(PPDMAUDIOSTREAM) pStream; + /** Associated mixer stream handle. */ + R3PTRTYPE(PAUDMIXSTREAM) pMixStrm; /** The stream's current configuration. */ } SB16DRIVERSTREAM, *PSB16DRIVERSTREAM; @@ -113,232 +146,277 @@ R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector; /** Stream for output. */ SB16DRIVERSTREAM Out; - /** Driver flags. */ - PDMAUDIODRVFLAGS fFlags; /** LUN # to which this driver has been assigned. */ uint8_t uLUN; /** Whether this driver is in an attached state or not. */ bool fAttached; /** The LUN description. */ - char szDesc[2+48]; + char szDesc[48 - 2]; } SB16DRIVER; /** Pointer to the per-LUN data. */ typedef SB16DRIVER *PSB16DRIVER; /** + * Runtime configurable debug stuff for a SB16 stream. + */ +typedef struct SB16STREAMDEBUGRT +{ + /** Whether debugging is enabled or not. */ + bool fEnabled; + uint8_t Padding[7]; + /** File for dumping DMA reads / writes. + * For input streams, this dumps data being written to the device DMA, + * whereas for output streams this dumps data being read from the device DMA. */ + R3PTRTYPE(PAUDIOHLPFILE) pFileDMA; +} SB16STREAMDEBUGRT; + +/** + * Debug stuff for a SB16 stream. + */ +typedef struct SB16STREAMDEBUG +{ + /** Runtime debug stuff. */ + SB16STREAMDEBUGRT Runtime; +} SB16STREAMDEBUG; + +/** + * Structure for keeping a SB16 hardware stream configuration. + */ +typedef struct SB16STREAMHWCFG +{ + /** IRQ # to use. */ + uint8_t uIrq; + /** Low DMA channel to use. */ + uint8_t uDmaChanLow; + /** High DMA channel to use. */ + uint8_t uDmaChanHigh; + /** IO port to use. */ + RTIOPORT uPort; + /** DSP version to expose. */ + uint16_t uVer; +} SB16STREAMHWCFG; + +/** * Structure for a SB16 stream. */ typedef struct SB16STREAM { - /** The stream's current configuration. */ - PDMAUDIOSTREAMCFG Cfg; + /** The stream's own index in \a aStreams of SB16STATE. + * Set to UINT8_MAX if not set (yet). */ + uint8_t uIdx; + uint16_t uTimerHz; + /** The timer for pumping data thru the attached LUN drivers. */ + TMTIMERHANDLE hTimerIO; + /** The timer interval for pumping data thru the LUN drivers in timer ticks. */ + uint64_t cTicksTimerIOInterval; + /** Timestamp of the last timer callback (sb16TimerIO). + * Used to calculate thetime actually elapsed between two timer callbacks. + * This currently ASSMUMES that we only have one single (output) stream. */ + uint64_t tsTimerIO; /** @todo Make this a per-stream value. */ + /** The stream's currentconfiguration. */ + PDMAUDIOSTREAMCFG Cfg; + /** The stream's defaulthardware configuration, mostly done by jumper settings back then. */ + SB16STREAMHWCFG HwCfgDefault; + /** The stream's hardware configuration set at runtime. + * Might differ from the default configuration above and is needed for live migration. */ + SB16STREAMHWCFG HwCfgRuntime; + + int fifo; + int dma_auto; + /** Whether to use the high (\c true) or the low (\c false) DMA channel. */ + int fDmaUseHigh; + int can_write; /** @todo r=andy BUGBUG Value never gets set to 0! */ + int time_const; + /** The DMA transfer (block)size in bytes. */ + int32_t cbDmaBlockSize; + int32_t cbDmaLeft; /** Note: Can be < 0. Needs to 32-bit for backwards compatibility. */ + /** Internal state of this stream. */ + SB16STREAMSTATE State; + /** Debug stuff. */ + SB16STREAMDEBUG Dbg; } SB16STREAM; /** Pointer to a SB16 stream */ typedef SB16STREAM *PSB16STREAM; /** + * SB16 debug settings. + */ +typedef struct SB16STATEDEBUG +{ + /** Whether debugging is enabled or not. */ + bool fEnabled; + bool afAlignment[7]; + /** Path where to dump the debug output to. + * Can be NULL, in which the system's temporary directory will be used then. */ + R3PTRTYPE(char *) pszOutPath; +} SB16STATEDEBUG; + +/** * The SB16 state. */ typedef struct SB16STATE { -#ifdef VBOX /** Pointer to the device instance. */ - PPDMDEVINSR3 pDevInsR3; + PPDMDEVINSR3 pDevInsR3; /** Pointer to the connector of the attached audio driver. */ - PPDMIAUDIOCONNECTOR pDrv; - int irqCfg; - int dmaCfg; - int hdmaCfg; - int portCfg; - int verCfg; -#endif - int irq; - int dma; - int hdma; - int port; - int ver; - - int in_index; - int out_data_len; - int fmt_stereo; - int fmt_signed; - int fmt_bits; - PDMAUDIOFMT fmt; - int dma_auto; - int block_size; - int fifo; - int freq; - int time_const; - int speaker; - int needed_bytes; - int cmd; - int use_hdma; - int highspeed; - int can_write; /** @todo Value never gets set to 0! */ - - int v2x6; - - uint8_t csp_param; - uint8_t csp_value; - uint8_t csp_mode; - uint8_t csp_index; - uint8_t csp_regs[256]; - uint8_t csp_reg83[4]; - int csp_reg83r; - int csp_reg83w; - - uint8_t in2_data[10]; - uint8_t out_data[50]; - uint8_t test_reg; - uint8_t last_read_byte; - int nzero; - - int left_till_irq; /** Note: Can be < 0. */ - - int dma_running; - int bytes_per_second; - int align; + PPDMIAUDIOCONNECTOR pDrv; + + int dsp_in_idx; + int dsp_out_data_len; + int dsp_in_needed_bytes; + int cmd; + int highspeed; + + int v2x6; + + uint8_t csp_param; + uint8_t csp_value; + uint8_t csp_mode; + uint8_t csp_index; + uint8_t csp_regs[256]; + uint8_t csp_reg83[4]; + int csp_reg83r; + int csp_reg83w; + + uint8_t dsp_in_data[10]; + uint8_t dsp_out_data[50]; + uint8_t test_reg; + uint8_t last_read_byte; + int nzero; - RTLISTANCHOR lstDrv; + RTLISTANCHOR lstDrv; /** IRQ timer */ - TMTIMERHANDLE hTimerIRQ; + TMTIMERHANDLE hTimerIRQ; /** The base interface for LUN\#0. */ - PDMIBASE IBase; - /** Output stream. */ - SB16STREAM Out; + PDMIBASE IBase; - /** The timer for pumping data thru the attached LUN drivers. */ - TMTIMERHANDLE hTimerIO; - /** The timer interval for pumping data thru the LUN drivers in timer ticks. */ - uint64_t cTicksTimerIOInterval; - /** Timestamp of the last timer callback (sb16TimerIO). - * Used to calculate the time actually elapsed between two timer callbacks. */ - uint64_t tsTimerIO; - /** Number of active (running) SDn streams. */ - uint8_t cStreamsActive; - /** Flag indicating whether the timer is active or not. */ - bool volatile fTimerActive; - uint8_t u8Padding1[5]; + /** Array of all SB16 hardware audio stream. */ + SB16STREAM aStreams[SB16_MAX_STREAMS]; + /** The device's software mixer. */ + R3PTRTYPE(PAUDIOMIXER) pMixer; + /** Audio sink for PCM output. */ + R3PTRTYPE(PAUDMIXSINK) pSinkOut; /** The two mixer I/O ports (port + 4). */ - IOMIOPORTHANDLE hIoPortsMixer; + IOMIOPORTHANDLE hIoPortsMixer; /** The 10 DSP I/O ports (port + 6). */ - IOMIOPORTHANDLE hIoPortsDsp; + IOMIOPORTHANDLE hIoPortsDsp; + + /** Debug settings. */ + SB16STATEDEBUG Dbg; /* mixer state */ - uint8_t mixer_nreg; - uint8_t mixer_regs[256]; + uint8_t mixer_nreg; + uint8_t mixer_regs[256]; + +#ifdef VBOX_WITH_STATISTICS + STAMPROFILE StatTimerIO; + STAMCOUNTER StatBytesRead; +#endif } SB16STATE; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ -static int sb16CheckAndReOpenOut(PPDMDEVINS pDevIns, PSB16STATE pThis); -static int sb16OpenOut(PPDMDEVINS pDevIns, PSB16STATE pThis, PPDMAUDIOSTREAMCFG pCfg); -static void sb16CloseOut(PSB16STATE pThis); -static void sb16TimerMaybeStart(PPDMDEVINS pDevIns, PSB16STATE pThis); -static void sb16TimerMaybeStop(PSB16STATE pThis); - - -#if 0 // unused // def DEBUG -DECLINLINE(void) log_dsp(PSB16STATE pThis) -{ - LogFlowFunc(("%s:%s:%d:%s:dmasize=%d:freq=%d:const=%d:speaker=%d\n", - pThis->fmt_stereo ? "Stereo" : "Mono", - pThis->fmt_signed ? "Signed" : "Unsigned", - pThis->fmt_bits, - pThis->dma_auto ? "Auto" : "Single", - pThis->block_size, - pThis->freq, - pThis->time_const, - pThis->speaker)); -} -#endif +DECLINLINE(PDMAUDIODIR) sb16GetDirFromIndex(uint8_t uIdx); + +static int sb16StreamEnable(PSB16STATE pThis, PSB16STREAM pStream, bool fEnable, bool fForce); +static void sb16StreamReset(PSB16STATE pThis, PSB16STREAM pStream); +static int sb16StreamOpen(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream); +static void sb16StreamClose(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream); +DECLINLINE(PAUDMIXSINK) sb16StreamIndexToSink(PSB16STATE pThis, uint8_t uIdx); +static void sb16StreamTransferScheduleNext(PSB16STATE pThis, PSB16STREAM pStream, uint32_t cSamples); +static int sb16StreamDoDmaOutput(PSB16STATE pThis, PSB16STREAM pStream, int uDmaChan, uint32_t offDma, uint32_t cbDma, uint32_t cbToRead, uint32_t *pcbRead); +static DECLCALLBACK(void) sb16StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser); + +static DECLCALLBACK(void) sb16TimerIO(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser); +static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser); +DECLINLINE(void) sb16TimerSet(PPDMDEVINS pDevIns, PSB16STREAM pStream, uint64_t cTicksToDeadline); + +static void sb16SpeakerControl(PSB16STATE pThis, bool fOn); +static void sb16UpdateVolume(PSB16STATE pThis); + -static void sb16SpeakerControl(PSB16STATE pThis, int on) + +static void sb16SpeakerControl(PSB16STATE pThis, bool fOn) { - pThis->speaker = on; - /* AUD_enable (pThis->voice, on); */ + RT_NOREF(pThis, fOn); + + /** @todo This currently does nothing. */ } -static void sb16Control(PPDMDEVINS pDevIns, PSB16STATE pThis, int hold) +static void sb16StreamControl(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream, bool fRun) { - int dma = pThis->use_hdma ? pThis->hdma : pThis->dma; - pThis->dma_running = hold; + unsigned uDmaChan = pStream->fDmaUseHigh ? pStream->HwCfgRuntime.uDmaChanHigh : pStream->HwCfgRuntime.uDmaChanLow; - LogFlowFunc(("hold %d high %d dma %d\n", hold, pThis->use_hdma, dma)); + LogFunc(("fRun=%RTbool, fDmaUseHigh=%RTbool, uDmaChan=%u\n", fRun, pStream->fDmaUseHigh, uDmaChan)); - PDMDevHlpDMASetDREQ(pThis->pDevInsR3, dma, hold); + PDMDevHlpDMASetDREQ(pThis->pDevInsR3, uDmaChan, fRun ? 1 : 0); - PSB16DRIVER pDrv; - RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + if (fRun != pStream->State.fEnabled) { - if (!pDrv->Out.pStream) - continue; - - int rc2 = pDrv->pConnector->pfnStreamControl(pDrv->pConnector, pDrv->Out.pStream, - hold == 1 ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE); - LogFlowFunc(("%s: rc=%Rrc\n", pDrv->Out.pStream->szName, rc2)); NOREF(rc2); - } + if (fRun) + { + int rc = VINF_SUCCESS; - if (hold) - { - pThis->cStreamsActive++; - sb16TimerMaybeStart(pDevIns, pThis); - PDMDevHlpDMASchedule(pThis->pDevInsR3); - } - else - { - if (pThis->cStreamsActive) - pThis->cStreamsActive--; - sb16TimerMaybeStop(pThis); - } -} + if (pStream->Cfg.Props.uHz > 0) + { + rc = sb16StreamOpen(pDevIns, pThis, pStream); + if (RT_SUCCESS(rc)) + sb16UpdateVolume(pThis); + } + else + AssertFailed(); /** @todo Buggy code? */ -/** - * @callback_method_impl{PFNTMTIMERDEV} - */ -static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) -{ - PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); - RT_NOREF(pvUser, pTimer); + if (RT_SUCCESS(rc)) + { + rc = sb16StreamEnable(pThis, pStream, true /* fEnable */, false /* fForce */); + if (RT_SUCCESS(rc)) + { + sb16TimerSet(pDevIns, pStream, pStream->cTicksTimerIOInterval); - pThis->can_write = 1; - PDMDevHlpISASetIrq(pDevIns, pThis->irq, 1); + PDMDevHlpDMASchedule(pThis->pDevInsR3); + } + } + } + else + { + sb16StreamEnable(pThis, pStream, false /* fEnable */, false /* fForce */); + } + } } #define DMA8_AUTO 1 #define DMA8_HIGH 2 -static void continue_dma8(PPDMDEVINS pDevIns, PSB16STATE pThis) +static void sb16DmaCmdContinue8(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream) { - sb16CheckAndReOpenOut(pDevIns, pThis); - sb16Control(pDevIns, pThis, 1); + sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */); } -static void dma_cmd8(PPDMDEVINS pDevIns, PSB16STATE pThis, int mask, int dma_len) +static void sb16DmaCmd8(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream, + int mask, int dma_len) { - pThis->fmt = PDMAUDIOFMT_U8; - pThis->use_hdma = 0; - pThis->fmt_bits = 8; - pThis->fmt_signed = 0; - pThis->fmt_stereo = (pThis->mixer_regs[0x0e] & 2) != 0; + pStream->fDmaUseHigh = 0; - if (-1 == pThis->time_const) + if (-1 == pStream->time_const) { - if (pThis->freq <= 0) - pThis->freq = 11025; + if (pStream->Cfg.Props.uHz == 0) + pStream->Cfg.Props.uHz = 11025; } else { - int tmp = (256 - pThis->time_const); - pThis->freq = (1000000 + (tmp / 2)) / tmp; + int tmp = (256 - pStream->time_const); + pStream->Cfg.Props.uHz = (1000000 + (tmp / 2)) / tmp; } + /** @todo r=bird: Use '(pThis->mixer_regs[0x0e] & 2) == 0 ? 1 : 2' like below? */ + unsigned cShiftChannels = PDMAudioPropsChannels(&pStream->Cfg.Props) >= 2 ? 1 : 0; + if (dma_len != -1) { - pThis->block_size = dma_len << pThis->fmt_stereo; + pStream->cbDmaBlockSize = dma_len << cShiftChannels; } else { @@ -351,61 +429,65 @@ SR does the same with even number Both use stereo, and Creatives own documentation states that 0x48 sets block size in bytes less one.. go figure */ - pThis->block_size &= ~pThis->fmt_stereo; + pStream->cbDmaBlockSize &= ~cShiftChannels; } - pThis->freq >>= pThis->fmt_stereo; - pThis->left_till_irq = pThis->block_size; - pThis->bytes_per_second = (pThis->freq << pThis->fmt_stereo); + pStream->Cfg.Props.uHz >>= cShiftChannels; + pStream->cbDmaLeft = pStream->cbDmaBlockSize; /* pThis->highspeed = (mask & DMA8_HIGH) != 0; */ - pThis->dma_auto = (mask & DMA8_AUTO) != 0; - pThis->align = (1 << pThis->fmt_stereo) - 1; + pStream->dma_auto = (mask & DMA8_AUTO) != 0; - if (pThis->block_size & pThis->align) - LogFlowFunc(("warning: misaligned block size %d, alignment %d\n", pThis->block_size, pThis->align + 1)); + PDMAudioPropsInit(&pStream->Cfg.Props, 1 /* 8-bit */, + false /* fSigned */, + (pThis->mixer_regs[0x0e] & 2) == 0 ? 1 : 2 /* Mono/Stereo */, + pStream->Cfg.Props.uHz); - LogFlowFunc(("freq %d, stereo %d, sign %d, bits %d, dma %d, auto %d, fifo %d, high %d\n", - pThis->freq, pThis->fmt_stereo, pThis->fmt_signed, pThis->fmt_bits, - pThis->block_size, pThis->dma_auto, pThis->fifo, pThis->highspeed)); + /** @todo Check if stream's DMA block size is properly aligned to the set PCM props. */ - continue_dma8(pDevIns, pThis); + sb16DmaCmdContinue8(pDevIns, pThis, pStream); sb16SpeakerControl(pThis, 1); } -static void dma_cmd(PPDMDEVINS pDevIns, PSB16STATE pThis, uint8_t cmd, uint8_t d0, int dma_len) +static void sb16DmaCmd(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream, + uint8_t cmd, uint8_t d0, int dma_len) { - pThis->use_hdma = cmd < 0xc0; - pThis->fifo = (cmd >> 1) & 1; - pThis->dma_auto = (cmd >> 2) & 1; - pThis->fmt_signed = (d0 >> 4) & 1; - pThis->fmt_stereo = (d0 >> 5) & 1; + pStream->fDmaUseHigh = cmd < 0xc0; + pStream->fifo = (cmd >> 1) & 1; + pStream->dma_auto = (cmd >> 2) & 1; + + pStream->Cfg.Props.fSigned = RT_BOOL(d0 & RT_BIT_32(4)); + PDMAudioPropsSetChannels(&pStream->Cfg.Props, 1 + ((d0 >> 5) & 1)); switch (cmd >> 4) { case 11: - pThis->fmt_bits = 16; + PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, 2 /*16-bit*/); break; case 12: - pThis->fmt_bits = 8; + PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, 1 /*8-bit*/); + break; + + default: + AssertFailed(); break; } - if (-1 != pThis->time_const) + if (-1 != pStream->time_const) { #if 1 - int tmp = 256 - pThis->time_const; - pThis->freq = (1000000 + (tmp / 2)) / tmp; + int tmp = 256 - pStream->time_const; + pStream->Cfg.Props.uHz = (1000000 + (tmp / 2)) / tmp; #else - /* pThis->freq = 1000000 / ((255 - pThis->time_const) << pThis->fmt_stereo); */ - pThis->freq = 1000000 / ((255 - pThis->time_const)); + /* pThis->freq = 1000000 / ((255 - pStream->time_const) << pThis->fmt_stereo); */ + pThis->freq = 1000000 / ((255 - pStream->time_const)); #endif - pThis->time_const = -1; + pStream->time_const = -1; } - pThis->block_size = dma_len + 1; - pThis->block_size <<= ((pThis->fmt_bits == 16) ? 1 : 0); - if (!pThis->dma_auto) + pStream->cbDmaBlockSize = dma_len + 1; + pStream->cbDmaBlockSize <<= PDMAudioPropsSampleSize(&pStream->Cfg.Props) == 2 ? 1 : 0; + if (!pStream->dma_auto) { /* * It is clear that for DOOM and auto-init this value @@ -413,52 +495,35 @@ * setsound.exe with single transfer mode wouldn't work without it * wonders of SB16 yet again. */ - pThis->block_size <<= pThis->fmt_stereo; + pStream->cbDmaBlockSize <<= PDMAudioPropsSampleSize(&pStream->Cfg.Props) == 2 ? 1 : 0; } - LogFlowFunc(("freq %d, stereo %d, sign %d, bits %d, dma %d, auto %d, fifo %d, high %d\n", - pThis->freq, pThis->fmt_stereo, pThis->fmt_signed, pThis->fmt_bits, - pThis->block_size, pThis->dma_auto, pThis->fifo, pThis->highspeed)); + pStream->cbDmaLeft = pStream->cbDmaBlockSize; - if (16 == pThis->fmt_bits) - pThis->fmt = pThis->fmt_signed ? PDMAUDIOFMT_S16 : PDMAUDIOFMT_U16; - else - pThis->fmt = pThis->fmt_signed ? PDMAUDIOFMT_S8 : PDMAUDIOFMT_U8; - - pThis->left_till_irq = pThis->block_size; - - pThis->bytes_per_second = (pThis->freq << pThis->fmt_stereo) << ((pThis->fmt_bits == 16) ? 1 : 0); pThis->highspeed = 0; - pThis->align = (1 << (pThis->fmt_stereo + (pThis->fmt_bits == 16))) - 1; - if (pThis->block_size & pThis->align) - { - LogFlowFunc(("warning: misaligned block size %d, alignment %d\n", - pThis->block_size, pThis->align + 1)); - } - sb16CheckAndReOpenOut(pDevIns, pThis); - sb16Control(pDevIns, pThis, 1); + /** @todo Check if stream's DMA block size is properly aligned to the set PCM props. */ + + sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */); sb16SpeakerControl(pThis, 1); } -static inline void dsp_out_data (PSB16STATE pThis, uint8_t val) +static inline void sb16DspSeData(PSB16STATE pThis, uint8_t val) { - LogFlowFunc(("outdata %#x\n", val)); - if ((size_t) pThis->out_data_len < sizeof (pThis->out_data)) { - pThis->out_data[pThis->out_data_len++] = val; - } + LogFlowFunc(("%#x\n", val)); + if ((size_t) pThis->dsp_out_data_len < sizeof (pThis->dsp_out_data)) + pThis->dsp_out_data[pThis->dsp_out_data_len++] = val; } -static inline uint8_t dsp_get_data (PSB16STATE pThis) +static inline uint8_t sb16DspGetData(PSB16STATE pThis) { - if (pThis->in_index) { - return pThis->in2_data[--pThis->in_index]; - } - LogFlowFunc(("buffer underflow\n")); + if (pThis->dsp_in_idx) + return pThis->dsp_in_data[--pThis->dsp_in_idx]; + AssertMsgFailed(("DSP input buffer underflow\n")); return 0; } -static void sb16HandleCommand(PPDMDEVINS pDevIns, PSB16STATE pThis, uint8_t cmd) +static void sb16DspCmdLookup(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream, uint8_t cmd) { LogFlowFunc(("command %#x\n", cmd)); @@ -476,234 +541,236 @@ LogFlowFunc(("%#x wrong bits\n", cmd)); } - pThis->needed_bytes = 3; + pThis->dsp_in_needed_bytes = 3; } else { - pThis->needed_bytes = 0; + pThis->dsp_in_needed_bytes = 0; + + /** @todo Use a mapping table with + * - a command verb (binary search) + * - required bytes + * - function callback handler + */ switch (cmd) { - case 0x03: - dsp_out_data(pThis, 0x10); /* pThis->csp_param); */ + case 0x03: /* ASP Status */ + sb16DspSeData(pThis, 0x10); /* pThis->csp_param); */ goto warn; - case 0x04: - pThis->needed_bytes = 1; + case 0x04: /* DSP Status (Obsolete) / ASP ??? */ + pThis->dsp_in_needed_bytes = 1; goto warn; - case 0x05: - pThis->needed_bytes = 2; + case 0x05: /* ASP ??? */ + pThis->dsp_in_needed_bytes = 2; goto warn; - case 0x08: + case 0x08: /* ??? */ /* __asm__ ("int3"); */ goto warn; - case 0x0e: - pThis->needed_bytes = 2; + case 0x09: /* ??? */ + sb16DspSeData(pThis, 0xf8); goto warn; - case 0x09: - dsp_out_data(pThis, 0xf8); + case 0x0e: /* ??? */ + pThis->dsp_in_needed_bytes = 2; goto warn; - case 0x0f: - pThis->needed_bytes = 1; + case 0x0f: /* ??? */ + pThis->dsp_in_needed_bytes = 1; goto warn; - case 0x10: - pThis->needed_bytes = 1; + case 0x10: /* Direct mode DAC */ + pThis->dsp_in_needed_bytes = 1; goto warn; - case 0x14: - pThis->needed_bytes = 2; - pThis->block_size = 0; + case 0x14: /* DAC DMA, 8-bit, uncompressed */ + pThis->dsp_in_needed_bytes = 2; + pStream->cbDmaBlockSize = 0; break; case 0x1c: /* Auto-Initialize DMA DAC, 8-bit */ - dma_cmd8(pDevIns, pThis, DMA8_AUTO, -1); + sb16DmaCmd8(pDevIns, pThis, pStream, DMA8_AUTO, -1); break; case 0x20: /* Direct ADC, Juice/PL */ - dsp_out_data(pThis, 0xff); + sb16DspSeData(pThis, 0xff); goto warn; - case 0x35: - LogFlowFunc(("0x35 - MIDI command not implemented\n")); + case 0x35: /* MIDI Read Interrupt + Write Poll (UART) */ + LogRelMax2(32, ("SB16: MIDI support not implemented yet\n")); break; - case 0x40: - pThis->freq = -1; - pThis->time_const = -1; - pThis->needed_bytes = 1; + case 0x40: /* Set Time Constant */ + pStream->time_const = -1; + pThis->dsp_in_needed_bytes = 1; break; - case 0x41: - pThis->freq = -1; - pThis->time_const = -1; - pThis->needed_bytes = 2; + case 0x41: /* Set sample rate for input */ + pStream->Cfg.Props.uHz = 0; /** @todo r=andy Why do we reset output stuff here? */ + pStream->time_const = -1; + pThis->dsp_in_needed_bytes = 2; break; - case 0x42: - pThis->freq = -1; - pThis->time_const = -1; - pThis->needed_bytes = 2; + case 0x42: /* Set sample rate for output */ + pStream->Cfg.Props.uHz = 0; + pStream->time_const = -1; + pThis->dsp_in_needed_bytes = 2; goto warn; - case 0x45: - dsp_out_data(pThis, 0xaa); + case 0x45: /* Continue Auto-Initialize DMA, 8-bit */ + sb16DspSeData(pThis, 0xaa); goto warn; - case 0x47: /* Continue Auto-Initialize DMA 16bit */ + case 0x47: /* Continue Auto-Initialize DMA, 16-bit */ break; - case 0x48: - pThis->needed_bytes = 2; + case 0x48: /* Set DMA Block Size */ + pThis->dsp_in_needed_bytes = 2; break; - case 0x74: - pThis->needed_bytes = 2; /* DMA DAC, 4-bit ADPCM */ - LogFlowFunc(("0x75 - DMA DAC, 4-bit ADPCM not implemented\n")); + case 0x74: /* DMA DAC, 4-bit ADPCM */ + pThis->dsp_in_needed_bytes = 2; + LogFlowFunc(("4-bit ADPCM not implemented yet\n")); break; case 0x75: /* DMA DAC, 4-bit ADPCM Reference */ - pThis->needed_bytes = 2; - LogFlowFunc(("0x74 - DMA DAC, 4-bit ADPCM Reference not implemented\n")); + pThis->dsp_in_needed_bytes = 2; + LogFlowFunc(("DMA DAC, 4-bit ADPCM Reference not implemented\n")); break; case 0x76: /* DMA DAC, 2.6-bit ADPCM */ - pThis->needed_bytes = 2; - LogFlowFunc(("0x74 - DMA DAC, 2.6-bit ADPCM not implemented\n")); + pThis->dsp_in_needed_bytes = 2; + LogFlowFunc(("DMA DAC, 2.6-bit ADPCM not implemented yet\n")); break; case 0x77: /* DMA DAC, 2.6-bit ADPCM Reference */ - pThis->needed_bytes = 2; - LogFlowFunc(("0x74 - DMA DAC, 2.6-bit ADPCM Reference not implemented\n")); + pThis->dsp_in_needed_bytes = 2; + LogFlowFunc(("ADPCM reference not implemented yet\n")); break; - case 0x7d: - LogFlowFunc(("0x7d - Autio-Initialize DMA DAC, 4-bit ADPCM Reference\n")); - LogFlowFunc(("not implemented\n")); + case 0x7d: /* Auto-Initialize DMA DAC, 4-bit ADPCM Reference */ + LogFlowFunc(("Autio-Initialize DMA DAC, 4-bit ADPCM reference not implemented yet\n")); break; - case 0x7f: - LogFlowFunc(("0x7d - Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference\n")); - LogFlowFunc(("not implemented\n")); + case 0x7f: /* Auto-Initialize DMA DAC, 16-bit ADPCM Reference */ + LogFlowFunc(("Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference not implemented yet\n")); break; - case 0x80: - pThis->needed_bytes = 2; + case 0x80: /* Silence DAC */ + pThis->dsp_in_needed_bytes = 2; break; - case 0x90: - case 0x91: - dma_cmd8(pDevIns, pThis, (((cmd & 1) == 0) ? 1 : 0) | DMA8_HIGH, -1); + case 0x90: /* Auto-Initialize DMA DAC, 8-bit (High Speed) */ + RT_FALL_THROUGH(); + case 0x91: /* Normal DMA DAC, 8-bit (High Speed) */ + sb16DmaCmd8(pDevIns, pThis, pStream, (((cmd & 1) == 0) ? 1 : 0) | DMA8_HIGH, -1); break; - case 0xd0: /* halt DMA operation. 8bit */ - sb16Control(pDevIns, pThis, 0); + case 0xd0: /* Halt DMA operation. 8bit */ + sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */); break; - case 0xd1: /* speaker on */ - sb16SpeakerControl(pThis, 1); + case 0xd1: /* Speaker on */ + sb16SpeakerControl(pThis, true /* fOn */); break; - case 0xd3: /* speaker off */ - sb16SpeakerControl(pThis, 0); + case 0xd3: /* Speaker off */ + sb16SpeakerControl(pThis, false /* fOn */); break; - case 0xd4: /* continue DMA operation. 8bit */ + case 0xd4: /* Continue DMA operation, 8-bit */ /* KQ6 (or maybe Sierras audblst.drv in general) resets the frequency between halt/continue */ - continue_dma8(pDevIns, pThis); + sb16DmaCmdContinue8(pDevIns, pThis, pStream); break; - case 0xd5: /* halt DMA operation. 16bit */ - sb16Control(pDevIns, pThis, 0); + case 0xd5: /* Halt DMA operation, 16-bit */ + sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */); break; - case 0xd6: /* continue DMA operation. 16bit */ - sb16Control(pDevIns, pThis, 1); + case 0xd6: /* Continue DMA operation, 16-bit */ + sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */); break; - case 0xd9: /* exit auto-init DMA after this block. 16bit */ - pThis->dma_auto = 0; + case 0xd9: /* Exit auto-init DMA after this block, 16-bit */ + pStream->dma_auto = 0; break; - case 0xda: /* exit auto-init DMA after this block. 8bit */ - pThis->dma_auto = 0; + case 0xda: /* Exit auto-init DMA after this block, 8-bit */ + pStream->dma_auto = 0; break; case 0xe0: /* DSP identification */ - pThis->needed_bytes = 1; + pThis->dsp_in_needed_bytes = 1; break; - case 0xe1: - dsp_out_data(pThis, pThis->ver & 0xff); - dsp_out_data(pThis, pThis->ver >> 8); + case 0xe1: /* DSP version */ + sb16DspSeData(pThis, RT_LO_U8(pStream->HwCfgRuntime.uVer)); + sb16DspSeData(pThis, RT_HI_U8(pStream->HwCfgRuntime.uVer)); break; - case 0xe2: - pThis->needed_bytes = 1; + case 0xe2: /* ??? */ + pThis->dsp_in_needed_bytes = 1; goto warn; - case 0xe3: + case 0xe3: /* DSP copyright */ { - for (int i = sizeof (e3) - 1; i >= 0; --i) - dsp_out_data(pThis, e3[i]); - + for (int i = sizeof(e3) - 1; i >= 0; --i) + sb16DspSeData(pThis, e3[i]); break; } - case 0xe4: /* write test reg */ - pThis->needed_bytes = 1; + case 0xe4: /* Write test register */ + pThis->dsp_in_needed_bytes = 1; break; - case 0xe7: + case 0xe7: /* ??? */ LogFlowFunc(("Attempt to probe for ESS (0xe7)?\n")); break; - case 0xe8: /* read test reg */ - dsp_out_data(pThis, pThis->test_reg); + case 0xe8: /* Read test register */ + sb16DspSeData(pThis, pThis->test_reg); break; - case 0xf2: - case 0xf3: - dsp_out_data(pThis, 0xaa); + case 0xf2: /* IRQ Request, 8-bit */ + RT_FALL_THROUGH(); + case 0xf3: /* IRQ Request, 16-bit */ + { + sb16DspSeData(pThis, 0xaa); pThis->mixer_regs[0x82] |= (cmd == 0xf2) ? 1 : 2; - PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1); + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1); break; + } - case 0xf8: - /* Undocumented, used by old Creative diagnostic programs. */ - dsp_out_data (pThis, 0); + case 0xf8: /* Undocumented, used by old Creative diagnostic programs */ + sb16DspSeData(pThis, 0); goto warn; - case 0xf9: - pThis->needed_bytes = 1; + case 0xf9: /* ??? */ + pThis->dsp_in_needed_bytes = 1; goto warn; - case 0xfa: - dsp_out_data (pThis, 0); + case 0xfa: /* ??? */ + sb16DspSeData(pThis, 0); goto warn; - case 0xfc: /* FIXME */ - dsp_out_data (pThis, 0); + case 0xfc: /* ??? */ + sb16DspSeData(pThis, 0); goto warn; default: - LogFlowFunc(("Unrecognized command %#x\n", cmd)); + LogFunc(("Unrecognized DSP command %#x, ignored\n", cmd)); break; } } - if (!pThis->needed_bytes) - LogFlow(("\n")); - exit: - if (!pThis->needed_bytes) + if (!pThis->dsp_in_needed_bytes) pThis->cmd = -1; else pThis->cmd = cmd; @@ -711,242 +778,211 @@ return; warn: - LogFlowFunc(("warning: command %#x,%d is not truly understood yet\n", cmd, pThis->needed_bytes)); + LogFunc(("warning: command %#x,%d is not truly understood yet\n", cmd, pThis->dsp_in_needed_bytes)); goto exit; } -static uint16_t dsp_get_lohi (PSB16STATE pThis) +DECLINLINE(uint16_t) sb16DspGetLoHi(PSB16STATE pThis) { - uint8_t hi = dsp_get_data (pThis); - uint8_t lo = dsp_get_data (pThis); - return (hi << 8) | lo; + const uint8_t hi = sb16DspGetData(pThis); + const uint8_t lo = sb16DspGetData(pThis); + return RT_MAKE_U16(lo, hi); } -static uint16_t dsp_get_hilo (PSB16STATE pThis) +DECLINLINE(uint16_t) sb16DspGetHiLo(PSB16STATE pThis) { - uint8_t lo = dsp_get_data (pThis); - uint8_t hi = dsp_get_data (pThis); - return (hi << 8) | lo; + const uint8_t lo = sb16DspGetData(pThis); + const uint8_t hi = sb16DspGetData(pThis); + return RT_MAKE_U16(lo, hi); } -static void complete(PPDMDEVINS pDevIns, PSB16STATE pThis) +static void sb16DspCmdComplete(PPDMDEVINS pDevIns, PSB16STATE pThis) { - int d0, d1, d2; - LogFlowFunc(("complete command %#x, in_index %d, needed_bytes %d\n", pThis->cmd, pThis->in_index, pThis->needed_bytes)); + LogFlowFunc(("Command %#x, in_index %d, needed_bytes %d\n", pThis->cmd, pThis->dsp_in_idx, pThis->dsp_in_needed_bytes)); + + int v0, v1, v2; + + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; /** @ŧodo Improve this. */ if (pThis->cmd > 0xaf && pThis->cmd < 0xd0) { - d2 = dsp_get_data (pThis); - d1 = dsp_get_data (pThis); - d0 = dsp_get_data (pThis); + v2 = sb16DspGetData(pThis); + v1 = sb16DspGetData(pThis); + v0 = sb16DspGetData(pThis); if (pThis->cmd & 8) - LogFlowFunc(("ADC params cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, d0, d1, d2)); + LogFlowFunc(("ADC params cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, v0, v1, v2)); else { - LogFlowFunc(("cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, d0, d1, d2)); - dma_cmd(pDevIns, pThis, pThis->cmd, d0, d1 + (d2 << 8)); + LogFlowFunc(("cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, v0, v1, v2)); + sb16DmaCmd(pDevIns, pThis, pStream, pThis->cmd, v0, v1 + (v2 << 8)); } } else { switch (pThis->cmd) { - case 0x04: - pThis->csp_mode = dsp_get_data (pThis); - pThis->csp_reg83r = 0; - pThis->csp_reg83w = 0; - LogFlowFunc(("CSP command 0x04: mode=%#x\n", pThis->csp_mode)); - break; + case 0x04: + pThis->csp_mode = sb16DspGetData(pThis); + pThis->csp_reg83r = 0; + pThis->csp_reg83w = 0; + LogFlowFunc(("CSP command 0x04: mode=%#x\n", pThis->csp_mode)); + break; - case 0x05: - pThis->csp_param = dsp_get_data (pThis); - pThis->csp_value = dsp_get_data (pThis); - LogFlowFunc(("CSP command 0x05: param=%#x value=%#x\n", pThis->csp_param, pThis->csp_value)); - break; + case 0x05: + pThis->csp_param = sb16DspGetData(pThis); + pThis->csp_value = sb16DspGetData(pThis); + LogFlowFunc(("CSP command 0x05: param=%#x value=%#x\n", pThis->csp_param, pThis->csp_value)); + break; - case 0x0e: - { - d0 = dsp_get_data(pThis); - d1 = dsp_get_data(pThis); - LogFlowFunc(("write CSP register %d <- %#x\n", d1, d0)); - if (d1 == 0x83) - { - LogFlowFunc(("0x83[%d] <- %#x\n", pThis->csp_reg83r, d0)); - pThis->csp_reg83[pThis->csp_reg83r % 4] = d0; - pThis->csp_reg83r += 1; - } - else - pThis->csp_regs[d1] = d0; - break; - } + case 0x0e: + v0 = sb16DspGetData(pThis); + v1 = sb16DspGetData(pThis); + LogFlowFunc(("write CSP register %d <- %#x\n", v1, v0)); + if (v1 == 0x83) + { + LogFlowFunc(("0x83[%d] <- %#x\n", pThis->csp_reg83r, v0)); + pThis->csp_reg83[pThis->csp_reg83r % 4] = v0; + pThis->csp_reg83r += 1; + } + else + pThis->csp_regs[v1] = v0; + break; - case 0x0f: - d0 = dsp_get_data(pThis); - LogFlowFunc(("read CSP register %#x -> %#x, mode=%#x\n", d0, pThis->csp_regs[d0], pThis->csp_mode)); - if (d0 == 0x83) - { - LogFlowFunc(("0x83[%d] -> %#x\n", pThis->csp_reg83w, pThis->csp_reg83[pThis->csp_reg83w % 4])); - dsp_out_data(pThis, pThis->csp_reg83[pThis->csp_reg83w % 4]); - pThis->csp_reg83w += 1; - } - else - dsp_out_data(pThis, pThis->csp_regs[d0]); - break; + case 0x0f: + v0 = sb16DspGetData(pThis); + LogFlowFunc(("read CSP register %#x -> %#x, mode=%#x\n", v0, pThis->csp_regs[v0], pThis->csp_mode)); + if (v0 == 0x83) + { + LogFlowFunc(("0x83[%d] -> %#x\n", pThis->csp_reg83w, pThis->csp_reg83[pThis->csp_reg83w % 4])); + sb16DspSeData(pThis, pThis->csp_reg83[pThis->csp_reg83w % 4]); + pThis->csp_reg83w += 1; + } + else + sb16DspSeData(pThis, pThis->csp_regs[v0]); + break; - case 0x10: - d0 = dsp_get_data(pThis); - LogFlowFunc(("cmd 0x10 d0=%#x\n", d0)); - break; + case 0x10: + v0 = sb16DspGetData(pThis); + LogFlowFunc(("cmd 0x10 d0=%#x\n", v0)); + break; - case 0x14: - dma_cmd8(pDevIns, pThis, 0, dsp_get_lohi (pThis) + 1); - break; + case 0x14: + sb16DmaCmd8(pDevIns, pThis, pStream, 0, sb16DspGetLoHi(pThis) + 1); + break; - case 0x40: - pThis->time_const = dsp_get_data(pThis); - LogFlowFunc(("set time const %d\n", pThis->time_const)); - break; + case 0x22: /* Sets the master volume. */ + /** @todo Setting the master volume is not implemented yet. */ + break; - case 0x42: /* FT2 sets output freq with this, go figure */ + case 0x40: /* Sets the timer constant; SB16 is able to use sample rates via 0x41 instead. */ + pStream->time_const = sb16DspGetData(pThis); + LogFlowFunc(("set time const %d\n", pStream->time_const)); + break; + + case 0x42: /* Sets the input rate (in Hz). */ #if 0 - LogFlowFunc(("cmd 0x42 might not do what it think it should\n")); + LogFlowFunc(("cmd 0x42 might not do what it think it should\n")); #endif - case 0x41: - pThis->freq = dsp_get_hilo(pThis); - LogFlowFunc(("set freq %d\n", pThis->freq)); - break; - - case 0x48: - pThis->block_size = dsp_get_lohi(pThis) + 1; - LogFlowFunc(("set dma block len %d\n", pThis->block_size)); - break; - - case 0x74: - case 0x75: - case 0x76: - case 0x77: - /* ADPCM stuff, ignore */ - break; + RT_FALL_THROUGH(); /** @todo BUGBUG FT2 sets output freq with this, go figure. */ - case 0x80: - { - uint32_t const freq = pThis->freq > 0 ? pThis->freq : 11025; - uint32_t const samples = dsp_get_lohi(pThis) + 1; - uint32_t const bytes = samples << pThis->fmt_stereo << (pThis->fmt_bits == 16 ? 1 : 0); - uint64_t const uTimerHz = PDMDevHlpTimerGetFreq(pDevIns, pThis->hTimerIRQ); - uint64_t const cTicks = (bytes * uTimerHz) / freq; - if (cTicks < uTimerHz / 1024) - PDMDevHlpISASetIrq(pDevIns, pThis->irq, 1); - else - PDMDevHlpTimerSetRelative(pDevIns, pThis->hTimerIRQ, cTicks, NULL); - LogFlowFunc(("mix silence: %d samples, %d bytes, %RU64 ticks\n", samples, bytes, cTicks)); - break; - } + case 0x41: /* Sets the output rate (in Hz). */ + pStream->Cfg.Props.uHz = sb16DspGetHiLo(pThis); + LogFlowFunc(("set freq to %RU16Hz\n", pStream->Cfg.Props.uHz)); + break; - case 0xe0: - d0 = dsp_get_data(pThis); - pThis->out_data_len = 0; - LogFlowFunc(("E0 data = %#x\n", d0)); - dsp_out_data(pThis, ~d0); - break; + case 0x48: + pStream->cbDmaBlockSize = sb16DspGetLoHi(pThis) + 1; + LogFlowFunc(("set dma block len %d\n", pStream->cbDmaBlockSize)); + break; - case 0xe2: - d0 = dsp_get_data(pThis); - LogFlow(("SB16:E2 = %#x\n", d0)); - break; + case 0x74: + case 0x75: + case 0x76: + case 0x77: + /* ADPCM stuff, ignore. */ + break; - case 0xe4: - pThis->test_reg = dsp_get_data(pThis); - break; + case 0x80: /* Sets the IRQ. */ + sb16StreamTransferScheduleNext(pThis, pStream, sb16DspGetLoHi(pThis) + 1); + break; - case 0xf9: - d0 = dsp_get_data(pThis); - LogFlowFunc(("command 0xf9 with %#x\n", d0)); - switch (d0) { - case 0x0e: - dsp_out_data(pThis, 0xff); + case 0xe0: + v0 = sb16DspGetData(pThis); + pThis->dsp_out_data_len = 0; + LogFlowFunc(("E0=%#x\n", v0)); + sb16DspSeData(pThis, ~v0); break; - case 0x0f: - dsp_out_data(pThis, 0x07); + case 0xe2: + v0 = sb16DspGetData(pThis); + LogFlowFunc(("E2=%#x\n", v0)); break; - case 0x37: - dsp_out_data(pThis, 0x38); + case 0xe4: + pThis->test_reg = sb16DspGetData(pThis); break; - default: - dsp_out_data(pThis, 0x00); + case 0xf9: + v0 = sb16DspGetData(pThis); + switch (v0) + { + case 0x0e: + sb16DspSeData(pThis, 0xff); + break; + + case 0x0f: + sb16DspSeData(pThis, 0x07); + break; + + case 0x37: + sb16DspSeData(pThis, 0x38); + break; + + default: + sb16DspSeData(pThis, 0x00); + break; + } break; - } - break; - default: - LogFlowFunc(("complete: unrecognized command %#x\n", pThis->cmd)); - return; + default: + LogRel2(("SB16: Unrecognized command %#x, skipping\n", pThis->cmd)); + return; } } - LogFlow(("\n")); pThis->cmd = -1; return; } -static void sb16CmdResetLegacy(PSB16STATE pThis) +static void sb16DspCmdResetLegacy(PSB16STATE pThis) { LogFlowFuncEnter(); - pThis->freq = 11025; - pThis->fmt_signed = 0; - pThis->fmt_bits = 8; - pThis->fmt_stereo = 0; - - /* At the moment we only have one stream, the output stream. */ - PPDMAUDIOSTREAMCFG pCfg = &pThis->Out.Cfg; - - pCfg->enmDir = PDMAUDIODIR_OUT; - pCfg->u.enmDst = PDMAUDIOPLAYBACKDST_FRONT; - pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; - - pCfg->Props.uHz = pThis->freq; - pCfg->Props.cChannels = 1; /* Mono */ - pCfg->Props.cbSample = 1 /* 8-bit */; - pCfg->Props.fSigned = false; - pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels); + /* Disable speaker(s). */ + sb16SpeakerControl(pThis, false /* fOn */); - AssertCompile(sizeof(pCfg->szName) >= sizeof("Output")); - memcpy(pCfg->szName, "Output", sizeof("Output")); - - sb16CloseOut(pThis); + /* + * Reset all streams. + */ + for (unsigned i = 0; i < SB16_MAX_STREAMS; i++) + sb16StreamReset(pThis, &pThis->aStreams[i]); } -static void sb16CmdReset(PPDMDEVINS pDevIns, PSB16STATE pThis) +static void sb16DspCmdReset(PSB16STATE pThis) { - PDMDevHlpISASetIrq(pDevIns, pThis->irq, 0); - if (pThis->dma_auto) - { - PDMDevHlpISASetIrq(pDevIns, pThis->irq, 1); - PDMDevHlpISASetIrq(pDevIns, pThis->irq, 0); - } - pThis->mixer_regs[0x82] = 0; - pThis->dma_auto = 0; - pThis->in_index = 0; - pThis->out_data_len = 0; - pThis->left_till_irq = 0; - pThis->needed_bytes = 0; - pThis->block_size = -1; + pThis->dsp_in_idx = 0; + pThis->dsp_out_data_len = 0; + pThis->dsp_in_needed_bytes = 0; pThis->nzero = 0; pThis->highspeed = 0; pThis->v2x6 = 0; pThis->cmd = -1; - dsp_out_data(pThis, 0xaa); - sb16SpeakerControl(pThis, 0); + sb16DspSeData(pThis, 0xaa); - sb16Control(pDevIns, pThis, 0); - sb16CmdResetLegacy(pThis); + sb16DspCmdResetLegacy(pThis); } /** @@ -957,6 +993,9 @@ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); RT_NOREF(pvUser, cb); + /** @todo Figure out how we can distinguish between streams. DSP port #, e.g. 0x220? */ + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; + LogFlowFunc(("write %#x <- %#x\n", offPort, u32)); switch (offPort) { @@ -970,11 +1009,11 @@ if (0 && pThis->highspeed) { pThis->highspeed = 0; - PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); - sb16Control(pDevIns, pThis, 0); + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0); + sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */); } else - sb16CmdReset(pDevIns, pThis); + sb16DspCmdReset(pThis); } pThis->v2x6 = 0; break; @@ -990,12 +1029,12 @@ break; case 0xb8: /* Panic */ - sb16CmdReset(pDevIns, pThis); + sb16DspCmdReset(pThis); break; case 0x39: - dsp_out_data(pThis, 0x38); - sb16CmdReset(pDevIns, pThis); + sb16DspSeData(pThis, 0x38); + sb16DspCmdReset(pThis); pThis->v2x6 = 0x39; break; @@ -1010,31 +1049,23 @@ if (pThis->highspeed) break; #endif - if (0 == pThis->needed_bytes) + if (0 == pThis->dsp_in_needed_bytes) { - sb16HandleCommand(pDevIns, pThis, u32); -#if 0 - if (0 == pThis->needed_bytes) { - log_dsp (pThis); - } -#endif + sb16DspCmdLookup(pDevIns, pThis, pStream, u32); } else { - if (pThis->in_index == sizeof (pThis->in2_data)) + if (pThis->dsp_in_idx == sizeof (pThis->dsp_in_data)) { - LogFlowFunc(("in data overrun\n")); + AssertMsgFailed(("DSP input data overrun\n")); } else { - pThis->in2_data[pThis->in_index++] = u32; - if (pThis->in_index == pThis->needed_bytes) + pThis->dsp_in_data[pThis->dsp_in_idx++] = u32; + if (pThis->dsp_in_idx == pThis->dsp_in_needed_bytes) { - pThis->needed_bytes = 0; - complete(pDevIns, pThis); -#if 0 - log_dsp (pThis); -#endif + pThis->dsp_in_needed_bytes = 0; + sb16DspCmdComplete(pDevIns, pThis); } } } @@ -1054,11 +1085,15 @@ */ static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortDspRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) { + RT_NOREF(pvUser, cb); + PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); + uint32_t retval; int ack = 0; - RT_NOREF(pvUser, cb); + /** @todo Figure out how we can distinguish between streams. */ + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; /** @todo reject non-byte access? * The spec does not mention a non-byte access so we should check how real hardware behaves. */ @@ -1070,9 +1105,9 @@ break; case 4: /* read data */ - if (pThis->out_data_len) + if (pThis->dsp_out_data_len) { - retval = pThis->out_data[--pThis->out_data_len]; + retval = pThis->dsp_out_data[--pThis->dsp_out_data_len]; pThis->last_read_byte = retval; } else @@ -1085,7 +1120,7 @@ break; case 6: /* 0 can write */ - retval = pThis->can_write ? 0 : 0x80; + retval = pStream->can_write ? 0 : 0x80; break; case 7: /* timer interrupt clear */ @@ -1094,12 +1129,12 @@ break; case 8: /* data available status | irq 8 ack */ - retval = (!pThis->out_data_len || pThis->highspeed) ? 0 : 0x80; + retval = (!pThis->dsp_out_data_len || pThis->highspeed) ? 0 : 0x80; if (pThis->mixer_regs[0x82] & 1) { ack = 1; pThis->mixer_regs[0x82] &= ~1; - PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0); } break; @@ -1109,7 +1144,7 @@ { ack = 1; pThis->mixer_regs[0x82] &= ~2; - PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0); } break; @@ -1126,7 +1161,9 @@ } -/* -=-=-=-=-=- Mixer -=-=-=-=-=- */ +/********************************************************************************************************************************* +* Mixer functions * +*********************************************************************************************************************************/ static uint8_t sb16MixRegToVol(PSB16STATE pThis, int reg) { @@ -1145,15 +1182,10 @@ * @param pThis SB16 state. * @param pVol Where to store the master volume information. */ -static void sb16GetMasterVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol) +DECLINLINE(void) sb16GetMasterVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol) { /* There's no mute switch, only volume controls. */ - uint8_t lvol = sb16MixRegToVol(pThis, 0x30); - uint8_t rvol = sb16MixRegToVol(pThis, 0x31); - - pVol->fMuted = false; - pVol->uLeft = lvol; - pVol->uRight = rvol; + PDMAudioVolumeInitFromStereo(pVol, false /*fMuted*/, sb16MixRegToVol(pThis, 0x30), sb16MixRegToVol(pThis, 0x31)); } /** @@ -1162,15 +1194,10 @@ * @param pThis SB16 state. * @param pVol Where to store the output stream volume information. */ -static void sb16GetPcmOutVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol) +DECLINLINE(void) sb16GetPcmOutVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol) { /* There's no mute switch, only volume controls. */ - uint8_t lvol = sb16MixRegToVol(pThis, 0x32); - uint8_t rvol = sb16MixRegToVol(pThis, 0x33); - - pVol->fMuted = false; - pVol->uLeft = lvol; - pVol->uRight = rvol; + PDMAudioVolumeInitFromStereo(pVol, false /*fMuted*/, sb16MixRegToVol(pThis, 0x32), sb16MixRegToVol(pThis, 0x33)); } static void sb16UpdateVolume(PSB16STATE pThis) @@ -1183,28 +1210,10 @@ /* Combine the master + output stream volume. */ PDMAUDIOVOLUME VolCombined; - RT_ZERO(VolCombined); - - VolCombined.fMuted = VolMaster.fMuted || VolOut.fMuted; - if (!VolCombined.fMuted) - { - VolCombined.uLeft = ( (VolOut.uLeft ? VolOut.uLeft : 1) - * (VolMaster.uLeft ? VolMaster.uLeft : 1)) / PDMAUDIO_VOLUME_MAX; - - VolCombined.uRight = ( (VolOut.uRight ? VolOut.uRight : 1) - * (VolMaster.uRight ? VolMaster.uRight : 1)) / PDMAUDIO_VOLUME_MAX; - } + PDMAudioVolumeCombine(&VolCombined, &VolMaster, &VolOut); - PSB16DRIVER pDrv; - RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) - { - PPDMAUDIOSTREAM pStream = pDrv->Out.pStream; - if (pStream) - { - int rc2 = pDrv->pConnector->pfnStreamSetVolume(pDrv->pConnector, pStream, &VolCombined); - AssertRC(rc2); - } - } + int rc2 = AudioMixerSinkSetVolume(pThis->pSinkOut, &VolCombined); + AssertRC(rc2); } static void sb16MixerReset(PSB16STATE pThis) @@ -1240,6 +1249,15 @@ /* Update the master (mixer) and PCM out volumes. */ sb16UpdateVolume(pThis); + + /* + * Reset mixer sinks. + * + * Do the reset here instead of in sb16StreamReset(); + * the mixer sink(s) might still have data to be processed when an audio stream gets reset. + */ + if (pThis->pSinkOut) + AudioMixerSinkReset(pThis->pSinkOut); } static int magic_of_irq(int irq) @@ -1282,8 +1300,9 @@ return -1; } -static int mixer_write_indexb(PSB16STATE pThis, uint8_t val) +static int sb16MixerWriteIndex(PSB16STATE pThis, PSB16STREAM pStream, uint8_t val) { + RT_NOREF(pStream); pThis->mixer_nreg = val; return VINF_SUCCESS; } @@ -1333,12 +1352,12 @@ } -static int mixer_write_datab(PSB16STATE pThis, uint8_t val) +static int sb16MixerWriteData(PSB16STATE pThis, PSB16STREAM pStream, uint8_t val) { bool fUpdateMaster = false; bool fUpdateStream = false; - LogFlowFunc(("sb16IoPortMixerWrite [%#x] <- %#x\n", pThis->mixer_nreg, val)); + LogFlowFunc(("[%#x] <- %#x\n", pThis->mixer_nreg, val)); switch (pThis->mixer_nreg) { @@ -1418,30 +1437,31 @@ case 0x80: { int irq = irq_of_magic(val); - LogFlowFunc(("setting irq to %d (val=%#x)\n", irq, val)); + LogRelMax2(64, ("SB16: Setting IRQ to %d\n", irq)); if (irq > 0) - pThis->irq = irq; + pStream->HwCfgRuntime.uIrq = irq; break; } case 0x81: { - int dma, hdma; - - dma = lsbindex (val & 0xf); - hdma = lsbindex (val & 0xf0); - if (dma != pThis->dma || hdma != pThis->hdma) - LogFlow(("SB16: attempt to change DMA 8bit %d(%d), 16bit %d(%d) (val=%#x)\n", - dma, pThis->dma, hdma, pThis->hdma, val)); + int dma = lsbindex(val & 0xf); + int hdma = lsbindex(val & 0xf0); + if ( dma != pStream->HwCfgRuntime.uDmaChanLow + || hdma != pStream->HwCfgRuntime.uDmaChanHigh) + { + LogRelMax2(64, ("SB16: Attempt to change DMA 8bit %d(%d), 16bit %d(%d)\n", + dma, pStream->HwCfgRuntime.uDmaChanLow, hdma, pStream->HwCfgRuntime.uDmaChanHigh)); + } #if 0 - pThis->dma = dma; - pThis->hdma = hdma; + pStream->dma = dma; + pStream->hdma = hdma; #endif break; } case 0x82: - LogFlowFunc(("attempt to write into IRQ status register (val=%#x)\n", val)); + LogRelMax2(64, ("SB16: Attempt to write into IRQ status register to %#x\n", val)); return VINF_SUCCESS; default: @@ -1470,24 +1490,27 @@ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); RT_NOREF(pvUser); + /** @todo Figure out how we can distinguish between streams. */ + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; + switch (cb) { case 1: switch (offPort) { case 0: - mixer_write_indexb(pThis, u32); + sb16MixerWriteIndex(pThis, pStream, u32); break; case 1: - mixer_write_datab(pThis, u32); + sb16MixerWriteData(pThis, pStream, u32); break; default: AssertFailed(); } break; case 2: - mixer_write_indexb(pThis, u32 & 0xff); - mixer_write_datab(pThis, (u32 >> 8) & 0xff); + sb16MixerWriteIndex(pThis, pStream, u32 & 0xff); + sb16MixerWriteData(pThis, pStream, (u32 >> 8) & 0xff); break; default: ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d u32=%#x\n", offPort, cb, u32)); @@ -1515,73 +1538,13 @@ } -/* -=-=-=-=-=- DMA -=-=-=-=-=- */ +/********************************************************************************************************************************* +* DMA handling * +*********************************************************************************************************************************/ /** * Worker for sb16DMARead. */ -static int sb16WriteAudio(PSB16STATE pThis, int nchan, uint32_t dma_pos, uint32_t dma_len, int len) -{ - uint8_t abBuf[_4K]; /** @todo Have a buffer on the heap. */ - uint32_t cbToWrite = len; - uint32_t cbWrittenTotal = 0; - - while (cbToWrite) - { - uint32_t cbToRead = RT_MIN(dma_len - dma_pos, cbToWrite); - if (cbToRead > sizeof(abBuf)) - cbToRead = sizeof(abBuf); - - uint32_t cbRead = 0; - int rc2 = PDMDevHlpDMAReadMemory(pThis->pDevInsR3, nchan, abBuf, dma_pos, cbToRead, &cbRead); - AssertMsgRC(rc2, (" from DMA failed: %Rrc\n", rc2)); - -#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA - if (cbRead) - { - RTFILE fh; - RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "sb16WriteAudio.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTFileWrite(fh, abBuf, cbRead, NULL); - RTFileClose(fh); - } -#endif - /* - * Write data to the backends. - */ - uint32_t cbWritten = 0; - - /* Just multiplex the output to the connected backends. - * No need to utilize the virtual mixer here (yet). */ - PSB16DRIVER pDrv; - RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) - { - if ( pDrv->Out.pStream - && DrvAudioHlpStreamStatusCanWrite(pDrv->pConnector->pfnStreamGetStatus(pDrv->pConnector, pDrv->Out.pStream))) - { - uint32_t cbWrittenToStream = 0; - rc2 = pDrv->pConnector->pfnStreamWrite(pDrv->pConnector, pDrv->Out.pStream, abBuf, cbRead, &cbWrittenToStream); - - LogFlowFunc(("\tLUN#%RU8: rc=%Rrc, cbWrittenToStream=%RU32\n", pDrv->uLUN, rc2, cbWrittenToStream)); - } - } - - cbWritten = cbRead; /* Always report everything written, as the backends need to keep up themselves. */ - - LogFlowFunc(("\tcbToRead=%RU32, cbToWrite=%RU32, cbWritten=%RU32, cbLeft=%RU32\n", - cbToRead, cbToWrite, cbWritten, cbToWrite - cbWrittenTotal)); - - if (!cbWritten) - break; - - Assert(cbToWrite >= cbWritten); - cbToWrite -= cbWritten; - dma_pos = (dma_pos + cbWritten) % dma_len; - cbWrittenTotal += cbWritten; - } - - return cbWrittenTotal; -} /** * @callback_method_impl{FNDMATRANSFERHANDLER, @@ -1590,99 +1553,107 @@ static DECLCALLBACK(uint32_t) sb16DMARead(PPDMDEVINS pDevIns, void *pvUser, unsigned uChannel, uint32_t off, uint32_t cb) { - RT_NOREF(pDevIns); - PSB16STATE pThis = (PSB16STATE)pvUser; - int till, copy, written, free; + PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); + AssertPtr(pThis); + PSB16STREAM pStream = (PSB16STREAM)pvUser; + AssertPtr(pStream); - if (pThis->block_size <= 0) + int till, copy, free; + + if (pStream->cbDmaBlockSize <= 0) { - LogFlowFunc(("invalid block size=%d uChannel=%d off=%d cb=%d\n", pThis->block_size, uChannel, off, cb)); + LogFlowFunc(("invalid block size=%d uChannel=%d off=%d cb=%d\n", pStream->cbDmaBlockSize, uChannel, off, cb)); return off; } - if (pThis->left_till_irq < 0) - pThis->left_till_irq = pThis->block_size; + if (pStream->cbDmaLeft < 0) + pStream->cbDmaLeft = pStream->cbDmaBlockSize; free = cb; copy = free; - till = pThis->left_till_irq; + till = pStream->cbDmaLeft; -#ifdef DEBUG_SB16_MOST - LogFlowFunc(("pos:%06d %d till:%d len:%d\n", off, free, till, cb)); -#endif + Log4Func(("pos=%d %d, till=%d, len=%d\n", off, free, till, cb)); if (copy >= till) { - if (0 == pThis->dma_auto) + if (0 == pStream->dma_auto) { copy = till; } else { - if (copy >= till + pThis->block_size) + if (copy >= till + pStream->cbDmaBlockSize) copy = till; /* Make sure we won't skip IRQs. */ } } - written = sb16WriteAudio(pThis, uChannel, off, cb, copy); - off = (off + written) % cb; - pThis->left_till_irq -= written; + STAM_COUNTER_ADD(&pThis->StatBytesRead, copy); + + uint32_t written = 0; /* Shut up GCC. */ + int rc = sb16StreamDoDmaOutput(pThis, pStream, uChannel, off, cb, copy, &written); + AssertRC(rc); + + /** @todo Convert the rest to uin32_t / size_t. */ + off = (off + (int)written) % cb; + pStream->cbDmaLeft -= (int)written; /** @todo r=andy left_till_irq can be < 0. Correct? Revisit this. */ - if (pThis->left_till_irq <= 0) + Log3Func(("pos %d/%d, free=%d, till=%d, copy=%d, written=%RU32, block_size=%d\n", + off, cb, free, pStream->cbDmaLeft, copy, copy, pStream->cbDmaBlockSize)); + + if (pStream->cbDmaLeft <= 0) { pThis->mixer_regs[0x82] |= (uChannel & 4) ? 2 : 1; - PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1); - if (0 == pThis->dma_auto) + + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1); + + if (0 == pStream->dma_auto) /** @todo r=andy BUGBUG Why do we first assert the IRQ if dma_auto is 0? Revisit this. */ { - sb16Control(pDevIns, pThis, 0); + sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */); sb16SpeakerControl(pThis, 0); } } - Log3Func(("pos %d/%d free %5d till %5d copy %5d written %5d block_size %5d\n", - off, cb, free, pThis->left_till_irq, copy, written, pThis->block_size)); - - while (pThis->left_till_irq <= 0) - pThis->left_till_irq += pThis->block_size; + while (pStream->cbDmaLeft <= 0) + pStream->cbDmaLeft += pStream->cbDmaBlockSize; return off; } -/* -=-=-=-=-=- I/O timer -=-=-=-=-=- */ +/********************************************************************************************************************************* +* Timer-related code * +*********************************************************************************************************************************/ -static void sb16TimerMaybeStart(PPDMDEVINS pDevIns, PSB16STATE pThis) +/** + * @callback_method_impl{PFNTMTIMERDEV} + */ +static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { - LogFlowFunc(("cStreamsActive=%RU8\n", pThis->cStreamsActive)); + PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); + RT_NOREF(pTimer, pThis); - if (pThis->cStreamsActive == 0) /* Only start the timer if there are no active streams. */ - return; + PSB16STREAM pStream = (PSB16STREAM)pvUser; + AssertPtrReturnVoid(pStream); - /* Set timer flag. */ - ASMAtomicWriteBool(&pThis->fTimerActive, true); + LogFlowFuncEnter(); - /* Update current time timestamp. */ - uint64_t tsNow = PDMDevHlpTimerGet(pDevIns, pThis->hTimerIO); - pThis->tsTimerIO = tsNow; - - /* Arm the timer. */ - PDMDevHlpTimerSet(pDevIns, pThis->hTimerIO, tsNow + pThis->cTicksTimerIOInterval); + pStream->can_write = 1; + PDMDevHlpISASetIrq(pDevIns, pStream->HwCfgRuntime.uIrq, 1); } /** - * This clears fTimerActive if no streams are active, so that the timer won't be - * rearmed then next time it fires. + * Sets the stream's I/O timer to a new expiration time. + * + * @param pDevIns The device instance. + * @param pStream SB16 stream to set timer for. + * @param cTicksToDeadline The number of ticks to the new deadline. */ -static void sb16TimerMaybeStop(PSB16STATE pThis) +DECLINLINE(void) sb16TimerSet(PPDMDEVINS pDevIns, PSB16STREAM pStream, uint64_t cTicksToDeadline) { - LogFlowFunc(("cStreamsActive=%RU8\n", pThis->cStreamsActive)); - - if (pThis->cStreamsActive) /* Some streams still active? Bail out. */ - return; - - /* Clear the timer flag. */ - ASMAtomicWriteBool(&pThis->fTimerActive, false); + int rc = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimerIO, cTicksToDeadline, NULL /*pu64Now*/); + AssertRC(rc); } /** @@ -1691,241 +1662,707 @@ static DECLCALLBACK(void) sb16TimerIO(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); - RT_NOREF(pTimer, pvUser); + STAM_PROFILE_START(&pThis->StatTimerIO, a); - uint64_t cTicksNow = PDMDevHlpTimerGet(pDevIns, pThis->hTimerIO); - bool fIsPlaying = false; /* Whether one or more streams are still playing. */ - bool fDoTransfer = false; + PSB16STREAM pStream = (PSB16STREAM)pvUser; + AssertPtrReturnVoid(pStream); + RT_NOREF(pTimer); - pThis->tsTimerIO = cTicksNow; + const uint64_t cTicksNow = PDMDevHlpTimerGet(pDevIns, pStream->hTimerIO); - PSB16DRIVER pDrv; - RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + pStream->tsTimerIO = cTicksNow; + + PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx); + AssertPtrReturnVoid(pSink); + + const bool fSinkActive = AudioMixerSinkIsActive(pSink); + + LogFlowFunc(("fSinkActive=%RTbool\n", fSinkActive)); + + /* Schedule the next transfer. */ + PDMDevHlpDMASchedule(pDevIns); + + if (fSinkActive) + { + /** @todo adjust cTicks down by now much cbOutMin represents. */ + sb16TimerSet(pDevIns, pStream, pStream->cTicksTimerIOInterval); + } + + AudioMixerSinkSignalUpdateJob(pSink); + + STAM_PROFILE_STOP(&pThis->StatTimerIO, a); +} + + +/********************************************************************************************************************************* +* LUN (driver) management * +*********************************************************************************************************************************/ + +/** + * Retrieves a specific driver stream of a SB16 driver. + * + * @returns Pointer to driver stream if found, or NULL if not found. + * @param pDrv Driver to retrieve driver stream for. + * @param enmDir Stream direction to retrieve. + * @param enmPath Stream destination / source to retrieve. + */ +static PSB16DRIVERSTREAM sb16GetDrvStream(PSB16DRIVER pDrv, PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath) +{ + PSB16DRIVERSTREAM pDrvStream = NULL; + + if (enmDir == PDMAUDIODIR_OUT) { - PPDMAUDIOSTREAM const pStream = pDrv->Out.pStream; - PPDMIAUDIOCONNECTOR const pConn = pDrv->pConnector; - if (pStream && pConn) + LogFunc(("enmPath=%d\n", enmPath)); + + switch (enmPath) { - int rc2 = pConn->pfnStreamIterate(pConn, pStream); - if (RT_SUCCESS(rc2)) + case PDMAUDIOPATH_OUT_FRONT: + pDrvStream = &pDrv->Out; + break; + default: + AssertFailed(); + break; + } + } + else + Assert(enmDir == PDMAUDIODIR_IN /** @todo Recording not implemented yet. */); + + return pDrvStream; +} + +/** + * Adds a driver stream to a specific mixer sink. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pMixSink Mixer sink to add driver stream to. + * @param pCfg Stream configuration to use. + * @param pDrv Driver stream to add. + */ +static int sb16AddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PSB16DRIVER pDrv) +{ + AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_NOT_IMPLEMENTED); /* We don't support recording for SB16 so far. */ + LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfg->szName)); + + int rc; + PSB16DRIVERSTREAM pDrvStream = sb16GetDrvStream(pDrv, pCfg->enmDir, pCfg->enmPath); + if (pDrvStream) + { + AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN)); + + PAUDMIXSTREAM pMixStrm; + rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pCfg, pDevIns, &pMixStrm); + LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc)); + if (RT_SUCCESS(rc)) + { + rc = AudioMixerSinkAddStream(pMixSink, pMixStrm); + LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc)); + if (RT_SUCCESS(rc)) + pDrvStream->pMixStrm = pMixStrm; + else + AudioMixerStreamDestroy(pMixStrm, pDevIns, true /*fImmediate*/); + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds all current driver streams to a specific mixer sink. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The SB16 state. + * @param pMixSink Mixer sink to add stream to. + * @param pCfg Stream configuration to use. + */ +static int sb16AddDrvStreams(PPDMDEVINS pDevIns, PSB16STATE pThis, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); + + int rc; + if (AudioHlpStreamCfgIsValid(pCfg)) + { + rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props, pCfg->Device.cMsSchedulingHint); + if (RT_SUCCESS(rc)) + { + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) { - rc2 = pConn->pfnStreamPlay(pConn, pStream, NULL /* cPlayed */); + int rc2 = sb16AddDrvStream(pDevIns, pMixSink, pCfg, pDrv); if (RT_FAILURE(rc2)) - { - LogFlowFunc(("%s: Failed playing stream, rc=%Rrc\n", pStream->szName, rc2)); - continue; - } + LogFunc(("Attaching stream failed with %Rrc\n", rc2)); - /* Only do the next DMA transfer if we're able to write the remaining data block. */ - fDoTransfer = pConn->pfnStreamGetWritable(pConn, pStream) > (unsigned)pThis->left_till_irq; + /* Do not pass failure to rc here, as there might be drivers which aren't + * configured / ready yet. */ } - - PDMAUDIOSTREAMSTS fStrmSts = pConn->pfnStreamGetStatus(pConn, pStream); - fIsPlaying |= RT_BOOL(fStrmSts & (PDMAUDIOSTREAMSTS_FLAGS_ENABLED | PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE)); } } + else + rc = VERR_INVALID_PARAMETER; - bool fTimerActive = ASMAtomicReadBool(&pThis->fTimerActive); - bool fArmTimer = fTimerActive || fIsPlaying; - LogFlowFunc(("fTimerActive=%RTbool, fIsPlaying=%RTbool\n", fTimerActive, fIsPlaying)); + LogFlowFuncLeaveRC(rc); + return rc; +} - if (fDoTransfer) +/** + * Removes a driver stream from a specific mixer sink. + * + * @param pDevIns The device instance. + * @param pMixSink Mixer sink to remove audio streams from. + * @param enmDir Stream direction to remove. + * @param enmPath Stream destination / source to remove. + * @param pDrv Driver stream to remove. + */ +static void sb16RemoveDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir, + PDMAUDIOPATH enmPath, PSB16DRIVER pDrv) +{ + PSB16DRIVERSTREAM pDrvStream = sb16GetDrvStream(pDrv, enmDir, enmPath); + if (pDrvStream) { - /* Schedule the next transfer. */ - PDMDevHlpDMASchedule(pDevIns); + if (pDrvStream->pMixStrm) + { + LogFlowFunc(("[LUN#%RU8]\n", pDrv->uLUN)); - /* Arm the timer at least one more time. */ - fArmTimer = true; + AudioMixerSinkRemoveStream(pMixSink, pDrvStream->pMixStrm); + + AudioMixerStreamDestroy(pDrvStream->pMixStrm, pDevIns, false /*fImmediate*/); + pDrvStream->pMixStrm = NULL; + } } +} - /* - * Recording. - */ - /** @todo Implement recording. */ +/** + * Removes all driver streams from a specific mixer sink. + * + * @param pDevIns The device instance. + * @param pThis The SB16 state. + * @param pMixSink Mixer sink to remove audio streams from. + * @param enmDir Stream direction to remove. + * @param enmPath Stream destination / source to remove. + */ +static void sb16RemoveDrvStreams(PPDMDEVINS pDevIns, PSB16STATE pThis, PAUDMIXSINK pMixSink, + PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath) +{ + AssertPtrReturnVoid(pMixSink); - if (fArmTimer) + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) { - /* Arm the timer again. */ - uint64_t cTicks = pThis->cTicksTimerIOInterval; - /** @todo adjust cTicks down by now much cbOutMin represents. */ - PDMDevHlpTimerSet(pDevIns, pThis->hTimerIO, cTicksNow + cTicks); + sb16RemoveDrvStream(pDevIns, pMixSink, enmDir, enmPath, pDrv); } } +/** + * Adds a specific SB16 driver to the driver chain. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The SB16 device state. + * @param pDrv The SB16 driver to add. + */ +static int sb16AddDrv(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16DRIVER pDrv) +{ + int rc = VINF_SUCCESS; + + for (unsigned i = 0; i < SB16_MAX_STREAMS; i++) + { + if (AudioHlpStreamCfgIsValid(&pThis->aStreams[i].Cfg)) + { + int rc2 = sb16AddDrvStream(pDevIns, sb16StreamIndexToSink(pThis, pThis->aStreams[i].uIdx), + &pThis->aStreams[i].Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } -/* -=-=-=-=-=- Streams? -=-=-=-=-=- */ + return rc; +} /** - * Creates the output PDM audio stream for a specific driver. + * Removes a specific SB16 driver from the driver chain and destroys its + * associated streams. + * + * This is only used by sb16Detach. * - * @returns IPRT status code. - * @param pCfg Stream configuration to use. - * @param pDrv Driver stream to create PDM stream for. + * @param pDevIns The device instance. + * @param pThis The SB16 device state. + * @param pDrv SB16 driver to remove. */ -static int sb16CreateDrvStream(PPDMAUDIOSTREAMCFG pCfg, PSB16DRIVER pDrv) +static void sb16RemoveDrv(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16DRIVER pDrv) { - AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); - Assert(DrvAudioHlpStreamCfgIsValid(pCfg)); + RT_NOREF(pDevIns); - PPDMAUDIOSTREAMCFG pCfgHost = DrvAudioHlpStreamCfgDup(pCfg); - if (!pCfgHost) - return VERR_NO_MEMORY; + /** @todo We only implement one single output (playback) stream at the moment. */ - LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfgHost->szName)); + if (pDrv->Out.pMixStrm) + { + AudioMixerSinkRemoveStream(pThis->pSinkOut, pDrv->Out.pMixStrm); + AudioMixerStreamDestroy(pDrv->Out.pMixStrm, pDevIns, true /*fImmediate*/); + pDrv->Out.pMixStrm = NULL; + } - AssertMsg(pDrv->Out.pStream == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN)); + RTListNodeRemove(&pDrv->Node); +} - /* Disable pre-buffering for SB16; not needed for that bit of data. */ - pCfgHost->Backend.cFramesPreBuffering = 0; - int rc = pDrv->pConnector->pfnStreamCreate(pDrv->pConnector, pCfgHost, pCfg /* pCfgGuest */, &pDrv->Out.pStream); - if (RT_SUCCESS(rc)) +/********************************************************************************************************************************* +* Stream handling * +*********************************************************************************************************************************/ + +static int sb16StreamDoDmaOutput(PSB16STATE pThis, PSB16STREAM pStream, int uDmaChan, uint32_t offDma, uint32_t cbDma, + uint32_t cbToRead, uint32_t *pcbRead) +{ + uint32_t cbFree = (uint32_t)RTCircBufFree(pStream->State.pCircBuf); + //Assert(cbToRead <= cbFree); /** @todo Add statistics for overflows. */ + cbToRead = RT_MIN(cbToRead, cbFree); + + uint32_t cbReadTotal = 0; + while (cbToRead) + { + void *pv = NULL; + size_t cb = 0; + RTCircBufAcquireWriteBlock(pStream->State.pCircBuf, RT_MIN(cbDma - offDma, cbToRead), &pv, &cb); + + uint32_t cbRead = 0; + int rc = PDMDevHlpDMAReadMemory(pThis->pDevInsR3, uDmaChan, pv, offDma, (uint32_t)cb, &cbRead); + if (RT_SUCCESS(rc)) + Assert(cbRead == cb); + else + { + AssertMsgFailed(("Reading from DMA failed: %Rrc (cbReadTotal=%#x)\n", rc, cbReadTotal)); + RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, 0); + if (cbReadTotal > 0) + break; + *pcbRead = 0; + return rc; + } + + if (RT_LIKELY(!pStream->Dbg.Runtime.pFileDMA)) + { /* likely */ } + else + AudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMA, pv, cbRead); + + RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, cbRead); + + Assert(cbToRead >= cbRead); + pStream->State.offWrite += cbRead; + offDma = (offDma + cbRead) % cbDma; + cbReadTotal += cbRead; + cbToRead -= cbRead; + } + + *pcbRead = cbReadTotal; + + /* Update buffer stats. */ + pStream->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf); + + return VINF_SUCCESS; +} + +/** + * Enables or disables a SB16 audio stream. + * + * @returns VBox status code. + * @param pThis The SB16 state. + * @param pStream The SB16 stream to enable or disable. + * @param fEnable Whether to enable or disable the stream. + * @param fForce Whether to force re-opening the stream or not. + * Otherwise re-opening only will happen if the PCM properties have changed. + */ +static int sb16StreamEnable(PSB16STATE pThis, PSB16STREAM pStream, bool fEnable, bool fForce) +{ + if ( !fForce + && fEnable == pStream->State.fEnabled) + return VINF_SUCCESS; + + LogFlowFunc(("fEnable=%RTbool, fForce=%RTbool, fStreamEnabled=%RTbool\n", fEnable, fForce, pStream->State.fEnabled)); + + PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx); + AssertPtrReturn(pSink, VERR_INTERNAL_ERROR_2); + + /* We only need to register the AIO update job the first time around as the requence doesn't change. */ + int rc; + if (fEnable && !pStream->State.fRegisteredAsyncUpdateJob) + { + rc = AudioMixerSinkAddUpdateJob(pSink, sb16StreamUpdateAsyncIoJob, pStream, RT_MS_1SEC / pStream->uTimerHz); + AssertRC(rc); + pStream->State.fRegisteredAsyncUpdateJob = RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS; + } + + /* Tell the mixer. */ + if (fEnable) + { + rc = AudioMixerSinkStart(pSink); + AssertRCReturn(rc, rc); + } + else { - pDrv->pConnector->pfnStreamRetain(pDrv->pConnector, pDrv->Out.pStream); - LogFlowFunc(("LUN#%RU8: Created output \"%s\", rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc)); + rc = AudioMixerSinkDrainAndStop(pSink, pStream->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStream->State.pCircBuf) : 0); + AssertRCReturn(rc, rc); } - DrvAudioHlpStreamCfgFree(pCfgHost); + pStream->State.fEnabled = fEnable; return rc; } /** - * Destroys the output PDM audio stream of a specific driver. + * Retrieves the audio mixer sink of a corresponding SB16 stream. * - * @param pDrv Driver stream to destroy PDM stream for. + * @returns Pointer to audio mixer sink if found, or NULL if not found / invalid. + * @param pThis The SB16 state. + * @param uIdx Stream index to get audio mixer sink for. */ -static void sb16DestroyDrvStreamOut(PSB16DRIVER pDrv) +DECLINLINE(PAUDMIXSINK) sb16StreamIndexToSink(PSB16STATE pThis, uint8_t uIdx) { - AssertPtr(pDrv); + AssertReturn(uIdx <= SB16_MAX_STREAMS, NULL); - if (pDrv->Out.pStream) - { - pDrv->pConnector->pfnStreamRelease(pDrv->pConnector, pDrv->Out.pStream); + /* Dead simple for now; make this more sophisticated if we have more stuff to cover. */ + if (uIdx == SB16_IDX_OUT) + return pThis->pSinkOut; /* Can be NULL if not configured / set up yet. */ - int rc2 = pDrv->pConnector->pfnStreamControl(pDrv->pConnector, pDrv->Out.pStream, PDMAUDIOSTREAMCMD_DISABLE); - AssertRC(rc2); + AssertMsgFailed(("No sink attached (yet) for index %RU8\n", uIdx)); + return NULL; +} + +/** + * Returns the audio direction of a specified stream descriptor. + * + * @returns Audio direction. + * @param uIdx Stream index to get audio direction for. + */ +DECLINLINE(PDMAUDIODIR) sb16GetDirFromIndex(uint8_t uIdx) +{ + AssertReturn(uIdx <= SB16_MAX_STREAMS, PDMAUDIODIR_INVALID); + + /* Dead simple for now; make this more sophisticated if we have more stuff to cover. */ + if (uIdx == SB16_IDX_OUT) + return PDMAUDIODIR_OUT; + + return PDMAUDIODIR_INVALID; +} - rc2 = pDrv->pConnector->pfnStreamDestroy(pDrv->pConnector, pDrv->Out.pStream); +/** + * Creates a SB16 audio stream. + * + * @returns VBox status code. + * @param pThis The SB16 state. + * @param pStream The SB16 stream to create. + * @param uIdx Stream index to assign. + */ +static int sb16StreamCreate(PSB16STATE pThis, PSB16STREAM pStream, uint8_t uIdx) +{ + LogFlowFuncEnter(); + + pStream->Dbg.Runtime.fEnabled = pThis->Dbg.fEnabled; + + if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled)) + { /* likely */ } + else + { + int rc2 = AudioHlpFileCreateF(&pStream->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV, + pThis->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/, + sb16GetDirFromIndex(pStream->uIdx) == PDMAUDIODIR_IN + ? "sb16StreamWriteSD%RU8" : "sb16StreamReadSD%RU8", pStream->uIdx); AssertRC(rc2); - pDrv->Out.pStream = NULL; + /* Delete stale debugging files from a former run. */ + AudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA); } + + pStream->uIdx = uIdx; + + return VINF_SUCCESS; } /** - * Checks if the output stream needs to be (re-)created and does so if needed. + * Destroys a SB16 audio stream. * - * @return VBox status code. - * @param pDevIns The device instance. - * @param pThis SB16 state. + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The SB16 state. + * @param pStream The SB16 stream to destroy. */ -static int sb16CheckAndReOpenOut(PPDMDEVINS pDevIns, PSB16STATE pThis) +static int sb16StreamDestroy(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream) { - AssertPtr(pThis); + LogFlowFuncEnter(); - int rc = VINF_SUCCESS; + sb16StreamClose(pDevIns, pThis, pStream); - if (pThis->freq > 0) + if (pStream->State.fRegisteredAsyncUpdateJob) { - /* At the moment we only have one stream, the output stream. */ - PDMAUDIOSTREAMCFG Cfg; - RT_ZERO(Cfg); + PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx); + if (pSink) + AudioMixerSinkRemoveUpdateJob(pSink, sb16StreamUpdateAsyncIoJob, pStream); + pStream->State.fRegisteredAsyncUpdateJob = false; + } - Cfg.Props.uHz = pThis->freq; - Cfg.Props.cChannels = 1 << pThis->fmt_stereo; - Cfg.Props.cbSample = pThis->fmt_bits / 8; - Cfg.Props.fSigned = RT_BOOL(pThis->fmt_signed); - Cfg.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Cfg.Props.cbSample, Cfg.Props.cChannels); + if (pStream->State.pCircBuf) + { + RTCircBufDestroy(pStream->State.pCircBuf); + pStream->State.pCircBuf = NULL; + } - if (!DrvAudioHlpPCMPropsAreEqual(&Cfg.Props, &pThis->Out.Cfg.Props)) - { - Cfg.enmDir = PDMAUDIODIR_OUT; - Cfg.u.enmDst = PDMAUDIOPLAYBACKDST_FRONT; - Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled)) + { /* likely */ } + else + { + AudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMA); + pStream->Dbg.Runtime.pFileDMA = NULL; + } + + pStream->uIdx = UINT8_MAX; + + return VINF_SUCCESS; +} + +/** + * Resets a SB16 stream. + * + * @param pThis The SB16 state. + * @param pStream The SB16 stream to reset. + */ +static void sb16StreamReset(PSB16STATE pThis, PSB16STREAM pStream) +{ + LogFlowFuncEnter(); - strcpy(Cfg.szName, "Output"); + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0); + if (pStream->dma_auto) + { + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1); + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0); + + pStream->dma_auto = 0; + } - sb16CloseOut(pThis); + sb16StreamControl(pThis->pDevInsR3, pThis, pStream, false /* fRun */); + sb16StreamEnable(pThis, pStream, false /* fEnable */, false /* fForce */); + + switch (pStream->uIdx) + { + case SB16_IDX_OUT: + { + pStream->Cfg.enmDir = PDMAUDIODIR_OUT; + pStream->Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT; - rc = sb16OpenOut(pDevIns, pThis, &Cfg); - AssertRC(rc); + PDMAudioPropsInit(&pStream->Cfg.Props, 1 /* 8-bit */, false /* fSigned */, 1 /* Mono */, 11025 /* uHz */); + RTStrCopy(pStream->Cfg.szName, sizeof(pStream->Cfg.szName), "Output"); + break; } + + default: + AssertFailed(); + break; } - else - sb16CloseOut(pThis); - LogFlowFuncLeaveRC(rc); - return rc; + pStream->cbDmaLeft = 0; + pStream->cbDmaBlockSize = 0; + pStream->can_write = 1; /** @ŧodo r=andy BUGBUG Figure out why we (still) need this. */ + + /** @todo Also reset corresponding DSP values here? */ } -static int sb16OpenOut(PPDMDEVINS pDevIns, PSB16STATE pThis, PPDMAUDIOSTREAMCFG pCfg) +/** + * Opens a SB16 stream with its current mixer settings. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The SB16 device state. + * @param pStream The SB16 stream to open. + * + * @note This currently only supports the one and only output stream. + */ +static int sb16StreamOpen(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream) { LogFlowFuncEnter(); - AssertPtr(pThis); - AssertPtr(pCfg); + AssertLogRelReturn(PDMAudioPropsAreValid(&pStream->Cfg.Props), VERR_INTERNAL_ERROR_5); + + switch (pStream->uIdx) + { + case SB16_IDX_OUT: + pStream->Cfg.enmDir = PDMAUDIODIR_OUT; + pStream->Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT; + + RTStrCopy(pStream->Cfg.szName, sizeof(pStream->Cfg.szName), "Output"); + break; + + default: + AssertFailed(); + break; + } + + LogRel2(("SB16: (Re-)Opening stream '%s' (%RU32Hz, %RU8 channels, %s%RU8)\n", pStream->Cfg.szName, pStream->Cfg.Props.uHz, + PDMAudioPropsChannels(&pStream->Cfg.Props), pStream->Cfg.Props.fSigned ? "S" : "U", + PDMAudioPropsSampleBits(&pStream->Cfg.Props))); + + /* (Re-)create the stream's internal ring buffer. */ + if (pStream->State.pCircBuf) + { + RTCircBufDestroy(pStream->State.pCircBuf); + pStream->State.pCircBuf = NULL; + } + + /** @todo r=bird: two DMA periods is probably too little. */ + const uint32_t cbCircBuf = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props, + (RT_MS_1SEC / pStream->uTimerHz) * 2 /* Use double buffering here */); + + int rc = RTCircBufCreate(&pStream->State.pCircBuf, cbCircBuf); + AssertRCReturn(rc, rc); + pStream->State.StatDmaBufSize = (uint32_t)RTCircBufSize(pStream->State.pCircBuf); + + /* Set scheduling hint. */ + pStream->Cfg.Device.cMsSchedulingHint = RT_MS_1SEC / RT_MIN(pStream->uTimerHz, 1); + + PAUDMIXSINK pMixerSink = sb16StreamIndexToSink(pThis, pStream->uIdx); + AssertPtrReturn(pMixerSink, VERR_INVALID_POINTER); - if (!DrvAudioHlpStreamCfgIsValid(pCfg)) - return VERR_INVALID_PARAMETER; + sb16RemoveDrvStreams(pDevIns, pThis, + sb16StreamIndexToSink(pThis, pStream->uIdx), pStream->Cfg.enmDir, pStream->Cfg.enmPath); - int rc = DrvAudioHlpStreamCfgCopy(&pThis->Out.Cfg, pCfg); + rc = sb16AddDrvStreams(pDevIns, pThis, pMixerSink, &pStream->Cfg); if (RT_SUCCESS(rc)) { - /* Set scheduling hint (if available). */ - if (pThis->cTicksTimerIOInterval) - pThis->Out.Cfg.Device.cMsSchedulingHint = 1000 /* ms */ - / ( PDMDevHlpTimerGetFreq(pDevIns, pThis->hTimerIO) - / RT_MIN(pThis->cTicksTimerIOInterval, 1)); - - PSB16DRIVER pDrv; - RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) - { - int rc2 = sb16CreateDrvStream(&pThis->Out.Cfg, pDrv); - if (RT_FAILURE(rc2)) - LogFunc(("Attaching stream failed with %Rrc\n", rc2)); + if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled)) + { /* likely */ } + else + { + /* Make sure to close + delete a former debug file, as the PCM format has changed (e.g. U8 -> S16). */ + if (AudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMA)) + { + AudioHlpFileClose(pStream->Dbg.Runtime.pFileDMA); + AudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA); + } - /* Do not pass failure to rc here, as there might be drivers which aren't - * configured / ready yet. */ + int rc2 = AudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS, + &pStream->Cfg.Props); + AssertRC(rc2); } - - sb16UpdateVolume(pThis); } LogFlowFuncLeaveRC(rc); return rc; } -static void sb16CloseOut(PSB16STATE pThis) +/** + * Closes a SB16 stream. + * + * @param pDevIns The device instance. + * @param pThis SB16 state. + * @param pStream The SB16 stream to close. + */ +static void sb16StreamClose(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream) { + RT_NOREF(pDevIns, pThis, pStream); + LogFlowFuncEnter(); - AssertPtr(pThis); - PSB16DRIVER pDrv; - RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + /* Nothing to do in here right now. */ +} + +static void sb16StreamTransferScheduleNext(PSB16STATE pThis, PSB16STREAM pStream, uint32_t cbBytes) +{ + RT_NOREF(pStream); + + uint64_t const uTimerHz = PDMDevHlpTimerGetFreq(pThis->pDevInsR3, pThis->hTimerIRQ); + + const uint64_t usBytes = PDMAudioPropsBytesToMicro(&pStream->Cfg.Props, cbBytes); + const uint64_t cTransferTicks = PDMDevHlpTimerFromMicro(pThis->pDevInsR3, pThis->hTimerIRQ, usBytes); + + LogFlowFunc(("%RU32 bytes -> %RU64 ticks\n", cbBytes, cTransferTicks)); + + if (cTransferTicks < uTimerHz / 1024) /** @todo Explain this. */ + { + LogFlowFunc(("IRQ\n")); + PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1); + } + else { - sb16DestroyDrvStreamOut(pDrv); + LogFlowFunc(("Scheduled\n")); + PDMDevHlpTimerSetRelative(pThis->pDevInsR3, pThis->hTimerIRQ, cTransferTicks, NULL); } +} + - LogFlowFuncLeave(); +/** + * Output streams: Pushes data to the mixer. + * + * @param pStream The SB16 stream. + * @param pSink The mixer sink to push to. + */ +static void sb16StreamPushToMixer(PSB16STREAM pStream, PAUDMIXSINK pSink) +{ +#ifdef LOG_ENABLED + uint64_t const offReadOld = pStream->State.offRead; +#endif + pStream->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink, + pStream->State.pCircBuf, + pStream->State.offRead, + pStream->uIdx, + /** @todo pStream->Dbg.Runtime.fEnabled + ? pStream->Dbg.Runtime.pFileStream :*/ NULL); + + Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStream->uIdx, + pStream->State.offRead - offReadOld, pStream->State.offRead)); + + /* Update buffer stats. */ + pStream->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf); } -/* -=-=-=-=-=- Saved state -=-=-=-=-=- */ +/** + * @callback_method_impl{FNAUDMIXSINKUPDATE} + * + * For output streams this moves data from the internal DMA buffer (in which + * ichac97R3StreamUpdateDma put it), thru the mixer and to the various backend + * audio devices. + */ +static DECLCALLBACK(void) sb16StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser) +{ + PSB16STATE const pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); + PSB16STREAM const pStream = (PSB16STREAM)pvUser; + Assert(pStream->uIdx == (uintptr_t)(pStream - &pThis->aStreams[0])); + Assert(pSink == sb16StreamIndexToSink(pThis, pStream->uIdx)); + RT_NOREF(pThis); + + /* + * Output. + */ + if (sb16GetDirFromIndex(pStream->uIdx) == PDMAUDIODIR_OUT) + sb16StreamPushToMixer(pStream, pSink); + /* + * No input streams at present. + */ + else + AssertFailed(); +} + + +/********************************************************************************************************************************* +* Saved state handling * +*********************************************************************************************************************************/ /** * @callback_method_impl{FNSSMDEVLIVEEXEC} */ static DECLCALLBACK(int) sb16LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass) { + RT_NOREF(uPass); + PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - RT_NOREF(uPass); - pHlp->pfnSSMPutS32(pSSM, pThis->irqCfg); - pHlp->pfnSSMPutS32(pSSM, pThis->dmaCfg); - pHlp->pfnSSMPutS32(pSSM, pThis->hdmaCfg); - pHlp->pfnSSMPutS32(pSSM, pThis->portCfg); - pHlp->pfnSSMPutS32(pSSM, pThis->verCfg); + /** Currently the saved state only contains the one-and-only output stream. */ + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; + + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uIrq); + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uDmaChanLow); + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uDmaChanHigh); + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uPort); + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uVer); return VINF_SSM_DONT_CALL_AGAIN; } @@ -1934,30 +2371,33 @@ */ static int sb16Save(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PSB16STATE pThis) { - pHlp->pfnSSMPutS32(pSSM, pThis->irq); - pHlp->pfnSSMPutS32(pSSM, pThis->dma); - pHlp->pfnSSMPutS32(pSSM, pThis->hdma); - pHlp->pfnSSMPutS32(pSSM, pThis->port); - pHlp->pfnSSMPutS32(pSSM, pThis->ver); - pHlp->pfnSSMPutS32(pSSM, pThis->in_index); - pHlp->pfnSSMPutS32(pSSM, pThis->out_data_len); - pHlp->pfnSSMPutS32(pSSM, pThis->fmt_stereo); - pHlp->pfnSSMPutS32(pSSM, pThis->fmt_signed); - pHlp->pfnSSMPutS32(pSSM, pThis->fmt_bits); - - pHlp->pfnSSMPutU32(pSSM, pThis->fmt); - - pHlp->pfnSSMPutS32(pSSM, pThis->dma_auto); - pHlp->pfnSSMPutS32(pSSM, pThis->block_size); - pHlp->pfnSSMPutS32(pSSM, pThis->fifo); - pHlp->pfnSSMPutS32(pSSM, pThis->freq); - pHlp->pfnSSMPutS32(pSSM, pThis->time_const); - pHlp->pfnSSMPutS32(pSSM, pThis->speaker); - pHlp->pfnSSMPutS32(pSSM, pThis->needed_bytes); + /* The saved state only contains the one-and-only output stream. */ + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; + + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uIrq); + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uDmaChanLow); + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uDmaChanHigh); + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uPort); + pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uVer); + pHlp->pfnSSMPutS32(pSSM, pThis->dsp_in_idx); + pHlp->pfnSSMPutS32(pSSM, pThis->dsp_out_data_len); + + pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsChannels(&pStream->Cfg.Props) >= 2 ? 1 : 0); + pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsIsSigned(&pStream->Cfg.Props) ? 1 : 0); + pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsSampleBits(&pStream->Cfg.Props)); + pHlp->pfnSSMPutU32(pSSM, 0); /* Legacy; was PDMAUDIOFMT, unused now. */ + + pHlp->pfnSSMPutS32(pSSM, pStream->dma_auto); + pHlp->pfnSSMPutS32(pSSM, pStream->cbDmaBlockSize); + pHlp->pfnSSMPutS32(pSSM, pStream->fifo); + pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsHz(&pStream->Cfg.Props)); + pHlp->pfnSSMPutS32(pSSM, pStream->time_const); + pHlp->pfnSSMPutS32(pSSM, 0); /* Legacy; was speaker control (on/off) for output stream. */ + pHlp->pfnSSMPutS32(pSSM, pThis->dsp_in_needed_bytes); pHlp->pfnSSMPutS32(pSSM, pThis->cmd); - pHlp->pfnSSMPutS32(pSSM, pThis->use_hdma); + pHlp->pfnSSMPutS32(pSSM, pStream->fDmaUseHigh); pHlp->pfnSSMPutS32(pSSM, pThis->highspeed); - pHlp->pfnSSMPutS32(pSSM, pThis->can_write); + pHlp->pfnSSMPutS32(pSSM, pStream->can_write); pHlp->pfnSSMPutS32(pSSM, pThis->v2x6); pHlp->pfnSSMPutU8 (pSSM, pThis->csp_param); @@ -1970,16 +2410,20 @@ pHlp->pfnSSMPutS32(pSSM, pThis->csp_reg83r); pHlp->pfnSSMPutS32(pSSM, pThis->csp_reg83w); - pHlp->pfnSSMPutMem(pSSM, pThis->in2_data, sizeof(pThis->in2_data)); - pHlp->pfnSSMPutMem(pSSM, pThis->out_data, sizeof(pThis->out_data)); + pHlp->pfnSSMPutMem(pSSM, pThis->dsp_in_data, sizeof(pThis->dsp_in_data)); + pHlp->pfnSSMPutMem(pSSM, pThis->dsp_out_data, sizeof(pThis->dsp_out_data)); pHlp->pfnSSMPutU8 (pSSM, pThis->test_reg); pHlp->pfnSSMPutU8 (pSSM, pThis->last_read_byte); pHlp->pfnSSMPutS32(pSSM, pThis->nzero); - pHlp->pfnSSMPutS32(pSSM, pThis->left_till_irq); - pHlp->pfnSSMPutS32(pSSM, pThis->dma_running); - pHlp->pfnSSMPutS32(pSSM, pThis->bytes_per_second); - pHlp->pfnSSMPutS32(pSSM, pThis->align); + pHlp->pfnSSMPutS32(pSSM, pStream->cbDmaLeft); + pHlp->pfnSSMPutS32(pSSM, pStream->State.fEnabled ? 1 : 0); + /* The stream's bitrate. Needed for backwards (legacy) compatibility. */ + pHlp->pfnSSMPutS32(pSSM, AudioHlpCalcBitrate(PDMAudioPropsSampleBits(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props), + PDMAudioPropsHz(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props), + PDMAudioPropsChannels(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props))); + /* Block size alignment, superfluous and thus not saved anymore. Needed for backwards (legacy) compatibility. */ + pHlp->pfnSSMPutS32(pSSM, 0); pHlp->pfnSSMPutS32(pSSM, pThis->mixer_nreg); return pHlp->pfnSSMPutMem(pSSM, pThis->mixer_regs, 256); @@ -2003,66 +2447,84 @@ static int sb16Load(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PSB16STATE pThis) { PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; /* The saved state only contains the one-and-only output stream. */ + int rc; + + int32_t i32Tmp; + pHlp->pfnSSMGetS32(pSSM, &i32Tmp); + pStream->HwCfgRuntime.uIrq = i32Tmp; /* IRQ. */ + pHlp->pfnSSMGetS32(pSSM, &i32Tmp); + pStream->HwCfgRuntime.uDmaChanLow = i32Tmp; /* Low (8-bit) DMA channel. */ + pHlp->pfnSSMGetS32(pSSM, &i32Tmp); + pStream->HwCfgRuntime.uDmaChanHigh = i32Tmp; /* High (16-bit) DMA channel. */ + pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Used I/O port. */ + pStream->HwCfgRuntime.uPort = i32Tmp; + pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* DSP version running. */ + pStream->HwCfgRuntime.uVer = i32Tmp; + pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_in_idx); + pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_out_data_len); + + rc = pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Number of channels. */ + AssertRCReturn(rc, rc); + AssertReturn((uint32_t)i32Tmp <= 1, VERR_INVALID_PARAMETER); /* Paranoia. */ + if (i32Tmp) /* PDMAudioPropsSetChannels() will assert if channels are 0 (will be re-set on DMA run command). */ + PDMAudioPropsSetChannels(&pStream->Cfg.Props, (uint8_t)i32Tmp); + pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Signed format bit. */ + pStream->Cfg.Props.fSigned = i32Tmp != 0; + rc = pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Sample size in bits. */ + AssertRCReturn(rc, rc); + if (i32Tmp) /* PDMAudioPropsSetSampleSize() will assert if sample size is 0 (will be re-set on DMA run command). */ + PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, (uint8_t)(i32Tmp / 8)); - pHlp->pfnSSMGetS32(pSSM, &pThis->irq); - pHlp->pfnSSMGetS32(pSSM, &pThis->dma); - pHlp->pfnSSMGetS32(pSSM, &pThis->hdma); - pHlp->pfnSSMGetS32(pSSM, &pThis->port); - pHlp->pfnSSMGetS32(pSSM, &pThis->ver); - pHlp->pfnSSMGetS32(pSSM, &pThis->in_index); - pHlp->pfnSSMGetS32(pSSM, &pThis->out_data_len); - pHlp->pfnSSMGetS32(pSSM, &pThis->fmt_stereo); - pHlp->pfnSSMGetS32(pSSM, &pThis->fmt_signed); - pHlp->pfnSSMGetS32(pSSM, &pThis->fmt_bits); - - PDMDEVHLP_SSM_GET_ENUM32_RET(pHlp, pSSM, pThis->fmt, PDMAUDIOFMT); - - pHlp->pfnSSMGetS32(pSSM, &pThis->dma_auto); - pHlp->pfnSSMGetS32(pSSM, &pThis->block_size); - pHlp->pfnSSMGetS32(pSSM, &pThis->fifo); - pHlp->pfnSSMGetS32(pSSM, &pThis->freq); - pHlp->pfnSSMGetS32(pSSM, &pThis->time_const); - pHlp->pfnSSMGetS32(pSSM, &pThis->speaker); - pHlp->pfnSSMGetS32(pSSM, &pThis->needed_bytes); + pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was PDMAUDIOFMT, unused now. */ + pHlp->pfnSSMGetS32(pSSM, &pStream->dma_auto); + pHlp->pfnSSMGetS32(pSSM, &pStream->cbDmaBlockSize); + pHlp->pfnSSMGetS32(pSSM, &pStream->fifo); + pHlp->pfnSSMGetS32(pSSM, &i32Tmp); pStream->Cfg.Props.uHz = i32Tmp; + pHlp->pfnSSMGetS32(pSSM, &pStream->time_const); + pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was speaker (on / off) for output stream. */ + pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_in_needed_bytes); pHlp->pfnSSMGetS32(pSSM, &pThis->cmd); - pHlp->pfnSSMGetS32(pSSM, &pThis->use_hdma); + pHlp->pfnSSMGetS32(pSSM, &pStream->fDmaUseHigh); /* Output stream: Whether to use the high or low DMA channel. */ pHlp->pfnSSMGetS32(pSSM, &pThis->highspeed); - pHlp->pfnSSMGetS32(pSSM, &pThis->can_write); + pHlp->pfnSSMGetS32(pSSM, &pStream->can_write); pHlp->pfnSSMGetS32(pSSM, &pThis->v2x6); pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param); pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_value); pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_mode); - pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param); /* Bug compatible! */ + pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param); /* Bug compatible! */ pHlp->pfnSSMGetMem(pSSM, pThis->csp_regs, 256); pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_index); pHlp->pfnSSMGetMem(pSSM, pThis->csp_reg83, 4); pHlp->pfnSSMGetS32(pSSM, &pThis->csp_reg83r); pHlp->pfnSSMGetS32(pSSM, &pThis->csp_reg83w); - pHlp->pfnSSMGetMem(pSSM, pThis->in2_data, sizeof(pThis->in2_data)); - pHlp->pfnSSMGetMem(pSSM, pThis->out_data, sizeof(pThis->out_data)); + pHlp->pfnSSMGetMem(pSSM, pThis->dsp_in_data, sizeof(pThis->dsp_in_data)); + pHlp->pfnSSMGetMem(pSSM, pThis->dsp_out_data, sizeof(pThis->dsp_out_data)); pHlp->pfnSSMGetU8 (pSSM, &pThis->test_reg); pHlp->pfnSSMGetU8 (pSSM, &pThis->last_read_byte); pHlp->pfnSSMGetS32(pSSM, &pThis->nzero); - pHlp->pfnSSMGetS32(pSSM, &pThis->left_till_irq); - pHlp->pfnSSMGetS32(pSSM, &pThis->dma_running); - pHlp->pfnSSMGetS32(pSSM, &pThis->bytes_per_second); - pHlp->pfnSSMGetS32(pSSM, &pThis->align); + pHlp->pfnSSMGetS32(pSSM, &pStream->cbDmaLeft); + pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: DMA currently running bit. */ + const bool fStreamEnabled = i32Tmp != 0; + pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was the output stream's current bitrate (in bytes). */ + pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was the output stream's DMA block alignment. */ int32_t mixer_nreg = 0; - int rc = pHlp->pfnSSMGetS32(pSSM, &mixer_nreg); + rc = pHlp->pfnSSMGetS32(pSSM, &mixer_nreg); AssertRCReturn(rc, rc); pThis->mixer_nreg = (uint8_t)mixer_nreg; rc = pHlp->pfnSSMGetMem(pSSM, pThis->mixer_regs, 256); AssertRCReturn(rc, rc); - if (pThis->dma_running) + if (fStreamEnabled) { - sb16CheckAndReOpenOut(pDevIns, pThis); - sb16Control(pDevIns, pThis, 1); - sb16SpeakerControl(pThis, pThis->speaker); + /* Sanity: If stream is going be enabled, PCM props must be valid. Otherwise the saved state is borked somehow. */ + AssertMsgReturn(AudioHlpPcmPropsAreValid(&pStream->Cfg.Props), + ("PCM properties for stream #%RU8 are invalid\n", pStream->uIdx), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */); } /* Update the master (mixer) and PCM out volumes. */ @@ -2085,6 +2547,9 @@ VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); if (uVersion > SB16_SAVE_STATE_VERSION_VBOX_30) { + /** Currently the saved state only contains the one-and-only output stream. */ + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; + int32_t irq; pHlp->pfnSSMGetS32(pSSM, &irq); int32_t dma; @@ -2097,19 +2562,19 @@ int rc = pHlp->pfnSSMGetS32(pSSM, &ver); AssertRCReturn (rc, rc); - if ( irq != pThis->irqCfg - || dma != pThis->dmaCfg - || hdma != pThis->hdmaCfg - || port != pThis->portCfg - || ver != pThis->verCfg) + if ( irq != pStream->HwCfgDefault.uIrq + || dma != pStream->HwCfgDefault.uDmaChanLow + || hdma != pStream->HwCfgDefault.uDmaChanHigh + || port != pStream->HwCfgDefault.uPort + || ver != pStream->HwCfgDefault.uVer) { return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("config changed: irq=%x/%x dma=%x/%x hdma=%x/%x port=%x/%x ver=%x/%x (saved/config)"), - irq, pThis->irqCfg, - dma, pThis->dmaCfg, - hdma, pThis->hdmaCfg, - port, pThis->portCfg, - ver, pThis->verCfg); + irq, pStream->HwCfgDefault.uIrq, + dma, pStream->HwCfgDefault.uDmaChanLow, + hdma, pStream->HwCfgDefault.uDmaChanHigh, + port, pStream->HwCfgDefault.uPort, + ver, pStream->HwCfgDefault.uVer); } } @@ -2120,7 +2585,26 @@ } -/* -=-=-=-=-=- IBase -=-=-=-=-=- */ +/********************************************************************************************************************************* +* Debug Info Items * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, sb16mixer} + */ +static DECLCALLBACK(void) sb16DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); + if (pThis->pMixer) + AudioMixerDebug(pThis->pMixer, pHlp, pszArgs); + else + pHlp->pfnPrintf(pHlp, "Mixer not available\n"); +} + + +/********************************************************************************************************************************* +* IBase implementation * +*********************************************************************************************************************************/ /** * @interface_method_impl{PDMIBASE,pfnQueryInterface} @@ -2135,104 +2619,84 @@ } -/* -=-=-=-=-=- Device -=-=-=-=-=- */ +/********************************************************************************************************************************* +* Device (PDM) handling * +*********************************************************************************************************************************/ /** - * Attach command, internal version. - * - * This is called to let the device attach to a driver for a specified LUN - * during runtime. This is not called during VM construction, the device - * constructor has to attach to all the available drivers. + * Worker for sb16Construct() and sb16Attach(). * * @returns VBox status code. * @param pThis SB16 state. * @param uLUN The logical unit which is being detached. - * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. * @param ppDrv Attached driver instance on success. Optional. */ -static int sb16AttachInternal(PSB16STATE pThis, unsigned uLUN, uint32_t fFlags, PSB16DRIVER *ppDrv) +static int sb16AttachInternal(PSB16STATE pThis, unsigned uLUN, PSB16DRIVER *ppDrv) { - RT_NOREF(fFlags); - /* - * Attach driver. + * Allocate a new driver structure and try attach the driver. */ PSB16DRIVER pDrv = (PSB16DRIVER)RTMemAllocZ(sizeof(SB16DRIVER)); - AssertReturn(pDrv, VERR_NO_MEMORY); + AssertPtrReturn(pDrv, VERR_NO_MEMORY); RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (SB16) for LUN #%u", uLUN); PPDMIBASE pDrvBase; int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN, &pThis->IBase, &pDrvBase, pDrv->szDesc); if (RT_SUCCESS(rc)) { - pDrv->pDrvBase = pDrvBase; pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR); - AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN #%u has no host audio interface, rc=%Rrc\n", uLUN, rc)); - pDrv->pSB16State = pThis; - pDrv->uLUN = uLUN; - - /* - * For now we always set the driver at LUN 0 as our primary - * host backend. This might change in the future. - */ - if (pDrv->uLUN == 0) - pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY; + AssertPtr(pDrv->pConnector); + if (RT_VALID_PTR(pDrv->pConnector)) + { + pDrv->pDrvBase = pDrvBase; + pDrv->pSB16State = pThis; + pDrv->uLUN = uLUN; - LogFunc(("LUN#%u: pCon=%p, drvFlags=0x%x\n", uLUN, pDrv->pConnector, pDrv->fFlags)); + /* Attach to driver list if not attached yet. */ + if (!pDrv->fAttached) + { + RTListAppend(&pThis->lstDrv, &pDrv->Node); + pDrv->fAttached = true; + } - /* Attach to driver list if not attached yet. */ - if (!pDrv->fAttached) - { - RTListAppend(&pThis->lstDrv, &pDrv->Node); - pDrv->fAttached = true; + if (ppDrv) + *ppDrv = pDrv; + LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector)); + return VINF_SUCCESS; } - - if (ppDrv) - *ppDrv = pDrv; + rc = VERR_PDM_MISSING_INTERFACE_BELOW; } + else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + LogFunc(("No attached driver for LUN #%u\n", uLUN)); else - { - if (rc == VERR_PDM_NO_ATTACHED_DRIVER) - LogFunc(("No attached driver for LUN #%u\n", uLUN)); - RTMemFree(pDrv); - } + LogFunc(("Failed to attached driver for LUN #%u: %Rrc\n", uLUN, rc)); + RTMemFree(pDrv); - LogFunc(("iLUN=%u, fFlags=0x%x, rc=%Rrc\n", uLUN, fFlags, rc)); + LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc)); return rc; } /** - * Detach command, internal version. - * - * This is called to let the device detach from a driver for a specified LUN - * during runtime. - * - * @returns VBox status code. - * @param pDrv Driver to detach device from. - */ -static int sb16DetachInternal(PSB16DRIVER pDrv) -{ - sb16DestroyDrvStreamOut(pDrv); - RTListNodeRemove(&pDrv->Node); - LogFunc(("uLUN=%u\n", pDrv->uLUN)); - return VINF_SUCCESS; -} - -/** * @interface_method_impl{PDMDEVREG,pfnAttach} */ static DECLCALLBACK(int) sb16Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) { PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); + RT_NOREF(fFlags); + LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags)); - LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags)); + /** @todo r=andy Any locking required here? */ PSB16DRIVER pDrv; - int rc2 = sb16AttachInternal(pThis, iLUN, fFlags, &pDrv); - if (RT_SUCCESS(rc2)) - rc2 = sb16CreateDrvStream(&pThis->Out.Cfg, pDrv); + int rc = sb16AttachInternal(pThis, iLUN, &pDrv); + if (RT_SUCCESS(rc)) + { + int rc2 = sb16AddDrv(pDevIns, pThis, pDrv); + if (RT_FAILURE(rc2)) + LogFunc(("sb16AddDrv failed with %Rrc (ignored)\n", rc2)); + } - return VINF_SUCCESS; + return rc; } /** @@ -2245,37 +2709,19 @@ LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags)); - PSB16DRIVER pDrv, pDrvNext; - RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, SB16DRIVER, Node) + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) { if (pDrv->uLUN == iLUN) { - int rc2 = sb16DetachInternal(pDrv); - if (RT_SUCCESS(rc2)) - { - RTMemFree(pDrv); - pDrv = NULL; - } - break; + sb16RemoveDrv(pDevIns, pThis, pDrv); + RTMemFree(pDrv); + return; } } + LogFunc(("LUN#%u was not found\n", iLUN)); } -/** - * Replaces a driver with a the NullAudio drivers. - * - * @returns VBox status code. - * @param pThis Device instance. - * @param iLun The logical unit which is being replaced. - */ -static int sb16ReconfigLunWithNullAudio(PSB16STATE pThis, unsigned iLun) -{ - int rc = PDMDevHlpDriverReconfigure2(pThis->pDevInsR3, iLun, "AUDIO", "NullAudio"); - if (RT_SUCCESS(rc)) - rc = sb16AttachInternal(pThis, iLun, 0 /* fFlags */, NULL /* ppDrv */); - LogFunc(("pThis=%p, iLun=%u, rc=%Rrc\n", pThis, iLun, rc)); - return rc; -} /** * @interface_method_impl{PDMDEVREG,pfnReset} @@ -2284,21 +2730,15 @@ { PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE); - /* Bring back the device to initial state, and especially make - * sure there's no interrupt or DMA activity. - */ - PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + LogRel2(("SB16: Reset\n")); pThis->mixer_regs[0x82] = 0; pThis->csp_regs[5] = 1; pThis->csp_regs[9] = 0xf8; - pThis->dma_auto = 0; - pThis->in_index = 0; - pThis->out_data_len = 0; - pThis->left_till_irq = 0; - pThis->needed_bytes = 0; - pThis->block_size = -1; + pThis->dsp_in_idx = 0; + pThis->dsp_out_data_len = 0; + pThis->dsp_in_needed_bytes = 0; pThis->nzero = 0; pThis->highspeed = 0; pThis->v2x6 = 0; @@ -2306,8 +2746,7 @@ sb16MixerReset(pThis); sb16SpeakerControl(pThis, 0); - sb16Control(pDevIns, pThis, 0); - sb16CmdResetLegacy(pThis); + sb16DspCmdResetLegacy(pThis); } /** @@ -2321,10 +2760,31 @@ LogRel2(("SB16: Powering off ...\n")); - PSB16DRIVER pDrv; - RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + /* + * Destroy all streams. + */ + for (unsigned i = 0; i < SB16_MAX_STREAMS; i++) + sb16StreamDestroy(pDevIns, pThis, &pThis->aStreams[i]); + + /* + * Destroy all sinks. + */ + if (pThis->pSinkOut) { - sb16DestroyDrvStreamOut(pDrv); + AudioMixerSinkDestroy(pThis->pSinkOut, pDevIns); + pThis->pSinkOut = NULL; + } + /** @todo Ditto for sinks. */ + + /* + * Note: Destroy the mixer while powering off and *not* in sb16Destruct, + * giving the mixer the chance to release any references held to + * PDM audio streams it maintains. + */ + if (pThis->pMixer) + { + AudioMixerDestroy(pThis->pMixer, pDevIns); + pThis->pMixer = NULL; } } @@ -2347,6 +2807,13 @@ RTMemFree(pDrv); } + /* We don't always go via PowerOff, so make sure the mixer is destroyed. */ + if (pThis->pMixer) + { + AudioMixerDestroy(pThis->pMixer, pDevIns); + pThis->pMixer = NULL; + } + return VINF_SUCCESS; } @@ -2377,50 +2844,104 @@ /* * Validate and read config data. */ - PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQ|DMA|DMA16|Port|Version|TimerHz", ""); - int rc = pHlp->pfnCFGMQuerySIntDef(pCfg, "IRQ", &pThis->irq, 5); + /* Note: For now we only support the one-and-only output stream. */ + PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; + + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQ|DMA|DMA16|Port|Version|TimerHz|DebugEnabled|DebugPathOut", ""); + int rc = pHlp->pfnCFGMQueryU8Def(pCfg, "IRQ", &pStream->HwCfgDefault.uIrq, 5); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"IRQ\" value")); - pThis->irqCfg = pThis->irq; + /* Sanity-check supported SB16 IRQs. */ + if ( 2 != pStream->HwCfgDefault.uIrq + && 5 != pStream->HwCfgDefault.uIrq + && 7 != pStream->HwCfgDefault.uIrq + && 10 != pStream->HwCfgDefault.uIrq) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"IRQ\" value.")); + pStream->HwCfgRuntime.uIrq = pStream->HwCfgDefault.uIrq; - rc = pHlp->pfnCFGMQuerySIntDef(pCfg, "DMA", &pThis->dma, 1); + rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA", &pStream->HwCfgDefault.uDmaChanLow, 1); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"DMA\" value")); - pThis->dmaCfg = pThis->dma; + if ( 0 != pStream->HwCfgDefault.uDmaChanLow + && 1 != pStream->HwCfgDefault.uDmaChanLow + && 3 != pStream->HwCfgDefault.uDmaChanLow) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"DMA\" value.")); + pStream->HwCfgRuntime.uDmaChanLow = pStream->HwCfgDefault.uDmaChanLow; - rc = pHlp->pfnCFGMQuerySIntDef(pCfg, "DMA16", &pThis->hdma, 5); + rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA16", &pStream->HwCfgDefault.uDmaChanHigh, 5); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"DMA16\" value")); - pThis->hdmaCfg = pThis->hdma; + if ( 5 != pStream->HwCfgDefault.uDmaChanHigh + && 6 != pStream->HwCfgDefault.uDmaChanHigh + && 7 != pStream->HwCfgDefault.uDmaChanHigh) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"DMA16\" value.")); + pStream->HwCfgRuntime.uDmaChanHigh = pStream->HwCfgDefault.uDmaChanHigh; - RTIOPORT Port; - rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &Port, 0x220); + rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pStream->HwCfgDefault.uPort, 0x220); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"Port\" value")); - pThis->port = Port; - pThis->portCfg = Port; + /* Sanity-check supported SB16 ports. */ + if ( 0x220 != pStream->HwCfgDefault.uPort + && 0x240 != pStream->HwCfgDefault.uPort + && 0x260 != pStream->HwCfgDefault.uPort + && 0x280 != pStream->HwCfgDefault.uPort) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"Port\" value. Did you specify it as a hex value (e.g. 0x220)?")); + pStream->HwCfgRuntime.uPort = pStream->HwCfgDefault.uPort; - uint16_t u16Version; - rc = pHlp->pfnCFGMQueryU16Def(pCfg, "Version", &u16Version, 0x0405); + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "Version", &pStream->HwCfgDefault.uVer, 0x0405); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"Version\" value")); - pThis->ver = u16Version; - pThis->verCfg = u16Version; + pStream->HwCfgRuntime.uVer = pStream->HwCfgDefault.uVer; + + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pStream->uTimerHz, SB16_TIMER_HZ_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: failed to read Hertz rate as unsigned integer")); + if (pStream->uTimerHz == 0) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Hertz rate is invalid")); + if (pStream->uTimerHz > 2048) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Maximum Hertz rate is 2048")); + + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThis->Dbg.fEnabled, false); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("SB16 configuration error: failed to read debugging enabled flag as boolean")); - uint16_t uTimerHz; - rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &uTimerHz, 100 /* Hz */); + rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThis->Dbg.pszOutPath, NULL); if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: failed to read Hertz (Hz) rate as unsigned integer")); - if (uTimerHz == 0) - return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: TimerHz is zero")); - if (uTimerHz > 2048) - return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Max TimerHz value is 2048.")); + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("SB16 configuration error: failed to read debugging output path flag as string")); + + if (pThis->Dbg.fEnabled) + LogRel2(("SB16: Debug output will be saved to '%s'\n", pThis->Dbg.pszOutPath)); + + /* + * Create internal software mixer. + * Must come before we do the device's mixer reset. + */ + rc = AudioMixerCreate("SB16 Mixer", 0 /* uFlags */, &pThis->pMixer); + AssertRCReturn(rc, rc); + + AssertRCReturn(rc, rc); + rc = AudioMixerCreateSink(pThis->pMixer, "PCM Output", + PDMAUDIODIR_OUT, pDevIns, &pThis->pSinkOut); + AssertRCReturn(rc, rc); + + /* + * Create all hardware streams. + * For now we have one stream only, namely the output (playback) stream. + */ + AssertCompile(RT_ELEMENTS(pThis->aStreams) == SB16_MAX_STREAMS); + for (unsigned i = 0; i < SB16_MAX_STREAMS; i++) + { + rc = sb16StreamCreate(pThis, &pThis->aStreams[i], i /* uIdx */); + AssertRCReturn(rc, rc); + } /* * Setup the mixer now that we've got the irq and dma channel numbers. */ - pThis->mixer_regs[0x80] = magic_of_irq(pThis->irq); - pThis->mixer_regs[0x81] = (1 << pThis->dma) | (1 << pThis->hdma); + pThis->mixer_regs[0x80] = magic_of_irq(pStream->HwCfgRuntime.uIrq); + pThis->mixer_regs[0x81] = (1 << pStream->HwCfgRuntime.uDmaChanLow) | (1 << pStream->HwCfgRuntime.uDmaChanHigh); pThis->mixer_regs[0x82] = 2 << 5; sb16MixerReset(pThis); @@ -2431,12 +2952,19 @@ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIRQ, pThis, TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "SB16 IRQ timer", &pThis->hTimerIRQ); AssertRCReturn(rc, rc); - rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIO, pThis, - TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "SB16 IO timer", &pThis->hTimerIO); - AssertRCReturn(rc, rc); - pThis->cTicksTimerIOInterval = PDMDevHlpTimerGetFreq(pDevIns, pThis->hTimerIO) / uTimerHz; - pThis->tsTimerIO = PDMDevHlpTimerGet(pDevIns, pThis->hTimerIO); - LogFunc(("Timer ticks=%RU64 (%RU16 Hz)\n", pThis->cTicksTimerIOInterval, uTimerHz)); + + static const char * const s_apszNames[] = { "SB16 OUT" }; + AssertCompile(RT_ELEMENTS(s_apszNames) == SB16_MAX_STREAMS); + for (unsigned i = 0; i < SB16_MAX_STREAMS; i++) + { + rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIO, &pThis->aStreams[i], + TMTIMER_FLAGS_DEFAULT_CRIT_SECT, s_apszNames[i], &pThis->aStreams[i].hTimerIO); + AssertRCReturn(rc, rc); + + pThis->aStreams[i].cTicksTimerIOInterval = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[i].hTimerIO) + / pThis->aStreams[i].uTimerHz; + pThis->aStreams[i].tsTimerIO = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[i].hTimerIO); + } /* * Register I/O and DMA. @@ -2466,28 +2994,29 @@ { NULL, NULL, NULL, NULL }, }; - rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->port + 0x04 /*uPort*/, 2 /*cPorts*/, + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pStream->HwCfgRuntime.uPort + 0x04 /*uPort*/, 2 /*cPorts*/, sb16IoPortMixerWrite, sb16IoPortMixerRead, "SB16 - Mixer", &s_aAllDescs[4], &pThis->hIoPortsMixer); AssertRCReturn(rc, rc); - rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->port + 0x06 /*uPort*/, 10 /*cPorts*/, + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pStream->HwCfgRuntime.uPort + 0x06 /*uPort*/, 10 /*cPorts*/, sb16IoPortDspWrite, sb16IoPortDspRead, "SB16 - DSP", &s_aAllDescs[6], &pThis->hIoPortsDsp); AssertRCReturn(rc, rc); - rc = PDMDevHlpDMARegister(pDevIns, pThis->hdma, sb16DMARead, pThis); + rc = PDMDevHlpDMARegister(pDevIns, pStream->HwCfgRuntime.uDmaChanHigh, sb16DMARead, &pThis->aStreams[SB16_IDX_OUT] /* pvUser */); AssertRCReturn(rc, rc); - rc = PDMDevHlpDMARegister(pDevIns, pThis->dma, sb16DMARead, pThis); + rc = PDMDevHlpDMARegister(pDevIns, pStream->HwCfgRuntime.uDmaChanLow, sb16DMARead, &pThis->aStreams[SB16_IDX_OUT] /* pvUser */); AssertRCReturn(rc, rc); - pThis->can_write = 1; - /* * Register Saved state. */ rc = PDMDevHlpSSMRegister3(pDevIns, SB16_SAVE_STATE_VERSION, sizeof(SB16STATE), sb16LiveExec, sb16SaveExec, sb16LoadExec); AssertRCReturn(rc, rc); + LogRel2(("SB16: Using port %#x, DMA%RU8, IRQ%RU8\n", + pStream->HwCfgRuntime.uPort, pStream->HwCfgRuntime.uDmaChanLow, pStream->HwCfgRuntime.uIrq)); + /* * Attach drivers. We ASSUME they are configured consecutively without any * gaps, so we stop when we hit the first LUN w/o a driver configured. @@ -2496,64 +3025,42 @@ { AssertBreak(iLun < UINT8_MAX); LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun)); - rc = sb16AttachInternal(pThis, iLun, 0 /* fFlags */, NULL /* ppDrv */); + rc = sb16AttachInternal(pThis, iLun, NULL /* ppDrv */); if (rc == VERR_PDM_NO_ATTACHED_DRIVER) { LogFunc(("cLUNs=%u\n", iLun)); break; } - if (rc == VERR_AUDIO_BACKEND_INIT_FAILED) - { - sb16ReconfigLunWithNullAudio(pThis, iLun); /* Pretend attaching to the NULL audio backend will never fail. */ - PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", - N_("Host audio backend initialization has failed. " - "Selecting the NULL audio backend with the consequence that no sound is audible")); - } - else - AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc); + AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc); } - sb16CmdResetLegacy(pThis); - -#ifdef VBOX_WITH_AUDIO_SB16_ONETIME_INIT - PSB16DRIVER pDrv, pNext; - RTListForEachSafe(&pThis->lstDrv, pDrv, pNext, SB16DRIVER, Node) - { - /* - * Only primary drivers are critical for the VM to run. Everything else - * might not worth showing an own error message box in the GUI. - */ - if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY)) - continue; + sb16DspCmdResetLegacy(pThis); - /** @todo No input streams available for SB16 yet. */ - if (!pDrv->Out.pStream) - continue; - - PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector; - AssertPtr(pCon); - if ( pCon == NULL /* paranoia */ - || !(pCon->pfnStreamGetStatus(pCon, pDrv->Out.pStream) & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED)) - { - LogRel(("SB16: Falling back to NULL backend (no sound audible)\n")); - - sb16CmdResetLegacy(pThis); - sb16ReconfigLunWithNullAudio(pThis, pDrv->uLUN); - pDrv = NULL; /* no longer valid */ - - PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", - N_("No audio devices could be opened. " - "Selecting the NULL audio backend with the consequence that no sound is audible")); - } + /* + * Register statistics. + */ +# ifdef VBOX_WITH_STATISTICS + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimerIO, STAMTYPE_PROFILE, "Timer", STAMUNIT_TICKS_PER_CALL, "Profiling sb16TimerIO."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead", STAMUNIT_BYTES, "Bytes read from SB16 emulation."); +# endif + for (unsigned idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++) + { + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Virtual internal buffer read position.", "Stream%u/offRead", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream); + PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", idxStream); } -#endif /* - * Delete debug file. + * Debug info items. */ -#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA - RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "sb16WriteAudio.pcm"); -#endif + //PDMDevHlpDBGFInfoRegister(pDevIns, "sb16", "SB16 registers. (sb16 [register case-insensitive])", sb16DbgInfo); + //PDMDevHlpDBGFInfoRegister(pDevIns, "sb16stream", "SB16 stream info. (sb16stream [stream number])", sb16DbgInfoStream); + PDMDevHlpDBGFInfoRegister(pDevIns, "sb16mixer", "SB16 mixer state.", sb16DbgInfoMixer); return VINF_SUCCESS; } @@ -2563,7 +3070,8 @@ /* .u32Version = */ PDM_DEVREG_VERSION, /* .uReserved0 = */ 0, /* .szName = */ "sb16", - /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE + | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */, /* .fClass = */ PDM_DEVREG_CLASS_AUDIO, /* .cMaxInstances = */ 1, /* .uSharedVersion = */ 42, diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvAudioCommon.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvAudioCommon.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvAudioCommon.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvAudioCommon.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,1929 +0,0 @@ -/* $Id: DrvAudioCommon.cpp $ */ -/** @file - * Intermedia audio driver, common routines. - * - * These are also used in the drivers which are bound to Main, e.g. the VRDE - * or the video audio recording drivers. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#include -#include -#include -#include -#include -#include -#include - -#define LOG_GROUP LOG_GROUP_DRV_AUDIO -#include - -#include -#include -#include -#include - -#include -#include - -#include "DrvAudio.h" -#include "AudioMixBuffer.h" - - -/********************************************************************************************************************************* -* Structures and Typedefs * -*********************************************************************************************************************************/ -/** - * Structure for building up a .WAV file header. - */ -typedef struct AUDIOWAVFILEHDR -{ - uint32_t u32RIFF; - uint32_t u32Size; - uint32_t u32WAVE; - - uint32_t u32Fmt; - uint32_t u32Size1; - uint16_t u16AudioFormat; - uint16_t u16NumChannels; - uint32_t u32SampleRate; - uint32_t u32ByteRate; - uint16_t u16BlockAlign; - uint16_t u16BitsPerSample; - - uint32_t u32ID2; - uint32_t u32Size2; -} AUDIOWAVFILEHDR, *PAUDIOWAVFILEHDR; -AssertCompileSize(AUDIOWAVFILEHDR, 11*4); - -/** - * Structure for keeeping the internal .WAV file data - */ -typedef struct AUDIOWAVFILEDATA -{ - /** The file header/footer. */ - AUDIOWAVFILEHDR Hdr; -} AUDIOWAVFILEDATA, *PAUDIOWAVFILEDATA; - - - - -/** - * Retrieves the matching PDMAUDIOFMT for given bits + signing flag. - * - * @return IPRT status code. - * @return PDMAUDIOFMT Resulting audio format or PDMAUDIOFMT_INVALID if invalid. - * @param cBits Bits to retrieve audio format for. - * @param fSigned Signed flag for bits to retrieve audio format for. - */ -PDMAUDIOFMT DrvAudioAudFmtBitsToAudFmt(uint8_t cBits, bool fSigned) -{ - if (fSigned) - { - switch (cBits) - { - case 8: return PDMAUDIOFMT_S8; - case 16: return PDMAUDIOFMT_S16; - case 32: return PDMAUDIOFMT_S32; - default: break; - } - } - else - { - switch (cBits) - { - case 8: return PDMAUDIOFMT_U8; - case 16: return PDMAUDIOFMT_U16; - case 32: return PDMAUDIOFMT_U32; - default: break; - } - } - - AssertMsgFailed(("Bogus audio bits %RU8\n", cBits)); - return PDMAUDIOFMT_INVALID; -} - -/** - * Clears a sample buffer by the given amount of audio frames with silence (according to the format - * given by the PCM properties). - * - * @param pPCMProps PCM properties to use for the buffer to clear. - * @param pvBuf Buffer to clear. - * @param cbBuf Size (in bytes) of the buffer. - * @param cFrames Number of audio frames to clear in the buffer. - */ -void DrvAudioHlpClearBuf(const PPDMAUDIOPCMPROPS pPCMProps, void *pvBuf, size_t cbBuf, uint32_t cFrames) -{ - AssertPtrReturnVoid(pPCMProps); - AssertPtrReturnVoid(pvBuf); - - if (!cbBuf || !cFrames) - return; - - Assert(pPCMProps->cbSample); - size_t cbToClear = DrvAudioHlpFramesToBytes(cFrames, pPCMProps); - Assert(cbBuf >= cbToClear); - - if (cbBuf < cbToClear) - cbToClear = cbBuf; - - Log2Func(("pPCMProps=%p, pvBuf=%p, cFrames=%RU32, fSigned=%RTbool, cBytes=%RU8\n", - pPCMProps, pvBuf, cFrames, pPCMProps->fSigned, pPCMProps->cbSample)); - - Assert(pPCMProps->fSwapEndian == false); /** @todo Swapping Endianness is not supported yet. */ - - if (pPCMProps->fSigned) - { - RT_BZERO(pvBuf, cbToClear); - } - else /* Unsigned formats. */ - { - switch (pPCMProps->cbSample) - { - case 1: /* 8 bit */ - { - memset(pvBuf, 0x80, cbToClear); - break; - } - - case 2: /* 16 bit */ - { - uint16_t *p = (uint16_t *)pvBuf; - uint16_t s = 0x0080; - - for (uint32_t i = 0; i < DrvAudioHlpBytesToFrames((uint32_t)cbToClear, pPCMProps); i++) - p[i] = s; - - break; - } - - /** @todo Add 24 bit? */ - - case 4: /* 32 bit */ - { - uint32_t *p = (uint32_t *)pvBuf; - uint32_t s = 0x00000080; - - for (uint32_t i = 0; i < DrvAudioHlpBytesToFrames((uint32_t)cbToClear, pPCMProps); i++) - p[i] = s; - - break; - } - - default: - { - AssertMsgFailed(("Invalid bytes per sample: %RU8\n", pPCMProps->cbSample)); - break; - } - } - } -} - -/** - * Returns an unique file name for this given audio connector instance. - * - * @return Allocated file name. Must be free'd using RTStrFree(). - * @param uInstance Driver / device instance. - * @param pszPath Path name of the file to delete. The path must exist. - * @param pszSuffix File name suffix to use. - */ -char *DrvAudioDbgGetFileNameA(uint8_t uInstance, const char *pszPath, const char *pszSuffix) -{ - char szFileName[64]; - RTStrPrintf(szFileName, sizeof(szFileName), "drvAudio%RU8-%s", uInstance, pszSuffix); - - char szFilePath[RTPATH_MAX]; - int rc2 = RTStrCopy(szFilePath, sizeof(szFilePath), pszPath); - AssertRC(rc2); - rc2 = RTPathAppend(szFilePath, sizeof(szFilePath), szFileName); - AssertRC(rc2); - - return RTStrDup(szFilePath); -} - -/** - * Allocates an audio device. - * - * @returns Newly allocated audio device, or NULL if failed. - * @param cbData How much additional data (in bytes) should be allocated to provide - * a (backend) specific area to store additional data. - * Optional, can be 0. - */ -PPDMAUDIODEVICE DrvAudioHlpDeviceAlloc(size_t cbData) -{ - PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)RTMemAllocZ(sizeof(PDMAUDIODEVICE)); - if (!pDev) - return NULL; - - if (cbData) - { - pDev->pvData = RTMemAllocZ(cbData); - if (!pDev->pvData) - { - RTMemFree(pDev); - return NULL; - } - } - - pDev->cbData = cbData; - - pDev->cMaxInputChannels = 0; - pDev->cMaxOutputChannels = 0; - - return pDev; -} - -/** - * Frees an audio device. - * - * @param pDev Device to free. - */ -void DrvAudioHlpDeviceFree(PPDMAUDIODEVICE pDev) -{ - if (!pDev) - return; - - Assert(pDev->cRefCount == 0); - - if (pDev->pvData) - { - Assert(pDev->cbData); - - RTMemFree(pDev->pvData); - pDev->pvData = NULL; - } - - RTMemFree(pDev); - pDev = NULL; -} - -/** - * Duplicates an audio device entry. - * - * @returns Duplicated audio device entry on success, or NULL on failure. - * @param pDev Audio device entry to duplicate. - * @param fCopyUserData Whether to also copy the user data portion or not. - */ -PPDMAUDIODEVICE DrvAudioHlpDeviceDup(const PPDMAUDIODEVICE pDev, bool fCopyUserData) -{ - AssertPtrReturn(pDev, NULL); - - PPDMAUDIODEVICE pDevDup = DrvAudioHlpDeviceAlloc(fCopyUserData ? pDev->cbData : 0); - if (pDevDup) - { - memcpy(pDevDup, pDev, sizeof(PDMAUDIODEVICE)); - - if ( fCopyUserData - && pDevDup->cbData) - { - memcpy(pDevDup->pvData, pDev->pvData, pDevDup->cbData); - } - else - { - pDevDup->cbData = 0; - pDevDup->pvData = NULL; - } - } - - return pDevDup; -} - -/** - * Initializes an audio device enumeration structure. - * - * @returns IPRT status code. - * @param pDevEnm Device enumeration to initialize. - */ -int DrvAudioHlpDeviceEnumInit(PPDMAUDIODEVICEENUM pDevEnm) -{ - AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER); - - RTListInit(&pDevEnm->lstDevices); - pDevEnm->cDevices = 0; - - return VINF_SUCCESS; -} - -/** - * Frees audio device enumeration data. - * - * @param pDevEnm Device enumeration to destroy. - */ -void DrvAudioHlpDeviceEnumFree(PPDMAUDIODEVICEENUM pDevEnm) -{ - if (!pDevEnm) - return; - - PPDMAUDIODEVICE pDev, pDevNext; - RTListForEachSafe(&pDevEnm->lstDevices, pDev, pDevNext, PDMAUDIODEVICE, Node) - { - RTListNodeRemove(&pDev->Node); - - DrvAudioHlpDeviceFree(pDev); - - pDevEnm->cDevices--; - } - - /* Sanity. */ - Assert(RTListIsEmpty(&pDevEnm->lstDevices)); - Assert(pDevEnm->cDevices == 0); -} - -/** - * Adds an audio device to a device enumeration. - * - * @return IPRT status code. - * @param pDevEnm Device enumeration to add device to. - * @param pDev Device to add. The pointer will be owned by the device enumeration then. - */ -int DrvAudioHlpDeviceEnumAdd(PPDMAUDIODEVICEENUM pDevEnm, PPDMAUDIODEVICE pDev) -{ - AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER); - AssertPtrReturn(pDev, VERR_INVALID_POINTER); - - RTListAppend(&pDevEnm->lstDevices, &pDev->Node); - pDevEnm->cDevices++; - - return VINF_SUCCESS; -} - -/** - * Duplicates a device enumeration. - * - * @returns Duplicated device enumeration, or NULL on failure. - * Must be free'd with DrvAudioHlpDeviceEnumFree(). - * @param pDevEnm Device enumeration to duplicate. - */ -PPDMAUDIODEVICEENUM DrvAudioHlpDeviceEnumDup(const PPDMAUDIODEVICEENUM pDevEnm) -{ - AssertPtrReturn(pDevEnm, NULL); - - PPDMAUDIODEVICEENUM pDevEnmDup = (PPDMAUDIODEVICEENUM)RTMemAlloc(sizeof(PDMAUDIODEVICEENUM)); - if (!pDevEnmDup) - return NULL; - - int rc2 = DrvAudioHlpDeviceEnumInit(pDevEnmDup); - AssertRC(rc2); - - PPDMAUDIODEVICE pDev; - RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node) - { - PPDMAUDIODEVICE pDevDup = DrvAudioHlpDeviceDup(pDev, true /* fCopyUserData */); - if (!pDevDup) - { - rc2 = VERR_NO_MEMORY; - break; - } - - rc2 = DrvAudioHlpDeviceEnumAdd(pDevEnmDup, pDevDup); - if (RT_FAILURE(rc2)) - { - DrvAudioHlpDeviceFree(pDevDup); - break; - } - } - - if (RT_FAILURE(rc2)) - { - DrvAudioHlpDeviceEnumFree(pDevEnmDup); - pDevEnmDup = NULL; - } - - return pDevEnmDup; -} - -/** - * Copies device enumeration entries from the source to the destination enumeration. - * - * @returns IPRT status code. - * @param pDstDevEnm Destination enumeration to store enumeration entries into. - * @param pSrcDevEnm Source enumeration to use. - * @param enmUsage Which entries to copy. Specify PDMAUDIODIR_ANY to copy all entries. - * @param fCopyUserData Whether to also copy the user data portion or not. - */ -int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm, - PDMAUDIODIR enmUsage, bool fCopyUserData) -{ - AssertPtrReturn(pDstDevEnm, VERR_INVALID_POINTER); - AssertPtrReturn(pSrcDevEnm, VERR_INVALID_POINTER); - - int rc = VINF_SUCCESS; - - PPDMAUDIODEVICE pSrcDev; - RTListForEach(&pSrcDevEnm->lstDevices, pSrcDev, PDMAUDIODEVICE, Node) - { - if ( enmUsage != PDMAUDIODIR_ANY - && enmUsage != pSrcDev->enmUsage) - { - continue; - } - - PPDMAUDIODEVICE pDstDev = DrvAudioHlpDeviceDup(pSrcDev, fCopyUserData); - if (!pDstDev) - { - rc = VERR_NO_MEMORY; - break; - } - - rc = DrvAudioHlpDeviceEnumAdd(pDstDevEnm, pDstDev); - if (RT_FAILURE(rc)) - break; - } - - return rc; -} - -/** - * Copies all device enumeration entries from the source to the destination enumeration. - * - * Note: Does *not* copy the user-specific data assigned to a device enumeration entry. - * To do so, use DrvAudioHlpDeviceEnumCopyEx(). - * - * @returns IPRT status code. - * @param pDstDevEnm Destination enumeration to store enumeration entries into. - * @param pSrcDevEnm Source enumeration to use. - */ -int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm) -{ - return DrvAudioHlpDeviceEnumCopyEx(pDstDevEnm, pSrcDevEnm, PDMAUDIODIR_ANY, false /* fCopyUserData */); -} - -/** - * Returns the default device of a given device enumeration. - * This assumes that only one default device per usage is set. - * - * @returns Default device if found, or NULL if none found. - * @param pDevEnm Device enumeration to get default device for. - * @param enmUsage Usage to get default device for. - */ -PPDMAUDIODEVICE DrvAudioHlpDeviceEnumGetDefaultDevice(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage) -{ - AssertPtrReturn(pDevEnm, NULL); - - PPDMAUDIODEVICE pDev; - RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node) - { - if (enmUsage != PDMAUDIODIR_ANY) - { - if (enmUsage != pDev->enmUsage) /* Wrong usage? Skip. */ - continue; - } - - if (pDev->fFlags & PDMAUDIODEV_FLAGS_DEFAULT) - return pDev; - } - - return NULL; -} - -/** - * Returns the number of enumerated devices of a given device enumeration. - * - * @returns Number of devices if found, or 0 if none found. - * @param pDevEnm Device enumeration to get default device for. - * @param enmUsage Usage to get default device for. - */ -uint16_t DrvAudioHlpDeviceEnumGetDeviceCount(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage) -{ - AssertPtrReturn(pDevEnm, 0); - - if (enmUsage == PDMAUDIODIR_ANY) - return pDevEnm->cDevices; - - uint32_t cDevs = 0; - - PPDMAUDIODEVICE pDev; - RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node) - { - if (enmUsage == pDev->enmUsage) - cDevs++; - } - - return cDevs; -} - -/** - * Logs an audio device enumeration. - * - * @param pszDesc Logging description. - * @param pDevEnm Device enumeration to log. - */ -void DrvAudioHlpDeviceEnumPrint(const char *pszDesc, const PPDMAUDIODEVICEENUM pDevEnm) -{ - AssertPtrReturnVoid(pszDesc); - AssertPtrReturnVoid(pDevEnm); - - LogFunc(("%s: %RU16 devices\n", pszDesc, pDevEnm->cDevices)); - - PPDMAUDIODEVICE pDev; - RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node) - { - char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags); - - LogFunc(("Device '%s':\n", pDev->szName)); - LogFunc(("\tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage))); - LogFunc(("\tFlags = %s\n", pszFlags ? pszFlags : "")); - LogFunc(("\tInput channels = %RU8\n", pDev->cMaxInputChannels)); - LogFunc(("\tOutput channels = %RU8\n", pDev->cMaxOutputChannels)); - LogFunc(("\tData = %p (%zu bytes)\n", pDev->pvData, pDev->cbData)); - - if (pszFlags) - RTStrFree(pszFlags); - } -} - -/** - * Converts an audio direction to a string. - * - * @returns Stringified audio direction, or "Unknown", if not found. - * @param enmDir Audio direction to convert. - */ -const char *DrvAudioHlpAudDirToStr(PDMAUDIODIR enmDir) -{ - switch (enmDir) - { - case PDMAUDIODIR_UNKNOWN: return "Unknown"; - case PDMAUDIODIR_IN: return "Input"; - case PDMAUDIODIR_OUT: return "Output"; - case PDMAUDIODIR_ANY: return "Duplex"; - default: break; - } - - AssertMsgFailed(("Invalid audio direction %ld\n", enmDir)); - return "Unknown"; -} - -/** - * Converts an audio mixer control to a string. - * - * @returns Stringified audio mixer control or "Unknown", if not found. - * @param enmMixerCtl Audio mixer control to convert. - */ -const char *DrvAudioHlpAudMixerCtlToStr(PDMAUDIOMIXERCTL enmMixerCtl) -{ - switch (enmMixerCtl) - { - case PDMAUDIOMIXERCTL_VOLUME_MASTER: return "Master Volume"; - case PDMAUDIOMIXERCTL_FRONT: return "Front"; - case PDMAUDIOMIXERCTL_CENTER_LFE: return "Center / LFE"; - case PDMAUDIOMIXERCTL_REAR: return "Rear"; - case PDMAUDIOMIXERCTL_LINE_IN: return "Line-In"; - case PDMAUDIOMIXERCTL_MIC_IN: return "Microphone-In"; - default: break; - } - - AssertMsgFailed(("Invalid mixer control %ld\n", enmMixerCtl)); - return "Unknown"; -} - -/** - * Converts an audio device flags to a string. - * - * @returns Stringified audio flags. Must be free'd with RTStrFree(). - * NULL if no flags set. - * @param fFlags Audio flags (PDMAUDIODEV_FLAGS_XXX) to convert. - */ -char *DrvAudioHlpAudDevFlagsToStrA(uint32_t fFlags) -{ -#define APPEND_FLAG_TO_STR(_aFlag) \ - if (fFlags & PDMAUDIODEV_FLAGS_##_aFlag) \ - { \ - if (pszFlags) \ - { \ - rc2 = RTStrAAppend(&pszFlags, " "); \ - if (RT_FAILURE(rc2)) \ - break; \ - } \ - \ - rc2 = RTStrAAppend(&pszFlags, #_aFlag); \ - if (RT_FAILURE(rc2)) \ - break; \ - } \ - - char *pszFlags = NULL; - int rc2 = VINF_SUCCESS; - - do - { - APPEND_FLAG_TO_STR(DEFAULT); - APPEND_FLAG_TO_STR(HOTPLUG); - APPEND_FLAG_TO_STR(BUGGY); - APPEND_FLAG_TO_STR(IGNORE); - APPEND_FLAG_TO_STR(LOCKED); - APPEND_FLAG_TO_STR(DEAD); - - } while (0); - - if (!pszFlags) - rc2 = RTStrAAppend(&pszFlags, "NONE"); - - if ( RT_FAILURE(rc2) - && pszFlags) - { - RTStrFree(pszFlags); - pszFlags = NULL; - } - -#undef APPEND_FLAG_TO_STR - - return pszFlags; -} - -/** - * Converts a playback destination enumeration to a string. - * - * @returns Stringified playback destination, or "Unknown", if not found. - * @param enmPlaybackDst Playback destination to convert. - */ -const char *DrvAudioHlpPlaybackDstToStr(const PDMAUDIOPLAYBACKDST enmPlaybackDst) -{ - switch (enmPlaybackDst) - { - case PDMAUDIOPLAYBACKDST_UNKNOWN: return "Unknown"; - case PDMAUDIOPLAYBACKDST_FRONT: return "Front"; - case PDMAUDIOPLAYBACKDST_CENTER_LFE: return "Center / LFE"; - case PDMAUDIOPLAYBACKDST_REAR: return "Rear"; - default: - break; - } - - AssertMsgFailed(("Invalid playback destination %ld\n", enmPlaybackDst)); - return "Unknown"; -} - -/** - * Converts a recording source enumeration to a string. - * - * @returns Stringified recording source, or "Unknown", if not found. - * @param enmRecSrc Recording source to convert. - */ -const char *DrvAudioHlpRecSrcToStr(const PDMAUDIORECSRC enmRecSrc) -{ - switch (enmRecSrc) - { - case PDMAUDIORECSRC_UNKNOWN: return "Unknown"; - case PDMAUDIORECSRC_MIC: return "Microphone In"; - case PDMAUDIORECSRC_CD: return "CD"; - case PDMAUDIORECSRC_VIDEO: return "Video"; - case PDMAUDIORECSRC_AUX: return "AUX"; - case PDMAUDIORECSRC_LINE: return "Line In"; - case PDMAUDIORECSRC_PHONE: return "Phone"; - default: - break; - } - - AssertMsgFailed(("Invalid recording source %ld\n", enmRecSrc)); - return "Unknown"; -} - -/** - * Returns wether the given audio format has signed bits or not. - * - * @return IPRT status code. - * @return bool @c true for signed bits, @c false for unsigned. - * @param enmFmt Audio format to retrieve value for. - */ -bool DrvAudioHlpAudFmtIsSigned(PDMAUDIOFMT enmFmt) -{ - switch (enmFmt) - { - case PDMAUDIOFMT_S8: - case PDMAUDIOFMT_S16: - case PDMAUDIOFMT_S32: - return true; - - case PDMAUDIOFMT_U8: - case PDMAUDIOFMT_U16: - case PDMAUDIOFMT_U32: - return false; - - default: - break; - } - - AssertMsgFailed(("Bogus audio format %ld\n", enmFmt)); - return false; -} - -/** - * Returns the bits of a given audio format. - * - * @return IPRT status code. - * @return uint8_t Bits of audio format. - * @param enmFmt Audio format to retrieve value for. - */ -uint8_t DrvAudioHlpAudFmtToBits(PDMAUDIOFMT enmFmt) -{ - switch (enmFmt) - { - case PDMAUDIOFMT_S8: - case PDMAUDIOFMT_U8: - return 8; - - case PDMAUDIOFMT_U16: - case PDMAUDIOFMT_S16: - return 16; - - case PDMAUDIOFMT_U32: - case PDMAUDIOFMT_S32: - return 32; - - default: - break; - } - - AssertMsgFailed(("Bogus audio format %ld\n", enmFmt)); - return 0; -} - -/** - * Converts an audio format to a string. - * - * @returns Stringified audio format, or "Unknown", if not found. - * @param enmFmt Audio format to convert. - */ -const char *DrvAudioHlpAudFmtToStr(PDMAUDIOFMT enmFmt) -{ - switch (enmFmt) - { - case PDMAUDIOFMT_U8: - return "U8"; - - case PDMAUDIOFMT_U16: - return "U16"; - - case PDMAUDIOFMT_U32: - return "U32"; - - case PDMAUDIOFMT_S8: - return "S8"; - - case PDMAUDIOFMT_S16: - return "S16"; - - case PDMAUDIOFMT_S32: - return "S32"; - - default: - break; - } - - AssertMsgFailed(("Bogus audio format %ld\n", enmFmt)); - return "Unknown"; -} - -/** - * Converts a given string to an audio format. - * - * @returns Audio format for the given string, or PDMAUDIOFMT_INVALID if not found. - * @param pszFmt String to convert to an audio format. - */ -PDMAUDIOFMT DrvAudioHlpStrToAudFmt(const char *pszFmt) -{ - AssertPtrReturn(pszFmt, PDMAUDIOFMT_INVALID); - - if (!RTStrICmp(pszFmt, "u8")) - return PDMAUDIOFMT_U8; - else if (!RTStrICmp(pszFmt, "u16")) - return PDMAUDIOFMT_U16; - else if (!RTStrICmp(pszFmt, "u32")) - return PDMAUDIOFMT_U32; - else if (!RTStrICmp(pszFmt, "s8")) - return PDMAUDIOFMT_S8; - else if (!RTStrICmp(pszFmt, "s16")) - return PDMAUDIOFMT_S16; - else if (!RTStrICmp(pszFmt, "s32")) - return PDMAUDIOFMT_S32; - - AssertMsgFailed(("Invalid audio format '%s'\n", pszFmt)); - return PDMAUDIOFMT_INVALID; -} - -/** - * Checks whether two given PCM properties are equal. - * - * @returns @c true if equal, @c false if not. - * @param pProps1 First properties to compare. - * @param pProps2 Second properties to compare. - */ -bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pProps1, const PPDMAUDIOPCMPROPS pProps2) -{ - AssertPtrReturn(pProps1, false); - AssertPtrReturn(pProps2, false); - - if (pProps1 == pProps2) /* If the pointers match, take a shortcut. */ - return true; - - return pProps1->uHz == pProps2->uHz - && pProps1->cChannels == pProps2->cChannels - && pProps1->cbSample == pProps2->cbSample - && pProps1->fSigned == pProps2->fSigned - && pProps1->fSwapEndian == pProps2->fSwapEndian; -} - -/** - * Checks whether given PCM properties are valid or not. - * - * Returns @c true if properties are valid, @c false if not. - * @param pProps PCM properties to check. - */ -bool DrvAudioHlpPCMPropsAreValid(const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, false); - - /* Minimum 1 channel (mono), maximum 7.1 (= 8) channels. */ - bool fValid = ( pProps->cChannels >= 1 - && pProps->cChannels <= 8); - - if (fValid) - { - switch (pProps->cbSample) - { - case 1: /* 8 bit */ - if (pProps->fSigned) - fValid = false; - break; - case 2: /* 16 bit */ - if (!pProps->fSigned) - fValid = false; - break; - /** @todo Do we need support for 24 bit samples? */ - case 4: /* 32 bit */ - if (!pProps->fSigned) - fValid = false; - break; - default: - fValid = false; - break; - } - } - - if (!fValid) - return false; - - fValid &= pProps->uHz > 0; - fValid &= pProps->cShift == PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cbSample, pProps->cChannels); - fValid &= pProps->fSwapEndian == false; /** @todo Handling Big Endian audio data is not supported yet. */ - - return fValid; -} - -/** - * Checks whether the given PCM properties are equal with the given - * stream configuration. - * - * @returns @c true if equal, @c false if not. - * @param pProps PCM properties to compare. - * @param pCfg Stream configuration to compare. - */ -bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pProps, const PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pProps, false); - AssertPtrReturn(pCfg, false); - - return DrvAudioHlpPCMPropsAreEqual(pProps, &pCfg->Props); -} - -/** - * Returns the bytes per frame for given PCM properties. - * - * @return Bytes per (audio) frame. - * @param pProps PCM properties to retrieve bytes per frame for. - */ -uint32_t DrvAudioHlpPCMPropsBytesPerFrame(const PPDMAUDIOPCMPROPS pProps) -{ - return PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); -} - -/** - * Prints PCM properties to the debug log. - * - * @param pProps Stream configuration to log. - */ -void DrvAudioHlpPCMPropsPrint(const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturnVoid(pProps); - - Log(("uHz=%RU32, cChannels=%RU8, cBits=%RU8%s", - pProps->uHz, pProps->cChannels, pProps->cbSample * 8, pProps->fSigned ? "S" : "U")); -} - -/** - * Converts PCM properties to a audio stream configuration. - * - * @return IPRT status code. - * @param pProps PCM properties to convert. - * @param pCfg Stream configuration to store result into. - */ -int DrvAudioHlpPCMPropsToStreamCfg(const PPDMAUDIOPCMPROPS pProps, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pProps, VERR_INVALID_POINTER); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - DrvAudioHlpStreamCfgInit(pCfg); - - memcpy(&pCfg->Props, pProps, sizeof(PDMAUDIOPCMPROPS)); - return VINF_SUCCESS; -} - -/** - * Initializes a stream configuration with its default values. - * - * @param pCfg Stream configuration to initialize. - */ -void DrvAudioHlpStreamCfgInit(PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturnVoid(pCfg); - - RT_ZERO(*pCfg); - - pCfg->Backend.cFramesPreBuffering = UINT32_MAX; /* Explicitly set to "undefined". */ -} - -/** - * Checks whether a given stream configuration is valid or not. - * - * Returns @c true if configuration is valid, @c false if not. - * @param pCfg Stream configuration to check. - */ -bool DrvAudioHlpStreamCfgIsValid(const PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pCfg, false); - - bool fValid = ( pCfg->enmDir == PDMAUDIODIR_IN - || pCfg->enmDir == PDMAUDIODIR_OUT); - - fValid &= ( pCfg->enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED - || pCfg->enmLayout == PDMAUDIOSTREAMLAYOUT_RAW); - - if (fValid) - fValid = DrvAudioHlpPCMPropsAreValid(&pCfg->Props); - - return fValid; -} - -/** - * Frees an allocated audio stream configuration. - * - * @param pCfg Audio stream configuration to free. - */ -void DrvAudioHlpStreamCfgFree(PPDMAUDIOSTREAMCFG pCfg) -{ - if (pCfg) - { - RTMemFree(pCfg); - pCfg = NULL; - } -} - -/** - * Copies a source stream configuration to a destination stream configuration. - * - * @returns IPRT status code. - * @param pDstCfg Destination stream configuration to copy source to. - * @param pSrcCfg Source stream configuration to copy to destination. - */ -int DrvAudioHlpStreamCfgCopy(PPDMAUDIOSTREAMCFG pDstCfg, const PPDMAUDIOSTREAMCFG pSrcCfg) -{ - AssertPtrReturn(pDstCfg, VERR_INVALID_POINTER); - AssertPtrReturn(pSrcCfg, VERR_INVALID_POINTER); - -#ifdef VBOX_STRICT - if (!DrvAudioHlpStreamCfgIsValid(pSrcCfg)) - { - AssertMsgFailed(("Stream config '%s' (%p) is invalid\n", pSrcCfg->szName, pSrcCfg)); - return VERR_INVALID_PARAMETER; - } -#endif - - memcpy(pDstCfg, pSrcCfg, sizeof(PDMAUDIOSTREAMCFG)); - - return VINF_SUCCESS; -} - -/** - * Duplicates an audio stream configuration. - * Must be free'd with DrvAudioHlpStreamCfgFree(). - * - * @return Duplicates audio stream configuration on success, or NULL on failure. - * @param pCfg Audio stream configuration to duplicate. - */ -PPDMAUDIOSTREAMCFG DrvAudioHlpStreamCfgDup(const PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pCfg, NULL); - -#ifdef VBOX_STRICT - if (!DrvAudioHlpStreamCfgIsValid(pCfg)) - { - AssertMsgFailed(("Stream config '%s' (%p) is invalid\n", pCfg->szName, pCfg)); - return NULL; - } -#endif - - PPDMAUDIOSTREAMCFG pDst = (PPDMAUDIOSTREAMCFG)RTMemAllocZ(sizeof(PDMAUDIOSTREAMCFG)); - if (!pDst) - return NULL; - - int rc2 = DrvAudioHlpStreamCfgCopy(pDst, pCfg); - if (RT_FAILURE(rc2)) - { - DrvAudioHlpStreamCfgFree(pDst); - pDst = NULL; - } - - AssertPtr(pDst); - return pDst; -} - -/** - * Prints an audio stream configuration to the debug log. - * - * @param pCfg Stream configuration to log. - */ -void DrvAudioHlpStreamCfgPrint(const PPDMAUDIOSTREAMCFG pCfg) -{ - if (!pCfg) - return; - - LogFunc(("szName=%s, enmDir=%RU32 (uHz=%RU32, cBits=%RU8%s, cChannels=%RU8)\n", - pCfg->szName, pCfg->enmDir, - pCfg->Props.uHz, pCfg->Props.cbSample * 8, pCfg->Props.fSigned ? "S" : "U", pCfg->Props.cChannels)); -} - -/** - * Converts a stream command to a string. - * - * @returns Stringified stream command, or "Unknown", if not found. - * @param enmCmd Stream command to convert. - */ -const char *DrvAudioHlpStreamCmdToStr(PDMAUDIOSTREAMCMD enmCmd) -{ - switch (enmCmd) - { - case PDMAUDIOSTREAMCMD_INVALID: return "Invalid"; - case PDMAUDIOSTREAMCMD_UNKNOWN: return "Unknown"; - case PDMAUDIOSTREAMCMD_ENABLE: return "Enable"; - case PDMAUDIOSTREAMCMD_DISABLE: return "Disable"; - case PDMAUDIOSTREAMCMD_PAUSE: return "Pause"; - case PDMAUDIOSTREAMCMD_RESUME: return "Resume"; - case PDMAUDIOSTREAMCMD_DRAIN: return "Drain"; - case PDMAUDIOSTREAMCMD_DROP: return "Drop"; - case PDMAUDIOSTREAMCMD_32BIT_HACK: - break; - } - AssertMsgFailed(("Invalid stream command %d\n", enmCmd)); - return "Unknown"; -} - -/** - * Returns @c true if the given stream status indicates a can-be-read-from stream, - * @c false if not. - * - * @returns @c true if ready to be read from, @c if not. - * @param fStatus Stream status to evaluate, PDMAUDIOSTREAMSTS_FLAGS_XXX. - */ -bool DrvAudioHlpStreamStatusCanRead(PDMAUDIOSTREAMSTS fStatus) -{ - AssertReturn(fStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false); - - return fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED - && fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED - && !(fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED) - && !(fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_REINIT); -} - -/** - * Returns @c true if the given stream status indicates a can-be-written-to stream, - * @c false if not. - * - * @returns @c true if ready to be written to, @c if not. - * @param fStatus Stream status to evaluate, PDMAUDIOSTREAMSTS_FLAGS_XXX. - */ -bool DrvAudioHlpStreamStatusCanWrite(PDMAUDIOSTREAMSTS fStatus) -{ - AssertReturn(fStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false); - - return fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED - && fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED - && !(fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED) - && !(fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE) - && !(fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_REINIT); -} - -/** - * Returns @c true if the given stream status indicates a ready-to-operate stream, - * @c false if not. - * - * @returns @c true if ready to operate, @c if not. - * @param fStatus Stream status to evaluate. - */ -bool DrvAudioHlpStreamStatusIsReady(PDMAUDIOSTREAMSTS fStatus) -{ - AssertReturn(fStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false); - - return fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED - && fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED - && !(fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_REINIT); -} - -/** - * Calculates the audio bit rate of the given bits per sample, the Hz and the number - * of audio channels. - * - * Divide the result by 8 to get the byte rate. - * - * @returns The calculated bit rate. - * @param cBits Number of bits per sample. - * @param uHz Hz (Hertz) rate. - * @param cChannels Number of audio channels. - */ -uint32_t DrvAudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels) -{ - return (cBits * uHz * cChannels); -} - -/** - * Calculates the audio bit rate out of a given audio stream configuration. - * - * Divide the result by 8 to get the byte rate. - * - * @returns The calculated bit rate. - * @param pProps PCM properties to calculate bitrate for. - * - * @remark - */ -uint32_t DrvAudioHlpCalcBitrate(const PPDMAUDIOPCMPROPS pProps) -{ - return DrvAudioHlpCalcBitrate(pProps->cbSample * 8, pProps->uHz, pProps->cChannels); -} - -/** - * Aligns the given byte amount to the given PCM properties and returns the aligned - * size. - * - * @return Aligned size (in bytes). - * @param cbSize Size (in bytes) to align. - * @param pProps PCM properties to align size to. - */ -uint32_t DrvAudioHlpBytesAlign(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!cbSize) - return 0; - - return PDMAUDIOPCMPROPS_B2F(pProps, cbSize) * PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); -} - -/** - * Returns if the the given size is properly aligned to the given PCM properties. - * - * @return @c true if properly aligned, @c false if not. - * @param cbSize Size (in bytes) to check alignment for. - * @param pProps PCM properties to use for checking the alignment. - */ -bool DrvAudioHlpBytesIsAligned(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!cbSize) - return true; - - return (cbSize % PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */) == 0); -} - -/** - * Returns the bytes per second for given PCM properties. - * - * @returns Bytes per second. - * @param pProps PCM properties to retrieve size for. - */ -DECLINLINE(uint64_t) drvAudioHlpBytesPerSec(const PPDMAUDIOPCMPROPS pProps) -{ - return PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */) * pProps->uHz; -} - -/** - * Returns the number of audio frames for a given amount of bytes. - * - * @return Calculated audio frames for given bytes. - * @param cbBytes Bytes to convert to audio frames. - * @param pProps PCM properties to calulate frames for. - */ -uint32_t DrvAudioHlpBytesToFrames(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - return PDMAUDIOPCMPROPS_B2F(pProps, cbBytes); -} - -/** - * Returns the time (in ms) for given byte amount and PCM properties. - * - * @return uint64_t Calculated time (in ms). - * @param cbBytes Amount of bytes to calculate time for. - * @param pProps PCM properties to calculate amount of bytes for. - * - * @note Does rounding up the result. - */ -uint64_t DrvAudioHlpBytesToMilli(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!pProps->uHz) /* Prevent division by zero. */ - return 0; - - const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); - - if (!cbFrame) /* Prevent division by zero. */ - return 0; - - uint64_t uTimeMs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_MS_1SEC; - - return (uTimeMs + pProps->uHz - 1) / pProps->uHz; -} - -/** - * Returns the time (in us) for given byte amount and PCM properties. - * - * @return uint64_t Calculated time (in us). - * @param cbBytes Amount of bytes to calculate time for. - * @param pProps PCM properties to calculate amount of bytes for. - * - * @note Does rounding up the result. - */ -uint64_t DrvAudioHlpBytesToMicro(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!pProps->uHz) /* Prevent division by zero. */ - return 0; - - const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); - - if (!cbFrame) /* Prevent division by zero. */ - return 0; - - uint64_t uTimeUs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_US_1SEC; - - return (uTimeUs + pProps->uHz - 1) / pProps->uHz; -} - -/** - * Returns the time (in ns) for given byte amount and PCM properties. - * - * @return uint64_t Calculated time (in ns). - * @param cbBytes Amount of bytes to calculate time for. - * @param pProps PCM properties to calculate amount of bytes for. - * - * @note Does rounding up the result. - */ -uint64_t DrvAudioHlpBytesToNano(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!pProps->uHz) /* Prevent division by zero. */ - return 0; - - const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); - - if (!cbFrame) /* Prevent division by zero. */ - return 0; - - uint64_t uTimeNs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_NS_1SEC; - - return (uTimeNs + pProps->uHz - 1) / pProps->uHz; -} - -/** - * Returns the bytes for a given audio frames amount and PCM properties. - * - * @return Calculated bytes for given audio frames. - * @param cFrames Amount of audio frames to calculate bytes for. - * @param pProps PCM properties to calculate bytes for. - */ -uint32_t DrvAudioHlpFramesToBytes(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!cFrames) - return 0; - - return cFrames * PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); -} - -/** - * Returns the time (in ms) for given audio frames amount and PCM properties. - * - * @return uint64_t Calculated time (in ms). - * @param cFrames Amount of audio frames to calculate time for. - * @param pProps PCM properties to calculate time (in ms) for. - */ -uint64_t DrvAudioHlpFramesToMilli(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!cFrames) - return 0; - - if (!pProps->uHz) /* Prevent division by zero. */ - return 0; - - return cFrames / ((double)pProps->uHz / (double)RT_MS_1SEC); -} - -/** - * Returns the time (in ns) for given audio frames amount and PCM properties. - * - * @return uint64_t Calculated time (in ns). - * @param cFrames Amount of audio frames to calculate time for. - * @param pProps PCM properties to calculate time (in ns) for. - */ -uint64_t DrvAudioHlpFramesToNano(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!cFrames) - return 0; - - if (!pProps->uHz) /* Prevent division by zero. */ - return 0; - - return cFrames / ((double)pProps->uHz / (double)RT_NS_1SEC); -} - -/** - * Returns the amount of bytes for a given time (in ms) and PCM properties. - * - * Note: The result will return an amount of bytes which is aligned to the audio frame size. - * - * @return uint32_t Calculated amount of bytes. - * @param uMs Time (in ms) to calculate amount of bytes for. - * @param pProps PCM properties to calculate amount of bytes for. - */ -uint32_t DrvAudioHlpMilliToBytes(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!uMs) - return 0; - - const uint32_t uBytesPerFrame = DrvAudioHlpPCMPropsBytesPerFrame(pProps); - - uint32_t uBytes = ((double)drvAudioHlpBytesPerSec(pProps) / (double)RT_MS_1SEC) * uMs; - if (uBytes % uBytesPerFrame) /* Any remainder? Make the returned bytes an integral number to the given frames. */ - uBytes = uBytes + (uBytesPerFrame - uBytes % uBytesPerFrame); - - Assert(uBytes % uBytesPerFrame == 0); /* Paranoia. */ - - return uBytes; -} - -/** - * Returns the amount of bytes for a given time (in ns) and PCM properties. - * - * Note: The result will return an amount of bytes which is aligned to the audio frame size. - * - * @return uint32_t Calculated amount of bytes. - * @param uNs Time (in ns) to calculate amount of bytes for. - * @param pProps PCM properties to calculate amount of bytes for. - */ -uint32_t DrvAudioHlpNanoToBytes(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - if (!uNs) - return 0; - - const uint32_t uBytesPerFrame = DrvAudioHlpPCMPropsBytesPerFrame(pProps); - - uint32_t uBytes = ((double)drvAudioHlpBytesPerSec(pProps) / (double)RT_NS_1SEC) * uNs; - if (uBytes % uBytesPerFrame) /* Any remainder? Make the returned bytes an integral number to the given frames. */ - uBytes = uBytes + (uBytesPerFrame - uBytes % uBytesPerFrame); - - Assert(uBytes % uBytesPerFrame == 0); /* Paranoia. */ - - return uBytes; -} - -/** - * Returns the amount of audio frames for a given time (in ms) and PCM properties. - * - * @return uint32_t Calculated amount of audio frames. - * @param uMs Time (in ms) to calculate amount of frames for. - * @param pProps PCM properties to calculate amount of frames for. - */ -uint32_t DrvAudioHlpMilliToFrames(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - const uint32_t cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); - if (!cbFrame) /* Prevent division by zero. */ - return 0; - - return DrvAudioHlpMilliToBytes(uMs, pProps) / cbFrame; -} - -/** - * Returns the amount of audio frames for a given time (in ns) and PCM properties. - * - * @return uint32_t Calculated amount of audio frames. - * @param uNs Time (in ns) to calculate amount of frames for. - * @param pProps PCM properties to calculate amount of frames for. - */ -uint32_t DrvAudioHlpNanoToFrames(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pProps, 0); - - const uint32_t cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); - if (!cbFrame) /* Prevent division by zero. */ - return 0; - - return DrvAudioHlpNanoToBytes(uNs, pProps) / cbFrame; -} - -/** - * Sanitizes the file name component so that unsupported characters - * will be replaced by an underscore ("_"). - * - * @return IPRT status code. - * @param pszPath Path to sanitize. - * @param cbPath Size (in bytes) of path to sanitize. - */ -int DrvAudioHlpFileNameSanitize(char *pszPath, size_t cbPath) -{ - RT_NOREF(cbPath); - int rc = VINF_SUCCESS; -#ifdef RT_OS_WINDOWS - /* Filter out characters not allowed on Windows platforms, put in by - RTTimeSpecToString(). */ - /** @todo Use something like RTPathSanitize() if available later some time. */ - static RTUNICP const s_uszValidRangePairs[] = - { - ' ', ' ', - '(', ')', - '-', '.', - '0', '9', - 'A', 'Z', - 'a', 'z', - '_', '_', - 0xa0, 0xd7af, - '\0' - }; - ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_uszValidRangePairs, '_' /* Replacement */); - if (cReplaced < 0) - rc = VERR_INVALID_UTF8_ENCODING; -#else - RT_NOREF(pszPath); -#endif - return rc; -} - -/** - * Constructs an unique file name, based on the given path and the audio file type. - * - * @returns IPRT status code. - * @param pszFile Where to store the constructed file name. - * @param cchFile Size (in characters) of the file name buffer. - * @param pszPath Base path to use. - * @param pszName A name for better identifying the file. - * @param uInstance Device / driver instance which is using this file. - * @param enmType Audio file type to construct file name for. - * @param fFlags File naming flags, PDMAUDIOFILENAME_FLAGS_XXX. - */ -int DrvAudioHlpFileNameGet(char *pszFile, size_t cchFile, const char *pszPath, const char *pszName, - uint32_t uInstance, PDMAUDIOFILETYPE enmType, uint32_t fFlags) -{ - AssertPtrReturn(pszFile, VERR_INVALID_POINTER); - AssertReturn(cchFile, VERR_INVALID_PARAMETER); - AssertPtrReturn(pszPath, VERR_INVALID_POINTER); - AssertPtrReturn(pszName, VERR_INVALID_POINTER); - /** @todo Validate fFlags. */ - - int rc; - - do - { - char szFilePath[RTPATH_MAX]; - rc = RTStrCopy(szFilePath, sizeof(szFilePath), pszPath); - AssertRCBreak(rc); - - /* Create it when necessary. */ - if (!RTDirExists(szFilePath)) - { - rc = RTDirCreateFullPath(szFilePath, RTFS_UNIX_IRWXU); - if (RT_FAILURE(rc)) - break; - } - - char szFileName[RTPATH_MAX]; - szFileName[0] = '\0'; - - if (fFlags & PDMAUDIOFILENAME_FLAGS_TS) - { - RTTIMESPEC time; - if (!RTTimeSpecToString(RTTimeNow(&time), szFileName, sizeof(szFileName))) - { - rc = VERR_BUFFER_OVERFLOW; - break; - } - - rc = DrvAudioHlpFileNameSanitize(szFileName, sizeof(szFileName)); - if (RT_FAILURE(rc)) - break; - - rc = RTStrCat(szFileName, sizeof(szFileName), "-"); - if (RT_FAILURE(rc)) - break; - } - - rc = RTStrCat(szFileName, sizeof(szFileName), pszName); - if (RT_FAILURE(rc)) - break; - - rc = RTStrCat(szFileName, sizeof(szFileName), "-"); - if (RT_FAILURE(rc)) - break; - - char szInst[16]; - RTStrPrintf2(szInst, sizeof(szInst), "%RU32", uInstance); - rc = RTStrCat(szFileName, sizeof(szFileName), szInst); - if (RT_FAILURE(rc)) - break; - - switch (enmType) - { - case PDMAUDIOFILETYPE_RAW: - rc = RTStrCat(szFileName, sizeof(szFileName), ".pcm"); - break; - - case PDMAUDIOFILETYPE_WAV: - rc = RTStrCat(szFileName, sizeof(szFileName), ".wav"); - break; - - default: - AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); - break; - } - - if (RT_FAILURE(rc)) - break; - - rc = RTPathAppend(szFilePath, sizeof(szFilePath), szFileName); - if (RT_FAILURE(rc)) - break; - - rc = RTStrCopy(pszFile, cchFile, szFilePath); - - } while (0); - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Creates an audio file. - * - * @returns IPRT status code. - * @param enmType Audio file type to open / create. - * @param pszFile File path of file to open or create. - * @param fFlags Audio file flags, PDMAUDIOFILE_FLAGS_XXX. - * @param ppFile Where to store the created audio file handle. - * Needs to be destroyed with DrvAudioHlpFileDestroy(). - */ -int DrvAudioHlpFileCreate(PDMAUDIOFILETYPE enmType, const char *pszFile, uint32_t fFlags, PPDMAUDIOFILE *ppFile) -{ - AssertPtrReturn(pszFile, VERR_INVALID_POINTER); - /** @todo Validate fFlags. */ - - PPDMAUDIOFILE pFile = (PPDMAUDIOFILE)RTMemAlloc(sizeof(PDMAUDIOFILE)); - if (!pFile) - return VERR_NO_MEMORY; - - int rc = VINF_SUCCESS; - - switch (enmType) - { - case PDMAUDIOFILETYPE_RAW: - case PDMAUDIOFILETYPE_WAV: - pFile->enmType = enmType; - break; - - default: - rc = VERR_INVALID_PARAMETER; - break; - } - - if (RT_SUCCESS(rc)) - { - RTStrPrintf(pFile->szName, RT_ELEMENTS(pFile->szName), "%s", pszFile); - pFile->hFile = NIL_RTFILE; - pFile->fFlags = fFlags; - pFile->pvData = NULL; - pFile->cbData = 0; - } - - if (RT_FAILURE(rc)) - { - RTMemFree(pFile); - pFile = NULL; - } - else - *ppFile = pFile; - - return rc; -} - -/** - * Destroys a formerly created audio file. - * - * @param pFile Audio file (object) to destroy. - */ -void DrvAudioHlpFileDestroy(PPDMAUDIOFILE pFile) -{ - if (!pFile) - return; - - DrvAudioHlpFileClose(pFile); - - RTMemFree(pFile); - pFile = NULL; -} - -/** - * Opens or creates an audio file. - * - * @returns IPRT status code. - * @param pFile Pointer to audio file handle to use. - * @param fOpen Open flags. - * Use PDMAUDIOFILE_DEFAULT_OPEN_FLAGS for the default open flags. - * @param pProps PCM properties to use. - */ -int DrvAudioHlpFileOpen(PPDMAUDIOFILE pFile, uint32_t fOpen, const PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pFile, VERR_INVALID_POINTER); - /** @todo Validate fOpen flags. */ - AssertPtrReturn(pProps, VERR_INVALID_POINTER); - - int rc; - - if (pFile->enmType == PDMAUDIOFILETYPE_RAW) - { - rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen); - } - else if (pFile->enmType == PDMAUDIOFILETYPE_WAV) - { - Assert(pProps->cChannels); - Assert(pProps->uHz); - Assert(pProps->cbSample); - - pFile->pvData = (PAUDIOWAVFILEDATA)RTMemAllocZ(sizeof(AUDIOWAVFILEDATA)); - if (pFile->pvData) - { - pFile->cbData = sizeof(PAUDIOWAVFILEDATA); - - PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData; - AssertPtr(pData); - - /* Header. */ - pData->Hdr.u32RIFF = AUDIO_MAKE_FOURCC('R','I','F','F'); - pData->Hdr.u32Size = 36; - pData->Hdr.u32WAVE = AUDIO_MAKE_FOURCC('W','A','V','E'); - - pData->Hdr.u32Fmt = AUDIO_MAKE_FOURCC('f','m','t',' '); - pData->Hdr.u32Size1 = 16; /* Means PCM. */ - pData->Hdr.u16AudioFormat = 1; /* PCM, linear quantization. */ - pData->Hdr.u16NumChannels = pProps->cChannels; - pData->Hdr.u32SampleRate = pProps->uHz; - pData->Hdr.u32ByteRate = DrvAudioHlpCalcBitrate(pProps) / 8; - pData->Hdr.u16BlockAlign = pProps->cChannels * pProps->cbSample; - pData->Hdr.u16BitsPerSample = pProps->cbSample * 8; - - /* Data chunk. */ - pData->Hdr.u32ID2 = AUDIO_MAKE_FOURCC('d','a','t','a'); - pData->Hdr.u32Size2 = 0; - - rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen); - if (RT_SUCCESS(rc)) - { - rc = RTFileWrite(pFile->hFile, &pData->Hdr, sizeof(pData->Hdr), NULL); - if (RT_FAILURE(rc)) - { - RTFileClose(pFile->hFile); - pFile->hFile = NIL_RTFILE; - } - } - - if (RT_FAILURE(rc)) - { - RTMemFree(pFile->pvData); - pFile->pvData = NULL; - pFile->cbData = 0; - } - } - else - rc = VERR_NO_MEMORY; - } - else - rc = VERR_INVALID_PARAMETER; - - if (RT_SUCCESS(rc)) - { - LogRel2(("Audio: Opened file '%s'\n", pFile->szName)); - } - else - LogRel(("Audio: Failed opening file '%s', rc=%Rrc\n", pFile->szName, rc)); - - return rc; -} - -/** - * Closes an audio file. - * - * @returns IPRT status code. - * @param pFile Audio file handle to close. - */ -int DrvAudioHlpFileClose(PPDMAUDIOFILE pFile) -{ - if (!pFile) - return VINF_SUCCESS; - - size_t cbSize = DrvAudioHlpFileGetDataSize(pFile); - - int rc = VINF_SUCCESS; - - if (pFile->enmType == PDMAUDIOFILETYPE_RAW) - { - if (RTFileIsValid(pFile->hFile)) - rc = RTFileClose(pFile->hFile); - } - else if (pFile->enmType == PDMAUDIOFILETYPE_WAV) - { - if (RTFileIsValid(pFile->hFile)) - { - PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData; - if (pData) /* The .WAV file data only is valid when a file actually has been created. */ - { - /* Update the header with the current data size. */ - RTFileWriteAt(pFile->hFile, 0, &pData->Hdr, sizeof(pData->Hdr), NULL); - } - - rc = RTFileClose(pFile->hFile); - } - - if (pFile->pvData) - { - RTMemFree(pFile->pvData); - pFile->pvData = NULL; - } - } - else - AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); - - if ( RT_SUCCESS(rc) - && !cbSize - && !(pFile->fFlags & PDMAUDIOFILE_FLAGS_KEEP_IF_EMPTY)) - { - rc = DrvAudioHlpFileDelete(pFile); - } - - pFile->cbData = 0; - - if (RT_SUCCESS(rc)) - { - pFile->hFile = NIL_RTFILE; - LogRel2(("Audio: Closed file '%s' (%zu bytes)\n", pFile->szName, cbSize)); - } - else - LogRel(("Audio: Failed closing file '%s', rc=%Rrc\n", pFile->szName, rc)); - - return rc; -} - -/** - * Deletes an audio file. - * - * @returns IPRT status code. - * @param pFile Audio file handle to delete. - */ -int DrvAudioHlpFileDelete(PPDMAUDIOFILE pFile) -{ - AssertPtrReturn(pFile, VERR_INVALID_POINTER); - - int rc = RTFileDelete(pFile->szName); - if (RT_SUCCESS(rc)) - { - LogRel2(("Audio: Deleted file '%s'\n", pFile->szName)); - } - else if (rc == VERR_FILE_NOT_FOUND) /* Don't bitch if the file is not around (anymore). */ - rc = VINF_SUCCESS; - - if (RT_FAILURE(rc)) - LogRel(("Audio: Failed deleting file '%s', rc=%Rrc\n", pFile->szName, rc)); - - return rc; -} - -/** - * Returns the raw audio data size of an audio file. - * - * Note: This does *not* include file headers and other data which does - * not belong to the actual PCM audio data. - * - * @returns Size (in bytes) of the raw PCM audio data. - * @param pFile Audio file handle to retrieve the audio data size for. - */ -size_t DrvAudioHlpFileGetDataSize(PPDMAUDIOFILE pFile) -{ - AssertPtrReturn(pFile, 0); - - size_t cbSize = 0; - - if (pFile->enmType == PDMAUDIOFILETYPE_RAW) - { - cbSize = RTFileTell(pFile->hFile); - } - else if (pFile->enmType == PDMAUDIOFILETYPE_WAV) - { - PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData; - if (pData) /* The .WAV file data only is valid when a file actually has been created. */ - cbSize = pData->Hdr.u32Size2; - } - - return cbSize; -} - -/** - * Returns whether the given audio file is open and in use or not. - * - * @return bool True if open, false if not. - * @param pFile Audio file handle to check open status for. - */ -bool DrvAudioHlpFileIsOpen(PPDMAUDIOFILE pFile) -{ - if (!pFile) - return false; - - return RTFileIsValid(pFile->hFile); -} - -/** - * Write PCM data to a wave (.WAV) file. - * - * @returns IPRT status code. - * @param pFile Audio file handle to write PCM data to. - * @param pvBuf Audio data to write. - * @param cbBuf Size (in bytes) of audio data to write. - * @param fFlags Additional write flags. Not being used at the moment and must be 0. - */ -int DrvAudioHlpFileWrite(PPDMAUDIOFILE pFile, const void *pvBuf, size_t cbBuf, uint32_t fFlags) -{ - AssertPtrReturn(pFile, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - - AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); /** @todo fFlags are currently not implemented. */ - - if (!cbBuf) - return VINF_SUCCESS; - - AssertReturn(RTFileIsValid(pFile->hFile), VERR_WRONG_ORDER); - - int rc; - - if (pFile->enmType == PDMAUDIOFILETYPE_RAW) - { - rc = RTFileWrite(pFile->hFile, pvBuf, cbBuf, NULL); - } - else if (pFile->enmType == PDMAUDIOFILETYPE_WAV) - { - PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData; - AssertPtr(pData); - - rc = RTFileWrite(pFile->hFile, pvBuf, cbBuf, NULL); - if (RT_SUCCESS(rc)) - { - pData->Hdr.u32Size += (uint32_t)cbBuf; - pData->Hdr.u32Size2 += (uint32_t)cbBuf; - } - } - else - rc = VERR_NOT_SUPPORTED; - - return rc; -} - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvAudio.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvAudio.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -1,9 +1,6 @@ /* $Id: DrvAudio.cpp $ */ /** @file - * Intermediate audio driver header. - * - * @remarks Intermediate audio driver for connecting the audio device emulation - * with the host backend. + * Intermediate audio driver - Connects the audio device emulation with the host backend. */ /* @@ -18,18 +15,26 @@ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_AUDIO #include #include #include #include #include +#include +#include #include #include #include #include +#include #include +#include #include #include "VBoxDD.h" @@ -37,3285 +42,4385 @@ #include #include -#include "DrvAudio.h" +#include "AudioHlp.h" #include "AudioMixBuffer.h" -#ifdef VBOX_WITH_AUDIO_ENUM -static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum); -#endif -static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream); -static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd); -static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd); -static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq); -static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); -static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream); -static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); -static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); -static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); -static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); -static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @name PDMAUDIOSTREAM_STS_XXX - Used internally by DRVAUDIOSTREAM::fStatus. + * @{ */ +/** No flags being set. */ +#define PDMAUDIOSTREAM_STS_NONE UINT32_C(0) +/** Set if the stream is enabled, clear if disabled. */ +#define PDMAUDIOSTREAM_STS_ENABLED RT_BIT_32(0) +/** Set if the stream is paused. + * Requires the ENABLED status to be set when used. */ +#define PDMAUDIOSTREAM_STS_PAUSED RT_BIT_32(1) +/** Output only: Set when the stream is draining. + * Requires the ENABLED status to be set when used. */ +#define PDMAUDIOSTREAM_STS_PENDING_DISABLE RT_BIT_32(2) -#ifndef VBOX_AUDIO_TESTCASE +/** Set if the backend for the stream has been created. + * + * This is generally always set after stream creation, but + * can be cleared if the re-initialization of the stream fails later on. + * Asynchronous init may still be incomplete, see + * PDMAUDIOSTREAM_STS_BACKEND_READY. */ +#define PDMAUDIOSTREAM_STS_BACKEND_CREATED RT_BIT_32(3) +/** The backend is ready (PDMIHOSTAUDIO::pfnStreamInitAsync is done). + * Requires the BACKEND_CREATED status to be set. */ +#define PDMAUDIOSTREAM_STS_BACKEND_READY RT_BIT_32(4) +/** Set if the stream needs to be re-initialized by the device (i.e. call + * PDMIAUDIOCONNECTOR::pfnStreamReInit). (The other status bits are preserved + * and are worked as normal while in this state, so that the stream can + * resume operation where it left off.) */ +#define PDMAUDIOSTREAM_STS_NEED_REINIT RT_BIT_32(5) +/** Validation mask for PDMIAUDIOCONNECTOR. */ +#define PDMAUDIOSTREAM_STS_VALID_MASK UINT32_C(0x0000003f) +/** Asserts the validity of the given stream status mask for PDMIAUDIOCONNECTOR. */ +#define PDMAUDIOSTREAM_STS_ASSERT_VALID(a_fStreamStatus) do { \ + AssertMsg(!((a_fStreamStatus) & ~PDMAUDIOSTREAM_STS_VALID_MASK), ("%#x\n", (a_fStreamStatus))); \ + Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_PAUSED) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_ENABLED)); \ + Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_PENDING_DISABLE) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_ENABLED)); \ + Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_BACKEND_READY) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_BACKEND_CREATED)); \ + } while (0) + +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Audio stream context. + * + * Needed for separating data from the guest and host side (per stream). + */ +typedef struct DRVAUDIOSTREAMCTX +{ + /** The stream's audio configuration. */ + PDMAUDIOSTREAMCFG Cfg; +} DRVAUDIOSTREAMCTX; + +/** + * Capture state of a stream wrt backend. + */ +typedef enum DRVAUDIOCAPTURESTATE +{ + /** Invalid zero value. */ + DRVAUDIOCAPTURESTATE_INVALID = 0, + /** No capturing or pre-buffering. */ + DRVAUDIOCAPTURESTATE_NO_CAPTURE, + /** Regular capturing. */ + DRVAUDIOCAPTURESTATE_CAPTURING, + /** Returning silence till the backend buffer has reched the configured + * pre-buffering level. */ + DRVAUDIOCAPTURESTATE_PREBUF, + /** End of valid values. */ + DRVAUDIOCAPTURESTATE_END +} DRVAUDIOCAPTURESTATE; + +/** + * Play state of a stream wrt backend. + */ +typedef enum DRVAUDIOPLAYSTATE +{ + /** Invalid zero value. */ + DRVAUDIOPLAYSTATE_INVALID = 0, + /** No playback or pre-buffering. */ + DRVAUDIOPLAYSTATE_NOPLAY, + /** Playing w/o any prebuffering. */ + DRVAUDIOPLAYSTATE_PLAY, + /** Parallel pre-buffering prior to a device switch (i.e. we're outputting to + * the old device and pre-buffering the same data in parallel). */ + DRVAUDIOPLAYSTATE_PLAY_PREBUF, + /** Initial pre-buffering or the pre-buffering for a device switch (if it + * the device setup took less time than filling up the pre-buffer). */ + DRVAUDIOPLAYSTATE_PREBUF, + /** The device initialization is taking too long, pre-buffering wraps around + * and drops samples. */ + DRVAUDIOPLAYSTATE_PREBUF_OVERDUE, + /** Same as play-prebuf, but we don't have a working output device any more. */ + DRVAUDIOPLAYSTATE_PREBUF_SWITCHING, + /** Working on committing the pre-buffered data. + * We'll typically leave this state immediately and go to PLAY, however if + * the backend cannot handle all the pre-buffered data at once, we'll stay + * here till it does. */ + DRVAUDIOPLAYSTATE_PREBUF_COMMITTING, + /** End of valid values. */ + DRVAUDIOPLAYSTATE_END +} DRVAUDIOPLAYSTATE; + + +/** + * Extended stream structure. + */ +typedef struct DRVAUDIOSTREAM +{ + /** The publicly visible bit. */ + PDMAUDIOSTREAM Core; + + /** Just an extra magic to verify that we allocated the stream rather than some + * faked up stuff from the device (DRVAUDIOSTREAM_MAGIC). */ + uintptr_t uMagic; + + /** List entry in DRVAUDIO::LstStreams. */ + RTLISTNODE ListEntry; + + /** Number of references to this stream. + * Only can be destroyed when the reference count reaches 0. */ + uint32_t volatile cRefs; + /** Stream status - PDMAUDIOSTREAM_STS_XXX. */ + uint32_t fStatus; + + /** Data to backend-specific stream data. + * This data block will be casted by the backend to access its backend-dependent data. + * + * That way the backends do not have access to the audio connector's data. */ + PPDMAUDIOBACKENDSTREAM pBackend; + + /** Set if pfnStreamCreate returned VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED. */ + bool fNeedAsyncInit; + /** The fImmediate parameter value for pfnStreamDestroy. */ + bool fDestroyImmediate; + bool afPadding[2]; + + /** Number of (re-)tries while re-initializing the stream. */ + uint32_t cTriesReInit; + + /** The last backend state we saw. + * This is used to detect state changes (for what that is worth). */ + PDMHOSTAUDIOSTREAMSTATE enmLastBackendState; + + /** The pre-buffering threshold expressed in bytes. */ + uint32_t cbPreBufThreshold; + + /** The pfnStreamInitAsync request handle. */ + PRTREQ hReqInitAsync; + + /** The nanosecond timestamp when the stream was started. */ + uint64_t nsStarted; + /** Internal stream position (as per pfnStreamPlay/pfnStreamCapture). */ + uint64_t offInternal; + + /** Timestamp (in ns) since last trying to re-initialize. + * Might be 0 if has not been tried yet. */ + uint64_t nsLastReInit; + /** Timestamp (in ns) since last iteration. */ + uint64_t nsLastIterated; + /** Timestamp (in ns) since last playback / capture. */ + uint64_t nsLastPlayedCaptured; + /** Timestamp (in ns) since last read (input streams) or + * write (output streams). */ + uint64_t nsLastReadWritten; -# if 0 /* unused */ -static PDMAUDIOFMT drvAudioGetConfFormat(PCFGMNODE pCfgHandle, const char *pszKey, - PDMAUDIOFMT enmDefault, bool *pfDefault) -{ - if ( pCfgHandle == NULL - || pszKey == NULL) + /** Union for input/output specifics depending on enmDir. */ + union { - *pfDefault = true; - return enmDefault; - } + /** + * The specifics for an audio input stream. + */ + struct + { + /** The capture state. */ + DRVAUDIOCAPTURESTATE enmCaptureState; - char *pszValue = NULL; - int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue); - if (RT_FAILURE(rc)) - { - *pfDefault = true; - return enmDefault; - } + struct + { + /** File for writing non-interleaved captures. */ + PAUDIOHLPFILE pFileCapture; + } Dbg; + struct + { + uint32_t cbBackendReadableBefore; + uint32_t cbBackendReadableAfter; +#ifdef VBOX_WITH_STATISTICS + STAMPROFILE ProfCapture; + STAMPROFILE ProfGetReadable; + STAMPROFILE ProfGetReadableBytes; +#endif + } Stats; + } In; - PDMAUDIOFMT fmt = DrvAudioHlpStrToAudFmt(pszValue); - if (fmt == PDMAUDIOFMT_INVALID) - { - *pfDefault = true; - return enmDefault; - } + /** + * The specifics for an audio output stream. + */ + struct + { + /** Space for pre-buffering. */ + uint8_t *pbPreBuf; + /** The size of the pre-buffer allocation (in bytes). */ + uint32_t cbPreBufAlloc; + /** The current pre-buffering read offset. */ + uint32_t offPreBuf; + /** Number of bytes we've pre-buffered. */ + uint32_t cbPreBuffered; + /** The play state. */ + DRVAUDIOPLAYSTATE enmPlayState; + + struct + { + /** File for writing stream playback. */ + PAUDIOHLPFILE pFilePlay; + } Dbg; + struct + { + uint32_t cbBackendWritableBefore; + uint32_t cbBackendWritableAfter; +#ifdef VBOX_WITH_STATISTICS + STAMPROFILE ProfPlay; + STAMPROFILE ProfGetWritable; + STAMPROFILE ProfGetWritableBytes; +#endif + } Stats; + } Out; + } RT_UNION_NM(u); +#ifdef VBOX_WITH_STATISTICS + STAMPROFILE StatProfGetState; + STAMPROFILE StatXfer; +#endif +} DRVAUDIOSTREAM; +/** Pointer to an extended stream structure. */ +typedef DRVAUDIOSTREAM *PDRVAUDIOSTREAM; + +/** Value for DRVAUDIOSTREAM::uMagic (Johann Sebastian Bach). */ +#define DRVAUDIOSTREAM_MAGIC UINT32_C(0x16850331) +/** Value for DRVAUDIOSTREAM::uMagic after destruction */ +#define DRVAUDIOSTREAM_MAGIC_DEAD UINT32_C(0x17500728) - *pfDefault = false; - return fmt; -} -static int drvAudioGetConfInt(PCFGMNODE pCfgHandle, const char *pszKey, - int iDefault, bool *pfDefault) +/** + * Audio driver configuration data, tweakable via CFGM. + */ +typedef struct DRVAUDIOCFG +{ + /** PCM properties to use. */ + PDMAUDIOPCMPROPS Props; + /** Whether using signed sample data or not. + * Needed in order to know whether there is a custom value set in CFGM or not. + * By default set to UINT8_MAX if not set to a custom value. */ + uint8_t uSigned; + /** Whether swapping endianess of sample data or not. + * Needed in order to know whether there is a custom value set in CFGM or not. + * By default set to UINT8_MAX if not set to a custom value. */ + uint8_t uSwapEndian; + /** Configures the period size (in ms). + * This value reflects the time in between each hardware interrupt on the + * backend (host) side. */ + uint32_t uPeriodSizeMs; + /** Configures the (ring) buffer size (in ms). Often is a multiple of uPeriodMs. */ + uint32_t uBufferSizeMs; + /** Configures the pre-buffering size (in ms). + * Time needed in buffer before the stream becomes active (pre buffering). + * The bigger this value is, the more latency for the stream will occur. + * Set to 0 to disable pre-buffering completely. + * By default set to UINT32_MAX if not set to a custom value. */ + uint32_t uPreBufSizeMs; + /** The driver's debugging configuration. */ + struct + { + /** Whether audio debugging is enabled or not. */ + bool fEnabled; + /** Where to store the debugging files. */ + char szPathOut[RTPATH_MAX]; + } Dbg; +} DRVAUDIOCFG; +/** Pointer to tweakable audio configuration. */ +typedef DRVAUDIOCFG *PDRVAUDIOCFG; +/** Pointer to const tweakable audio configuration. */ +typedef DRVAUDIOCFG const *PCDRVAUDIOCFG; + + +/** + * Audio driver instance data. + * + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVAUDIO { + /** Read/Write critical section for guarding changes to pHostDrvAudio and + * BackendCfg during deteach/attach. Mostly taken in shared mode. + * @note Locking order: Must be entered after CritSectGlobals. + * @note Locking order: Must be entered after PDMAUDIOSTREAM::CritSect. */ + RTCRITSECTRW CritSectHotPlug; + /** Critical section for protecting: + * - LstStreams + * - cStreams + * - In.fEnabled + * - In.cStreamsFree + * - Out.fEnabled + * - Out.cStreamsFree + * @note Locking order: Must be entered before PDMAUDIOSTREAM::CritSect. + * @note Locking order: Must be entered before CritSectHotPlug. */ + RTCRITSECTRW CritSectGlobals; + /** List of audio streams (DRVAUDIOSTREAM). */ + RTLISTANCHOR LstStreams; + /** Number of streams in the list. */ + size_t cStreams; + struct + { + /** Whether this driver's input streams are enabled or not. + * This flag overrides all the attached stream statuses. */ + bool fEnabled; + /** Max. number of free input streams. + * UINT32_MAX for unlimited streams. */ + uint32_t cStreamsFree; + } In; + struct + { + /** Whether this driver's output streams are enabled or not. + * This flag overrides all the attached stream statuses. */ + bool fEnabled; + /** Max. number of free output streams. + * UINT32_MAX for unlimited streams. */ + uint32_t cStreamsFree; + } Out; + + /** Audio configuration settings retrieved from the backend. + * The szName field is used for the DriverName config value till we get the + * authoritative name from the backend (only for logging). */ + PDMAUDIOBACKENDCFG BackendCfg; + /** Our audio connector interface. */ + PDMIAUDIOCONNECTOR IAudioConnector; + /** Interface used by the host backend. */ + PDMIHOSTAUDIOPORT IHostAudioPort; + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + /** Pointer to audio driver below us. */ + PPDMIHOSTAUDIO pHostDrvAudio; - if ( pCfgHandle == NULL - || pszKey == NULL) - { - *pfDefault = true; - return iDefault; - } + /** Request pool if the backend needs it for async stream creation. */ + RTREQPOOL hReqPool; - uint64_t u64Data = 0; - int rc = CFGMR3QueryInteger(pCfgHandle, pszKey, &u64Data); - if (RT_FAILURE(rc)) - { - *pfDefault = true; - return iDefault; +#ifdef VBOX_WITH_AUDIO_ENUM + /** Handle to the timer for delayed re-enumeration of backend devices. */ + TMTIMERHANDLE hEnumTimer; + /** Unique name for the the disable-iteration timer. */ + char szEnumTimerName[24]; +#endif + + /** Input audio configuration values (static). */ + DRVAUDIOCFG CfgIn; + /** Output audio configuration values (static). */ + DRVAUDIOCFG CfgOut; + + STAMCOUNTER StatTotalStreamsCreated; +} DRVAUDIO; +/** Pointer to the instance data of an audio driver. */ +typedef DRVAUDIO *PDRVAUDIO; +/** Pointer to const instance data of an audio driver. */ +typedef DRVAUDIO const *PCDRVAUDIO; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd); +static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd); +static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx); +static uint32_t drvAudioStreamRetainInternal(PDRVAUDIOSTREAM pStreamEx); +static uint32_t drvAudioStreamReleaseInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fMayDestroy); +static void drvAudioStreamResetInternal(PDRVAUDIOSTREAM pStreamEx); - } - *pfDefault = false; - return u64Data; +/** Buffer size for drvAudioStreamStatusToStr. */ +# define DRVAUDIO_STATUS_STR_MAX sizeof("BACKEND_CREATED BACKEND_READY ENABLED PAUSED PENDING_DISABLED NEED_REINIT 0x12345678") + +/** + * Converts an audio stream status to a string. + * + * @returns pszDst + * @param pszDst Buffer to convert into, at least minimum size is + * DRVAUDIO_STATUS_STR_MAX. + * @param fStatus Stream status flags to convert. + */ +static const char *drvAudioStreamStatusToStr(char pszDst[DRVAUDIO_STATUS_STR_MAX], uint32_t fStatus) +{ + static const struct + { + const char *pszMnemonic; + uint32_t cchMnemnonic; + uint32_t fFlag; + } s_aFlags[] = + { + { RT_STR_TUPLE("BACKEND_CREATED "), PDMAUDIOSTREAM_STS_BACKEND_CREATED }, + { RT_STR_TUPLE("BACKEND_READY "), PDMAUDIOSTREAM_STS_BACKEND_READY }, + { RT_STR_TUPLE("ENABLED "), PDMAUDIOSTREAM_STS_ENABLED }, + { RT_STR_TUPLE("PAUSED "), PDMAUDIOSTREAM_STS_PAUSED }, + { RT_STR_TUPLE("PENDING_DISABLE "), PDMAUDIOSTREAM_STS_PENDING_DISABLE }, + { RT_STR_TUPLE("NEED_REINIT "), PDMAUDIOSTREAM_STS_NEED_REINIT }, + }; + if (!fStatus) + strcpy(pszDst, "NONE"); + else + { + char *psz = pszDst; + for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++) + if (fStatus & s_aFlags[i].fFlag) + { + memcpy(psz, s_aFlags[i].pszMnemonic, s_aFlags[i].cchMnemnonic); + psz += s_aFlags[i].cchMnemnonic; + fStatus &= ~s_aFlags[i].fFlag; + if (!fStatus) + break; + } + if (fStatus == 0) + psz[-1] = '\0'; + else + psz += RTStrPrintf(psz, DRVAUDIO_STATUS_STR_MAX - (psz - pszDst), "%#x", fStatus); + Assert((uintptr_t)(psz - pszDst) <= DRVAUDIO_STATUS_STR_MAX); + } + return pszDst; } -static const char *drvAudioGetConfStr(PCFGMNODE pCfgHandle, const char *pszKey, - const char *pszDefault, bool *pfDefault) + +/** + * Get play state name string. + */ +static const char *drvAudioPlayStateName(DRVAUDIOPLAYSTATE enmState) { - if ( pCfgHandle == NULL - || pszKey == NULL) + switch (enmState) { - *pfDefault = true; - return pszDefault; + case DRVAUDIOPLAYSTATE_INVALID: return "INVALID"; + case DRVAUDIOPLAYSTATE_NOPLAY: return "NOPLAY"; + case DRVAUDIOPLAYSTATE_PLAY: return "PLAY"; + case DRVAUDIOPLAYSTATE_PLAY_PREBUF: return "PLAY_PREBUF"; + case DRVAUDIOPLAYSTATE_PREBUF: return "PREBUF"; + case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: return "PREBUF_OVERDUE"; + case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: return "PREBUF_SWITCHING"; + case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: return "PREBUF_COMMITTING"; + case DRVAUDIOPLAYSTATE_END: + break; } + return "BAD"; +} - char *pszValue = NULL; - int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue); - if (RT_FAILURE(rc)) +#ifdef LOG_ENABLED +/** + * Get capture state name string. + */ +static const char *drvAudioCaptureStateName(DRVAUDIOCAPTURESTATE enmState) +{ + switch (enmState) { - *pfDefault = true; - return pszDefault; + case DRVAUDIOCAPTURESTATE_INVALID: return "INVALID"; + case DRVAUDIOCAPTURESTATE_NO_CAPTURE: return "NO_CAPTURE"; + case DRVAUDIOCAPTURESTATE_CAPTURING: return "CAPTURING"; + case DRVAUDIOCAPTURESTATE_PREBUF: return "PREBUF"; + case DRVAUDIOCAPTURESTATE_END: + break; } - - *pfDefault = false; - return pszValue; + return "BAD"; } +#endif -# endif /* unused */ - -#ifdef LOG_ENABLED /** - * Converts an audio stream status to a string. + * Checks if the stream status is one that can be read from. * - * @returns Stringified stream status flags. Must be free'd with RTStrFree(). - * "NONE" if no flags set. - * @param fStatus Stream status flags to convert. + * @returns @c true if ready to be read from, @c false if not. + * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX. + * @note Not for backend statuses (use PDMAudioStrmStatusBackendCanRead)! */ -static char *dbgAudioStreamStatusToStr(PDMAUDIOSTREAMSTS fStatus) +DECLINLINE(bool) PDMAudioStrmStatusCanRead(uint32_t fStatus) { -#define APPEND_FLAG_TO_STR(_aFlag) \ - if (fStatus & PDMAUDIOSTREAMSTS_FLAGS_##_aFlag) \ - { \ - if (pszFlags) \ - { \ - rc2 = RTStrAAppend(&pszFlags, " "); \ - if (RT_FAILURE(rc2)) \ - break; \ - } \ - \ - rc2 = RTStrAAppend(&pszFlags, #_aFlag); \ - if (RT_FAILURE(rc2)) \ - break; \ - } \ + PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus); + AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false); + return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED + | PDMAUDIOSTREAM_STS_ENABLED + | PDMAUDIOSTREAM_STS_PAUSED + | PDMAUDIOSTREAM_STS_NEED_REINIT)) + == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED + | PDMAUDIOSTREAM_STS_ENABLED); +} - char *pszFlags = NULL; - int rc2 = VINF_SUCCESS; +/** + * Checks if the stream status is one that can be written to. + * + * @returns @c true if ready to be written to, @c false if not. + * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX. + * @note Not for backend statuses (use PDMAudioStrmStatusBackendCanWrite)! + */ +DECLINLINE(bool) PDMAudioStrmStatusCanWrite(uint32_t fStatus) +{ + PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus); + AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false); + return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED + | PDMAUDIOSTREAM_STS_ENABLED + | PDMAUDIOSTREAM_STS_PAUSED + | PDMAUDIOSTREAM_STS_PENDING_DISABLE + | PDMAUDIOSTREAM_STS_NEED_REINIT)) + == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED + | PDMAUDIOSTREAM_STS_ENABLED); +} - do - { - APPEND_FLAG_TO_STR(INITIALIZED ); - APPEND_FLAG_TO_STR(ENABLED ); - APPEND_FLAG_TO_STR(PAUSED ); - APPEND_FLAG_TO_STR(PENDING_DISABLE); - APPEND_FLAG_TO_STR(PENDING_REINIT ); - } while (0); +/** + * Checks if the stream status is a ready-to-operate one. + * + * @returns @c true if ready to operate, @c false if not. + * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX. + * @note Not for backend statuses! + */ +DECLINLINE(bool) PDMAudioStrmStatusIsReady(uint32_t fStatus) +{ + PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus); + AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false); + return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED + | PDMAUDIOSTREAM_STS_ENABLED + | PDMAUDIOSTREAM_STS_NEED_REINIT)) + == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED + | PDMAUDIOSTREAM_STS_ENABLED); +} - if (!pszFlags) - rc2 = RTStrAAppend(&pszFlags, "NONE"); - if ( RT_FAILURE(rc2) - && pszFlags) +/** + * Wrapper around PDMIHOSTAUDIO::pfnStreamGetStatus and checks the result. + * + * @returns A PDMHOSTAUDIOSTREAMSTATE value. + * @param pThis Pointer to the DrvAudio instance data. + * @param pStreamEx The stream to get the backend status for. + */ +DECLINLINE(PDMHOSTAUDIOSTREAMSTATE) drvAudioStreamGetBackendState(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) +{ + if (pThis->pHostDrvAudio) { - RTStrFree(pszFlags); - pszFlags = NULL; + PDMHOSTAUDIOSTREAMSTATE enmState = pThis->pHostDrvAudio->pfnStreamGetState(pThis->pHostDrvAudio, pStreamEx->pBackend); + Log9Func(("%s: %s\n", pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmState) )); + Assert( enmState > PDMHOSTAUDIOSTREAMSTATE_INVALID + && enmState < PDMHOSTAUDIOSTREAMSTATE_END + && (enmState != PDMHOSTAUDIOSTREAMSTATE_DRAINING || pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)); + return enmState; } - -#undef APPEND_FLAG_TO_STR - - return pszFlags; + Log9Func(("%s: not-working\n", pStreamEx->Core.Cfg.szName)); + return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; } -#endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */ -# if 0 /* unused */ -static int drvAudioProcessOptions(PCFGMNODE pCfgHandle, const char *pszPrefix, audio_option *paOpts, size_t cOpts) + +/** + * Worker for drvAudioStreamProcessBackendStateChange that completes draining. + */ +DECLINLINE(void) drvAudioStreamProcessBackendStateChangeWasDraining(PDRVAUDIOSTREAM pStreamEx) { - AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER); - AssertPtrReturn(pszPrefix, VERR_INVALID_POINTER); - /* oaOpts and cOpts are optional. */ + Log(("drvAudioStreamProcessBackendStateChange: Stream '%s': Done draining - disabling stream.\n", pStreamEx->Core.Cfg.szName)); + pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE); + drvAudioStreamResetInternal(pStreamEx); +} - PCFGMNODE pCfgChildHandle = NULL; - PCFGMNODE pCfgChildChildHandle = NULL; - /* If pCfgHandle is NULL, let NULL be passed to get int and get string functions.. - * The getter function will return default values. - */ - if (pCfgHandle != NULL) - { - /* If its audio general setting, need to traverse to one child node. - * /Devices/ichac97/0/LUN#0/Config/Audio - */ - if(!strncmp(pszPrefix, "AUDIO", 5)) /** @todo Use a \#define */ - { - pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle); - if(pCfgChildHandle) - pCfgHandle = pCfgChildHandle; - } - else - { - /* If its driver specific configuration , then need to traverse two level deep child - * child nodes. for eg. in case of DirectSoundConfiguration item - * /Devices/ichac97/0/LUN#0/Config/Audio/DirectSoundConfig - */ - pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle); - if (pCfgChildHandle) - { - pCfgChildChildHandle = CFGMR3GetFirstChild(pCfgChildHandle); - if (pCfgChildChildHandle) - pCfgHandle = pCfgChildChildHandle; - } - } - } +/** + * Processes backend state change. + * + * @returns the new state value. + */ +static PDMHOSTAUDIOSTREAMSTATE drvAudioStreamProcessBackendStateChange(PDRVAUDIOSTREAM pStreamEx, + PDMHOSTAUDIOSTREAMSTATE enmNewState, + PDMHOSTAUDIOSTREAMSTATE enmOldState) +{ + PDMAUDIODIR const enmDir = pStreamEx->Core.Cfg.enmDir; +#ifdef LOG_ENABLED + DRVAUDIOPLAYSTATE const enmPlayState = enmDir == PDMAUDIODIR_OUT + ? pStreamEx->Out.enmPlayState : DRVAUDIOPLAYSTATE_INVALID; + DRVAUDIOCAPTURESTATE const enmCaptureState = enmDir == PDMAUDIODIR_IN + ? pStreamEx->In.enmCaptureState : DRVAUDIOCAPTURESTATE_INVALID; +#endif + Assert(enmNewState != enmOldState); + Assert(enmOldState > PDMHOSTAUDIOSTREAMSTATE_INVALID && enmOldState < PDMHOSTAUDIOSTREAMSTATE_END); + AssertReturn(enmNewState > PDMHOSTAUDIOSTREAMSTATE_INVALID && enmNewState < PDMHOSTAUDIOSTREAMSTATE_END, enmOldState); - for (size_t i = 0; i < cOpts; i++) + /* + * Figure out what happend and how that reflects on the playback state and stuff. + */ + switch (enmNewState) { - audio_option *pOpt = &paOpts[i]; - if (!pOpt->valp) - { - LogFlowFunc(("Option value pointer for `%s' is not set\n", pOpt->name)); - continue; - } + case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING: + /* Guess we're switching device. Nothing to do because the backend will tell us, right? */ + break; - bool fUseDefault; + case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING: + case PDMHOSTAUDIOSTREAMSTATE_INACTIVE: + /* The stream has stopped working or is inactive. Switch stop any draining & to noplay mode. */ + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE) + drvAudioStreamProcessBackendStateChangeWasDraining(pStreamEx); + if (enmDir == PDMAUDIODIR_OUT) + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY; + else + pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_NO_CAPTURE; + break; - switch (pOpt->tag) - { - case AUD_OPT_BOOL: - case AUD_OPT_INT: + case PDMHOSTAUDIOSTREAMSTATE_OKAY: + switch (enmOldState) { - int *intp = (int *)pOpt->valp; - *intp = drvAudioGetConfInt(pCfgHandle, pOpt->name, *intp, &fUseDefault); + case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING: + /* Should be taken care of elsewhere, so do nothing. */ + break; - break; - } + case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING: + case PDMHOSTAUDIOSTREAMSTATE_INACTIVE: + /* Go back to pre-buffering/playing depending on whether it is enabled + or not, resetting the stream state. */ + drvAudioStreamResetInternal(pStreamEx); + break; - case AUD_OPT_FMT: - { - PDMAUDIOFMT *fmtp = (PDMAUDIOFMT *)pOpt->valp; - *fmtp = drvAudioGetConfFormat(pCfgHandle, pOpt->name, *fmtp, &fUseDefault); + case PDMHOSTAUDIOSTREAMSTATE_DRAINING: + /* Complete the draining. May race the iterate code. */ + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE) + drvAudioStreamProcessBackendStateChangeWasDraining(pStreamEx); + break; - break; + /* no default: */ + case PDMHOSTAUDIOSTREAMSTATE_OKAY: /* impossible */ + case PDMHOSTAUDIOSTREAMSTATE_INVALID: + case PDMHOSTAUDIOSTREAMSTATE_END: + case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK: + break; } + break; - case AUD_OPT_STR: - { - const char **strp = (const char **)pOpt->valp; - *strp = drvAudioGetConfStr(pCfgHandle, pOpt->name, *strp, &fUseDefault); + case PDMHOSTAUDIOSTREAMSTATE_DRAINING: + /* We do all we need to do when issuing the DRAIN command. */ + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE); + break; - break; - } + /* no default: */ + case PDMHOSTAUDIOSTREAMSTATE_INVALID: + case PDMHOSTAUDIOSTREAMSTATE_END: + case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK: + break; + } - default: - LogFlowFunc(("Bad value tag for option `%s' - %d\n", pOpt->name, pOpt->tag)); - fUseDefault = false; - break; - } + if (enmDir == PDMAUDIODIR_OUT) + LogFunc(("Output stream '%s': %s/%s -> %s/%s\n", pStreamEx->Core.Cfg.szName, + PDMHostAudioStreamStateGetName(enmOldState), drvAudioPlayStateName(enmPlayState), + PDMHostAudioStreamStateGetName(enmNewState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); + else + LogFunc(("Input stream '%s': %s/%s -> %s/%s\n", pStreamEx->Core.Cfg.szName, + PDMHostAudioStreamStateGetName(enmOldState), drvAudioCaptureStateName(enmCaptureState), + PDMHostAudioStreamStateGetName(enmNewState), drvAudioCaptureStateName(pStreamEx->In.enmCaptureState) )); - if (!pOpt->overridenp) - pOpt->overridenp = &pOpt->overriden; + pStreamEx->enmLastBackendState = enmNewState; + return enmNewState; +} - *pOpt->overridenp = !fUseDefault; - } - return VINF_SUCCESS; +/** + * This gets the backend state and handles changes compared to + * DRVAUDIOSTREAM::enmLastBackendState (updated). + * + * @returns A PDMHOSTAUDIOSTREAMSTATE value. + * @param pThis Pointer to the DrvAudio instance data. + * @param pStreamEx The stream to get the backend status for. + */ +DECLINLINE(PDMHOSTAUDIOSTREAMSTATE) drvAudioStreamGetBackendStateAndProcessChanges(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) +{ + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); + if (pStreamEx->enmLastBackendState == enmBackendState) + return enmBackendState; + return drvAudioStreamProcessBackendStateChange(pStreamEx, enmBackendState, pStreamEx->enmLastBackendState); } -# endif /* unused */ -#endif /* !VBOX_AUDIO_TESTCASE */ + +#ifdef VBOX_WITH_AUDIO_ENUM /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl} + * Enumerates all host audio devices. + * + * This functionality might not be implemented by all backends and will return + * VERR_NOT_SUPPORTED if not being supported. + * + * @note Must not hold the driver's critical section! + * + * @returns VBox status code. + * @param pThis Driver instance to be called. + * @param fLog Whether to print the enumerated device to the release log or not. + * @param pDevEnum Where to store the device enumeration. + * + * @remarks This is currently ONLY used for release logging. */ -static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface, - PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +static DECLCALLBACK(int) drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIOHOSTENUM pDevEnum) { - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); - if (!pStream) - return VINF_SUCCESS; + int rc; - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + /* + * If the backend supports it, do a device enumeration. + */ + if (pThis->pHostDrvAudio->pfnGetDevices) + { + PDMAUDIOHOSTENUM DevEnum; + rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum); + if (RT_SUCCESS(rc)) + { + if (fLog) + { + LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->BackendCfg.szName)); - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; + PPDMAUDIOHOSTDEV pDev; + RTListForEach(&DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry) + { + char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN]; + LogRel(("Audio: Device '%s':\n" + "Audio: ID = %s\n" + "Audio: Usage = %s\n" + "Audio: Flags = %s\n" + "Audio: Input channels = %RU8\n" + "Audio: Output channels = %RU8\n", + pDev->pszName, pDev->pszId ? pDev->pszId : "", + PDMAudioDirGetName(pDev->enmUsage), PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags), + pDev->cMaxInputChannels, pDev->cMaxOutputChannels)); + } + } - LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd))); + if (pDevEnum) + rc = PDMAudioHostEnumCopy(pDevEnum, &DevEnum, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/); - rc = drvAudioStreamControlInternal(pThis, pStream, enmStreamCmd); + PDMAudioHostEnumDelete(&DevEnum); + } + else + { + if (fLog) + LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->BackendCfg.szName, rc)); + /* Not fatal. */ + } + } + else + { + rc = VERR_NOT_SUPPORTED; - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; + if (fLog) + LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->BackendCfg.szName)); + } + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + LogFunc(("Returning %Rrc\n", rc)); return rc; } +#endif /* VBOX_WITH_AUDIO_ENUM */ + + +/********************************************************************************************************************************* +* PDMIAUDIOCONNECTOR * +*********************************************************************************************************************************/ /** - * Controls an audio stream. - * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Stream to control. - * @param enmStreamCmd Control command. + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable} */ -static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + LogFlowFunc(("enmDir=%s fEnable=%d\n", PDMAudioDirGetName(enmDir), fEnable)); - LogFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd))); - -#ifdef LOG_ENABLED - char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); - LogFlowFunc(("fStatus=%s\n", pszStreamSts)); - RTStrFree(pszStreamSts); -#endif /* LOG_ENABLED */ + /* + * Figure which status flag variable is being updated. + */ + bool *pfEnabled; + if (enmDir == PDMAUDIODIR_IN) + pfEnabled = &pThis->In.fEnabled; + else if (enmDir == PDMAUDIODIR_OUT) + pfEnabled = &pThis->Out.fEnabled; + else + AssertFailedReturn(VERR_INVALID_PARAMETER); - int rc = VINF_SUCCESS; + /* + * Grab the driver wide lock and check it. Ignore call if no change. + */ + int rc = RTCritSectRwEnterExcl(&pThis->CritSectGlobals); + AssertRCReturn(rc, rc); - switch (enmStreamCmd) + if (fEnable != *pfEnabled) { - case PDMAUDIOSTREAMCMD_ENABLE: - { - if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED)) - { - /* Is a pending disable outstanding? Then disable first. */ - if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE) - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); - - if (RT_SUCCESS(rc)) - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE); + LogRel(("Audio: %s %s for driver '%s'\n", + fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->BackendCfg.szName)); - if (RT_SUCCESS(rc)) - pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAGS_ENABLED; - } - break; - } + /* + * When enabling, we must update flag before calling drvAudioStreamControlInternalBackend. + */ + if (fEnable) + *pfEnabled = true; - case PDMAUDIOSTREAMCMD_DISABLE: + /* + * Update the backend status for the streams in the given direction. + * + * The pThis->Out.fEnable / pThis->In.fEnable status flags only reflect in the + * direction of the backend, drivers and devices above us in the chain does not + * know about this. When disabled playback goes to /dev/null and we capture + * only silence. This means pStreamEx->fStatus holds the nominal status + * and we'll use it to restore the operation. (See also @bugref{9882}.) + */ + PDRVAUDIOSTREAM pStreamEx; + RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry) { - if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED) + if (pStreamEx->Core.Cfg.enmDir == enmDir) { + RTCritSectEnter(&pStreamEx->Core.CritSect); + /* - * For playback (output) streams first mark the host stream as pending disable, - * so that the rest of the remaining audio data will be played first before - * closing the stream. + * When (re-)enabling a stream, clear the disabled warning bit again. */ - if (pStream->enmDir == PDMAUDIODIR_OUT) - { - LogFunc(("[%s] Pending disable/pause\n", pStream->szName)); - pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE; - } + if (fEnable) + pStreamEx->Core.fWarningsShown &= ~PDMAUDIOSTREAM_WARN_FLAGS_DISABLED; - /* Can we close the host stream as well (not in pending disable mode)? */ - if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE)) + /* + * We don't need to do anything unless the stream is enabled. + * Paused includes enabled, as does draining, but we only want the former. + */ + uint32_t const fStatus = pStreamEx->fStatus; + if (fStatus & PDMAUDIOSTREAM_STS_ENABLED) { - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); - if (RT_SUCCESS(rc)) - drvAudioStreamResetInternal(pThis, pStream); + const char *pszOperation; + int rc2; + if (fEnable) + { + if (!(fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)) + { + /** @todo r=bird: We need to redo pre-buffering OR switch to + * DRVAUDIOPLAYSTATE_PREBUF_SWITCHING playback mode when disabling + * output streams. The former is preferred if associated with + * reporting the stream as INACTIVE. */ + rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE); + pszOperation = "enable"; + if (RT_SUCCESS(rc2) && (fStatus & PDMAUDIOSTREAM_STS_PAUSED)) + { + rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE); + pszOperation = "pause"; + } + } + else + { + rc2 = VINF_SUCCESS; + pszOperation = NULL; + } + } + else + { + rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + pszOperation = "disable"; + } + if (RT_FAILURE(rc2)) + { + LogRel(("Audio: Failed to %s %s stream '%s': %Rrc\n", + pszOperation, enmDir == PDMAUDIODIR_IN ? "input" : "output", pStreamEx->Core.Cfg.szName, rc2)); + if (RT_SUCCESS(rc)) + rc = rc2; /** @todo r=bird: This isn't entirely helpful to the caller since we'll update the status + * regardless of the status code we return. And anyway, there is nothing that can be done + * about individual stream by the caller... */ + } } - } - break; - } - case PDMAUDIOSTREAMCMD_PAUSE: - { - if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED)) - { - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_PAUSE); - if (RT_SUCCESS(rc)) - pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAGS_PAUSED; + RTCritSectLeave(&pStreamEx->Core.CritSect); } - break; } - case PDMAUDIOSTREAMCMD_RESUME: - { - if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED) - { - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_RESUME); - if (RT_SUCCESS(rc)) - pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAGS_PAUSED; - } - break; - } - - case PDMAUDIOSTREAMCMD_DROP: - { - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DROP); - if (RT_SUCCESS(rc)) - { - drvAudioStreamDropInternal(pThis, pStream); - } - break; - } - - default: - rc = VERR_NOT_IMPLEMENTED; - break; + /* + * When disabling, we must update the status flag after the + * drvAudioStreamControlInternalBackend(DISABLE) calls. + */ + *pfEnabled = fEnable; } - if (RT_FAILURE(rc)) - LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc)); - + RTCritSectRwLeaveExcl(&pThis->CritSectGlobals); + LogFlowFuncLeaveRC(rc); return rc; } + /** - * Controls a stream's backend. - * If the stream has no backend available, VERR_NOT_FOUND is returned. - * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Stream to control. - * @param enmStreamCmd Control command. + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled} */ -static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - -#ifdef LOG_ENABLED - char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); - LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), pszStreamSts)); - RTStrFree(pszStreamSts); -#endif /* LOG_ENABLED */ - - if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */ - return VINF_SUCCESS; + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals); + AssertRCReturn(rc, false); - LogRel2(("Audio: %s stream '%s'\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName)); - - int rc = VINF_SUCCESS; - - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - { - if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED)) - rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_ENABLE); - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - { - if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED) - rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DISABLE); - break; - } + bool fEnabled; + if (enmDir == PDMAUDIODIR_IN) + fEnabled = pThis->In.fEnabled; + else if (enmDir == PDMAUDIODIR_OUT) + fEnabled = pThis->Out.fEnabled; + else + AssertFailedStmt(fEnabled = false); - case PDMAUDIOSTREAMCMD_PAUSE: - { - /* Only pause if the stream is enabled. */ - if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED)) - break; + RTCritSectRwLeaveShared(&pThis->CritSectGlobals); + return fEnabled; +} - if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED)) - rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_PAUSE); - break; - } - case PDMAUDIOSTREAMCMD_RESUME: - { - /* Only need to resume if the stream is enabled. */ - if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED)) - break; +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig} + */ +static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg) +{ + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + AssertRCReturn(rc, rc); - if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED) - rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_RESUME); - break; - } + if (pThis->pHostDrvAudio) + rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg); + else + rc = VERR_PDM_NO_ATTACHED_DRIVER; - case PDMAUDIOSTREAMCMD_DRAIN: - { - rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DRAIN); - break; - } + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + LogFlowFuncLeaveRC(rc); + return rc; +} - case PDMAUDIOSTREAMCMD_DROP: - { - rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DROP); - break; - } - default: - { - AssertMsgFailed(("Command %RU32 not implemented\n", enmStreamCmd)); - rc = VERR_NOT_IMPLEMENTED; - break; - } - } +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir) +{ + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + AssertRCReturn(rc, PDMAUDIOBACKENDSTS_UNKNOWN); - if (RT_FAILURE(rc)) + PDMAUDIOBACKENDSTS fBackendStatus; + if (pThis->pHostDrvAudio) { - if ( rc != VERR_NOT_IMPLEMENTED - && rc != VERR_NOT_SUPPORTED - && rc != VERR_AUDIO_STREAM_NOT_READY) - { - LogRel(("Audio: %s stream '%s' failed with %Rrc\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName, rc)); - } - - LogFunc(("[%s] %s failed with %Rrc\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), rc)); + if (pThis->pHostDrvAudio->pfnGetStatus) + fBackendStatus = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir); + else + fBackendStatus = PDMAUDIOBACKENDSTS_UNKNOWN; } + else + fBackendStatus = PDMAUDIOBACKENDSTS_NOT_ATTACHED; - return rc; + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + LogFlowFunc(("LEAVE - %#x\n", fBackendStatus)); + return fBackendStatus; } + /** - * Initializes an audio stream with a given host and guest stream configuration. + * Frees an audio stream and its allocated resources. * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Stream to initialize. - * @param pCfgHost Stream configuration to use for the host side (backend). - * @param pCfgGuest Stream configuration to use for the guest side. + * @param pStreamEx Audio stream to free. After this call the pointer will + * not be valid anymore. */ -static int drvAudioStreamInitInternal(PDRVAUDIO pThis, - PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest) +static void drvAudioStreamFree(PDRVAUDIOSTREAM pStreamEx) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER); - - /* - * Init host stream. - */ - - /* Set the host's default audio data layout. */ - pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; - -#ifdef LOG_ENABLED - LogFunc(("[%s] Requested host format:\n", pStream->szName)); - DrvAudioHlpStreamCfgPrint(pCfgHost); -#endif + if (pStreamEx) + { + LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName)); + Assert(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC); + Assert(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC); - LogRel2(("Audio: Creating stream '%s'\n", pStream->szName)); - LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %u%s, %RU8 %s\n", - pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName, - pCfgGuest->Props.uHz, pCfgGuest->Props.cbSample * 8, pCfgGuest->Props.fSigned ? "S" : "U", - pCfgGuest->Props.cChannels, pCfgGuest->Props.cChannels == 1 ? "Channel" : "Channels")); - LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %u%s, %RU8 %s\n", - pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName, - pCfgHost->Props.uHz, pCfgHost->Props.cbSample * 8, pCfgHost->Props.fSigned ? "S" : "U", - pCfgHost->Props.cChannels, pCfgHost->Props.cChannels == 1 ? "Channel" : "Channels")); + pStreamEx->Core.uMagic = ~PDMAUDIOSTREAM_MAGIC; + pStreamEx->pBackend = NULL; + pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC_DEAD; - PDMAUDIOSTREAMCFG CfgHostAcq; - int rc = drvAudioStreamCreateInternalBackend(pThis, pStream, pCfgHost, &CfgHostAcq); - if (RT_FAILURE(rc)) - return rc; + RTCritSectDelete(&pStreamEx->Core.CritSect); -#ifdef LOG_ENABLED - LogFunc(("[%s] Acquired host format:\n", pStream->szName)); - DrvAudioHlpStreamCfgPrint(&CfgHostAcq); -#endif + RTMemFree(pStreamEx); + } +} - LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %u%s, %RU8 %s\n", - CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName, - CfgHostAcq.Props.uHz, CfgHostAcq.Props.cbSample * 8, CfgHostAcq.Props.fSigned ? "S" : "U", - CfgHostAcq.Props.cChannels, CfgHostAcq.Props.cChannels == 1 ? "Channel" : "Channels")); - /* Let the user know if the backend changed some of the tweakable values. */ - if (CfgHostAcq.Backend.cFramesBufferSize != pCfgHost->Backend.cFramesBufferSize) - LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", - DrvAudioHlpFramesToMilli(pCfgHost->Backend.cFramesBufferSize, &pCfgHost->Props), pCfgHost->Backend.cFramesBufferSize, - DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cFramesBufferSize, &CfgHostAcq.Props), CfgHostAcq.Backend.cFramesBufferSize)); +/** + * Adjusts the request stream configuration, applying our settings. + * + * This also does some basic validations. + * + * Used by both the stream creation and stream configuration hinting code. + * + * @returns VBox status code. + * @param pThis Pointer to the DrvAudio instance data. + * @param pCfg The configuration that should be adjusted. + * @param pszName Stream name to use when logging warnings and errors. + */ +static int drvAudioStreamAdjustConfig(PCDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfg, const char *pszName) +{ + /* Get the right configuration for the stream to be created. */ + PCDRVAUDIOCFG pDrvCfg = pCfg->enmDir == PDMAUDIODIR_IN ? &pThis->CfgIn: &pThis->CfgOut; - if (CfgHostAcq.Backend.cFramesPeriod != pCfgHost->Backend.cFramesPeriod) - LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", - DrvAudioHlpFramesToMilli(pCfgHost->Backend.cFramesPeriod, &pCfgHost->Props), pCfgHost->Backend.cFramesPeriod, - DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cFramesPeriod, &CfgHostAcq.Props), CfgHostAcq.Backend.cFramesPeriod)); + /* Fill in the tweakable parameters into the requested host configuration. + * All parameters in principle can be changed and returned by the backend via the acquired configuration. */ - if (CfgHostAcq.Backend.cFramesPreBuffering != pCfgHost->Backend.cFramesPreBuffering) - LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", - DrvAudioHlpFramesToMilli(pCfgHost->Backend.cFramesPreBuffering, &pCfgHost->Props), pCfgHost->Backend.cFramesPreBuffering, - DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cFramesPreBuffering, &CfgHostAcq.Props), CfgHostAcq.Backend.cFramesPreBuffering)); /* - * Configure host buffers. + * PCM */ - - /* Check if the backend did return sane values and correct if necessary. - * Should never happen with our own backends, but you never know ... */ - if (CfgHostAcq.Backend.cFramesBufferSize < CfgHostAcq.Backend.cFramesPreBuffering) + if (PDMAudioPropsSampleSize(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */ { - LogRel2(("Audio: Warning: Pre-buffering size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), " - "setting pre-buffering size to %RU32 frames\n", - CfgHostAcq.Backend.cFramesPreBuffering, pStream->szName, CfgHostAcq.Backend.cFramesBufferSize, CfgHostAcq.Backend.cFramesBufferSize)); - CfgHostAcq.Backend.cFramesPreBuffering = CfgHostAcq.Backend.cFramesBufferSize; + PDMAudioPropsSetSampleSize(&pCfg->Props, PDMAudioPropsSampleSize(&pDrvCfg->Props)); + LogRel2(("Audio: Using custom sample size of %RU8 bytes for stream '%s'\n", + PDMAudioPropsSampleSize(&pCfg->Props), pszName)); } - if (CfgHostAcq.Backend.cFramesPeriod > CfgHostAcq.Backend.cFramesBufferSize) + if (pDrvCfg->Props.uHz) /* Anything set via custom extra-data? */ { - LogRel2(("Audio: Warning: Period size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), setting to %RU32 frames\n", - CfgHostAcq.Backend.cFramesPeriod, pStream->szName, CfgHostAcq.Backend.cFramesBufferSize, CfgHostAcq.Backend.cFramesBufferSize)); - CfgHostAcq.Backend.cFramesPeriod = CfgHostAcq.Backend.cFramesBufferSize; + pCfg->Props.uHz = pDrvCfg->Props.uHz; + LogRel2(("Audio: Using custom Hz rate %RU32 for stream '%s'\n", pCfg->Props.uHz, pszName)); } - uint64_t msBufferSize = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cFramesBufferSize, &CfgHostAcq.Props); - - LogRel2(("Audio: Buffer size of stream '%s' is %RU64ms (%RU32 frames)\n", - pStream->szName, msBufferSize, CfgHostAcq.Backend.cFramesBufferSize)); - - /* If no own pre-buffer is set, let the backend choose. */ - uint64_t msPreBuf = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cFramesPreBuffering, &CfgHostAcq.Props); - LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64ms (%RU32 frames)\n", - pStream->szName, msPreBuf, CfgHostAcq.Backend.cFramesPreBuffering)); - - /* Make sure the configured buffer size by the backend at least can hold the configured latency. */ - const uint32_t msPeriod = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cFramesPeriod, &CfgHostAcq.Props); - - LogRel2(("Audio: Period size of stream '%s' is %RU64ms (%RU32 frames)\n", - pStream->szName, msPeriod, CfgHostAcq.Backend.cFramesPeriod)); - - if ( pCfgGuest->Device.cMsSchedulingHint /* Any scheduling hint set? */ - && pCfgGuest->Device.cMsSchedulingHint > msPeriod) /* This might lead to buffer underflows. */ + if (pDrvCfg->uSigned != UINT8_MAX) /* Anything set via custom extra-data? */ { - LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n", - pStream->szName, pCfgGuest->Device.cMsSchedulingHint, msPeriod)); + pCfg->Props.fSigned = RT_BOOL(pDrvCfg->uSigned); + LogRel2(("Audio: Using custom %s sample format for stream '%s'\n", + pCfg->Props.fSigned ? "signed" : "unsigned", pszName)); } - /* Destroy any former mixing buffer. */ - AudioMixBufDestroy(&pStream->Host.MixBuf); - - /* Make sure to (re-)set the host buffer's shift size. */ - CfgHostAcq.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(CfgHostAcq.Props.cbSample, CfgHostAcq.Props.cChannels); + if (pDrvCfg->uSwapEndian != UINT8_MAX) /* Anything set via custom extra-data? */ + { + pCfg->Props.fSwapEndian = RT_BOOL(pDrvCfg->uSwapEndian); + LogRel2(("Audio: Using custom %s endianess for samples of stream '%s'\n", + pCfg->Props.fSwapEndian ? "swapped" : "original", pszName)); + } - rc = AudioMixBufInit(&pStream->Host.MixBuf, pStream->szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize); - AssertRC(rc); + if (PDMAudioPropsChannels(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */ + { + PDMAudioPropsSetChannels(&pCfg->Props, PDMAudioPropsChannels(&pDrvCfg->Props)); + LogRel2(("Audio: Using custom %RU8 channel(s) for stream '%s'\n", PDMAudioPropsChannels(&pDrvCfg->Props), pszName)); + } - /* Make a copy of the acquired host stream configuration. */ - rc = DrvAudioHlpStreamCfgCopy(&pStream->Host.Cfg, &CfgHostAcq); - AssertRC(rc); + /* Validate PCM properties. */ + if (!AudioHlpPcmPropsAreValid(&pCfg->Props)) + { + LogRel(("Audio: Invalid custom PCM properties set for stream '%s', cannot create stream\n", pszName)); + return VERR_INVALID_PARAMETER; + } /* - * Init guest stream. + * Buffer size */ + const char *pszWhat = "device-specific"; + if (pDrvCfg->uBufferSizeMs) + { + pCfg->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uBufferSizeMs); + pszWhat = "custom"; + } - if (pCfgGuest->Device.cMsSchedulingHint) - LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n", - pStream->szName, pCfgGuest->Device.cMsSchedulingHint, - DrvAudioHlpMilliToBytes(pCfgGuest->Device.cMsSchedulingHint, &pCfgGuest->Props))); - - /* Destroy any former mixing buffer. */ - AudioMixBufDestroy(&pStream->Guest.MixBuf); + if (!pCfg->Backend.cFramesBufferSize) /* Set default buffer size if nothing explicitly is set. */ + { + pCfg->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfg->Props, 300 /*ms*/); + pszWhat = "default"; + } - /* Set the guests's default audio data layout. */ - pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + LogRel2(("Audio: Using %s buffer size %RU64 ms / %RU32 frames for stream '%s'\n", + pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize), + pCfg->Backend.cFramesBufferSize, pszName)); - /* Make sure to (re-)set the guest buffer's shift size. */ - pCfgGuest->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgGuest->Props.cbSample, pCfgGuest->Props.cChannels); + /* + * Period size + */ + pszWhat = "device-specific"; + if (pDrvCfg->uPeriodSizeMs) + { + pCfg->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uPeriodSizeMs); + pszWhat = "custom"; + } - rc = AudioMixBufInit(&pStream->Guest.MixBuf, pStream->szName, &pCfgGuest->Props, CfgHostAcq.Backend.cFramesBufferSize); - AssertRC(rc); + if (!pCfg->Backend.cFramesPeriod) /* Set default period size if nothing explicitly is set. */ + { + pCfg->Backend.cFramesPeriod = pCfg->Backend.cFramesBufferSize / 4; + pszWhat = "default"; + } - /* Make a copy of the guest stream configuration. */ - rc = DrvAudioHlpStreamCfgCopy(&pStream->Guest.Cfg, pCfgGuest); - AssertRC(rc); + if (pCfg->Backend.cFramesPeriod >= pCfg->Backend.cFramesBufferSize / 2) + { + LogRel(("Audio: Warning! Stream '%s': The stream period size (%RU64ms, %s) cannot be more than half the buffer size (%RU64ms)!\n", + pszName, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPeriod), pszWhat, + PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize))); + pCfg->Backend.cFramesPeriod = pCfg->Backend.cFramesBufferSize / 2; + } - if (RT_FAILURE(rc)) - LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStream->szName, rc)); + LogRel2(("Audio: Using %s period size %RU64 ms / %RU32 frames for stream '%s'\n", + pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPeriod), + pCfg->Backend.cFramesPeriod, pszName)); - if (pCfgGuest->enmDir == PDMAUDIODIR_IN) + /* + * Pre-buffering size + */ + pszWhat = "device-specific"; + if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */ { - /* Host (Parent) -> Guest (Child). */ - rc = AudioMixBufLinkTo(&pStream->Host.MixBuf, &pStream->Guest.MixBuf); - AssertRC(rc); + pCfg->Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uPreBufSizeMs); + pszWhat = "custom"; } - else + else /* No, then either use the default or device-specific settings (if any). */ { - /* Guest (Parent) -> Host (Child). */ - rc = AudioMixBufLinkTo(&pStream->Guest.MixBuf, &pStream->Host.MixBuf); - AssertRC(rc); + if (pCfg->Backend.cFramesPreBuffering == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */ + { + /* Pre-buffer 50% for both output & input. Capping both at 200ms. + The 50% reasoning being that we need to have sufficient slack space + in both directions as the guest DMA timer might be delayed by host + scheduling as well as sped up afterwards because of TM catch-up. */ + uint32_t const cFramesMax = PDMAudioPropsMilliToFrames(&pCfg->Props, 200); + pCfg->Backend.cFramesPreBuffering = pCfg->Backend.cFramesBufferSize / 2; + pCfg->Backend.cFramesPreBuffering = RT_MIN(pCfg->Backend.cFramesPreBuffering, cFramesMax); + pszWhat = "default"; + } } -#ifdef VBOX_WITH_STATISTICS - char szStatName[255]; - - if (pCfgGuest->enmDir == PDMAUDIODIR_IN) + if (pCfg->Backend.cFramesPreBuffering >= pCfg->Backend.cFramesBufferSize) { - RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesCaptured", pStream->szName); - PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalFramesCaptured, - szStatName, STAMUNIT_COUNT, "Total frames played."); - RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesCaptured", pStream->szName); - PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalTimesCaptured, - szStatName, STAMUNIT_COUNT, "Total number of playbacks."); - RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesRead", pStream->szName); - PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalFramesRead, - szStatName, STAMUNIT_COUNT, "Total frames read."); - RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesRead", pStream->szName); - PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalTimesRead, - szStatName, STAMUNIT_COUNT, "Total number of reads."); - } - else if (pCfgGuest->enmDir == PDMAUDIODIR_OUT) - { - RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesPlayed", pStream->szName); - PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesPlayed, - szStatName, STAMUNIT_COUNT, "Total frames played."); - - RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesPlayed", pStream->szName); - PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesPlayed, - szStatName, STAMUNIT_COUNT, "Total number of playbacks."); - RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesWritten", pStream->szName); - PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesWritten, - szStatName, STAMUNIT_COUNT, "Total frames written."); - - RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesWritten", pStream->szName); - PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesWritten, - szStatName, STAMUNIT_COUNT, "Total number of writes."); + LogRel(("Audio: Warning! Stream '%s': Pre-buffering (%RU64ms, %s) cannot equal or exceed the buffer size (%RU64ms)!\n", + pszName, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize), pszWhat, + PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPreBuffering) )); + pCfg->Backend.cFramesPreBuffering = pCfg->Backend.cFramesBufferSize - 1; } - else - AssertFailed(); -#endif - LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc)); - return rc; + LogRel2(("Audio: Using %s pre-buffering size %RU64 ms / %RU32 frames for stream '%s'\n", + pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPreBuffering), + pCfg->Backend.cFramesPreBuffering, pszName)); + + return VINF_SUCCESS; } + /** - * Frees an audio stream and its allocated resources. - * - * @param pStream Audio stream to free. - * After this call the pointer will not be valid anymore. + * Worker thread function for drvAudioStreamConfigHint that's used when + * PDMAUDIOBACKEND_F_ASYNC_HINT is in effect. */ -static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream) +static DECLCALLBACK(void) drvAudioStreamConfigHintWorker(PDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfg) { - if (!pStream) - return; + LogFlowFunc(("pThis=%p pCfg=%p\n", pThis, pCfg)); + AssertPtrReturnVoid(pCfg); + int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + AssertRCReturnVoid(rc); - LogFunc(("[%s]\n", pStream->szName)); - - if (pStream->pvBackend) + PPDMIHOSTAUDIO const pHostDrvAudio = pThis->pHostDrvAudio; + if (pHostDrvAudio) { - Assert(pStream->cbBackend); - RTMemFree(pStream->pvBackend); - pStream->pvBackend = NULL; + AssertPtr(pHostDrvAudio->pfnStreamConfigHint); + if (pHostDrvAudio->pfnStreamConfigHint) + pHostDrvAudio->pfnStreamConfigHint(pHostDrvAudio, pCfg); } + PDMAudioStrmCfgFree(pCfg); - RTMemFree(pStream); - pStream = NULL; + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + LogFlowFunc(("returns\n")); } -#ifdef VBOX_WITH_AUDIO_CALLBACKS -/** - * Schedules a re-initialization of all current audio streams. - * The actual re-initialization will happen at some later point in time. - * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - */ -static int drvAudioScheduleReInitInternal(PDRVAUDIO pThis) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - - LogFunc(("\n")); - - /* Mark all host streams to re-initialize. */ - PPDMAUDIOSTREAM pStream; - RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node) - pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAGS_PENDING_REINIT; - -# ifdef VBOX_WITH_AUDIO_ENUM - /* Re-enumerate all host devices as soon as possible. */ - pThis->fEnumerateDevices = true; -# endif - - return VINF_SUCCESS; -} -#endif /* VBOX_WITH_AUDIO_CALLBACKS */ /** - * Re-initializes an audio stream with its existing host and guest stream configuration. - * This might be the case if the backend told us we need to re-initialize because something - * on the host side has changed. - * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Stream to re-initialize. + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamConfigHint} */ -static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +static DECLCALLBACK(void) drvAudioStreamConfigHint(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAMCFG pCfg) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertReturnVoid(pCfg->enmDir == PDMAUDIODIR_IN || pCfg->enmDir == PDMAUDIODIR_OUT); - LogFlowFunc(("[%s]\n", pStream->szName)); - - /* - * Gather current stream status. - */ - bool fIsEnabled = RT_BOOL(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED); /* Stream is enabled? */ + int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + AssertRCReturnVoid(rc); /* - * Destroy and re-create stream on backend side. + * Don't do anything unless the backend has a pfnStreamConfigHint method + * and the direction is currently enabled. */ - int rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); - if (RT_SUCCESS(rc)) + if ( pThis->pHostDrvAudio + && pThis->pHostDrvAudio->pfnStreamConfigHint) { - rc = drvAudioStreamDestroyInternalBackend(pThis, pStream); - if (RT_SUCCESS(rc)) + if (pCfg->enmDir == PDMAUDIODIR_OUT ? pThis->Out.fEnabled : pThis->In.fEnabled) { - PDMAUDIOSTREAMCFG CfgHostAcq; - rc = drvAudioStreamCreateInternalBackend(pThis, pStream, &pStream->Host.Cfg, &CfgHostAcq); - /** @todo Validate (re-)acquired configuration with pStream->Host.Cfg? */ + /* + * Adjust the configuration (applying out settings) then call the backend driver. + */ + rc = drvAudioStreamAdjustConfig(pThis, pCfg, pCfg->szName); + AssertLogRelRC(rc); if (RT_SUCCESS(rc)) { -#ifdef LOG_ENABLED - LogFunc(("[%s] Acquired host format:\n", pStream->szName)); - DrvAudioHlpStreamCfgPrint(&CfgHostAcq); -#endif + rc = VERR_CALLBACK_RETURN; + if (pThis->BackendCfg.fFlags & PDMAUDIOBACKEND_F_ASYNC_HINT) + { + PPDMAUDIOSTREAMCFG pDupCfg = PDMAudioStrmCfgDup(pCfg); + if (pDupCfg) + { + rc = RTReqPoolCallVoidNoWait(pThis->hReqPool, (PFNRT)drvAudioStreamConfigHintWorker, 2, pThis, pDupCfg); + if (RT_SUCCESS(rc)) + LogFlowFunc(("Asynchronous call running on worker thread.\n")); + else + PDMAudioStrmCfgFree(pDupCfg); + } + } + if (RT_FAILURE_NP(rc)) + { + LogFlowFunc(("Doing synchronous call...\n")); + pThis->pHostDrvAudio->pfnStreamConfigHint(pThis->pHostDrvAudio, pCfg); + } } } + else + LogFunc(("Ignoring hint because direction is not currently enabled\n")); } + else + LogFlowFunc(("Ignoring hint because backend has no pfnStreamConfigHint method.\n")); - /* Drop all old data. */ - drvAudioStreamDropInternal(pThis, pStream); - - /* - * Restore previous stream state. - */ - if (fIsEnabled) - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE); - - if (RT_FAILURE(rc)) - LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStream->szName, rc)); - - LogFunc(("[%s] Returning %Rrc\n", pStream->szName, rc)); - return rc; + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); } + /** - * Drops all audio data (and associated state) of a stream. + * Common worker for synchronizing the ENABLED and PAUSED status bits with the + * backend after it becomes ready. * - * @param pThis Pointer to driver instance. - * @param pStream Stream to drop data for. + * Used by async init and re-init. + * + * @note Is sometimes called w/o having entered DRVAUDIO::CritSectHotPlug. + * Caller must however own the stream critsect. */ -static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +static int drvAudioStreamUpdateBackendOnStatus(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, const char *pszWhen) { - RT_NOREF(pThis); - - LogFunc(("[%s]\n", pStream->szName)); - - AudioMixBufReset(&pStream->Guest.MixBuf); - AudioMixBufReset(&pStream->Host.MixBuf); - - pStream->tsLastIteratedNs = 0; - pStream->tsLastPlayedCapturedNs = 0; - pStream->tsLastReadWrittenNs = 0; - - pStream->fThresholdReached = false; + int rc = VINF_SUCCESS; + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED) + { + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE); + if (RT_SUCCESS(rc)) + { + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PAUSED) + { + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE); + if (RT_FAILURE(rc)) + LogRelMax(64, ("Audio: Failed to pause stream '%s' after %s: %Rrc\n", pStreamEx->Core.Cfg.szName, pszWhen, rc)); + } + } + else + LogRelMax(64, ("Audio: Failed to enable stream '%s' after %s: %Rrc\n", pStreamEx->Core.Cfg.szName, pszWhen, rc)); + } + return rc; } + /** - * Resets a given audio stream. + * For performing PDMIHOSTAUDIO::pfnStreamInitAsync on a worker thread. * - * @param pThis Pointer to driver instance. - * @param pStream Stream to reset. + * @param pThis Pointer to the DrvAudio instance data. + * @param pStreamEx The stream. One reference for us to release. */ -static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +static DECLCALLBACK(void) drvAudioStreamInitAsync(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) { - drvAudioStreamDropInternal(pThis, pStream); + LogFlow(("pThis=%p pStreamEx=%p (%s)\n", pThis, pStreamEx, pStreamEx->Core.Cfg.szName)); - LogFunc(("[%s]\n", pStream->szName)); + int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + AssertRCReturnVoid(rc); - pStream->fStatus = PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED; - -#ifdef VBOX_WITH_STATISTICS /* - * Reset statistics. + * Do the init job. */ - if (pStream->enmDir == PDMAUDIODIR_IN) - { - STAM_COUNTER_RESET(&pStream->In.Stats.TotalFramesCaptured); - STAM_COUNTER_RESET(&pStream->In.Stats.TotalFramesRead); - STAM_COUNTER_RESET(&pStream->In.Stats.TotalTimesCaptured); - STAM_COUNTER_RESET(&pStream->In.Stats.TotalTimesRead); - } - else if (pStream->enmDir == PDMAUDIODIR_OUT) - { - STAM_COUNTER_RESET(&pStream->Out.Stats.TotalFramesPlayed); - STAM_COUNTER_RESET(&pStream->Out.Stats.TotalFramesWritten); - STAM_COUNTER_RESET(&pStream->Out.Stats.TotalTimesPlayed); - STAM_COUNTER_RESET(&pStream->Out.Stats.TotalTimesWritten); + bool fDestroyed; + PPDMIHOSTAUDIO pIHostDrvAudio = pThis->pHostDrvAudio; + AssertPtr(pIHostDrvAudio); + if (pIHostDrvAudio && pIHostDrvAudio->pfnStreamInitAsync) + { + fDestroyed = pStreamEx->cRefs <= 1; + rc = pIHostDrvAudio->pfnStreamInitAsync(pIHostDrvAudio, pStreamEx->pBackend, fDestroyed); + LogFlow(("pfnStreamInitAsync returns %Rrc (on %p, fDestroyed=%d)\n", rc, pStreamEx, fDestroyed)); } else - AssertFailed(); -#endif -} + { + fDestroyed = true; + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } -/** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamWrite} - */ -static DECLCALLBACK(int) drvAudioStreamWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, - const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(cbBuf, VERR_INVALID_PARAMETER); - /* pcbWritten is optional. */ - - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); - - AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, - ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n", - pStream->szName, DrvAudioHlpAudDirToStr(pStream->enmDir))); - - AssertMsg(DrvAudioHlpBytesIsAligned(cbBuf, &pStream->Guest.Cfg.Props), - ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStream->szName, cbBuf)); - - uint32_t cbWrittenTotal = 0; - - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; - -#ifdef VBOX_WITH_STATISTICS - STAM_PROFILE_ADV_START(&pThis->Stats.DelayOut, out); -#endif - -#ifdef LOG_ENABLED - char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); - AssertPtr(pszStreamSts); -#endif + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + RTCritSectEnter(&pStreamEx->Core.CritSect); - /* Whether to discard the incoming data or not. */ - bool fToBitBucket = false; - - do + /* + * On success, update the backend on the stream status and mark it ready for business. + */ + if (RT_SUCCESS(rc) && !fDestroyed) { - if ( !pThis->Out.fEnabled - || !DrvAudioHlpStreamStatusIsReady(pStream->fStatus)) - { - rc = VERR_AUDIO_STREAM_NOT_READY; - break; - } - - if (pThis->pHostDrvAudio) - { - /* If the backend's stream is not writable, all written data goes to /dev/null. */ - if (!DrvAudioHlpStreamStatusCanWrite( - pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend))) - { - fToBitBucket = true; - break; - } - } - - const uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Host.MixBuf); - if (cbFree < cbBuf) - LogRel2(("Audio: Lost audio output (%RU64ms, %RU32 free but needs %RU32) due to full host stream buffer '%s'\n", - DrvAudioHlpBytesToMilli(cbBuf - cbFree, &pStream->Host.Cfg.Props), cbFree, cbBuf, pStream->szName)); - - uint32_t cbToWrite = RT_MIN(cbBuf, cbFree); - if (!cbToWrite) - { - rc = VERR_BUFFER_OVERFLOW; - break; - } + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); - /* We use the guest side mixing buffer as an intermediate buffer to do some - * (first) processing (if needed), so always write the incoming data at offset 0. */ - uint32_t cfGstWritten = 0; - rc = AudioMixBufWriteAt(&pStream->Guest.MixBuf, 0 /* offFrames */, pvBuf, cbToWrite, &cfGstWritten); - if ( RT_FAILURE(rc) - || !cfGstWritten) - { - AssertMsgFailed(("[%s] Write failed: cbToWrite=%RU32, cfWritten=%RU32, rc=%Rrc\n", - pStream->szName, cbToWrite, cfGstWritten, rc)); - break; - } + /* + * Update the backend state. + */ + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY; /* before the backend control call! */ - if (pThis->Out.Cfg.Dbg.fEnabled) - DrvAudioHlpFileWrite(pStream->Out.Dbg.pFileStreamWrite, pvBuf, cbToWrite, 0 /* fFlags */); + rc = drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "asynchronous initialization completed"); - uint32_t cfGstMixed = 0; - if (cfGstWritten) + /* + * Modify the play state if output stream. + */ + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT) { - int rc2 = AudioMixBufMixToParentEx(&pStream->Guest.MixBuf, 0 /* cSrcOffset */, cfGstWritten /* cSrcFrames */, - &cfGstMixed /* pcSrcMixed */); - if (RT_FAILURE(rc2)) + DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState; + switch (enmPlayState) { - AssertMsgFailed(("[%s] Mixing failed: cbToWrite=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n", - pStream->szName, cbToWrite, cfGstWritten, cfGstMixed, rc2)); + case DRVAUDIOPLAYSTATE_PREBUF: + case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: + break; + case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING; + break; + case DRVAUDIOPLAYSTATE_NOPLAY: + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF; + break; + case DRVAUDIOPLAYSTATE_PLAY: + case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: + break; /* possible race here, so don't assert. */ + case DRVAUDIOPLAYSTATE_PLAY_PREBUF: + AssertFailedBreak(); + /* no default */ + case DRVAUDIOPLAYSTATE_END: + case DRVAUDIOPLAYSTATE_INVALID: + break; } - else - { - const uint64_t tsNowNs = RTTimeNanoTS(); - - Log3Func(("[%s] Writing %RU32 frames (%RU64ms)\n", - pStream->szName, cfGstWritten, DrvAudioHlpFramesToMilli(cfGstWritten, &pStream->Guest.Cfg.Props))); + LogFunc(("enmPlayState: %s -> %s\n", drvAudioPlayStateName(enmPlayState), + drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); + } - Log3Func(("[%s] Last written %RU64ns (%RU64ms), now filled with %RU64ms -- %RU8%%\n", - pStream->szName, tsNowNs - pStream->tsLastReadWrittenNs, - (tsNowNs - pStream->tsLastReadWrittenNs) / RT_NS_1MS, - DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props), - AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf))); + /* + * Update the last backend state. + */ + pStreamEx->enmLastBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); - pStream->tsLastReadWrittenNs = tsNowNs; - /* Keep going. */ - } + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + } + /* + * Don't quite know what to do on failure... + */ + else if (!fDestroyed) + { + LogRelMax(64, ("Audio: Failed to initialize stream '%s': %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); + } - if (RT_SUCCESS(rc)) - rc = rc2; + /* + * Release the request handle, must be done while inside the critical section. + */ + if (pStreamEx->hReqInitAsync != NIL_RTREQ) + { + LogFlowFunc(("Releasing hReqInitAsync=%p\n", pStreamEx->hReqInitAsync)); + RTReqRelease(pStreamEx->hReqInitAsync); + pStreamEx->hReqInitAsync = NIL_RTREQ; + } - cbWrittenTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfGstWritten); + RTCritSectLeave(&pStreamEx->Core.CritSect); -#ifdef VBOX_WITH_STATISTICS - STAM_COUNTER_ADD(&pThis->Stats.TotalFramesWritten, cfGstWritten); - STAM_COUNTER_ADD(&pThis->Stats.TotalFramesMixedOut, cfGstMixed); - Assert(cfGstWritten >= cfGstMixed); - STAM_COUNTER_ADD(&pThis->Stats.TotalFramesLostOut, cfGstWritten - cfGstMixed); - STAM_COUNTER_ADD(&pThis->Stats.TotalBytesWritten, cbWrittenTotal); + /* + * Release our stream reference. + */ + uint32_t cRefs = drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/); + LogFlowFunc(("returns (fDestroyed=%d, cRefs=%u)\n", fDestroyed, cRefs)); RT_NOREF(cRefs); +} - STAM_COUNTER_ADD(&pStream->Out.Stats.TotalFramesWritten, cfGstWritten); - STAM_COUNTER_INC(&pStream->Out.Stats.TotalTimesWritten); -#endif - } - Log3Func(("[%s] Dbg: cbBuf=%RU32, cbToWrite=%RU32, cfHstUsed=%RU32, cfHstfLive=%RU32, cfGstWritten=%RU32, " - "cfGstMixed=%RU32, cbWrittenTotal=%RU32, rc=%Rrc\n", - pStream->szName, cbBuf, cbToWrite, AudioMixBufUsed(&pStream->Host.MixBuf), - AudioMixBufLive(&pStream->Host.MixBuf), cfGstWritten, cfGstMixed, cbWrittenTotal, rc)); +/** + * Worker for drvAudioStreamInitInternal and drvAudioStreamReInitInternal that + * creates the backend (host driver) side of an audio stream. + * + * @returns VBox status code. + * @param pThis Pointer to driver instance. + * @param pStreamEx Stream to create backend for. The Core.Cfg field + * contains the requested configuration when we're called + * and the actual configuration when successfully + * returning. + * + * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set): + * - per global extra-data + * - per-VM extra-data + * - requested configuration (by pCfgReq) + * - default value + */ +static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) +{ + AssertMsg((pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED) == 0, + ("Stream '%s' already initialized in backend\n", pStreamEx->Core.Cfg.szName)); - } while (0); + /* + * Adjust the stream config, applying defaults and any overriding settings. + */ + int rc = drvAudioStreamAdjustConfig(pThis, &pStreamEx->Core.Cfg, pStreamEx->Core.Cfg.szName); + if (RT_FAILURE(rc)) + return rc; + PDMAUDIOSTREAMCFG const CfgReq = pStreamEx->Core.Cfg; -#ifdef LOG_ENABLED - RTStrFree(pszStreamSts); -#endif + /* + * Call the host driver to create the stream. + */ + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); - int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertLogRelMsgStmt(RT_VALID_PTR(pThis->pHostDrvAudio), + ("Audio: %p\n", pThis->pHostDrvAudio), rc = VERR_PDM_NO_ATTACHED_DRIVER); if (RT_SUCCESS(rc)) - rc = rc2; - + AssertLogRelMsgStmt(pStreamEx->Core.cbBackend == pThis->BackendCfg.cbStream, + ("Audio: Backend changed? cbBackend changed from %#x to %#x\n", + pStreamEx->Core.cbBackend, pThis->BackendCfg.cbStream), + rc = VERR_STATE_CHANGED); + if (RT_SUCCESS(rc)) + rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStreamEx->pBackend, &CfgReq, &pStreamEx->Core.Cfg); if (RT_SUCCESS(rc)) { - if (fToBitBucket) - { - Log3Func(("[%s] Backend stream not ready (yet), discarding written data\n", pStream->szName)); - cbWrittenTotal = cbBuf; /* Report all data as being written to the caller. */ - } + pStreamEx->enmLastBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); - if (pcbWritten) - *pcbWritten = cbWrittenTotal; - } + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); - return rc; -} + AssertLogRelReturn(pStreamEx->pBackend->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INTERNAL_ERROR_3); + AssertLogRelReturn(pStreamEx->pBackend->pStream == &pStreamEx->Core, VERR_INTERNAL_ERROR_3); -/** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain} - */ -static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) -{ - AssertPtrReturn(pInterface, UINT32_MAX); - AssertPtrReturn(pStream, UINT32_MAX); + /* Must set the backend-initialized flag now or the backend won't be + destroyed (this used to be done at the end of this function, with + several possible early return paths before it). */ + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_CREATED; + } + else + { + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + if (rc == VERR_NOT_SUPPORTED) + LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStreamEx->Core.Cfg.szName)); + else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE) + LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", + pStreamEx->Core.Cfg.szName)); + else + LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); + return rc; + } - NOREF(pInterface); + /* Remember if we need to call pfnStreamInitAsync. */ + pStreamEx->fNeedAsyncInit = rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED; + AssertStmt(rc != VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED || pThis->pHostDrvAudio->pfnStreamInitAsync != NULL, + pStreamEx->fNeedAsyncInit = false); + AssertMsg( rc != VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED + || pStreamEx->enmLastBackendState == PDMHOSTAUDIOSTREAMSTATE_INITIALIZING, + ("rc=%Rrc %s\n", rc, PDMHostAudioStreamStateGetName(pStreamEx->enmLastBackendState))); - return ++pStream->cRefs; -} + PPDMAUDIOSTREAMCFG const pCfgAcq = &pStreamEx->Core.Cfg; -/** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease} - */ -static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) -{ - AssertPtrReturn(pInterface, UINT32_MAX); - AssertPtrReturn(pStream, UINT32_MAX); + /* + * Validate acquired configuration. + */ + char szTmp[PDMAUDIOPROPSTOSTRING_MAX]; + LogFunc(("Backend returned: %s\n", PDMAudioStrmCfgToString(pCfgAcq, szTmp, sizeof(szTmp)) )); + AssertLogRelMsgReturn(AudioHlpStreamCfgIsValid(pCfgAcq), + ("Audio: Creating stream '%s' returned an invalid backend configuration (%s), skipping\n", + pCfgAcq->szName, PDMAudioPropsToString(&pCfgAcq->Props, szTmp, sizeof(szTmp))), + VERR_INVALID_PARAMETER); - NOREF(pInterface); + /* Let the user know that the backend changed one of the values requested above. */ + if (pCfgAcq->Backend.cFramesBufferSize != CfgReq.Backend.cFramesBufferSize) + LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", + PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesBufferSize), CfgReq.Backend.cFramesBufferSize, + PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize)); - if (pStream->cRefs > 1) /* 1 reference always is kept by this audio driver. */ - pStream->cRefs--; + if (pCfgAcq->Backend.cFramesPeriod != CfgReq.Backend.cFramesPeriod) + LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", + PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesPeriod), CfgReq.Backend.cFramesPeriod, + PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod), pCfgAcq->Backend.cFramesPeriod)); - return pStream->cRefs; -} + /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */ + if (CfgReq.Backend.cFramesPreBuffering) + { + if (pCfgAcq->Backend.cFramesPreBuffering != CfgReq.Backend.cFramesPreBuffering) + LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", + PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesPreBuffering), CfgReq.Backend.cFramesPreBuffering, + PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering)); -/** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate} - */ -static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); + if (pCfgAcq->Backend.cFramesPreBuffering > pCfgAcq->Backend.cFramesBufferSize) + { + pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesBufferSize; + LogRel2(("Audio: Pre-buffering size bigger than buffer size for stream '%s', adjusting to %RU64ms (%RU32 frames)\n", pCfgAcq->szName, + PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering)); + } + } + else if (CfgReq.Backend.cFramesPreBuffering == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */ + { + LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pCfgAcq->szName)); + pCfgAcq->Backend.cFramesPreBuffering = 0; + } - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + /* + * Check if the backend did return sane values and correct if necessary. + */ + uint32_t const cFramesPreBufferingMax = pCfgAcq->Backend.cFramesBufferSize - RT_MIN(16, pCfgAcq->Backend.cFramesBufferSize); + if (pCfgAcq->Backend.cFramesPreBuffering > cFramesPreBufferingMax) + { + LogRel2(("Audio: Warning! Pre-buffering size of %RU32 frames for stream '%s' is too close to or larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n", + pCfgAcq->Backend.cFramesPreBuffering, pCfgAcq->szName, pCfgAcq->Backend.cFramesBufferSize, cFramesPreBufferingMax)); + AssertMsgFailed(("cFramesPreBuffering=%#x vs cFramesPreBufferingMax=%#x\n", pCfgAcq->Backend.cFramesPreBuffering, cFramesPreBufferingMax)); + pCfgAcq->Backend.cFramesPreBuffering = cFramesPreBufferingMax; + } - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; + if (pCfgAcq->Backend.cFramesPeriod > pCfgAcq->Backend.cFramesBufferSize) + { + LogRel2(("Audio: Warning! Period size of %RU32 frames for stream '%s' is larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n", + pCfgAcq->Backend.cFramesPeriod, pCfgAcq->szName, pCfgAcq->Backend.cFramesBufferSize, pCfgAcq->Backend.cFramesBufferSize / 2)); + AssertMsgFailed(("cFramesPeriod=%#x vs cFramesBufferSize=%#x\n", pCfgAcq->Backend.cFramesPeriod, pCfgAcq->Backend.cFramesBufferSize)); + pCfgAcq->Backend.cFramesPeriod = pCfgAcq->Backend.cFramesBufferSize / 2; + } - rc = drvAudioStreamIterateInternal(pThis, pStream); + LogRel2(("Audio: Buffer size for stream '%s' is %RU64 ms / %RU32 frames\n", pCfgAcq->szName, + PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize)); + LogRel2(("Audio: Pre-buffering size for stream '%s' is %RU64 ms / %RU32 frames\n", pCfgAcq->szName, + PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering)); + LogRel2(("Audio: Scheduling hint for stream '%s' is %RU32ms / %RU32 frames\n", pCfgAcq->szName, + pCfgAcq->Device.cMsSchedulingHint, PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCfgAcq->Device.cMsSchedulingHint))); - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; + /* Make sure the configured buffer size by the backend at least can hold the configured latency. */ + uint32_t const cMsPeriod = PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod); + LogRel2(("Audio: Period size of stream '%s' is %RU64 ms / %RU32 frames\n", + pCfgAcq->szName, cMsPeriod, pCfgAcq->Backend.cFramesPeriod)); + /** @todo r=bird: This is probably a misleading/harmless warning as we'd just + * have to transfer more each time we move data. The period is generally + * pure irrelevant fiction anyway. A more relevant comparison would + * be to half the buffer size, i.e. making sure we get scheduled often + * enough to keep the buffer at least half full (probably more + * sensible if the buffer size was more than 2x scheduling periods). */ + if ( CfgReq.Device.cMsSchedulingHint /* Any scheduling hint set? */ + && CfgReq.Device.cMsSchedulingHint > cMsPeriod) /* This might lead to buffer underflows. */ + LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n", + pCfgAcq->szName, CfgReq.Device.cMsSchedulingHint, cMsPeriod)); - if (RT_FAILURE(rc)) - LogFlowFuncLeaveRC(rc); + /* + * Done, just log the result: + */ + LogFunc(("Acquired stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) )); + LogRel2(("Audio: Acquired stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) )); - return rc; + return VINF_SUCCESS; } + /** - * Re-initializes the given stream if it is scheduled for this operation. + * Worker for drvAudioStreamCreate that initializes the audio stream. * * @returns VBox status code. - * @param pThis Pointer to driver instance. - * @param pStream Stream to check and maybe re-initialize. + * @param pThis Pointer to driver instance. + * @param pStreamEx Stream to initialize. Caller already set a few fields. + * The Core.Cfg field contains the requested configuration + * when we're called and the actual configuration when + * successfully returning. */ -static int drvAudioStreamMaybeReInit(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) { - int rc = VINF_SUCCESS; + /* + * Init host stream. + */ + pStreamEx->Core.uMagic = PDMAUDIOSTREAM_MAGIC; - if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_REINIT) - { -#ifdef VBOX_WITH_AUDIO_ENUM - if (pThis->fEnumerateDevices) - { - /* Re-enumerate all host devices. */ - drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */); + char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX]; + LogFunc(("Requested stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) )); + LogRel2(("Audio: Creating stream: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) )); + + int rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx); + if (RT_FAILURE(rc)) + return rc; - pThis->fEnumerateDevices = false; + /* + * Configure host buffers. + */ + Assert(pStreamEx->cbPreBufThreshold == 0); + if (pStreamEx->Core.Cfg.Backend.cFramesPreBuffering != 0) + pStreamEx->cbPreBufThreshold = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, + pStreamEx->Core.Cfg.Backend.cFramesPreBuffering); + + /* Allocate space for pre-buffering of output stream w/o mixing buffers. */ + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT) + { + Assert(pStreamEx->Out.cbPreBufAlloc == 0); + Assert(pStreamEx->Out.cbPreBuffered == 0); + Assert(pStreamEx->Out.offPreBuf == 0); + if (pStreamEx->Core.Cfg.Backend.cFramesPreBuffering != 0) + { + uint32_t cbPreBufAlloc = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, + pStreamEx->Core.Cfg.Backend.cFramesBufferSize); + cbPreBufAlloc = RT_MIN(RT_ALIGN_32(pStreamEx->cbPreBufThreshold + _8K, _4K), cbPreBufAlloc); + cbPreBufAlloc = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbPreBufAlloc); + pStreamEx->Out.cbPreBufAlloc = cbPreBufAlloc; + pStreamEx->Out.pbPreBuf = (uint8_t *)RTMemAllocZ(cbPreBufAlloc); + AssertReturn(pStreamEx->Out.pbPreBuf, VERR_NO_MEMORY); } -#endif /* VBOX_WITH_AUDIO_ENUM */ + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY; /* Changed upon enable. */ + } - /* Remove the pending re-init flag in any case, regardless whether the actual re-initialization succeeded - * or not. If it failed, the backend needs to notify us again to try again at some later point in time. */ - pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAGS_PENDING_REINIT; - rc = drvAudioStreamReInitInternal(pThis, pStream); + /* + * Register statistics. + */ + PPDMDRVINS const pDrvIns = pThis->pDrvIns; + /** @todo expose config and more. */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesBufferSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "The size of the backend buffer (in frames)", "%s/0-HostBackendBufSize", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesPeriod, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "The size of the backend period (in frames)", "%s/0-HostBackendPeriodSize", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesPreBuffering, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Pre-buffer size (in frames)", "%s/0-HostBackendPreBufferSize", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Device.cMsSchedulingHint, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Device DMA scheduling hint (in milliseconds)", "%s/0-DeviceSchedulingHint", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_HZ, + "Backend stream frequency", "%s/Hz", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Props.cbFrame, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Backend frame size", "%s/Framesize", pStreamEx->Core.Cfg.szName); + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN) + { + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.cbBackendReadableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Free space in backend buffer before play", "%s/0-HostBackendBufReadableBefore", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.cbBackendReadableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Free space in backend buffer after play", "%s/0-HostBackendBufReadableAfter", pStreamEx->Core.Cfg.szName); +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfCapture, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Profiling time spent in StreamCapture", "%s/ProfStreamCapture", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfGetReadable, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Profiling time spent in StreamGetReadable", "%s/ProfStreamGetReadable", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfGetReadableBytes, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Readable byte stats", "%s/ProfStreamGetReadableBytes", pStreamEx->Core.Cfg.szName); +#endif + } + else + { + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Free space in backend buffer before play", "%s/0-HostBackendBufWritableBefore", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Free space in backend buffer after play", "%s/0-HostBackendBufWritableAfter", pStreamEx->Core.Cfg.szName); +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfPlay, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Profiling time spent in StreamPlay", "%s/ProfStreamPlay", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfGetWritable, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Profiling time spent in StreamGetWritable", "%s/ProfStreamGetWritable", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfGetWritableBytes, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Writeable byte stats", "%s/ProfStreamGetWritableBytes", pStreamEx->Core.Cfg.szName); +#endif } +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->StatProfGetState, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, + "Profiling time spent in StreamGetState", "%s/ProfStreamGetState", pStreamEx->Core.Cfg.szName); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->StatXfer, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Byte transfer stats (excluding pre-buffering)", "%s/Transfers", pStreamEx->Core.Cfg.szName); +#endif + PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->offInternal, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_NONE, + "Internal stream offset", "%s/offInternal", pStreamEx->Core.Cfg.szName); + LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); return rc; } + /** - * Does one iteration of an audio stream. - * This function gives the backend the chance of iterating / altering data and - * does the actual mixing between the guest <-> host mixing buffers. - * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Stream to iterate. + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate} */ -static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, uint32_t fFlags, PCPDMAUDIOSTREAMCFG pCfgReq, + PPDMAUDIOSTREAM *ppStream) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - - if (!pThis->pHostDrvAudio) - return VINF_SUCCESS; - - if (!pStream) - return VINF_SUCCESS; - - /* Is the stream scheduled for re-initialization? Do so now. */ - int rc = drvAudioStreamMaybeReInit(pThis, pStream); - if (RT_FAILURE(rc)) - return rc; + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + /* + * Assert sanity. + */ + AssertReturn(!(fFlags & ~PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF), VERR_INVALID_FLAGS); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(ppStream, VERR_INVALID_POINTER); + *ppStream = NULL; + LogFlowFunc(("pCfgReq=%s\n", pCfgReq->szName)); #ifdef LOG_ENABLED - char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); - Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts)); - RTStrFree(pszStreamSts); -#endif /* LOG_ENABLED */ + PDMAudioStrmCfgLog(pCfgReq); +#endif + AssertReturn(AudioHlpStreamCfgIsValid(pCfgReq), VERR_INVALID_PARAMETER); + AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_NOT_SUPPORTED); - /* Not enabled or paused? Skip iteration. */ - if ( !(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED) - || (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED)) + /* + * Grab a free stream count now. + */ + int rc = RTCritSectRwEnterExcl(&pThis->CritSectGlobals); + AssertRCReturn(rc, rc); + + uint32_t * const pcFreeStreams = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.cStreamsFree : &pThis->Out.cStreamsFree; + if (*pcFreeStreams > 0) + *pcFreeStreams -= 1; + else { - return VINF_SUCCESS; + RTCritSectRwLeaveExcl(&pThis->CritSectGlobals); + LogFlowFunc(("Maximum number of host %s streams reached\n", PDMAudioDirGetName(pCfgReq->enmDir) )); + return pCfgReq->enmDir == PDMAUDIODIR_IN ? VERR_AUDIO_NO_FREE_INPUT_STREAMS : VERR_AUDIO_NO_FREE_OUTPUT_STREAMS; } - /* Whether to try closing a pending to close stream. */ - bool fTryClosePending = false; + RTCritSectRwLeaveExcl(&pThis->CritSectGlobals); - do - { - rc = pThis->pHostDrvAudio->pfnStreamIterate(pThis->pHostDrvAudio, pStream->pvBackend); - if (RT_FAILURE(rc)) - break; + /* + * Get and check the backend size. + * + * Since we'll have to leave the hot-plug lock before we call the backend, + * we'll have revalidate the size at that time. + */ + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); - if (pStream->enmDir == PDMAUDIODIR_OUT) - { - /* No audio frames to transfer from guest to host (anymore)? - * Then try closing this stream if marked so in the next block. */ - const uint32_t cFramesLive = AudioMixBufLive(&pStream->Host.MixBuf); - fTryClosePending = cFramesLive == 0; - Log3Func(("[%s] fTryClosePending=%RTbool, cFramesLive=%RU32\n", pStream->szName, fTryClosePending, cFramesLive)); - } - - /* Has the host stream marked as pending to disable? - * Try disabling the stream then. */ - if ( pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE - && fTryClosePending) - { - /* Tell the backend to drain the stream, that is, play the remaining (buffered) data - * on the backend side. */ - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DRAIN); - if (rc == VERR_NOT_SUPPORTED) /* Not all backends support draining. */ - rc = VINF_SUCCESS; + size_t const cbHstStrm = pThis->BackendCfg.cbStream; + AssertStmt(cbHstStrm >= sizeof(PDMAUDIOBACKENDSTREAM), rc = VERR_OUT_OF_RANGE); + AssertStmt(cbHstStrm < _16M, rc = VERR_OUT_OF_RANGE); + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + if (RT_SUCCESS(rc)) + { + /* + * Allocate and initialize common state. + */ + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)RTMemAllocZ(sizeof(DRVAUDIOSTREAM) + RT_ALIGN_Z(cbHstStrm, 64)); + if (pStreamEx) + { + rc = RTCritSectInit(&pStreamEx->Core.CritSect); /* (drvAudioStreamFree assumes it's initailized) */ if (RT_SUCCESS(rc)) { - if (pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional to implement. */ + PPDMAUDIOBACKENDSTREAM pBackend = (PPDMAUDIOBACKENDSTREAM)(pStreamEx + 1); + pBackend->uMagic = PDMAUDIOBACKENDSTREAM_MAGIC; + pBackend->pStream = &pStreamEx->Core; + + pStreamEx->pBackend = pBackend; + pStreamEx->Core.Cfg = *pCfgReq; + pStreamEx->Core.cbBackend = (uint32_t)cbHstStrm; + pStreamEx->fDestroyImmediate = true; + pStreamEx->hReqInitAsync = NIL_RTREQ; + pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC; + + /* Make a unqiue stream name including the host (backend) driver name. */ + AssertPtr(pThis->pHostDrvAudio); + size_t cchName = RTStrPrintf(pStreamEx->Core.Cfg.szName, RT_ELEMENTS(pStreamEx->Core.Cfg.szName), "[%s] %s:0", + pThis->BackendCfg.szName, pCfgReq->szName[0] != '\0' ? pCfgReq->szName : ""); + if (cchName < sizeof(pStreamEx->Core.Cfg.szName)) { - const uint32_t cxPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pStream->pvBackend); - Log3Func(("[%s] cxPending=%RU32\n", pStream->szName, cxPending)); - - /* Only try close pending if no audio data is pending on the backend-side anymore. */ - fTryClosePending = cxPending == 0; + RTCritSectRwEnterShared(&pThis->CritSectGlobals); + for (uint32_t i = 0; i < 256; i++) + { + bool fDone = true; + PDRVAUDIOSTREAM pIt; + RTListForEach(&pThis->LstStreams, pIt, DRVAUDIOSTREAM, ListEntry) + { + if (strcmp(pIt->Core.Cfg.szName, pStreamEx->Core.Cfg.szName) == 0) + { + RTStrPrintf(pStreamEx->Core.Cfg.szName, RT_ELEMENTS(pStreamEx->Core.Cfg.szName), "[%s] %s:%u", + pThis->BackendCfg.szName, pCfgReq->szName[0] != '\0' ? pCfgReq->szName : "", + i); + fDone = false; + break; + } + } + if (fDone) + break; + } + RTCritSectRwLeaveShared(&pThis->CritSectGlobals); } - if (fTryClosePending) + /* + * Try to init the rest. + */ + rc = drvAudioStreamInitInternal(pThis, pStreamEx); + if (RT_SUCCESS(rc)) { - LogFunc(("[%s] Closing pending stream\n", pStream->szName)); - rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); - if (RT_SUCCESS(rc)) + /* Set initial reference counts. */ + pStreamEx->cRefs = pStreamEx->fNeedAsyncInit ? 2 : 1; + + /* Add it to the list. */ + RTCritSectRwEnterExcl(&pThis->CritSectGlobals); + + RTListAppend(&pThis->LstStreams, &pStreamEx->ListEntry); + pThis->cStreams++; + STAM_REL_COUNTER_INC(&pThis->StatTotalStreamsCreated); + + RTCritSectRwLeaveExcl(&pThis->CritSectGlobals); + + /* + * Init debug stuff if enabled (ignore failures). + */ + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + { + if (pThis->CfgIn.Dbg.fEnabled) + AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileCapture, pThis->CfgIn.Dbg.szPathOut, + "DrvAudioCapture", pThis->pDrvIns->iInstance, &pStreamEx->Core.Cfg.Props); + } + else /* Out */ + { + if (pThis->CfgOut.Dbg.fEnabled) + AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFilePlay, pThis->CfgOut.Dbg.szPathOut, + "DrvAudioPlay", pThis->pDrvIns->iInstance, &pStreamEx->Core.Cfg.Props); + } + + /* + * Kick off the asynchronous init. + */ + if (!pStreamEx->fNeedAsyncInit) { - pStream->fStatus &= ~(PDMAUDIOSTREAMSTS_FLAGS_ENABLED | PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE); - drvAudioStreamDropInternal(pThis, pStream); + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); } else - LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pStream->szName, rc)); + { + int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, &pStreamEx->hReqInitAsync, + RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvAudioStreamInitAsync, 2, pThis, pStreamEx); + LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2)); + AssertRCStmt(rc2, drvAudioStreamInitAsync(pThis, pStreamEx)); + } + +#ifdef VBOX_STRICT + /* + * Assert lock order to make sure the lock validator picks up on it. + */ + RTCritSectRwEnterShared(&pThis->CritSectGlobals); + RTCritSectEnter(&pStreamEx->Core.CritSect); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + RTCritSectLeave(&pStreamEx->Core.CritSect); + RTCritSectRwLeaveShared(&pThis->CritSectGlobals); +#endif + + *ppStream = &pStreamEx->Core; + LogFlowFunc(("returns VINF_SUCCESS (pStreamEx=%p)\n", pStreamEx)); + return VINF_SUCCESS; } + + LogFunc(("drvAudioStreamInitInternal failed: %Rrc\n", rc)); + int rc2 = drvAudioStreamUninitInternal(pThis, pStreamEx); + AssertRC(rc2); + drvAudioStreamFree(pStreamEx); } + else + RTMemFree(pStreamEx); } + else + rc = VERR_NO_MEMORY; + } - } while (0); - - /* Update timestamps. */ - pStream->tsLastIteratedNs = RTTimeNanoTS(); - - if (RT_FAILURE(rc)) - LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc)); + /* + * Give back the stream count, we couldn't use it after all. + */ + RTCritSectRwEnterExcl(&pThis->CritSectGlobals); + *pcFreeStreams += 1; + RTCritSectRwLeaveExcl(&pThis->CritSectGlobals); + LogFlowFuncLeaveRC(rc); return rc; } + /** - * Plays an audio host output stream which has been configured for non-interleaved (layout) data. + * Calls the backend to give it the chance to destroy its part of the audio stream. + * + * Called from drvAudioPowerOff, drvAudioStreamUninitInternal and + * drvAudioStreamReInitInternal. * - * @return IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Stream to play. - * @param cfToPlay Number of audio frames to play. - * @param pcfPlayed Returns number of audio frames played. Optional. + * @returns VBox status code. + * @param pThis Pointer to driver instance. + * @param pStreamEx Audio stream destruct backend for. */ -static int drvAudioStreamPlayNonInterleaved(PDRVAUDIO pThis, - PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed) +static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - /* pcfPlayed is optional. */ + AssertPtr(pThis); + AssertPtr(pStreamEx); - if (!cfToPlay) - { - if (pcfPlayed) - *pcfPlayed = 0; - return VINF_SUCCESS; - } + int rc = VINF_SUCCESS; - /* Sanity. */ - Assert(pStream->enmDir == PDMAUDIODIR_OUT); - Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED); - - int rc = VINF_SUCCESS; - - uint32_t cfPlayedTotal = 0; - - uint8_t auBuf[256]; /** @todo Get rid of this here. */ - - uint32_t cfLeft = cfToPlay; - uint32_t cbChunk = sizeof(auBuf); +#ifdef LOG_ENABLED + char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; +#endif + LogFunc(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus))); - while (cfLeft) + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED) { - uint32_t cfRead = 0; - rc = AudioMixBufAcquireReadBlock(&pStream->Host.MixBuf, - auBuf, RT_MIN(cbChunk, AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfLeft)), - &cfRead); - if (RT_FAILURE(rc)) - break; - - uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfRead); - Assert(cbRead <= cbChunk); - - uint32_t cfPlayed = 0; - uint32_t cbPlayed = 0; - rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend, - auBuf, cbRead, &cbPlayed); - if ( RT_SUCCESS(rc) - && cbPlayed) - { - if (pThis->Out.Cfg.Dbg.fEnabled) - DrvAudioHlpFileWrite(pStream->Out.Dbg.pFilePlayNonInterleaved, auBuf, cbPlayed, 0 /* fFlags */); - - if (cbRead != cbPlayed) - LogRel2(("Audio: Host stream '%s' played wrong amount (%RU32 bytes read but played %RU32)\n", - pStream->szName, cbRead, cbPlayed)); - - cfPlayed = AUDIOMIXBUF_B2F(&pStream->Host.MixBuf, cbPlayed); - cfPlayedTotal += cfPlayed; - - Assert(cfLeft >= cfPlayed); - cfLeft -= cfPlayed; - } - - AudioMixBufReleaseReadBlock(&pStream->Host.MixBuf, cfPlayed); - - if (RT_FAILURE(rc)) - break; - } + AssertPtr(pStreamEx->pBackend); - Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc)); + /* Check if the pointer to the host audio driver is still valid. + * It can be NULL if we were called in drvAudioDestruct, for example. */ + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); /** @todo needed? */ + if (pThis->pHostDrvAudio) + rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStreamEx->pBackend, pStreamEx->fDestroyImmediate); + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); - if (RT_SUCCESS(rc)) - { - if (pcfPlayed) - *pcfPlayed = cfPlayedTotal; + pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY); + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); } + LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); return rc; } + /** - * Plays an audio host output stream which has been configured for raw audio (layout) data. + * Uninitializes an audio stream - worker for drvAudioStreamDestroy, + * drvAudioDestruct and drvAudioStreamCreate. * - * @return IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Stream to play. - * @param cfToPlay Number of audio frames to play. - * @param pcfPlayed Returns number of audio frames played. Optional. + * @returns VBox status code. + * @param pThis Pointer to driver instance. + * @param pStreamEx Pointer to audio stream to uninitialize. */ -static int drvAudioStreamPlayRaw(PDRVAUDIO pThis, - PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed) +static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - /* pcfPlayed is optional. */ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertMsgReturn(pStreamEx->cRefs <= 1, + ("Stream '%s' still has %RU32 references held when uninitializing\n", pStreamEx->Core.Cfg.szName, pStreamEx->cRefs), + VERR_WRONG_ORDER); + LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.Cfg.szName, pStreamEx->cRefs)); - /* Sanity. */ - Assert(pStream->enmDir == PDMAUDIODIR_OUT); - Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW); + RTCritSectEnter(&pStreamEx->Core.CritSect); - if (!cfToPlay) + /* + * ... + */ + if (pStreamEx->fDestroyImmediate) + drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + int rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx); + + /* Free pre-buffer space. */ + if ( pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT + && pStreamEx->Out.pbPreBuf) + { + RTMemFree(pStreamEx->Out.pbPreBuf); + pStreamEx->Out.pbPreBuf = NULL; + pStreamEx->Out.cbPreBufAlloc = 0; + pStreamEx->Out.cbPreBuffered = 0; + pStreamEx->Out.offPreBuf = 0; + } + + if (RT_SUCCESS(rc)) { - if (pcfPlayed) - *pcfPlayed = 0; - return VINF_SUCCESS; +#ifdef LOG_ENABLED + if (pStreamEx->fStatus != PDMAUDIOSTREAM_STS_NONE) + { + char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; + LogFunc(("[%s] Warning: Still has %s set when uninitializing\n", + pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus))); + } +#endif + pStreamEx->fStatus = PDMAUDIOSTREAM_STS_NONE; } - int rc = VINF_SUCCESS; + PPDMDRVINS const pDrvIns = pThis->pDrvIns; + PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, pStreamEx->Core.Cfg.szName); - uint32_t cfPlayedTotal = 0; + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN) + { + if (pThis->CfgIn.Dbg.fEnabled) + { + AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileCapture); + pStreamEx->In.Dbg.pFileCapture = NULL; + } + } + else + { + Assert(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT); + if (pThis->CfgOut.Dbg.fEnabled) + { + AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFilePlay); + pStreamEx->Out.Dbg.pFilePlay = NULL; + } + } - PDMAUDIOFRAME aFrameBuf[_4K]; /** @todo Get rid of this here. */ + RTCritSectLeave(&pStreamEx->Core.CritSect); + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} - uint32_t cfLeft = cfToPlay; - while (cfLeft) - { - uint32_t cfRead = 0; - rc = AudioMixBufPeek(&pStream->Host.MixBuf, cfLeft, aFrameBuf, - RT_MIN(cfLeft, RT_ELEMENTS(aFrameBuf)), &cfRead); +/** + * Internal release function. + * + * @returns New reference count, UINT32_MAX if bad stream. + * @param pThis Pointer to the DrvAudio instance data. + * @param pStreamEx The stream to reference. + * @param fMayDestroy Whether the caller is allowed to implicitly destroy + * the stream or not. + */ +static uint32_t drvAudioStreamReleaseInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fMayDestroy) +{ + AssertPtrReturn(pStreamEx, UINT32_MAX); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX); + Assert(!RTCritSectIsOwner(&pStreamEx->Core.CritSect)); + + uint32_t cRefs = ASMAtomicDecU32(&pStreamEx->cRefs); + if (cRefs != 0) + Assert(cRefs < _1K); + else if (fMayDestroy) + { +/** @todo r=bird: Caching one stream in each direction for some time, + * depending on the time it took to create it. drvAudioStreamCreate can use it + * if the configuration matches, otherwise it'll throw it away. This will + * provide a general speedup independ of device (HDA used to do this, but + * doesn't) and backend implementation. Ofc, the backend probably needs an + * opt-out here. */ + int rc = drvAudioStreamUninitInternal(pThis, pStreamEx); if (RT_SUCCESS(rc)) { - if (cfRead) - { - uint32_t cfPlayed; + RTCritSectRwEnterExcl(&pThis->CritSectGlobals); - /* Note: As the stream layout is RPDMAUDIOSTREAMLAYOUT_RAW, operate on audio frames - * rather on bytes. */ - Assert(cfRead <= RT_ELEMENTS(aFrameBuf)); - rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend, - aFrameBuf, cfRead, &cfPlayed); - if ( RT_FAILURE(rc) - || !cfPlayed) - { - break; - } + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN) + pThis->In.cStreamsFree++; + else /* Out */ + pThis->Out.cStreamsFree++; + pThis->cStreams--; - cfPlayedTotal += cfPlayed; - Assert(cfPlayedTotal <= cfToPlay); + RTListNodeRemove(&pStreamEx->ListEntry); - Assert(cfLeft >= cfRead); - cfLeft -= cfRead; - } - else - { - if (rc == VINF_AUDIO_MORE_DATA_AVAILABLE) /* Do another peeking round if there is more data available. */ - continue; + RTCritSectRwLeaveExcl(&pThis->CritSectGlobals); - break; - } + drvAudioStreamFree(pStreamEx); + } + else + { + LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); + /** @todo r=bird: What's the plan now? */ } - else if (RT_FAILURE(rc)) - break; } - - Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc)); - - if (RT_SUCCESS(rc)) + else { - if (pcfPlayed) - *pcfPlayed = cfPlayedTotal; - + cRefs = ASMAtomicIncU32(&pStreamEx->cRefs); + AssertFailed(); } - return rc; + Log12Func(("returns %u (%s)\n", cRefs, cRefs > 0 ? pStreamEx->Core.Cfg.szName : "destroyed")); + return cRefs; } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay} + * Asynchronous worker for drvAudioStreamDestroy. + * + * Does DISABLE and releases reference, possibly destroying the stream. + * + * @param pThis Pointer to the DrvAudio instance data. + * @param pStreamEx The stream. One reference for us to release. + * @param fImmediate How to treat draining streams. */ -static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, - PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed) +static DECLCALLBACK(void) drvAudioStreamDestroyAsync(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fImmediate) { - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - /* pcFramesPlayed is optional. */ - - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); - - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; - - AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, - ("Stream '%s' is not an output stream and therefore cannot be played back (direction is 0x%x)\n", - pStream->szName, pStream->enmDir)); - - uint32_t cfPlayedTotal = 0; - - PDMAUDIOSTREAMSTS fStrmStatus = pStream->fStatus; + LogFlowFunc(("pThis=%p pStreamEx=%p (%s) fImmediate=%RTbool\n", pThis, pStreamEx, pStreamEx->Core.Cfg.szName, fImmediate)); #ifdef LOG_ENABLED - char *pszStreamSts = dbgAudioStreamStatusToStr(fStrmStatus); - Log3Func(("[%s] Start fStatus=%s\n", pStream->szName, pszStreamSts)); - RTStrFree(pszStreamSts); -#endif /* LOG_ENABLED */ + uint64_t const nsStart = RTTimeNanoTS(); +#endif + RTCritSectEnter(&pStreamEx->Core.CritSect); + + pStreamEx->fDestroyImmediate = fImmediate; /* Do NOT adjust for draining status, just pass it as-is. CoreAudio needs this. */ - do + if (!fImmediate && (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)) + LogFlowFunc(("No DISABLE\n")); + else { - if (!pThis->pHostDrvAudio) - { - rc = VERR_PDM_NO_ATTACHED_DRIVER; - break; - } + int rc2 = drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + LogFlowFunc(("DISABLE done: %Rrc\n", rc2)); + AssertRC(rc2); + } - if ( !pThis->Out.fEnabled - || !DrvAudioHlpStreamStatusIsReady(fStrmStatus)) - { - rc = VERR_AUDIO_STREAM_NOT_READY; - break; - } + RTCritSectLeave(&pStreamEx->Core.CritSect); - const uint32_t cFramesLive = AudioMixBufLive(&pStream->Host.MixBuf); -#ifdef LOG_ENABLED - const uint8_t uLivePercent = (100 * cFramesLive) / AudioMixBufSize(&pStream->Host.MixBuf); -#endif - const uint64_t tsDeltaPlayedCapturedNs = RTTimeNanoTS() - pStream->tsLastPlayedCapturedNs; - const uint32_t cfPassedReal = DrvAudioHlpNanoToFrames(tsDeltaPlayedCapturedNs, &pStream->Host.Cfg.Props); + drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/); - const uint32_t cFramesPeriod = pStream->Host.Cfg.Backend.cFramesPeriod; + LogFlowFunc(("returning (after %'RU64 ns)\n", RTTimeNanoTS() - nsStart)); +} - Log3Func(("[%s] Last played %RU64ns (%RU64ms), filled with %RU64ms (%RU8%%) total (fThresholdReached=%RTbool)\n", - pStream->szName, tsDeltaPlayedCapturedNs, tsDeltaPlayedCapturedNs / RT_NS_1MS_64, - DrvAudioHlpFramesToMilli(cFramesLive, &pStream->Host.Cfg.Props), uLivePercent, pStream->fThresholdReached)); - if ( pStream->fThresholdReached /* Has the treshold been reached (e.g. are we in playing stage) ... */ - && cFramesLive == 0) /* ... and we now have no live samples to process? */ - { - LogRel2(("Audio: Buffer underrun for stream '%s' occurred (%RU64ms passed)\n", - pStream->szName, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props))); +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, bool fImmediate) +{ + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); - if (pStream->Host.Cfg.Backend.cFramesPreBuffering) /* Any pre-buffering configured? */ - { - /* Enter pre-buffering stage again. */ - pStream->fThresholdReached = false; - } - } + /* Ignore NULL streams. */ + if (!pStream) + return VINF_SUCCESS; - bool fDoPlay = pStream->fThresholdReached; - bool fJustStarted = false; - if (!fDoPlay) - { - /* Did we reach the backend's playback (pre-buffering) threshold? Can be 0 if no threshold set. */ - if (cFramesLive >= pStream->Host.Cfg.Backend.cFramesPreBuffering) - { - LogRel2(("Audio: Stream '%s' buffering complete\n", pStream->szName)); - Log3Func(("[%s] Dbg: Buffering complete\n", pStream->szName)); - fDoPlay = true; - } - /* Some audio files are shorter than the pre-buffering level (e.g. the "click" Explorer sounds on some Windows guests), - * so make sure that we also play those by checking if the stream already is pending disable mode, even if we didn't - * hit the pre-buffering watermark yet. - * - * To reproduce, use "Windows Navigation Start.wav" on Windows 7 (2824 samples). */ - else if ( cFramesLive - && pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE) - { - LogRel2(("Audio: Stream '%s' buffering complete (short sound)\n", pStream->szName)); - Log3Func(("[%s] Dbg: Buffering complete (short)\n", pStream->szName)); - fDoPlay = true; - } + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; /* Note! Do not touch pStream after this! */ + AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); + LogFlowFunc(("ENTER - %p (%s) fImmediate=%RTbool\n", pStreamEx, pStreamEx->Core.Cfg.szName, fImmediate)); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->pBackend && pStreamEx->pBackend->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INVALID_MAGIC); - if (fDoPlay) - { - pStream->fThresholdReached = true; - fJustStarted = true; - LogRel2(("Audio: Stream '%s' started playing\n", pStream->szName)); - Log3Func(("[%s] Dbg: started playing\n", pStream->szName)); - } - else /* Not yet, so still buffering audio data. */ - LogRel2(("Audio: Stream '%s' is buffering (%RU8%% complete)\n", - pStream->szName, (100 * cFramesLive) / pStream->Host.Cfg.Backend.cFramesPreBuffering)); - } + /* + * The main difference from a regular release is that this will disable + * (or drain if we could) the stream and we can cancel any pending + * pfnStreamInitAsync call. + */ + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, rc); - if (fDoPlay) + if (pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC) + { + if (pStreamEx->cRefs > 0 && pStreamEx->cRefs < UINT32_MAX / 4) { - uint32_t cfWritable = PDMAUDIOPCMPROPS_B2F(&pStream->Host.Cfg.Props, - pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStream->pvBackend)); - - uint32_t cfToPlay = 0; - if (fJustStarted) - cfToPlay = RT_MIN(cfWritable, cFramesPeriod); + char szStatus[DRVAUDIO_STATUS_STR_MAX]; + LogRel2(("Audio: Destroying stream '%s': cRefs=%u; status: %s; backend: %s; hReqInitAsync=%p\n", + pStreamEx->Core.Cfg.szName, pStreamEx->cRefs, drvAudioStreamStatusToStr(szStatus, pStreamEx->fStatus), + PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)), + pStreamEx->hReqInitAsync)); - if (!cfToPlay) + /* Try cancel pending async init request and release the it. */ + if (pStreamEx->hReqInitAsync != NIL_RTREQ) { - /* Did we reach/pass (in real time) the device scheduling slot? - * Play as much as we can write to the backend then. */ - if (cfPassedReal >= DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.cMsSchedulingHint, &pStream->Host.Cfg.Props)) - cfToPlay = cfWritable; - } + Assert(pStreamEx->cRefs >= 2); + int rc2 = RTReqCancel(pStreamEx->hReqInitAsync); - if (cfToPlay > cFramesLive) /* Don't try to play more than available. */ - cfToPlay = cFramesLive; -#ifdef DEBUG - Log3Func(("[%s] Playing %RU32 frames (%RU64ms), now filled with %RU64ms -- %RU8%%\n", - pStream->szName, cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props), - DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props), - AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf))); -#endif - if (cfToPlay) - { - if (pThis->pHostDrvAudio->pfnStreamPlayBegin) - pThis->pHostDrvAudio->pfnStreamPlayBegin(pThis->pHostDrvAudio, pStream->pvBackend); + RTReqRelease(pStreamEx->hReqInitAsync); + pStreamEx->hReqInitAsync = NIL_RTREQ; + + RTCritSectLeave(&pStreamEx->Core.CritSect); /* (exit before releasing the stream to avoid assertion) */ - if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED)) + if (RT_SUCCESS(rc2)) { - rc = drvAudioStreamPlayNonInterleaved(pThis, pStream, cfToPlay, &cfPlayedTotal); + LogFlowFunc(("Successfully cancelled pending pfnStreamInitAsync call (hReqInitAsync=%p).\n", + pStreamEx->hReqInitAsync)); + drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/); } - else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW) + else { - rc = drvAudioStreamPlayRaw(pThis, pStream, cfToPlay, &cfPlayedTotal); + LogFlowFunc(("Failed to cancel pending pfnStreamInitAsync call (hReqInitAsync=%p): %Rrc\n", + pStreamEx->hReqInitAsync, rc2)); + Assert(rc2 == VERR_RT_REQUEST_STATE); } - else - AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); - - if (pThis->pHostDrvAudio->pfnStreamPlayEnd) - pThis->pHostDrvAudio->pfnStreamPlayEnd(pThis->pHostDrvAudio, pStream->pvBackend); - - pStream->tsLastPlayedCapturedNs = RTTimeNanoTS(); } + else + RTCritSectLeave(&pStreamEx->Core.CritSect); - Log3Func(("[%s] Dbg: fJustStarted=%RTbool, cfSched=%RU32 (%RU64ms), cfPassedReal=%RU32 (%RU64ms), " - "cFramesLive=%RU32 (%RU64ms), cFramesPeriod=%RU32 (%RU64ms), cfWritable=%RU32 (%RU64ms), " - "-> cfToPlay=%RU32 (%RU64ms), cfPlayed=%RU32 (%RU64ms)\n", - pStream->szName, fJustStarted, - DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.cMsSchedulingHint, &pStream->Host.Cfg.Props), - pStream->Guest.Cfg.Device.cMsSchedulingHint, - cfPassedReal, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props), - cFramesLive, DrvAudioHlpFramesToMilli(cFramesLive, &pStream->Host.Cfg.Props), - cFramesPeriod, DrvAudioHlpFramesToMilli(cFramesPeriod, &pStream->Host.Cfg.Props), - cfWritable, DrvAudioHlpFramesToMilli(cfWritable, &pStream->Host.Cfg.Props), - cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props), - cfPlayedTotal, DrvAudioHlpFramesToMilli(cfPlayedTotal, &pStream->Host.Cfg.Props))); + /* + * Now, if the backend requests asynchronous disabling and destruction + * push the disabling and destroying over to a worker thread. + * + * This is a general offloading feature that all backends should make use of, + * however it's rather precarious on macs where stopping an already draining + * stream may take 8-10ms which naturally isn't something we should be doing + * on an EMT. + */ + if (!(pThis->BackendCfg.fFlags & PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY)) + drvAudioStreamDestroyAsync(pThis, pStreamEx, fImmediate); + else + { + int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL /*phReq*/, + RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvAudioStreamDestroyAsync, 3, pThis, pStreamEx, fImmediate); + LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2)); + AssertRCStmt(rc2, drvAudioStreamDestroyAsync(pThis, pStreamEx, fImmediate)); + } } - - if (RT_SUCCESS(rc)) + else { - AudioMixBufFinish(&pStream->Host.MixBuf, cfPlayedTotal); - -#ifdef VBOX_WITH_STATISTICS - STAM_COUNTER_ADD (&pThis->Stats.TotalFramesOut, cfPlayedTotal); - STAM_PROFILE_ADV_STOP(&pThis->Stats.DelayOut, out); - - STAM_COUNTER_ADD (&pStream->Out.Stats.TotalFramesPlayed, cfPlayedTotal); - STAM_COUNTER_INC (&pStream->Out.Stats.TotalTimesPlayed); -#endif + AssertLogRelMsgFailedStmt(("%p cRefs=%#x\n", pStreamEx, pStreamEx->cRefs), rc = VERR_CALLER_NO_REFERENCE); + RTCritSectLeave(&pStreamEx->Core.CritSect); /*??*/ } - - } while (0); - -#ifdef LOG_ENABLED - uint32_t cFramesLive = AudioMixBufLive(&pStream->Host.MixBuf); - pszStreamSts = dbgAudioStreamStatusToStr(fStrmStatus); - Log3Func(("[%s] End fStatus=%s, cFramesLive=%RU32, cfPlayedTotal=%RU32, rc=%Rrc\n", - pStream->szName, pszStreamSts, cFramesLive, cfPlayedTotal, rc)); - RTStrFree(pszStreamSts); -#endif /* LOG_ENABLED */ - - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; - - if (RT_SUCCESS(rc)) + } + else { - if (pcFramesPlayed) - *pcFramesPlayed = cfPlayedTotal; + AssertLogRelMsgFailedStmt(("%p uMagic=%#x\n", pStreamEx, pStreamEx->uMagic), rc = VERR_INVALID_MAGIC); + RTCritSectLeave(&pStreamEx->Core.CritSect); /*??*/ } - if (RT_FAILURE(rc)) - LogFlowFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc)); - + LogFlowFuncLeaveRC(rc); return rc; } + /** - * Captures non-interleaved input from a host stream. + * Drops all audio data (and associated state) of a stream. + * + * Used by drvAudioStreamIterateInternal(), drvAudioStreamResetOnDisable(), and + * drvAudioStreamReInitInternal(). * - * @returns IPRT status code. - * @param pThis Driver instance. - * @param pStream Stream to capture from. - * @param pcfCaptured Number of (host) audio frames captured. Optional. + * @param pStreamEx Stream to drop data for. */ -static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured) +static void drvAudioStreamResetInternal(PDRVAUDIOSTREAM pStreamEx) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - /* pcfCaptured is optional. */ - - /* Sanity. */ - Assert(pStream->enmDir == PDMAUDIODIR_IN); - Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED); + LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName)); + Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect)); - int rc = VINF_SUCCESS; + pStreamEx->nsLastIterated = 0; + pStreamEx->nsLastPlayedCaptured = 0; + pStreamEx->nsLastReadWritten = 0; + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT) + { + pStreamEx->Out.cbPreBuffered = 0; + pStreamEx->Out.offPreBuf = 0; + pStreamEx->Out.enmPlayState = pStreamEx->cbPreBufThreshold > 0 + ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY; + } + else + pStreamEx->In.enmCaptureState = pStreamEx->cbPreBufThreshold > 0 + ? DRVAUDIOCAPTURESTATE_PREBUF : DRVAUDIOCAPTURESTATE_CAPTURING; +} - uint32_t cfCapturedTotal = 0; - AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable); +/** + * Re-initializes an audio stream with its existing host and guest stream + * configuration. + * + * This might be the case if the backend told us we need to re-initialize + * because something on the host side has changed. + * + * @note Does not touch the stream's status flags. + * + * @returns VBox status code. + * @param pThis Pointer to driver instance. + * @param pStreamEx Stream to re-initialize. + */ +static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) +{ + char szTmp[RT_MAX(PDMAUDIOSTRMCFGTOSTRING_MAX, DRVAUDIO_STATUS_STR_MAX)]; + LogFlowFunc(("[%s] status: %s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szTmp, pStreamEx->fStatus) )); + Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect)); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); - uint8_t auBuf[_1K]; /** @todo Get rid of this. */ - uint32_t cbBuf = sizeof(auBuf); + /* + * Destroy and re-create stream on backend side. + */ + if ( (pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY)) + == (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY)) + drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); - uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend); - if (!cbReadable) - Log2Func(("[%s] No readable data available\n", pStream->szName)); + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED) + drvAudioStreamDestroyInternalBackend(pThis, pStreamEx); - uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Guest.MixBuf); /* Parent */ - if (!cbFree) - Log2Func(("[%s] Buffer full\n", pStream->szName)); + int rc = VERR_AUDIO_STREAM_NOT_READY; + if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)) + { + drvAudioStreamResetInternal(pStreamEx); - if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */ - cbReadable = cbFree; + RT_BZERO(pStreamEx->pBackend + 1, pStreamEx->Core.cbBackend - sizeof(*pStreamEx->pBackend)); - while (cbReadable) - { - uint32_t cbCaptured; - rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend, - auBuf, RT_MIN(cbReadable, cbBuf), &cbCaptured); - if (RT_FAILURE(rc)) + rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx); + if (RT_SUCCESS(rc)) { - int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); - AssertRC(rc2); + LogFunc(("Acquired host config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) )); + /** @todo Validate (re-)acquired configuration with pStreamEx->Core.Core.Cfg? + * drvAudioStreamInitInternal() does some setup and a bunch of + * validations + adjustments of the stream config, so this surely is quite + * optimistic. */ + if (true) + { + /* + * Kick off the asynchronous init. + */ + if (!pStreamEx->fNeedAsyncInit) + { + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + } + else + { + drvAudioStreamRetainInternal(pStreamEx); + int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, &pStreamEx->hReqInitAsync, + RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvAudioStreamInitAsync, 2, pThis, pStreamEx); + LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2)); + AssertRCStmt(rc2, drvAudioStreamInitAsync(pThis, pStreamEx)); + } - break; + /* + * Update the backend on the stream state if it's ready, otherwise + * let the worker thread do it after the async init has completed. + */ + if ( (pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_BACKEND_READY | PDMAUDIOSTREAM_STS_BACKEND_CREATED)) + == (PDMAUDIOSTREAM_STS_BACKEND_READY | PDMAUDIOSTREAM_STS_BACKEND_CREATED)) + { + rc = drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "re-initializing"); + /** @todo not sure if we really need to care about this status code... */ + } + else if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED) + { + Assert(pStreamEx->hReqInitAsync != NIL_RTREQ); + LogFunc(("Asynchronous stream init (%p) ...\n", pStreamEx->hReqInitAsync)); + } + else + { + LogRel(("Audio: Re-initializing stream '%s' somehow failed, status: %s\n", pStreamEx->Core.Cfg.szName, + drvAudioStreamStatusToStr(szTmp, pStreamEx->fStatus) )); + AssertFailed(); + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + } } + else + LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); + } + else + { + LogRel(("Audio: Re-initializing stream '%s' failed to destroy previous backend.\n", pStreamEx->Core.Cfg.szName)); + AssertFailed(); + } - Assert(cbCaptured <= cbBuf); - if (cbCaptured > cbBuf) /* Paranoia. */ - cbCaptured = cbBuf; + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + LogFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); + return rc; +} - if (!cbCaptured) /* Nothing captured? Take a shortcut. */ - break; - /* We use the host side mixing buffer as an intermediate buffer to do some - * (first) processing (if needed), so always write the incoming data at offset 0. */ - uint32_t cfHstWritten = 0; - rc = AudioMixBufWriteAt(&pStream->Host.MixBuf, 0 /* offFrames */, auBuf, cbCaptured, &cfHstWritten); - if ( RT_FAILURE(rc) - || !cfHstWritten) - { - AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n", - pStream->szName, cbCaptured, cfHstWritten, rc)); - break; - } +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamReInit} + */ +static DECLCALLBACK(int) drvAudioStreamReInit(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT, VERR_INVALID_STATE); + LogFlowFunc(("\n")); + + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, rc); - if (pThis->In.Cfg.Dbg.fEnabled) - DrvAudioHlpFileWrite(pStream->In.Dbg.pFileCaptureNonInterleaved, auBuf, cbCaptured, 0 /* fFlags */); + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT) + { + const unsigned cMaxTries = 5; + const uint64_t nsNow = RTTimeNanoTS(); - uint32_t cfHstMixed = 0; - if (cfHstWritten) + /* Throttle re-initializing streams on failure. */ + if ( pStreamEx->cTriesReInit < cMaxTries + && pStreamEx->hReqInitAsync == NIL_RTREQ + && ( pStreamEx->nsLastReInit == 0 + || nsNow - pStreamEx->nsLastReInit >= RT_NS_1SEC * pStreamEx->cTriesReInit)) { - int rc2 = AudioMixBufMixToParentEx(&pStream->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */, - &cfHstMixed /* pcSrcMixed */); - Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n", - pStream->szName, cbCaptured, cfHstWritten, cfHstMixed, rc2)); - AssertRC(rc2); - } - - Assert(cbReadable >= cbCaptured); - cbReadable -= cbCaptured; - cfCapturedTotal += cfHstMixed; - } + rc = drvAudioStreamReInitInternal(pThis, pStreamEx); + if (RT_SUCCESS(rc)) + { + /* Remove the pending re-init flag on success. */ + pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + } + else + { + pStreamEx->nsLastReInit = nsNow; + pStreamEx->cTriesReInit++; - if (RT_SUCCESS(rc)) - { - if (cfCapturedTotal) - Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc)); + /* Did we exceed our tries re-initializing the stream? + * Then this one is dead-in-the-water, so disable it for further use. */ + if (pStreamEx->cTriesReInit >= cMaxTries) + { + LogRel(("Audio: Re-initializing stream '%s' exceeded maximum retries (%u), leaving as disabled\n", + pStreamEx->Core.Cfg.szName, cMaxTries)); + + /* Don't try to re-initialize anymore and mark as disabled. */ + /** @todo should mark it as not-initialized too, shouldn't we? */ + pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_NEED_REINIT | PDMAUDIOSTREAM_STS_ENABLED); + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + + /* Note: Further writes to this stream go to / will be read from the bit bucket (/dev/null) from now on. */ + } + } + } + else + Log8Func(("cTriesReInit=%d hReqInitAsync=%p nsLast=%RU64 nsNow=%RU64 nsDelta=%RU64\n", pStreamEx->cTriesReInit, + pStreamEx->hReqInitAsync, pStreamEx->nsLastReInit, nsNow, nsNow - pStreamEx->nsLastReInit)); + +#ifdef LOG_ENABLED + char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; +#endif + Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus))); } else - LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStream->szName, rc)); + { + AssertFailed(); + rc = VERR_INVALID_STATE; + } - if (pcfCaptured) - *pcfCaptured = cfCapturedTotal; + RTCritSectLeave(&pStreamEx->Core.CritSect); + LogFlowFuncLeaveRC(rc); return rc; } + /** - * Captures raw input from a host stream. - * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without - * no data layout processing done in between. - * - * Needed for e.g. the VRDP audio backend (in Main). + * Internal retain function. * - * @returns IPRT status code. - * @param pThis Driver instance. - * @param pStream Stream to capture from. - * @param pcfCaptured Number of (host) audio frames captured. Optional. + * @returns New reference count, UINT32_MAX if bad stream. + * @param pStreamEx The stream to reference. */ -static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured) +static uint32_t drvAudioStreamRetainInternal(PDRVAUDIOSTREAM pStreamEx) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - /* pcfCaptured is optional. */ - - /* Sanity. */ - Assert(pStream->enmDir == PDMAUDIODIR_IN); - Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW); - - int rc = VINF_SUCCESS; - - uint32_t cfCapturedTotal = 0; + AssertPtrReturn(pStreamEx, UINT32_MAX); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX); - AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable); + uint32_t const cRefs = ASMAtomicIncU32(&pStreamEx->cRefs); + Assert(cRefs > 1); + Assert(cRefs < _1K); - /* Note: Raw means *audio frames*, not bytes! */ - uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend); - if (!cfReadable) - Log2Func(("[%s] No readable data available\n", pStream->szName)); - - const uint32_t cfFree = AudioMixBufFree(&pStream->Guest.MixBuf); /* Parent */ - if (!cfFree) - Log2Func(("[%s] Buffer full\n", pStream->szName)); + Log12Func(("returns %u (%s)\n", cRefs, pStreamEx->Core.Cfg.szName)); + return cRefs; +} - if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */ - cfReadable = cfFree; - - while (cfReadable) - { - PPDMAUDIOFRAME paFrames; - uint32_t cfWritable; - rc = AudioMixBufPeekMutable(&pStream->Host.MixBuf, cfReadable, &paFrames, &cfWritable); - if ( RT_FAILURE(rc) - || !cfWritable) - { - break; - } - - uint32_t cfCaptured; - rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend, - paFrames, cfWritable, &cfCaptured); - if (RT_FAILURE(rc)) - { - int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); - AssertRC(rc2); - - break; - } - - Assert(cfCaptured <= cfWritable); - if (cfCaptured > cfWritable) /* Paranoia. */ - cfCaptured = cfWritable; - - Assert(cfReadable >= cfCaptured); - cfReadable -= cfCaptured; - cfCapturedTotal += cfCaptured; - } - - Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc)); - - if (pcfCaptured) - *pcfCaptured = cfCapturedTotal; - - return rc; -} /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture} + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain} */ -static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface, - PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured) +static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) { - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + RT_NOREF(pInterface); + return drvAudioStreamRetainInternal((PDRVAUDIOSTREAM)pStream); +} - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; - AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, - ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n", - pStream->szName, pStream->enmDir)); +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease} + */ +static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + return drvAudioStreamReleaseInternal(RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector), + (PDRVAUDIOSTREAM)pStream, + false /*fMayDestroy*/); +} - uint32_t cfCaptured = 0; -#ifdef LOG_ENABLED - char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); - Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts)); - RTStrFree(pszStreamSts); -#endif /* LOG_ENABLED */ +/** + * Controls a stream's backend. + * + * @returns VBox status code. + * @param pThis Pointer to driver instance. + * @param pStreamEx Stream to control. + * @param enmStreamCmd Control command. + * + * @note Caller has entered the critical section of the stream. + * @note Can be called w/o having entered DRVAUDIO::CritSectHotPlug. + */ +static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtr(pThis); + AssertPtr(pStreamEx); + Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect)); - do - { - if (!pThis->pHostDrvAudio) - { - rc = VERR_PDM_NO_ATTACHED_DRIVER; - break; - } + int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + AssertRCReturn(rc, rc); - if ( !pThis->In.fEnabled - || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus)) + /* + * Whether to propagate commands down to the backend. + * + * 1. If the stream direction is disabled on the driver level, we should + * obviously not call the backend. Our stream status will reflect the + * actual state so drvAudioEnable() can tell the backend if the user + * re-enables the stream direction. + * + * 2. If the backend hasn't finished initializing yet, don't try call + * it to start/stop/pause/whatever the stream. (Better to do it here + * than to replicate this in the relevant backends.) When the backend + * finish initializing the stream, we'll update it about the stream state. + */ + bool const fDirEnabled = pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN + ? pThis->In.fEnabled : pThis->Out.fEnabled; + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); + /* ^^^ (checks pThis->pHostDrvAudio != NULL too) */ + + char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; + LogRel2(("Audio: %s stream '%s' backend (%s is %s; status: %s; backend-status: %s)\n", + PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir), + fDirEnabled ? "enabled" : "disabled", drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus), + PDMHostAudioStreamStateGetName(enmBackendState) )); + + if (fDirEnabled) + { + if ( (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY /* don't really need this check, do we? */) + && ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY + || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING) ) { - rc = VERR_AUDIO_STREAM_NOT_READY; - break; - } - - /* - * Do the actual capturing. - */ - - if (pThis->pHostDrvAudio->pfnStreamCaptureBegin) - pThis->pHostDrvAudio->pfnStreamCaptureBegin(pThis->pHostDrvAudio, pStream->pvBackend); + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + rc = pThis->pHostDrvAudio->pfnStreamEnable(pThis->pHostDrvAudio, pStreamEx->pBackend); + break; - if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED)) - { - rc = drvAudioStreamCaptureNonInterleaved(pThis, pStream, &cfCaptured); - } - else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW) - { - rc = drvAudioStreamCaptureRaw(pThis, pStream, &cfCaptured); - } - else - AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + case PDMAUDIOSTREAMCMD_DISABLE: + rc = pThis->pHostDrvAudio->pfnStreamDisable(pThis->pHostDrvAudio, pStreamEx->pBackend); + break; - if (pThis->pHostDrvAudio->pfnStreamCaptureEnd) - pThis->pHostDrvAudio->pfnStreamCaptureEnd(pThis->pHostDrvAudio, pStream->pvBackend); + case PDMAUDIOSTREAMCMD_PAUSE: + rc = pThis->pHostDrvAudio->pfnStreamPause(pThis->pHostDrvAudio, pStreamEx->pBackend); + break; - if (RT_SUCCESS(rc)) - { - Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCaptured, rc)); + case PDMAUDIOSTREAMCMD_RESUME: + rc = pThis->pHostDrvAudio->pfnStreamResume(pThis->pHostDrvAudio, pStreamEx->pBackend); + break; -#ifdef VBOX_WITH_STATISTICS - STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured); + case PDMAUDIOSTREAMCMD_DRAIN: + if (pThis->pHostDrvAudio->pfnStreamDrain) + rc = pThis->pHostDrvAudio->pfnStreamDrain(pThis->pHostDrvAudio, pStreamEx->pBackend); + else + rc = VERR_NOT_SUPPORTED; + break; - STAM_COUNTER_ADD(&pStream->In.Stats.TotalFramesCaptured, cfCaptured); -#endif - } - else if (RT_UNLIKELY(RT_FAILURE(rc))) - { - LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStream->szName, rc)); + default: + AssertMsgFailedBreakStmt(("Command %RU32 not implemented\n", enmStreamCmd), rc = VERR_INTERNAL_ERROR_2); + } + if (RT_SUCCESS(rc)) + Log2Func(("[%s] %s succeeded (%Rrc)\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc)); + else + { + LogFunc(("[%s] %s failed with %Rrc\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc)); + if ( rc != VERR_NOT_IMPLEMENTED + && rc != VERR_NOT_SUPPORTED + && rc != VERR_AUDIO_STREAM_NOT_READY) + LogRel(("Audio: %s stream '%s' failed with %Rrc\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.Cfg.szName, rc)); + } } + else + LogFlowFunc(("enmBackendStat(=%s) != OKAY || !(fStatus(=%#x) & BACKEND_READY)\n", + PDMHostAudioStreamStateGetName(enmBackendState), pStreamEx->fStatus)); + } + else + LogFlowFunc(("fDirEnabled=false\n")); - } while (0); - - if (pcFramesCaptured) - *pcFramesCaptured = cfCaptured; - - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; - - if (RT_FAILURE(rc)) - LogFlowFuncLeaveRC(rc); - + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); return rc; } -#ifdef VBOX_WITH_AUDIO_CALLBACKS + /** - * Duplicates an audio callback. + * Resets the given audio stream. * - * @returns Pointer to duplicated callback, or NULL on failure. - * @param pCB Callback to duplicate. + * @param pStreamEx Stream to reset. */ -static PPDMAUDIOCBRECORD drvAudioCallbackDuplicate(PPDMAUDIOCBRECORD pCB) +static void drvAudioStreamResetOnDisable(PDRVAUDIOSTREAM pStreamEx) { - AssertPtrReturn(pCB, NULL); + drvAudioStreamResetInternal(pStreamEx); - PPDMAUDIOCBRECORD pCBCopy = (PPDMAUDIOCBRECORD)RTMemDup((void *)pCB, sizeof(PDMAUDIOCBRECORD)); - if (!pCBCopy) - return NULL; + LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName)); - if (pCB->pvCtx) - { - pCBCopy->pvCtx = RTMemDup(pCB->pvCtx, pCB->cbCtx); - if (!pCBCopy->pvCtx) - { - RTMemFree(pCBCopy); - return NULL; - } + pStreamEx->fStatus &= PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY; + pStreamEx->Core.fWarningsShown = PDMAUDIOSTREAM_WARN_FLAGS_NONE; - pCBCopy->cbCtx = pCB->cbCtx; +#ifdef VBOX_WITH_STATISTICS + /* + * Reset statistics. + */ + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN) + { } - - return pCBCopy; -} - -/** - * Destroys a given callback. - * - * @param pCB Callback to destroy. - */ -static void drvAudioCallbackDestroy(PPDMAUDIOCBRECORD pCB) -{ - if (!pCB) - return; - - RTListNodeRemove(&pCB->Node); - if (pCB->pvCtx) + else if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT) { - Assert(pCB->cbCtx); - RTMemFree(pCB->pvCtx); } - RTMemFree(pCB); + else + AssertFailed(); +#endif } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnRegisterCallbacks} - */ -static DECLCALLBACK(int) drvAudioRegisterCallbacks(PPDMIAUDIOCONNECTOR pInterface, - PPDMAUDIOCBRECORD paCallbacks, size_t cCallbacks) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(paCallbacks, VERR_INVALID_POINTER); - AssertReturn(cCallbacks, VERR_INVALID_PARAMETER); + * Controls an audio stream. + * + * @returns VBox status code. + * @param pThis Pointer to driver instance. + * @param pStreamEx Stream to control. + * @param enmStreamCmd Control command. + */ +static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtr(pThis); + AssertPtr(pStreamEx); + Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect)); - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); +#ifdef LOG_ENABLED + char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; +#endif + LogFunc(("[%s] enmStreamCmd=%s fStatus=%s\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd), + drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus))); - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; + int rc = VINF_SUCCESS; - for (size_t i = 0; i < cCallbacks; i++) + switch (enmStreamCmd) { - PPDMAUDIOCBRECORD pCB = drvAudioCallbackDuplicate(&paCallbacks[i]); - if (!pCB) - { - rc = VERR_NO_MEMORY; - break; - } - - switch (pCB->enmSource) - { - case PDMAUDIOCBSOURCE_DEVICE: + case PDMAUDIOSTREAMCMD_ENABLE: + if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)) { - switch (pCB->Device.enmType) + /* Are we still draining this stream? Then we must disable it first. */ + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE) { - case PDMAUDIODEVICECBTYPE_DATA_INPUT: - RTListAppend(&pThis->In.lstCB, &pCB->Node); - break; - - case PDMAUDIODEVICECBTYPE_DATA_OUTPUT: - RTListAppend(&pThis->Out.lstCB, &pCB->Node); - break; - - default: - AssertMsgFailed(("Not supported\n")); - break; + LogFunc(("Stream '%s' is still draining - disabling...\n", pStreamEx->Core.Cfg.szName)); + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + AssertRC(rc); + if (drvAudioStreamGetBackendState(pThis, pStreamEx) != PDMHOSTAUDIOSTREAMSTATE_DRAINING) + { + pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE); + drvAudioStreamResetInternal(pStreamEx); + rc = VINF_SUCCESS; + } } - break; - } + if (RT_SUCCESS(rc)) + { + /* Reset the state before we try to start. */ + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); + pStreamEx->enmLastBackendState = enmBackendState; + pStreamEx->offInternal = 0; - default: - AssertMsgFailed(("Not supported\n")); - break; - } - } + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT) + { + pStreamEx->Out.cbPreBuffered = 0; + pStreamEx->Out.offPreBuf = 0; + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY; + switch (enmBackendState) + { + case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING: + if (pStreamEx->cbPreBufThreshold > 0) + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF; + break; + case PDMHOSTAUDIOSTREAMSTATE_DRAINING: + AssertFailed(); + RT_FALL_THROUGH(); + case PDMHOSTAUDIOSTREAMSTATE_OKAY: + pStreamEx->Out.enmPlayState = pStreamEx->cbPreBufThreshold > 0 + ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY; + break; + case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING: + case PDMHOSTAUDIOSTREAMSTATE_INACTIVE: + break; + /* no default */ + case PDMHOSTAUDIOSTREAMSTATE_INVALID: + case PDMHOSTAUDIOSTREAMSTATE_END: + case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK: + break; + } + LogFunc(("ENABLE: enmBackendState=%s enmPlayState=%s\n", PDMHostAudioStreamStateGetName(enmBackendState), + drvAudioPlayStateName(pStreamEx->Out.enmPlayState))); + } + else + { + pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_NO_CAPTURE; + switch (enmBackendState) + { + case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING: + pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_PREBUF; + break; + case PDMHOSTAUDIOSTREAMSTATE_DRAINING: + AssertFailed(); + RT_FALL_THROUGH(); + case PDMHOSTAUDIOSTREAMSTATE_OKAY: + pStreamEx->In.enmCaptureState = pStreamEx->cbPreBufThreshold > 0 + ? DRVAUDIOCAPTURESTATE_PREBUF : DRVAUDIOCAPTURESTATE_CAPTURING; + break; + case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING: + case PDMHOSTAUDIOSTREAMSTATE_INACTIVE: + break; + /* no default */ + case PDMHOSTAUDIOSTREAMSTATE_INVALID: + case PDMHOSTAUDIOSTREAMSTATE_END: + case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK: + break; + } + LogFunc(("ENABLE: enmBackendState=%s enmCaptureState=%s\n", PDMHostAudioStreamStateGetName(enmBackendState), + drvAudioCaptureStateName(pStreamEx->In.enmCaptureState))); + } - /** @todo Undo allocations on error. */ + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE); + if (RT_SUCCESS(rc)) + { + pStreamEx->nsStarted = RTTimeNanoTS(); + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_ENABLED; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + } + } + } + break; - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; + case PDMAUDIOSTREAMCMD_DISABLE: + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED) + { + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + LogFunc(("DISABLE '%s': Backend DISABLE -> %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); + if (RT_SUCCESS(rc)) /** @todo ignore this and reset it anyway? */ + drvAudioStreamResetOnDisable(pStreamEx); + } + break; - return rc; -} -#endif /* VBOX_WITH_AUDIO_CALLBACKS */ + case PDMAUDIOSTREAMCMD_PAUSE: + if ((pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) == PDMAUDIOSTREAM_STS_ENABLED) + { + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE); + if (RT_SUCCESS(rc)) + { + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PAUSED; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + } + } + break; -#ifdef VBOX_WITH_AUDIO_CALLBACKS -/** - * @callback_method_impl{FNPDMHOSTAUDIOCALLBACK, Backend callback implementation.} - * - * @par Important: - * No calls back to the backend within this function, as the backend - * might hold any locks / critical sections while executing this - * callback. Will result in some ugly deadlocks (or at least locking - * order violations) then. - */ -static DECLCALLBACK(int) drvAudioBackendCallback(PPDMDRVINS pDrvIns, PDMAUDIOBACKENDCBTYPE enmType, void *pvUser, size_t cbUser) -{ - AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); - RT_NOREF(pvUser, cbUser); - /* pvUser and cbUser are optional. */ + case PDMAUDIOSTREAMCMD_RESUME: + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PAUSED) + { + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED); + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_RESUME); + if (RT_SUCCESS(rc)) + { + pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_PAUSED; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + } + } + break; - /* Get the upper driver (PDMIAUDIOCONNECTOR). */ - AssertPtr(pDrvIns->pUpBase); - PPDMIAUDIOCONNECTOR pInterface = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR); - AssertPtr(pInterface); - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + case PDMAUDIOSTREAMCMD_DRAIN: + /* + * Only for output streams and we don't want this command more than once. + */ + AssertReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_FUNCTION); + AssertBreak(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)); + if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED) + { + rc = VERR_INTERNAL_ERROR_2; + switch (pStreamEx->Out.enmPlayState) + { + case DRVAUDIOPLAYSTATE_PREBUF: + if (pStreamEx->Out.cbPreBuffered > 0) + { + LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data...\n", pStreamEx->Core.Cfg.szName)); + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING; + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + rc = VINF_SUCCESS; + break; + } + RT_FALL_THROUGH(); + case DRVAUDIOPLAYSTATE_NOPLAY: + case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: + case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: + LogFunc(("DRAIN '%s': Nothing to drain (enmPlayState=%s)\n", + pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState))); + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + AssertRC(rc); + drvAudioStreamResetOnDisable(pStreamEx); + break; - int rc = RTCritSectEnter(&pThis->CritSect); - AssertRCReturn(rc, rc); + case DRVAUDIOPLAYSTATE_PLAY: + case DRVAUDIOPLAYSTATE_PLAY_PREBUF: + LogFunc(("DRAIN '%s': Initiating backend draining (enmPlayState=%s -> NOPLAY) ...\n", + pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState))); + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY; + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN); + if (RT_SUCCESS(rc)) + { + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + } + else + { + LogFunc(("DRAIN '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n", + pStreamEx->Core.Cfg.szName, rc)); + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + AssertRC(rc); + drvAudioStreamResetOnDisable(pStreamEx); + } + break; - LogFunc(("pThis=%p, enmType=%RU32, pvUser=%p, cbUser=%zu\n", pThis, enmType, pvUser, cbUser)); + case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: + LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data (already committing)...\n", + pStreamEx->Core.Cfg.szName)); + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + rc = VINF_SUCCESS; + break; - switch (enmType) - { - case PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED: - LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->szName)); - rc = drvAudioScheduleReInitInternal(pThis); + /* no default */ + case DRVAUDIOPLAYSTATE_INVALID: + case DRVAUDIOPLAYSTATE_END: + AssertFailedBreak(); + } + } break; default: - AssertMsgFailed(("Not supported\n")); + rc = VERR_NOT_IMPLEMENTED; break; } - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; + if (RT_FAILURE(rc)) + LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc)); - LogFlowFunc(("Returning %Rrc\n", rc)); return rc; } -#endif /* VBOX_WITH_AUDIO_CALLBACKS */ -#ifdef VBOX_WITH_AUDIO_ENUM + /** - * Enumerates all host audio devices. - * - * This functionality might not be implemented by all backends and will return - * VERR_NOT_SUPPORTED if not being supported. - * - * @returns IPRT status code. - * @param pThis Driver instance to be called. - * @param fLog Whether to print the enumerated device to the release log or not. - * @param pDevEnum Where to store the device enumeration. + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl} */ -static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum) +static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface, + PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) { - int rc; - - /* - * If the backend supports it, do a device enumeration. - */ - if (pThis->pHostDrvAudio->pfnGetDevices) - { - PDMAUDIODEVICEENUM DevEnum; - rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum); - if (RT_SUCCESS(rc)) - { - if (fLog) - LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->szName)); + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); - PPDMAUDIODEVICE pDev; - RTListForEach(&DevEnum.lstDevices, pDev, PDMAUDIODEVICE, Node) - { - if (fLog) - { - char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags); - - LogRel(("Audio: Device '%s':\n", pDev->szName)); - LogRel(("Audio: \tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage))); - LogRel(("Audio: \tFlags = %s\n", pszFlags ? pszFlags : "")); - LogRel(("Audio: \tInput channels = %RU8\n", pDev->cMaxInputChannels)); - LogRel(("Audio: \tOutput channels = %RU8\n", pDev->cMaxOutputChannels)); - - if (pszFlags) - RTStrFree(pszFlags); - } - } + /** @todo r=bird: why? It's not documented to ignore NULL streams. */ + if (!pStream) + return VINF_SUCCESS; + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); - if (pDevEnum) - rc = DrvAudioHlpDeviceEnumCopy(pDevEnum, &DevEnum); + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, rc); - DrvAudioHlpDeviceEnumFree(&DevEnum); - } - else - { - if (fLog) - LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->szName, rc)); - /* Not fatal. */ - } - } - else - { - rc = VERR_NOT_SUPPORTED; + LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd))); - if (fLog) - LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->szName)); - } + rc = drvAudioStreamControlInternal(pThis, pStreamEx, enmStreamCmd); - LogFunc(("Returning %Rrc\n", rc)); + RTCritSectLeave(&pStreamEx->Core.CritSect); return rc; } -#endif /* VBOX_WITH_AUDIO_ENUM */ + /** - * Initializes the host backend and queries its initial configuration. - * If the host backend fails, VERR_AUDIO_BACKEND_INIT_FAILED will be returned. + * Copy data to the pre-buffer, ring-buffer style. * - * Note: As this routine is called when attaching to the device LUN in the - * device emulation, we either check for success or VERR_AUDIO_BACKEND_INIT_FAILED. - * Everything else is considered as fatal and must be handled separately in - * the device emulation! - * - * @return IPRT status code. - * @param pThis Driver instance to be called. - * @param pCfgHandle CFGM configuration handle to use for this driver. - */ -static int drvAudioHostInit(PDRVAUDIO pThis, PCFGMNODE pCfgHandle) -{ - /* pCfgHandle is optional. */ - NOREF(pCfgHandle); - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - AssertPtr(pThis->pHostDrvAudio); - int rc = pThis->pHostDrvAudio->pfnInit(pThis->pHostDrvAudio); - if (RT_FAILURE(rc)) - { - LogRel(("Audio: Initialization of host driver '%s' failed with %Rrc\n", pThis->szName, rc)); - return VERR_AUDIO_BACKEND_INIT_FAILED; - } + * The @a cbMax parameter is almost always set to the threshold size, the + * exception is when commiting the buffer and we want to top it off to reduce + * the number of transfers to the backend (the first transfer may start + * playback, so more data is better). + */ +static int drvAudioStreamPreBuffer(PDRVAUDIOSTREAM pStreamEx, const uint8_t *pbBuf, uint32_t cbBuf, uint32_t cbMax) +{ + uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc; + AssertReturn(cbAlloc >= cbMax, VERR_INTERNAL_ERROR_3); + AssertReturn(cbAlloc >= 8, VERR_INTERNAL_ERROR_4); + AssertReturn(cbMax >= 8, VERR_INTERNAL_ERROR_5); + + uint32_t offRead = pStreamEx->Out.offPreBuf; + uint32_t cbCur = pStreamEx->Out.cbPreBuffered; + AssertStmt(offRead < cbAlloc, offRead %= cbAlloc); + AssertStmt(cbCur <= cbMax, offRead = (offRead + cbCur - cbMax) % cbAlloc; cbCur = cbMax); /* - * Get the backend configuration. + * First chunk. */ - rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg); - if (RT_FAILURE(rc)) - { - LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc)); - return VERR_AUDIO_BACKEND_INIT_FAILED; - } - - pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn; - pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut; - - LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree)); - - LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once\n", - pThis->szName, - /* Clamp for logging. Unlimited streams are defined by UINT32_MAX. */ - RT_MIN(64, pThis->In.cStreamsFree), RT_MIN(64, pThis->Out.cStreamsFree))); - -#ifdef VBOX_WITH_AUDIO_ENUM - int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */); - if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */ - AssertRC(rc2); + uint32_t offWrite = (offRead + cbCur) % cbAlloc; + uint32_t cbToCopy = RT_MIN(cbAlloc - offWrite, cbBuf); + memcpy(&pStreamEx->Out.pbPreBuf[offWrite], pbBuf, cbToCopy); + + /* Advance. */ + offWrite = (offWrite + cbToCopy) % cbAlloc; + for (;;) + { + pbBuf += cbToCopy; + cbCur += cbToCopy; + if (cbCur > cbMax) + offRead = (offRead + cbCur - cbMax) % cbAlloc; + cbBuf -= cbToCopy; + if (!cbBuf) + break; - RT_NOREF(rc2); - /* Ignore rc. */ -#endif + /* + * Second+ chunk, from the start of the buffer. + * + * Note! It is assumed very unlikely that we will ever see a cbBuf larger than + * cbMax, so we don't waste space on clipping cbBuf here (can happen with + * custom pre-buffer sizes). + */ + Assert(offWrite == 0); + cbToCopy = RT_MIN(cbAlloc, cbBuf); + memcpy(pStreamEx->Out.pbPreBuf, pbBuf, cbToCopy); + } -#ifdef VBOX_WITH_AUDIO_CALLBACKS /* - * If the backend supports it, offer a callback to this connector. + * Update the pre-buffering size and position. */ - if (pThis->pHostDrvAudio->pfnSetCallback) - { - rc2 = pThis->pHostDrvAudio->pfnSetCallback(pThis->pHostDrvAudio, drvAudioBackendCallback); - if (RT_FAILURE(rc2)) - LogRel(("Audio: Error registering callback for host driver '%s', rc=%Rrc\n", pThis->szName, rc2)); - /* Not fatal. */ - } -#endif - - LogFlowFuncLeave(); + pStreamEx->Out.cbPreBuffered = RT_MIN(cbCur, cbMax); + pStreamEx->Out.offPreBuf = offRead; return VINF_SUCCESS; } + /** - * Handles state changes for all audio streams. + * Worker for drvAudioStreamPlay() and drvAudioStreamPreBufComitting(). * - * @param pDrvIns Pointer to driver instance. - * @param enmCmd Stream command to set for all streams. + * Caller owns the lock. */ -static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd) +static int drvAudioStreamPlayLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, + const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten) { - PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); - PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); - - LogFlowFunc(("enmCmd=%s\n", DrvAudioHlpStreamCmdToStr(enmCmd))); + Log3Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbBuf)); - int rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); + uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend); + pStreamEx->Out.Stats.cbBackendWritableBefore = cbWritable; - if (pThis->pHostDrvAudio) + uint32_t cbWritten = 0; + int rc = VINF_SUCCESS; + uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Cfg.Props); + while (cbBuf >= cbFrame && cbWritable >= cbFrame) { - PPDMAUDIOSTREAM pStream; - RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node) - drvAudioStreamControlInternal(pThis, pStream, enmCmd); - } - - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); -} - -/** - * Retrieves an audio configuration from the specified CFGM node. - * - * @return VBox status code. - * @param pThis Driver instance to be called. - * @param pCfg Where to store the retrieved audio configuration to. - * @param pNode Where to get the audio configuration from. - */ -static int drvAudioGetCfgFromCFGM(PDRVAUDIO pThis, PDRVAUDIOCFG pCfg, PCFGMNODE pNode) -{ - RT_NOREF(pThis); + uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, RT_MIN(cbBuf, cbWritable)); + uint32_t cbWrittenNow = 0; + rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToWrite, &cbWrittenNow); + if (RT_SUCCESS(rc)) + { + if (cbWrittenNow != cbToWrite) + Log3Func(("%s: @%#RX64: Wrote fewer bytes than requested: %#x, requested %#x\n", + pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbWrittenNow, cbToWrite)); +#ifdef DEBUG_bird + Assert(cbWrittenNow == cbToWrite); +#endif + AssertStmt(cbWrittenNow <= cbToWrite, cbWrittenNow = cbToWrite); + cbWritten += cbWrittenNow; + cbBuf -= cbWrittenNow; + pbBuf += cbWrittenNow; + pStreamEx->offInternal += cbWrittenNow; + } + else + { + *pcbWritten = cbWritten; + LogFunc(("%s: @%#RX64: pfnStreamPlay failed writing %#x bytes (%#x previous written, %#x writable): %Rrc\n", + pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbToWrite, cbWritten, cbWritable, rc)); + return cbWritten ? VINF_SUCCESS : rc; + } - /* Debug stuff. */ - CFGMR3QueryBoolDef(pNode, "DebugEnabled", &pCfg->Dbg.fEnabled, false); - int rc2 = CFGMR3QueryString(pNode, "DebugPathOut", pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut)); - if ( RT_FAILURE(rc2) - || !strlen(pCfg->Dbg.szPathOut)) - { - RTStrPrintf(pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH); + cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend); } - if (pCfg->Dbg.fEnabled) - LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", pThis->szName, pCfg->Dbg.szPathOut)); - - /* Buffering stuff. */ - CFGMR3QueryU32Def(pNode, "PeriodSizeMs", &pCfg->uPeriodSizeMs, 0); - CFGMR3QueryU32Def(pNode, "BufferSizeMs", &pCfg->uBufferSizeMs, 0); - CFGMR3QueryU32Def(pNode, "PreBufferSizeMs", &pCfg->uPreBufSizeMs, UINT32_MAX /* No custom value set */); + STAM_PROFILE_ADD_PERIOD(&pStreamEx->StatXfer, cbWritten); + *pcbWritten = cbWritten; + pStreamEx->Out.Stats.cbBackendWritableAfter = cbWritable; + if (cbWritten) + pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS(); - LogFunc(("pCfg=%p, uPeriodSizeMs=%RU32, uBufferSizeMs=%RU32, uPreBufSizeMs=%RU32\n", - pCfg, pCfg->uPeriodSizeMs, pCfg->uBufferSizeMs, pCfg->uPreBufSizeMs)); - - return VINF_SUCCESS; + Log3Func(("%s: @%#RX64: Wrote %#x bytes (%#x bytes left)\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbWritten, cbBuf)); + return rc; } + /** - * Intializes an audio driver instance. - * - * @returns IPRT status code. - * @param pDrvIns Pointer to driver instance. - * @param pCfgHandle CFGM handle to use for configuration. + * Worker for drvAudioStreamPlay() and drvAudioStreamPreBufComitting(). */ -static int drvAudioInit(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle) +static int drvAudioStreamPlayToPreBuffer(PDRVAUDIOSTREAM pStreamEx, const void *pvBuf, uint32_t cbBuf, uint32_t cbMax, + uint32_t *pcbWritten) { - AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER); - AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); - - PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); - - int rc = RTCritSectInit(&pThis->CritSect); - AssertRCReturn(rc, rc); - - /* - * Configure driver from CFGM. - */ -#ifdef DEBUG - CFGMR3Dump(pCfgHandle); -#endif - - pThis->fTerminate = false; - pThis->pCFGMNode = pCfgHandle; - - int rc2 = CFGMR3QueryString(pThis->pCFGMNode, "DriverName", pThis->szName, sizeof(pThis->szName)); - if (RT_FAILURE(rc2)) - RTStrPrintf(pThis->szName, sizeof(pThis->szName), "Untitled"); - - /* By default we don't enable anything if wrongly / not set-up. */ - CFGMR3QueryBoolDef(pThis->pCFGMNode, "InputEnabled", &pThis->In.fEnabled, false); - CFGMR3QueryBoolDef(pThis->pCFGMNode, "OutputEnabled", &pThis->Out.fEnabled, false); - - LogRel2(("Audio: Verbose logging for driver '%s' enabled\n", pThis->szName)); - - LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n", - pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled")); - - /* - * Load configurations. - */ - rc = drvAudioGetCfgFromCFGM(pThis, &pThis->In.Cfg, pThis->pCFGMNode); + int rc = drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, cbBuf, cbMax); if (RT_SUCCESS(rc)) - rc = drvAudioGetCfgFromCFGM(pThis, &pThis->Out.Cfg, pThis->pCFGMNode); + { + *pcbWritten = cbBuf; + pStreamEx->offInternal += cbBuf; + Log3Func(("[%s] Pre-buffering (%s): wrote %#x bytes => %#x bytes / %u%%\n", + pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState), cbBuf, pStreamEx->Out.cbPreBuffered, + pStreamEx->Out.cbPreBuffered * 100 / RT_MAX(pStreamEx->cbPreBufThreshold, 1))); - LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc)); + } + else + *pcbWritten = 0; return rc; } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead} + * Used when we're committing (transfering) the pre-buffered bytes to the + * device. + * + * This is called both from drvAudioStreamPlay() and + * drvAudioStreamIterateInternal(). + * + * @returns VBox status code. + * @param pThis Pointer to the DrvAudio instance data. + * @param pStreamEx The stream to commit the pre-buffering for. + * @param pbBuf Buffer with new bytes to write. Can be NULL when called + * in the PENDING_DISABLE state from + * drvAudioStreamIterateInternal(). + * @param cbBuf Number of new bytes. Can be zero. + * @param pcbWritten Where to return the number of bytes written. + * + * @note Locking: Stream critsect and hot-plug in shared mode. */ -static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, - void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +static int drvAudioStreamPreBufComitting(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, + const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten) { - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(cbBuf, VERR_INVALID_PARAMETER); - /* pcbRead is optional. */ - - AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, - ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n", - pStream->szName, pStream->enmDir)); - - uint32_t cbReadTotal = 0; - - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; - - do + /* + * First, top up the buffer with new data from pbBuf. + */ + *pcbWritten = 0; + if (cbBuf > 0) { - if ( !pThis->In.fEnabled - || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus)) + uint32_t const cbToCopy = RT_MIN(pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered, cbBuf); + if (cbToCopy > 0) { - rc = VERR_AUDIO_STREAM_NOT_READY; - break; + int rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pbBuf, cbBuf, pStreamEx->Out.cbPreBufAlloc, pcbWritten); + AssertRCReturn(rc, rc); + pbBuf += cbToCopy; + cbBuf -= cbToCopy; } + } - /* - * Read from the parent buffer (that is, the guest buffer) which - * should have the audio data in the format the guest needs. - */ - uint32_t cfReadTotal = 0; - - const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStream->Guest.MixBuf, cbBuf); - - uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStream->Guest.MixBuf)); - while (cfToRead) - { - uint32_t cfRead; - rc = AudioMixBufAcquireReadBlock(&pStream->Guest.MixBuf, - (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), - AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfToRead), &cfRead); - if (RT_FAILURE(rc)) - break; - -#ifdef VBOX_WITH_STATISTICS - const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfRead); - - STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead); - - STAM_COUNTER_ADD(&pStream->In.Stats.TotalFramesRead, cfRead); - STAM_COUNTER_INC(&pStream->In.Stats.TotalTimesRead); -#endif - Assert(cfToRead >= cfRead); - cfToRead -= cfRead; - - cfReadTotal += cfRead; - - AudioMixBufReleaseReadBlock(&pStream->Guest.MixBuf, cfRead); - } + AssertReturn(pThis->pHostDrvAudio, VERR_AUDIO_BACKEND_NOT_ATTACHED); - if (cfReadTotal) - { - if (pThis->In.Cfg.Dbg.fEnabled) - DrvAudioHlpFileWrite(pStream->In.Dbg.pFileStreamRead, - pvBuf, AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), 0 /* fFlags */); + /* + * Write the pre-buffered chunk. + */ + int rc = VINF_SUCCESS; + uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc; + AssertReturn(cbAlloc > 0, VERR_INTERNAL_ERROR_2); + uint32_t off = pStreamEx->Out.offPreBuf; + AssertStmt(off < pStreamEx->Out.cbPreBufAlloc, off %= cbAlloc); + uint32_t cbLeft = pStreamEx->Out.cbPreBuffered; + while (cbLeft > 0) + { + uint32_t const cbToWrite = RT_MIN(cbAlloc - off, cbLeft); + Assert(cbToWrite > 0); + + uint32_t cbPreBufWritten = 0; + rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, &pStreamEx->Out.pbPreBuf[off], + cbToWrite, &cbPreBufWritten); + AssertRCBreak(rc); + if (!cbPreBufWritten) + break; + AssertStmt(cbPreBufWritten <= cbToWrite, cbPreBufWritten = cbToWrite); + off = (off + cbPreBufWritten) % cbAlloc; + cbLeft -= cbPreBufWritten; + } - AudioMixBufFinish(&pStream->Guest.MixBuf, cfReadTotal); - } + if (cbLeft == 0) + { + LogFunc(("@%#RX64: Wrote all %#x bytes of pre-buffered audio data. %s -> PLAY\n", pStreamEx->offInternal, + pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); + pStreamEx->Out.cbPreBuffered = 0; + pStreamEx->Out.offPreBuf = 0; + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY; - /* If we were not able to read as much data as requested, fill up the returned - * data with silence. - * - * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */ - if (cfReadTotal < cfBuf) + if (cbBuf > 0) { - Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName, - DrvAudioHlpFramesToMilli(cfBuf - cfReadTotal, &pStream->Guest.Cfg.Props), - DrvAudioHlpFramesToMilli(cfBuf, &pStream->Guest.Cfg.Props))); - - DrvAudioHlpClearBuf(&pStream->Guest.Cfg.Props, - (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), - AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfBuf - cfReadTotal), - cfBuf - cfReadTotal); - - cfReadTotal = cfBuf; + uint32_t cbWritten2 = 0; + rc = drvAudioStreamPlayLocked(pThis, pStreamEx, pbBuf, cbBuf, &cbWritten2); + if (RT_SUCCESS(rc)) + *pcbWritten += cbWritten2; } - - cbReadTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal); - - pStream->tsLastReadWrittenNs = RTTimeNanoTS(); - - Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc)); - - } while (0); - - - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; - - if (RT_SUCCESS(rc)) + else + pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS(); + } + else { - if (pcbRead) - *pcbRead = cbReadTotal; + if (cbLeft != pStreamEx->Out.cbPreBuffered) + pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS(); + + LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: wrote %#x out of %#x + %#x - rc=%Rrc *pcbWritten=%#x %s -> PREBUF_COMMITTING\n", + pStreamEx->offInternal, pStreamEx->Core.Cfg.szName, pStreamEx->Out.cbPreBuffered - cbLeft, + pStreamEx->Out.cbPreBuffered, cbBuf, rc, *pcbWritten, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); + AssertMsg( pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING + || pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF + || RT_FAILURE(rc), + ("Buggy host driver buffer reporting? cbLeft=%#x cbPreBuffered=%#x enmPlayState=%s\n", + cbLeft, pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); + + pStreamEx->Out.cbPreBuffered = cbLeft; + pStreamEx->Out.offPreBuf = off; + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING; } - return rc; + return *pcbWritten ? VINF_SUCCESS : rc; } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate} + * Does one iteration of an audio stream. + * + * This function gives the backend the chance of iterating / altering data and + * does the actual mixing between the guest <-> host mixing buffers. + * + * @returns VBox status code. + * @param pThis Pointer to driver instance. + * @param pStreamEx Stream to iterate. */ -static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, - PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest, - PPDMAUDIOSTREAM *ppStream) +static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) { - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER); - AssertPtrReturn(ppStream, VERR_INVALID_POINTER); - - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); - - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); - LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName)); -#ifdef DEBUG - DrvAudioHlpStreamCfgPrint(pCfgHost); - DrvAudioHlpStreamCfgPrint(pCfgGuest); +#ifdef LOG_ENABLED + char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; #endif + Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus))); - PPDMAUDIOSTREAM pStream = NULL; - -#define RC_BREAK(x) { rc = x; break; } + /* Not enabled or paused? Skip iteration. */ + if ((pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) != PDMAUDIOSTREAM_STS_ENABLED) + return VINF_SUCCESS; - do + /* + * Pending disable is really what we're here for. + * + * This only happens to output streams. We ASSUME the caller (MixerBuffer) + * implements a timeout on the draining, so we skip that here. + */ + if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)) + { /* likely until we get to the end of the stream at least. */ } + else { - if ( !DrvAudioHlpStreamCfgIsValid(pCfgHost) - || !DrvAudioHlpStreamCfgIsValid(pCfgGuest)) - { - RC_BREAK(VERR_INVALID_PARAMETER); - } - - /* Make sure that both configurations actually intend the same thing. */ - if (pCfgHost->enmDir != pCfgGuest->enmDir) - { - AssertMsgFailed(("Stream configuration directions do not match\n")); - RC_BREAK(VERR_INVALID_PARAMETER); - } - - /* Note: cbHstStrm will contain the size of the data the backend needs to operate on. */ - size_t cbHstStrm; - if (pCfgHost->enmDir == PDMAUDIODIR_IN) - { - if (!pThis->In.cStreamsFree) - { - LogFlowFunc(("Maximum number of host input streams reached\n")); - RC_BREAK(VERR_AUDIO_NO_FREE_INPUT_STREAMS); - } - - cbHstStrm = pThis->BackendCfg.cbStreamIn; - } - else /* Out */ - { - if (!pThis->Out.cStreamsFree) - { - LogFlowFunc(("Maximum number of host output streams reached\n")); - RC_BREAK(VERR_AUDIO_NO_FREE_OUTPUT_STREAMS); - } - - cbHstStrm = pThis->BackendCfg.cbStreamOut; - } + AssertReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, VINF_SUCCESS); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); /* - * Allocate and initialize common state. + * Move pre-buffered samples to the backend. */ - - pStream = (PPDMAUDIOSTREAM)RTMemAllocZ(sizeof(PDMAUDIOSTREAM)); - AssertPtrBreakStmt(pStream, rc = VERR_NO_MEMORY); - - /* Retrieve host driver name for easier identification. */ - AssertPtr(pThis->pHostDrvAudio); - PPDMDRVINS pDrvAudioInst = PDMIBASE_2_PDMDRV(pThis->pDrvIns->pDownBase); - AssertPtr(pDrvAudioInst); - AssertPtr(pDrvAudioInst->pReg); - - Assert(pDrvAudioInst->pReg->szName[0] != '\0'); - RTStrPrintf(pStream->szName, RT_ELEMENTS(pStream->szName), "[%s] %s", - pDrvAudioInst->pReg->szName[0] != '\0' ? pDrvAudioInst->pReg->szName : "Untitled", - pCfgHost->szName[0] != '\0' ? pCfgHost->szName : ""); - - pStream->enmDir = pCfgHost->enmDir; - - /* - * Allocate and init backend-specific data. - */ - - if (cbHstStrm) /* High unlikely that backends do not have an own space for data, but better check. */ - { - pStream->pvBackend = RTMemAllocZ(cbHstStrm); - AssertPtrBreakStmt(pStream->pvBackend, rc = VERR_NO_MEMORY); - - pStream->cbBackend = cbHstStrm; - } - - /* - * Try to init the rest. - */ - - rc = drvAudioStreamInitInternal(pThis, pStream, pCfgHost, pCfgGuest); - if (RT_FAILURE(rc)) - break; - - } while (0); - -#undef RC_BREAK - - if (RT_FAILURE(rc)) - { - Log(("drvAudioStreamCreate: failed - %Rrc\n", rc)); - if (pStream) + if (pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING) { - int rc2 = drvAudioStreamUninitInternal(pThis, pStream); - if (RT_SUCCESS(rc2)) + if (pStreamEx->Out.cbPreBuffered > 0) { - drvAudioStreamFree(pStream); - pStream = NULL; + uint32_t cbIgnored = 0; + drvAudioStreamPreBufComitting(pThis, pStreamEx, NULL, 0, &cbIgnored); + Log3Func(("Stream '%s': Transferred %#x bytes\n", pStreamEx->Core.Cfg.szName, cbIgnored)); } - } - } - else - { - /* Append the stream to our stream list. */ - RTListAppend(&pThis->lstStreams, &pStream->Node); - - /* Set initial reference counts. */ - pStream->cRefs = 1; - - char szFile[RTPATH_MAX]; - if (pCfgHost->enmDir == PDMAUDIODIR_IN) - { - if (pThis->In.Cfg.Dbg.fEnabled) + if (pStreamEx->Out.cbPreBuffered == 0) { - int rc2 = DrvAudioHlpFileNameGet(szFile, sizeof(szFile), pThis->In.Cfg.Dbg.szPathOut, "CaptureNonInterleaved", - pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - if (RT_SUCCESS(rc2)) - { - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAGS_NONE, - &pStream->In.Dbg.pFileCaptureNonInterleaved); - if (RT_SUCCESS(rc2)) - rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileCaptureNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStream->Host.Cfg.Props); - } + Log3Func(("Stream '%s': No more pre-buffered data -> NOPLAY + backend DRAIN\n", pStreamEx->Core.Cfg.szName)); + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY; - if (RT_SUCCESS(rc2)) - { - rc2 = DrvAudioHlpFileNameGet(szFile, sizeof(szFile), pThis->In.Cfg.Dbg.szPathOut, "StreamRead", - pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - if (RT_SUCCESS(rc2)) - { - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAGS_NONE, - &pStream->In.Dbg.pFileStreamRead); - if (RT_SUCCESS(rc2)) - rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileStreamRead, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStream->Host.Cfg.Props); - } - } - } - - if (pThis->In.cStreamsFree) - pThis->In.cStreamsFree--; - } - else /* Out */ - { - if (pThis->Out.Cfg.Dbg.fEnabled) - { - int rc2 = DrvAudioHlpFileNameGet(szFile, sizeof(szFile), pThis->Out.Cfg.Dbg.szPathOut, "PlayNonInterleaved", - pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - if (RT_SUCCESS(rc2)) - { - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAGS_NONE, - &pStream->Out.Dbg.pFilePlayNonInterleaved); - if (RT_SUCCESS(rc2)) - rc = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFilePlayNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStream->Host.Cfg.Props); - } - - if (RT_SUCCESS(rc2)) + int rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN); + if (RT_FAILURE(rc)) { - rc2 = DrvAudioHlpFileNameGet(szFile, sizeof(szFile), pThis->Out.Cfg.Dbg.szPathOut, "StreamWrite", - pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - if (RT_SUCCESS(rc2)) - { - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAGS_NONE, - &pStream->Out.Dbg.pFileStreamWrite); - if (RT_SUCCESS(rc2)) - rc2 = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFileStreamWrite, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStream->Host.Cfg.Props); - } + LogFunc(("Stream '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n", + pStreamEx->Core.Cfg.szName, rc)); + rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + AssertRC(rc); + drvAudioStreamResetOnDisable(pStreamEx); } } + } + else + Assert(pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_NOPLAY); - if (pThis->Out.cStreamsFree) - pThis->Out.cStreamsFree--; + /* + * Check the backend status to see if it's still draining and to + * update our status when it stops doing so. + */ + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); + if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING) + { + uint32_t cbIgnored = 0; + pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, NULL, 0, &cbIgnored); + } + else + { + LogFunc(("Stream '%s': Backend finished draining.\n", pStreamEx->Core.Cfg.szName)); + drvAudioStreamResetOnDisable(pStreamEx); } -#ifdef VBOX_WITH_STATISTICS - STAM_COUNTER_ADD(&pThis->Stats.TotalStreamsCreated, 1); -#endif - *ppStream = pStream; + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); } - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; + /* Update timestamps. */ + pStreamEx->nsLastIterated = RTTimeNanoTS(); - LogFlowFuncLeaveRC(rc); - return rc; + return VINF_SUCCESS; /** @todo r=bird: What can the caller do with an error status here? */ } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable} + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate} */ -static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable) +static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) { - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, rc); - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; + rc = drvAudioStreamIterateInternal(pThis, pStreamEx); - bool *pfEnabled; - if (enmDir == PDMAUDIODIR_IN) - pfEnabled = &pThis->In.fEnabled; - else if (enmDir == PDMAUDIODIR_OUT) - pfEnabled = &pThis->Out.fEnabled; - else - AssertFailedReturn(VERR_INVALID_PARAMETER); + RTCritSectLeave(&pStreamEx->Core.CritSect); - if (fEnable != *pfEnabled) - { - LogRel(("Audio: %s %s for driver '%s'\n", - fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName)); + if (RT_FAILURE(rc)) + LogFlowFuncLeaveRC(rc); + return rc; +} - PPDMAUDIOSTREAM pStream; - RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node) - { - if (pStream->enmDir != enmDir) /* Skip unwanted streams. */ - continue; - int rc2 = drvAudioStreamControlInternal(pThis, pStream, - fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE); - if (RT_FAILURE(rc2)) - { - if (rc2 == VERR_AUDIO_STREAM_NOT_READY) - { - LogRel(("Audio: Stream '%s' not available\n", pStream->szName)); - } - else - LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n", - fEnable ? "enable" : "disable", enmDir == PDMAUDIODIR_IN ? "input" : "output", pStream->szName, rc2)); - } +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetState} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTATE) drvAudioStreamGetState(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamEx, PDMAUDIOSTREAMSTATE_INVALID); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTATE_INVALID); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTATE_INVALID); + STAM_PROFILE_START(&pStreamEx->StatProfGetState, a); - if (RT_SUCCESS(rc)) - rc = rc2; + /* + * Get the status mask. + */ + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, PDMAUDIOSTREAMSTATE_INVALID); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx); + uint32_t const fStrmStatus = pStreamEx->fStatus; + PDMAUDIODIR const enmDir = pStreamEx->Core.Cfg.enmDir; + Assert(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT); - /* Keep going. */ - } + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + RTCritSectLeave(&pStreamEx->Core.CritSect); - *pfEnabled = fEnable; + /* + * Translate it to state enum value. + */ + PDMAUDIOSTREAMSTATE enmState; + if (!(fStrmStatus & PDMAUDIOSTREAM_STS_NEED_REINIT)) + { + if (fStrmStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED) + { + if ( (fStrmStatus & PDMAUDIOSTREAM_STS_ENABLED) + && (enmDir == PDMAUDIODIR_IN ? pThis->In.fEnabled : pThis->Out.fEnabled) + && ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY + || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING + || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_INITIALIZING )) + enmState = enmDir == PDMAUDIODIR_IN ? PDMAUDIOSTREAMSTATE_ENABLED_READABLE : PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE; + else + enmState = PDMAUDIOSTREAMSTATE_INACTIVE; + } + else + enmState = PDMAUDIOSTREAMSTATE_NOT_WORKING; } + else + enmState = PDMAUDIOSTREAMSTATE_NEED_REINIT; - int rc3 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc3; - - LogFlowFuncLeaveRC(rc); - return rc; + STAM_PROFILE_STOP(&pStreamEx->StatProfGetState, a); +#ifdef LOG_ENABLED + char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; +#endif + Log3Func(("[%s] returns %s (status: %s)\n", pStreamEx->Core.Cfg.szName, PDMAudioStreamStateGetName(enmState), + drvAudioStreamStatusToStr(szStreamSts, fStrmStatus))); + return enmState; } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled} + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable} */ -static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir) +static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) { - AssertPtrReturn(pInterface, false); + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamEx, 0); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0); + AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"), 0); + STAM_PROFILE_START(&pStreamEx->Out.Stats.ProfGetWritable, a); + + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, 0); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + + /* + * Use the playback and backend states to determin how much can be written, if anything. + */ + uint32_t cbWritable = 0; + DRVAUDIOPLAYSTATE const enmPlayMode = pStreamEx->Out.enmPlayState; + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); + if ( PDMAudioStrmStatusCanWrite(pStreamEx->fStatus) + && pThis->pHostDrvAudio != NULL + && enmBackendState != PDMHOSTAUDIOSTREAMSTATE_DRAINING) + { + switch (enmPlayMode) + { + /* + * Whatever the backend can hold. + */ + case DRVAUDIOPLAYSTATE_PLAY: + case DRVAUDIOPLAYSTATE_PLAY_PREBUF: + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY); + Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */); + cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend); + break; - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + /* + * Whatever we've got of available space in the pre-buffer. + * Note! For the last round when we pass the pre-buffering threshold, we may + * report fewer bytes than what a DMA timer period for the guest device + * typically produces, however that should be transfered in the following + * round that goes directly to the backend buffer. + */ + case DRVAUDIOPLAYSTATE_PREBUF: + cbWritable = pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered; + if (!cbWritable) + cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, 2); + break; - int rc2 = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc2)) - return false; + /* + * These are slightly more problematic and can go wrong if the pre-buffer is + * manually configured to be smaller than the output of a typeical DMA timer + * period for the guest device. So, to overcompensate, we just report back + * the backend buffer size (the pre-buffer is circular, so no overflow issue). + */ + case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: + case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: + cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, + RT_MAX(pStreamEx->Core.Cfg.Backend.cFramesBufferSize, + pStreamEx->Core.Cfg.Backend.cFramesPreBuffering)); + break; - bool *pfEnabled; - if (enmDir == PDMAUDIODIR_IN) - pfEnabled = &pThis->In.fEnabled; - else if (enmDir == PDMAUDIODIR_OUT) - pfEnabled = &pThis->Out.fEnabled; - else - AssertFailedReturn(false); + case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: + { + /* Buggy backend: We weren't able to copy all the pre-buffered data to it + when reaching the threshold. Try escape this situation, or at least + keep the extra buffering to a minimum. We must try write something + as long as there is space for it, as we need the pfnStreamWrite call + to move the data. */ + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY); + Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */); + uint32_t const cbMin = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, 8); + cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend); + if (cbWritable >= pStreamEx->Out.cbPreBuffered + cbMin) + cbWritable -= pStreamEx->Out.cbPreBuffered + cbMin / 2; + else + cbWritable = RT_MIN(cbMin, pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered); + AssertLogRel(cbWritable); + break; + } - const bool fIsEnabled = *pfEnabled; + case DRVAUDIOPLAYSTATE_NOPLAY: + break; + case DRVAUDIOPLAYSTATE_INVALID: + case DRVAUDIOPLAYSTATE_END: + AssertFailed(); + break; + } - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); + /* Make sure to align the writable size to the host's frame size. */ + cbWritable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbWritable); + } - return fIsEnabled; + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + STAM_PROFILE_ADD_PERIOD(&pStreamEx->Out.Stats.ProfGetWritableBytes, cbWritable); + STAM_PROFILE_STOP(&pStreamEx->Out.Stats.ProfGetWritable, a); + RTCritSectLeave(&pStreamEx->Core.CritSect); + Log3Func(("[%s] cbWritable=%#RX32 (%RU64ms) enmPlayMode=%s enmBackendState=%s\n", + pStreamEx->Core.Cfg.szName, cbWritable, PDMAudioPropsBytesToMilli(&pStreamEx->Core.Cfg.Props, cbWritable), + drvAudioPlayStateName(enmPlayMode), PDMHostAudioStreamStateGetName(enmBackendState) )); + return cbWritable; } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig} + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay} */ -static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg) +static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) { + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + + /* + * Check input and sanity. + */ AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + uint32_t uTmp; + if (pcbWritten) + AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER); + else + pcbWritten = &uTmp; - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, + ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n", + pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir)), VERR_ACCESS_DENIED); + + AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Core.Cfg.Props, cbBuf), + ("Stream '%s' got a non-frame-aligned write (%#RX32 bytes)\n", pStreamEx->Core.Cfg.szName, cbBuf)); + STAM_PROFILE_START(&pStreamEx->Out.Stats.ProfPlay, a); - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_FAILURE(rc)) - return rc; + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, rc); - if (pThis->pHostDrvAudio) + /* + * First check that we can write to the stream, and if not, + * whether to just drop the input into the bit bucket. + */ + if (PDMAudioStrmStatusIsReady(pStreamEx->fStatus)) { - if (pThis->pHostDrvAudio->pfnGetConfig) - rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg); - else - rc = VERR_NOT_SUPPORTED; - } - else - rc = VERR_PDM_NO_ATTACHED_DRIVER; + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + if ( pThis->Out.fEnabled /* (see @bugref{9882}) */ + && pThis->pHostDrvAudio != NULL) + { + /* + * Get the backend state and process changes to it since last time we checked. + */ + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx); - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; + /* + * Do the transfering. + */ + switch (pStreamEx->Out.enmPlayState) + { + case DRVAUDIOPLAYSTATE_PLAY: + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY); + Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY); + rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten); + break; - LogFlowFuncLeaveRC(rc); - return rc; -} + case DRVAUDIOPLAYSTATE_PLAY_PREBUF: + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY); + Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY); + rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten); + drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, *pcbWritten, pStreamEx->cbPreBufThreshold); + break; -/** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir) -{ - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + case DRVAUDIOPLAYSTATE_PREBUF: + if (cbBuf + pStreamEx->Out.cbPreBuffered < pStreamEx->cbPreBufThreshold) + rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten); + else if ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY + && (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY)) + { + Log3Func(("[%s] Pre-buffering completing: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x\n", + pStreamEx->Core.Cfg.szName, cbBuf, pStreamEx->Out.cbPreBuffered, + cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->cbPreBufThreshold)); + rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten); + } + else + { + Log3Func(("[%s] Pre-buffering completing but device not ready: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x; PREBUF -> PREBUF_OVERDUE\n", + pStreamEx->Core.Cfg.szName, cbBuf, pStreamEx->Out.cbPreBuffered, + cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->cbPreBufThreshold)); + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_OVERDUE; + rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten); + } + break; - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: + Assert( !(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY) + || enmBackendState != PDMHOSTAUDIOSTREAMSTATE_OKAY); + RT_FALL_THRU(); + case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: + rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten); + break; - PDMAUDIOBACKENDSTS backendSts = PDMAUDIOBACKENDSTS_UNKNOWN; + case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY); + Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY); + rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten); + break; - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_SUCCESS(rc)) - { - if (pThis->pHostDrvAudio) - { - if (pThis->pHostDrvAudio->pfnGetStatus) - backendSts = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir); + case DRVAUDIOPLAYSTATE_NOPLAY: + *pcbWritten = cbBuf; + pStreamEx->offInternal += cbBuf; + Log3Func(("[%s] Discarding the data, backend state: %s\n", pStreamEx->Core.Cfg.szName, + PDMHostAudioStreamStateGetName(enmBackendState) )); + break; + + default: + *pcbWritten = cbBuf; + AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->Out.enmPlayState, cbBuf)); + } + + if (!pStreamEx->Out.Dbg.pFilePlay || RT_FAILURE(rc)) + { /* likely */ } + else + AudioHlpFileWrite(pStreamEx->Out.Dbg.pFilePlay, pvBuf, *pcbWritten); } else - backendSts = PDMAUDIOBACKENDSTS_NOT_ATTACHED; - - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; + { + *pcbWritten = cbBuf; + pStreamEx->offInternal += cbBuf; + Log3Func(("[%s] Backend stream %s, discarding the data\n", pStreamEx->Core.Cfg.szName, + !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet")); + } + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); } + else + rc = VERR_AUDIO_STREAM_NOT_READY; - LogFlowFuncLeaveRC(rc); - return backendSts; + STAM_PROFILE_STOP(&pStreamEx->Out.Stats.ProfPlay, a); + RTCritSectLeave(&pStreamEx->Core.CritSect); + return rc; } + /** * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable} */ static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) { - AssertPtrReturn(pInterface, 0); - AssertPtrReturn(pStream, 0); - - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); - - int rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); - - AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n")); + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamEx, 0); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0); + AssertMsg(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n")); + STAM_PROFILE_START(&pStreamEx->In.Stats.ProfGetReadable, a); + + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, 0); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + /* + * Use the capture state to determin how much can be written, if anything. + */ uint32_t cbReadable = 0; - - if ( pThis->pHostDrvAudio - && DrvAudioHlpStreamStatusCanRead(pStream->fStatus)) + DRVAUDIOCAPTURESTATE const enmCaptureState = pStreamEx->In.enmCaptureState; + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); RT_NOREF(enmBackendState); + if ( PDMAudioStrmStatusCanRead(pStreamEx->fStatus) + && pThis->pHostDrvAudio != NULL) { - const uint32_t cfReadable = AudioMixBufLive(&pStream->Guest.MixBuf); - - cbReadable = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadable); - - if (!cbReadable) + switch (enmCaptureState) { /* - * If nothing is readable, check if the stream on the backend side is ready to be read from. - * If it isn't, return the number of bytes readable since the last read from this stream. - * - * This is needed for backends (e.g. VRDE) which do not provide any input data in certain - * situations, but the device emulation needs input data to keep the DMA transfers moving. - * Reading the actual data from a stream then will return silence then. + * Whatever the backend has to offer when in capture mode. + */ + case DRVAUDIOCAPTURESTATE_CAPTURING: + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY); + Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */); + cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend); + break; + + /* + * Same calculation as in drvAudioStreamCaptureSilence, only we cap it + * at the pre-buffering threshold so we don't get into trouble when we + * switch to capture mode between now and pfnStreamCapture. */ - if (!DrvAudioHlpStreamStatusCanRead( - pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend))) + case DRVAUDIOCAPTURESTATE_PREBUF: { - cbReadable = DrvAudioHlpNanoToBytes(RTTimeNanoTS() - pStream->tsLastReadWrittenNs, - &pStream->Host.Cfg.Props); - Log3Func(("[%s] Backend stream not ready, returning silence\n", pStream->szName)); + uint64_t const cNsStream = RTTimeNanoTS() - pStreamEx->nsStarted; + uint64_t const offCur = PDMAudioPropsNanoToBytes64(&pStreamEx->Core.Cfg.Props, cNsStream); + if (offCur > pStreamEx->offInternal) + { + uint64_t const cbUnread = offCur - pStreamEx->offInternal; + cbReadable = (uint32_t)RT_MIN(pStreamEx->cbPreBufThreshold, cbUnread); + } + break; } + + case DRVAUDIOCAPTURESTATE_NO_CAPTURE: + break; + + case DRVAUDIOCAPTURESTATE_INVALID: + case DRVAUDIOCAPTURESTATE_END: + AssertFailed(); + break; } - /* Make sure to align the readable size to the guest's frame size. */ - cbReadable = DrvAudioHlpBytesAlign(cbReadable, &pStream->Guest.Cfg.Props); + /* Make sure to align the readable size to the host's frame size. */ + cbReadable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbReadable); } - Log3Func(("[%s] cbReadable=%RU32 (%RU64ms)\n", - pStream->szName, cbReadable, DrvAudioHlpBytesToMilli(cbReadable, &pStream->Host.Cfg.Props))); + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + STAM_PROFILE_ADD_PERIOD(&pStreamEx->In.Stats.ProfGetReadableBytes, cbReadable); + STAM_PROFILE_STOP(&pStreamEx->In.Stats.ProfGetReadable, a); + RTCritSectLeave(&pStreamEx->Core.CritSect); + Log3Func(("[%s] cbReadable=%#RX32 (%RU64ms) enmCaptureMode=%s enmBackendState=%s\n", + pStreamEx->Core.Cfg.szName, cbReadable, PDMAudioPropsBytesToMilli(&pStreamEx->Core.Cfg.Props, cbReadable), + drvAudioCaptureStateName(enmCaptureState), PDMHostAudioStreamStateGetName(enmBackendState) )); + return cbReadable; +} - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); - /* Return bytes instead of audio frames. */ - return cbReadable; +/** + * Worker for drvAudioStreamCapture that returns silence. + * + * The amount of silence returned is a function of how long the stream has been + * enabled. + * + * @returns VINF_SUCCESS + * @param pStreamEx The stream to commit the pre-buffering for. + * @param pbBuf The output buffer. + * @param cbBuf The size of the output buffer. + * @param pcbRead Where to return the number of bytes actually read. + */ +static int drvAudioStreamCaptureSilence(PDRVAUDIOSTREAM pStreamEx, uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + /** @todo Does not take paused time into account... */ + uint64_t const cNsStream = RTTimeNanoTS() - pStreamEx->nsStarted; + uint64_t const offCur = PDMAudioPropsNanoToBytes64(&pStreamEx->Core.Cfg.Props, cNsStream); + if (offCur > pStreamEx->offInternal) + { + uint64_t const cbUnread = offCur - pStreamEx->offInternal; + uint32_t const cbToClear = (uint32_t)RT_MIN(cbBuf, cbUnread); + *pcbRead = cbToClear; + pStreamEx->offInternal += cbToClear; + cbBuf -= cbToClear; + PDMAudioPropsClearBuffer(&pStreamEx->Core.Cfg.Props, pbBuf, cbToClear, + PDMAudioPropsBytesToFrames(&pStreamEx->Core.Cfg.Props, cbToClear)); + } + else + *pcbRead = 0; + Log4Func(("%s: @%#RX64: Read %#x bytes of silence (%#x bytes left)\n", + pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, *pcbRead, cbBuf)); + return VINF_SUCCESS; } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable} + * Worker for drvAudioStreamCapture. */ -static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +static int drvAudioStreamCaptureLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, + uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbRead) { - AssertPtrReturn(pInterface, 0); - AssertPtrReturn(pStream, 0); + Log4Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbBuf)); - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); - - int rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); - - AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n")); - - uint32_t cbWritable = 0; + uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend); + pStreamEx->In.Stats.cbBackendReadableBefore = cbReadable; - /* Note: We don't propage the backend stream's status to the outside -- it's the job of this - * audio connector to make sense of it. */ - if (DrvAudioHlpStreamStatusCanWrite(pStream->fStatus)) + uint32_t cbRead = 0; + int rc = VINF_SUCCESS; + uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Cfg.Props); + while (cbBuf >= cbFrame && cbReadable >= cbFrame) { - cbWritable = AudioMixBufFreeBytes(&pStream->Host.MixBuf); + uint32_t const cbToRead = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, RT_MIN(cbBuf, cbReadable)); + uint32_t cbReadNow = 0; + rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToRead, &cbReadNow); + if (RT_SUCCESS(rc)) + { + if (cbReadNow != cbToRead) + Log4Func(("%s: @%#RX64: Read fewer bytes than requested: %#x, requested %#x\n", + pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbReadNow, cbToRead)); +#ifdef DEBUG_bird + Assert(cbReadNow == cbToRead); +#endif + AssertStmt(cbReadNow <= cbToRead, cbReadNow = cbToRead); + cbRead += cbReadNow; + cbBuf -= cbReadNow; + pbBuf += cbReadNow; + pStreamEx->offInternal += cbReadNow; + } + else + { + *pcbRead = cbRead; + LogFunc(("%s: @%#RX64: pfnStreamCapture failed read %#x bytes (%#x previous read, %#x readable): %Rrc\n", + pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbToRead, cbRead, cbReadable, rc)); + return cbRead ? VINF_SUCCESS : rc; + } - /* Make sure to align the writable size to the host's frame size. */ - cbWritable = DrvAudioHlpBytesAlign(cbWritable, &pStream->Host.Cfg.Props); + cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend); } - Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n", - pStream->szName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pStream->Host.Cfg.Props))); + STAM_PROFILE_ADD_PERIOD(&pStreamEx->StatXfer, cbRead); + *pcbRead = cbRead; + pStreamEx->In.Stats.cbBackendReadableAfter = cbReadable; + if (cbRead) + pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS(); - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); - - return cbWritable; + Log4Func(("%s: @%#RX64: Read %#x bytes (%#x bytes left)\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbRead, cbBuf)); + return rc; } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus} + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture} */ -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) { - AssertPtrReturn(pInterface, false); + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); + AssertPtr(pThis); - if (!pStream) - return PDMAUDIOSTREAMSTS_FLAGS_NONE; + /* + * Check input and sanity. + */ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + uint32_t uTmp; + if (pcbRead) + AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER); + else + pcbRead = &uTmp; + + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN, + ("Stream '%s' is not an input stream and therefore cannot be read from (direction is '%s')\n", + pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir)), VERR_ACCESS_DENIED); + + AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Core.Cfg.Props, cbBuf), + ("Stream '%s' got a non-frame-aligned write (%#RX32 bytes)\n", pStreamEx->Core.Cfg.szName, cbBuf)); + STAM_PROFILE_START(&pStreamEx->In.Stats.ProfCapture, a); + + int rc = RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertRCReturn(rc, rc); + + /* + * First check that we can read from the stream, and if not, + * whether to just drop the input into the bit bucket. + */ + if (PDMAudioStrmStatusIsReady(pStreamEx->fStatus)) + { + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + if ( pThis->In.fEnabled /* (see @bugref{9882}) */ + && pThis->pHostDrvAudio != NULL) + { + /* + * Get the backend state and process changes to it since last time we checked. + */ + PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx); - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + /* + * Do the transfering. + */ + switch (pStreamEx->In.enmCaptureState) + { + case DRVAUDIOCAPTURESTATE_CAPTURING: + Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY); + Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY); + rc = drvAudioStreamCaptureLocked(pThis, pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead); + break; - int rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); + case DRVAUDIOCAPTURESTATE_PREBUF: + if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY) + { + uint32_t const cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, + pStreamEx->pBackend); + if (cbReadable >= pStreamEx->cbPreBufThreshold) + { + Log4Func(("[%s] Pre-buffering completed: cbReadable=%#x vs cbPreBufThreshold=%#x (cbBuf=%#x)\n", + pStreamEx->Core.Cfg.szName, cbReadable, pStreamEx->cbPreBufThreshold, cbBuf)); + pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_CAPTURING; + rc = drvAudioStreamCaptureLocked(pThis, pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead); + break; + } + pStreamEx->In.Stats.cbBackendReadableBefore = cbReadable; + pStreamEx->In.Stats.cbBackendReadableAfter = cbReadable; + Log4Func(("[%s] Pre-buffering: Got %#x out of %#x\n", + pStreamEx->Core.Cfg.szName, cbReadable, pStreamEx->cbPreBufThreshold)); + } + else + Log4Func(("[%s] Pre-buffering: Backend status %s\n", + pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmBackendState) )); + drvAudioStreamCaptureSilence(pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead); + break; - /* Is the stream scheduled for re-initialization? Do so now. */ - int rc = drvAudioStreamMaybeReInit(pThis, pStream); - if (RT_FAILURE(rc)) /** @todo r=aeichner What is the correct operation in the failure case? */ - LogRel(("Audio: Reinitializing the stream in drvAudioStreamGetStatus() failed with %Rrc\n", rc)); + case DRVAUDIOCAPTURESTATE_NO_CAPTURE: + *pcbRead = 0; + Log4Func(("[%s] Not capturing - backend state: %s\n", + pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmBackendState) )); + break; - PDMAUDIOSTREAMSTS fStrmStatus = pStream->fStatus; + default: + *pcbRead = 0; + AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->In.enmCaptureState, cbBuf)); + } -#ifdef LOG_ENABLED - char *pszStreamSts = dbgAudioStreamStatusToStr(fStrmStatus); - Log3Func(("[%s] %s\n", pStream->szName, pszStreamSts)); - RTStrFree(pszStreamSts); -#endif + if (!pStreamEx->In.Dbg.pFileCapture || RT_FAILURE(rc)) + { /* likely */ } + else + AudioHlpFileWrite(pStreamEx->In.Dbg.pFileCapture, pvBuf, *pcbRead); + } + else + { + *pcbRead = 0; + Log4Func(("[%s] Backend stream %s, returning no data\n", pStreamEx->Core.Cfg.szName, + !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet")); + } + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + } + else + rc = VERR_AUDIO_STREAM_NOT_READY; + + STAM_PROFILE_STOP(&pStreamEx->In.Stats.ProfCapture, a); + RTCritSectLeave(&pStreamEx->Core.CritSect); + return rc; +} + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIOPORT interface implementation. * +*********************************************************************************************************************************/ + +/** + * Worker for drvAudioHostPort_DoOnWorkerThread with stream argument, called on + * worker thread. + */ +static DECLCALLBACK(void) drvAudioHostPort_DoOnWorkerThreadStreamWorker(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, + uintptr_t uUser, void *pvUser) +{ + LogFlowFunc(("pThis=%p uUser=%#zx pvUser=%p\n", pThis, uUser, pvUser)); + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pStreamEx); + AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC); - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); + /* + * The CritSectHotPlug lock should not be needed here as detach will destroy + * the thread pool. So, we'll leave taking the stream lock to the worker we're + * calling as there are no lock order concerns. + */ + PPDMIHOSTAUDIO const pIHostDrvAudio = pThis->pHostDrvAudio; + AssertPtrReturnVoid(pIHostDrvAudio); + AssertPtrReturnVoid(pIHostDrvAudio->pfnDoOnWorkerThread); + pIHostDrvAudio->pfnDoOnWorkerThread(pIHostDrvAudio, pStreamEx->pBackend, uUser, pvUser); - return fStrmStatus; + drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/); + LogFlowFunc(("returns\n")); } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume} + * Worker for drvAudioHostPort_DoOnWorkerThread without stream argument, called + * on worker thread. + * + * This wrapper isn't technically required, but it helps with logging and a few + * extra sanity checks. */ -static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol) +static DECLCALLBACK(void) drvAudioHostPort_DoOnWorkerThreadWorker(PDRVAUDIO pThis, uintptr_t uUser, void *pvUser) { - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pVol, VERR_INVALID_POINTER); + LogFlowFunc(("pThis=%p uUser=%#zx pvUser=%p\n", pThis, uUser, pvUser)); + AssertPtrReturnVoid(pThis); - LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStream->szName, pVol->uLeft, pVol->uRight, pVol->fMuted)); + /* + * The CritSectHotPlug lock should not be needed here as detach will destroy + * the thread pool. + */ + PPDMIHOSTAUDIO const pIHostDrvAudio = pThis->pHostDrvAudio; + AssertPtrReturnVoid(pIHostDrvAudio); + AssertPtrReturnVoid(pIHostDrvAudio->pfnDoOnWorkerThread); - AudioMixBufSetVolume(&pStream->Guest.MixBuf, pVol); - AudioMixBufSetVolume(&pStream->Host.MixBuf, pVol); + pIHostDrvAudio->pfnDoOnWorkerThread(pIHostDrvAudio, NULL, uUser, pvUser); - return VINF_SUCCESS; + LogFlowFunc(("returns\n")); } + /** - * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy} + * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnDoOnWorkerThread} */ -static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +static DECLCALLBACK(int) drvAudioHostPort_DoOnWorkerThread(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream, + uintptr_t uUser, void *pvUser) { - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); - - int rc = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc); - - LogRel2(("Audio: Destroying stream '%s'\n", pStream->szName)); - - LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs)); - if (pStream->cRefs > 1) - rc = VERR_WRONG_ORDER; + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort); + LogFlowFunc(("pStream=%p uUser=%#zx pvUser=%p\n", pStream, uUser, pvUser)); - if (RT_SUCCESS(rc)) + /* + * Assert some sanity. + */ + PDRVAUDIOSTREAM pStreamEx; + if (!pStream) + pStreamEx = NULL; + else { - rc = drvAudioStreamUninitInternal(pThis, pStream); - if (RT_FAILURE(rc)) - LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStream->szName, rc)); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INVALID_MAGIC); + pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream; + AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); + AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); } - if (RT_SUCCESS(rc)) + int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + AssertRCReturn(rc, rc); + + Assert(pThis->hReqPool != NIL_RTREQPOOL); + AssertPtr(pThis->pHostDrvAudio); + if ( pThis->hReqPool != NIL_RTREQPOOL + && pThis->pHostDrvAudio != NULL) { - if (pStream->enmDir == PDMAUDIODIR_IN) - { - pThis->In.cStreamsFree++; - } - else /* Out */ + AssertPtr(pThis->pHostDrvAudio->pfnDoOnWorkerThread); + if (pThis->pHostDrvAudio->pfnDoOnWorkerThread) { - pThis->Out.cStreamsFree++; + /* + * Try do the work. + */ + if (!pStreamEx) + { + rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL /*phReq*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvAudioHostPort_DoOnWorkerThreadWorker, 3, pThis, uUser, pvUser); + AssertRC(rc); + } + else + { + uint32_t cRefs = drvAudioStreamRetainInternal(pStreamEx); + if (cRefs != UINT32_MAX) + { + rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvAudioHostPort_DoOnWorkerThreadStreamWorker, + 4, pThis, pStreamEx, uUser, pvUser); + AssertRC(rc); + if (RT_FAILURE(rc)) + { + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + } + } + else + rc = VERR_INVALID_PARAMETER; + } } - - RTListNodeRemove(&pStream->Node); - - drvAudioStreamFree(pStream); - pStream = NULL; + else + rc = VERR_INVALID_FUNCTION; } + else + rc = VERR_INVALID_STATE; - int rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; - - LogFlowFuncLeaveRC(rc); + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + LogFlowFunc(("returns %Rrc\n", rc)); return rc; } + /** - * Creates an audio stream on the backend side. - * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Audio stream to create the backend side for. - * @param pCfgReq Requested audio stream configuration to use for stream creation. - * @param pCfgAcq Acquired audio stream configuration returned by the backend. - * - * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set): - * - per global extra-data - * - per-VM extra-data - * - requested configuration (by pCfgReq) - * - default value + * Marks a stream for re-init. */ -static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, - PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +static void drvAudioStreamMarkNeedReInit(PDRVAUDIOSTREAM pStreamEx, const char *pszCaller) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - AssertMsg((pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED) == 0, - ("Stream '%s' already initialized in backend\n", pStream->szName)); + LogFlow((LOG_FN_FMT ": Flagging %s for re-init.\n", pszCaller, pStreamEx->Core.Cfg.szName)); RT_NOREF(pszCaller); + Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect)); - /* Get the right configuration for the stream to be created. */ - PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg; + pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_NEED_REINIT; + PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus); + pStreamEx->cTriesReInit = 0; + pStreamEx->nsLastReInit = 0; +} - /* Fill in the tweakable parameters into the requested host configuration. - * All parameters in principle can be changed and returned by the backend via the acquired configuration. */ - char szWhat[64]; /* Log where a value came from. */ +/** + * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnNotifyDeviceChanged} + */ +static DECLCALLBACK(void) drvAudioHostPort_NotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser) +{ + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort); + AssertReturnVoid(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT); + LogRel(("Audio: The %s device for %s is changing.\n", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->BackendCfg.szName)); /* - * Period size + * Grab the list lock in shared mode and do the work. */ - if (pDrvCfg->uPeriodSizeMs) - { - pCfgReq->Backend.cFramesPeriod = DrvAudioHlpMilliToFrames(pDrvCfg->uPeriodSizeMs, &pCfgReq->Props); - RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM"); - } + int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals); + AssertRCReturnVoid(rc); - if (!pCfgReq->Backend.cFramesPeriod) /* Set default period size if nothing explicitly is set. */ + PDRVAUDIOSTREAM pStreamEx; + RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry) { - pCfgReq->Backend.cFramesPeriod = DrvAudioHlpMilliToFrames(50 /* ms */, &pCfgReq->Props); - RTStrPrintf(szWhat, sizeof(szWhat), "default"); - } - else - RTStrPrintf(szWhat, sizeof(szWhat), "device-specific"); + if (pStreamEx->Core.Cfg.enmDir == enmDir) + { + RTCritSectEnter(&pStreamEx->Core.CritSect); + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); - LogRel2(("Audio: Using %s period size (%RU64ms, %RU32 frames) for stream '%s'\n", - szWhat, - DrvAudioHlpFramesToMilli(pCfgReq->Backend.cFramesPeriod, &pCfgReq->Props), pCfgReq->Backend.cFramesPeriod, pStream->szName)); + if (pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged) + { + LogFlowFunc(("Calling pfnStreamNotifyDeviceChanged on %s, old backend state: %s...\n", pStreamEx->Core.Cfg.szName, + PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)) )); + pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged(pThis->pHostDrvAudio, pStreamEx->pBackend, pvUser); + LogFlowFunc(("New stream backend state: %s\n", + PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)) )); + } + else + drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__); - /* - * Buffer size - */ - if (pDrvCfg->uBufferSizeMs) - { - pCfgReq->Backend.cFramesBufferSize = DrvAudioHlpMilliToFrames(pDrvCfg->uBufferSizeMs, &pCfgReq->Props); - RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM"); + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + RTCritSectLeave(&pStreamEx->Core.CritSect); + } } - if (!pCfgReq->Backend.cFramesBufferSize) /* Set default buffer size if nothing explicitly is set. */ - { - pCfgReq->Backend.cFramesBufferSize = DrvAudioHlpMilliToFrames(250 /* ms */, &pCfgReq->Props); - RTStrPrintf(szWhat, sizeof(szWhat), "default"); - } - else - RTStrPrintf(szWhat, sizeof(szWhat), "device-specific"); + RTCritSectRwLeaveShared(&pThis->CritSectGlobals); +} + - LogRel2(("Audio: Using %s buffer size (%RU64ms, %RU32 frames) for stream '%s'\n", - szWhat, - DrvAudioHlpFramesToMilli(pCfgReq->Backend.cFramesBufferSize, &pCfgReq->Props), pCfgReq->Backend.cFramesBufferSize, pStream->szName)); +/** + * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnStreamNotifyPreparingDeviceSwitch} + */ +static DECLCALLBACK(void) drvAudioHostPort_StreamNotifyPreparingDeviceSwitch(PPDMIHOSTAUDIOPORT pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); /* - * Pre-buffering size + * Backend stream to validated DrvAudio stream: */ - if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */ - { - pCfgReq->Backend.cFramesPreBuffering = DrvAudioHlpMilliToFrames(pDrvCfg->uPreBufSizeMs, &pCfgReq->Props); - RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM"); - } - else /* No, then either use the default or device-specific settings (if any). */ + AssertPtrReturnVoid(pStream); + AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream; + AssertPtrReturnVoid(pStreamEx); + AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC); + AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC); + LogFlowFunc(("pStreamEx=%p '%s'\n", pStreamEx, pStreamEx->Core.Cfg.szName)); + + /* + * Grab the lock and do switch the state (only needed for output streams for now). + */ + RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pStreamEx->Core.CritSect)); /* paranoia */ + + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT) { - if (pCfgReq->Backend.cFramesPreBuffering == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */ + if (pStreamEx->cbPreBufThreshold > 0) { - /* For pre-buffering to finish the buffer at least must be full one time. */ - pCfgReq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesBufferSize; - RTStrPrintf(szWhat, sizeof(szWhat), "default"); + DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState; + switch (enmPlayState) + { + case DRVAUDIOPLAYSTATE_PREBUF: + case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: + case DRVAUDIOPLAYSTATE_NOPLAY: + case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: /* simpler */ + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_SWITCHING; + break; + case DRVAUDIOPLAYSTATE_PLAY: + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY_PREBUF; + break; + case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: + case DRVAUDIOPLAYSTATE_PLAY_PREBUF: + break; + /* no default */ + case DRVAUDIOPLAYSTATE_END: + case DRVAUDIOPLAYSTATE_INVALID: + break; + } + LogFunc(("%s -> %s\n", drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); } else - RTStrPrintf(szWhat, sizeof(szWhat), "device-specific"); + LogFunc(("No pre-buffering configured.\n")); } + else + LogFunc(("input stream, nothing to do.\n")); + + RTCritSectLeave(&pStreamEx->Core.CritSect); +} + - LogRel2(("Audio: Using %s pre-buffering size (%RU64ms, %RU32 frames) for stream '%s'\n", - szWhat, - DrvAudioHlpFramesToMilli(pCfgReq->Backend.cFramesPreBuffering, &pCfgReq->Props), pCfgReq->Backend.cFramesPreBuffering, pStream->szName)); +/** + * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnStreamNotifyDeviceChanged} + */ +static DECLCALLBACK(void) drvAudioHostPort_StreamNotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, + PPDMAUDIOBACKENDSTREAM pStream, bool fReInit) +{ + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort); /* - * Validate input. + * Backend stream to validated DrvAudio stream: */ - if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPeriod) - { - LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n", - pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cFramesBufferSize, &pCfgReq->Props), - DrvAudioHlpFramesToMilli(pCfgReq->Backend.cFramesPeriod, &pCfgReq->Props))); - return VERR_INVALID_PARAMETER; - } + AssertPtrReturnVoid(pStream); + AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC); + PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream; + AssertPtrReturnVoid(pStreamEx); + AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC); + AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC); + + /* + * Grab the lock and do the requested work. + */ + RTCritSectEnter(&pStreamEx->Core.CritSect); + AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pStreamEx->Core.CritSect)); /* paranoia */ - if ( pCfgReq->Backend.cFramesPreBuffering != UINT32_MAX /* Custom pre-buffering set? */ - && pCfgReq->Backend.cFramesPreBuffering) + if (fReInit) + drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__); + else { - if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPreBuffering) + /* + * Adjust the stream state now that the device has (perhaps finally) been switched. + * + * For enabled output streams, we must update the play state. We could try commit + * pre-buffered data here, but it's really not worth the hazzle and risk (don't + * know which thread we're on, do we now). + */ + AssertStmt(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT), + pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT); + + + if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT) { - LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n", - pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cFramesPreBuffering, &pCfgReq->Props), - DrvAudioHlpFramesToMilli(pCfgReq->Backend.cFramesBufferSize, &pCfgReq->Props))); - return VERR_INVALID_PARAMETER; + DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState; + pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF; + LogFunc(("%s: %s -> %s\n", pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(enmPlayState), + drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); + RT_NOREF(enmPlayState); } - } - /* Make the acquired host configuration the requested host configuration initially, - * in case the backend does not report back an acquired configuration. */ - int rc = DrvAudioHlpStreamCfgCopy(pCfgAcq, pCfgReq); - if (RT_FAILURE(rc)) - { - LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n", - pStream->szName)); - return rc; + /* Disable and then fully resync. */ + /** @todo This doesn't work quite reliably if we're in draining mode + * (PENDING_DISABLE, so the backend needs to take care of that prior to calling + * us. Sigh. The idea was to avoid extra state mess in the backend... */ + drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "device changed"); } - rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStream->pvBackend, pCfgReq, pCfgAcq); - if (RT_FAILURE(rc)) - { - if (rc == VERR_NOT_SUPPORTED) - LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStream->szName)); - else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE) - LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", pStream->szName)); - else - LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStream->szName, rc)); + RTCritSectLeave(&pStreamEx->Core.CritSect); +} - return rc; - } - /* Validate acquired configuration. */ - if (!DrvAudioHlpStreamCfgIsValid(pCfgAcq)) - { - LogRel(("Audio: Creating stream '%s' returned an invalid backend configuration, skipping\n", pStream->szName)); - return VERR_INVALID_PARAMETER; +#ifdef VBOX_WITH_AUDIO_ENUM +/** + * @callback_method_impl{FNTMTIMERDRV, Re-enumerate backend devices.} + * + * Used to do/trigger re-enumeration of backend devices with a delay after we + * got notification as there can be further notifications following shortly + * after the first one. Also good to get it of random COM/whatever threads. + */ +static DECLCALLBACK(void) drvAudioEnumerateTimer(PPDMDRVINS pDrvIns, PTMTIMER pTimer, void *pvUser) +{ + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + RT_NOREF(pTimer, pvUser); + + /* Try push the work over to the thread-pool if we've got one. */ + RTCritSectRwEnterShared(&pThis->CritSectHotPlug); + if (pThis->hReqPool != NIL_RTREQPOOL) + { + int rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvAudioDevicesEnumerateInternal, + 3, pThis, true /*fLog*/, (PPDMAUDIOHOSTENUM)NULL /*pDevEnum*/); + LogFunc(("RTReqPoolCallEx: %Rrc\n", rc)); + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); + if (RT_SUCCESS(rc)) + return; } + else + RTCritSectRwLeaveShared(&pThis->CritSectHotPlug); - /* Let the user know that the backend changed one of the values requested above. */ - if (pCfgAcq->Backend.cFramesBufferSize != pCfgReq->Backend.cFramesBufferSize) - LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", - pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cFramesBufferSize, &pCfgAcq->Props), pCfgAcq->Backend.cFramesBufferSize)); - - if (pCfgAcq->Backend.cFramesPeriod != pCfgReq->Backend.cFramesPeriod) - LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", - pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cFramesPeriod, &pCfgAcq->Props), pCfgAcq->Backend.cFramesPeriod)); + LogFunc(("Calling drvAudioDevicesEnumerateInternal...\n")); + drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */); +} +#endif /* VBOX_WITH_AUDIO_ENUM */ - /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */ - if ( pCfgReq->Backend.cFramesPreBuffering - && pCfgAcq->Backend.cFramesPreBuffering != pCfgReq->Backend.cFramesPreBuffering) - { - LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", - pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cFramesPreBuffering, &pCfgAcq->Props), pCfgAcq->Backend.cFramesPreBuffering)); - } - else if (pCfgReq->Backend.cFramesPreBuffering == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */ - { - LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pStream->szName)); - pCfgAcq->Backend.cFramesPreBuffering = 0; + +/** + * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnNotifyDevicesChanged} + */ +static DECLCALLBACK(void) drvAudioHostPort_NotifyDevicesChanged(PPDMIHOSTAUDIOPORT pInterface) +{ + PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort); + LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->BackendCfg.szName)); + +#ifdef RT_OS_DARWIN /** @todo Remove legacy behaviour: */ + /* Mark all host streams to re-initialize. */ + int rc2 = RTCritSectRwEnterShared(&pThis->CritSectGlobals); + AssertRCReturnVoid(rc2); + PDRVAUDIOSTREAM pStreamEx; + RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry) + { + RTCritSectEnter(&pStreamEx->Core.CritSect); + drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__); + RTCritSectLeave(&pStreamEx->Core.CritSect); } + RTCritSectRwLeaveShared(&pThis->CritSectGlobals); +#endif - /* Sanity for detecting buggy backends. */ - AssertMsgReturn(pCfgAcq->Backend.cFramesPeriod < pCfgAcq->Backend.cFramesBufferSize, - ("Acquired period size must be smaller than buffer size\n"), - VERR_INVALID_PARAMETER); - AssertMsgReturn(pCfgAcq->Backend.cFramesPreBuffering <= pCfgAcq->Backend.cFramesBufferSize, - ("Acquired pre-buffering size must be smaller or as big as the buffer size\n"), - VERR_INVALID_PARAMETER); +#ifdef VBOX_WITH_AUDIO_ENUM + /* + * Re-enumerate all host devices with a tiny delay to avoid re-doing this + * when a bunch of changes happens at once (they typically do on windows). + * We'll keep postponing it till it quiesces for a fraction of a second. + */ + int rc = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hEnumTimer, RT_MS_1SEC / 3); + AssertRC(rc); +#endif +} - pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED; - return VINF_SUCCESS; +/********************************************************************************************************************************* +* PDMIBASE interface implementation. * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID)); + + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIOPORT, &pThis->IHostAudioPort); + + return NULL; } + +/********************************************************************************************************************************* +* PDMDRVREG interface implementation. * +*********************************************************************************************************************************/ + /** - * Calls the backend to give it the chance to destroy its part of the audio stream. + * Power Off notification. * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Audio stream destruct backend for. + * @param pDrvIns The driver instance data. */ -static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - int rc = VINF_SUCCESS; + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); -#ifdef LOG_ENABLED - char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); - LogFunc(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts)); -#endif + LogFlowFuncEnter(); - if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED) + /** @todo locking? */ + if (pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */ { - AssertPtr(pStream->pvBackend); + /* + * Just destroy the host stream on the backend side. + * The rest will either be destructed by the device emulation or + * in drvAudioDestruct(). + */ + int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals); + AssertRCReturnVoid(rc); - /* Check if the pointer to the host audio driver is still valid. - * It can be NULL if we were called in drvAudioDestruct, for example. */ - if (pThis->pHostDrvAudio) - rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStream->pvBackend); + PDRVAUDIOSTREAM pStreamEx; + RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry) + { + RTCritSectEnter(&pStreamEx->Core.CritSect); + drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); + RTCritSectLeave(&pStreamEx->Core.CritSect); + } - pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED; + RTCritSectRwLeaveShared(&pThis->CritSectGlobals); } -#ifdef LOG_ENABLED - RTStrFree(pszStreamSts); -#endif - - LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc)); - return rc; + LogFlowFuncLeave(); } + /** - * Uninitializes an audio stream. + * Detach notification. * - * @returns IPRT status code. - * @param pThis Pointer to driver instance. - * @param pStream Pointer to audio stream to uninitialize. + * @param pDrvIns The driver instance data. + * @param fFlags Detach flags. */ -static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags) { - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + RT_NOREF(fFlags); - LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs)); + int rc = RTCritSectRwEnterExcl(&pThis->CritSectHotPlug); + AssertLogRelRCReturnVoid(rc); - AssertMsgReturn(pStream->cRefs <= 1, - ("Stream '%s' still has %RU32 references held when uninitializing\n", pStream->szName, pStream->cRefs), - VERR_WRONG_ORDER); + LogFunc(("%s (detached %p, hReqPool=%p)\n", pThis->BackendCfg.szName, pThis->pHostDrvAudio, pThis->hReqPool)); - int rc = drvAudioStreamControlInternal(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); - if (RT_SUCCESS(rc)) - rc = drvAudioStreamDestroyInternalBackend(pThis, pStream); + /* + * Must first destroy the thread pool first so we are certain no threads + * are still using the instance being detached. Release lock while doing + * this as the thread functions may need to take it to complete. + */ + if (pThis->pHostDrvAudio && pThis->hReqPool != NIL_RTREQPOOL) + { + RTREQPOOL hReqPool = pThis->hReqPool; + pThis->hReqPool = NIL_RTREQPOOL; - /* Destroy mixing buffers. */ - AudioMixBufDestroy(&pStream->Guest.MixBuf); - AudioMixBufDestroy(&pStream->Host.MixBuf); + RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug); - if (RT_SUCCESS(rc)) - { -#ifdef LOG_ENABLED - if (pStream->fStatus != PDMAUDIOSTREAMSTS_FLAGS_NONE) - { - char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); - LogFunc(("[%s] Warning: Still has %s set when uninitializing\n", pStream->szName, pszStreamSts)); - RTStrFree(pszStreamSts); - } -#endif - pStream->fStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE; + RTReqPoolRelease(hReqPool); + + RTCritSectRwEnterExcl(&pThis->CritSectHotPlug); } - if (pStream->enmDir == PDMAUDIODIR_IN) + /* + * Now we can safely set pHostDrvAudio to NULL. + */ + pThis->pHostDrvAudio = NULL; + + RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug); +} + + +/** + * Initializes the host backend and queries its initial configuration. + * + * @returns VBox status code. + * @param pThis Driver instance to be called. + */ +static int drvAudioHostInit(PDRVAUDIO pThis) +{ + LogFlowFuncEnter(); + + /* + * Check the function pointers, make sure the ones we define as + * mandatory are present. + */ + PPDMIHOSTAUDIO pIHostDrvAudio = pThis->pHostDrvAudio; + AssertPtrReturn(pIHostDrvAudio, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnSetDevice, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnDoOnWorkerThread, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnStreamConfigHint, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnStreamInitAsync, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnStreamNotifyDeviceChanged, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamEnable, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamDisable, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamPause, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamResume, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnStreamDrain, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER); + AssertPtrNullReturn(pIHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamGetState, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER); + AssertPtrReturn(pIHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER); + + /* + * Get the backend configuration. + * + * Note! Limit the number of streams to max 128 in each direction to + * prevent wasting resources. + * Note! Take care not to wipe the DriverName config value on failure. + */ + PDMAUDIOBACKENDCFG BackendCfg; + RT_ZERO(BackendCfg); + int rc = pIHostDrvAudio->pfnGetConfig(pIHostDrvAudio, &BackendCfg); + if (RT_SUCCESS(rc)) { -#ifdef VBOX_WITH_STATISTICS - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalFramesCaptured); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalTimesCaptured); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalFramesRead); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalTimesRead); -#endif - if (pThis->In.Cfg.Dbg.fEnabled) - { - DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileCaptureNonInterleaved); - pStream->In.Dbg.pFileCaptureNonInterleaved = NULL; + if (LogIsEnabled() && strcmp(BackendCfg.szName, pThis->BackendCfg.szName) != 0) + LogFunc(("BackendCfg.szName: '%s' -> '%s'\n", pThis->BackendCfg.szName, BackendCfg.szName)); + pThis->BackendCfg = BackendCfg; + pThis->In.cStreamsFree = RT_MIN(BackendCfg.cMaxStreamsIn, 128); + pThis->Out.cStreamsFree = RT_MIN(BackendCfg.cMaxStreamsOut, 128); - DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileStreamRead); - pStream->In.Dbg.pFileStreamRead = NULL; - } + LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree)); } - else if (pStream->enmDir == PDMAUDIODIR_OUT) + else { -#ifdef VBOX_WITH_STATISTICS - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesPlayed); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesPlayed); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesWritten); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesWritten); + LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->BackendCfg.szName, rc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + + LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n", + pThis->BackendCfg.szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree)); + +#ifdef VBOX_WITH_AUDIO_ENUM + int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */); + if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */ + AssertRC(rc2); + /* Ignore rc2. */ #endif - if (pThis->Out.Cfg.Dbg.fEnabled) - { - DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFilePlayNonInterleaved); - pStream->Out.Dbg.pFilePlayNonInterleaved = NULL; - DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFileStreamWrite); - pStream->Out.Dbg.pFileStreamWrite = NULL; - } + /* + * Create a thread pool if stream creation can be asynchronous. + * + * The pool employs no pushback as the caller is typically EMT and + * shouldn't be delayed. + * + * The number of threads limits and the device implementations use + * of pfnStreamDestroy limits the number of streams pending async + * init. We use RTReqCancel in drvAudioStreamDestroy to allow us + * to release extra reference held by the pfnStreamInitAsync call + * if successful. Cancellation will only be possible if the call + * hasn't been picked up by a worker thread yet, so the max number + * of threads in the pool defines how many destroyed streams that + * can be lingering. (We must keep this under control, otherwise + * an evil guest could just rapidly trigger stream creation and + * destruction to consume host heap and hog CPU resources for + * configuring audio backends.) + */ + if ( pThis->hReqPool == NIL_RTREQPOOL + && ( pIHostDrvAudio->pfnStreamInitAsync + || pIHostDrvAudio->pfnDoOnWorkerThread + || (pThis->BackendCfg.fFlags & (PDMAUDIOBACKEND_F_ASYNC_HINT | PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY)) )) + { + char szName[16]; + RTStrPrintf(szName, sizeof(szName), "Aud%uWr", pThis->pDrvIns->iInstance); + RTREQPOOL hReqPool = NIL_RTREQPOOL; + rc = RTReqPoolCreate(3 /*cMaxThreads*/, RT_MS_30SEC /*cMsMinIdle*/, UINT32_MAX /*cThreadsPushBackThreshold*/, + 1 /*cMsMaxPushBack*/, szName, &hReqPool); + LogFlowFunc(("Creating thread pool '%s': %Rrc, hReqPool=%p\n", szName, rc, hReqPool)); + AssertRCReturn(rc, rc); + + rc = RTReqPoolSetCfgVar(hReqPool, RTREQPOOLCFGVAR_THREAD_FLAGS, RTTHREADFLAGS_COM_MTA); + AssertRCReturnStmt(rc, RTReqPoolRelease(hReqPool), rc); + + rc = RTReqPoolSetCfgVar(hReqPool, RTREQPOOLCFGVAR_MIN_THREADS, 1); + AssertRC(rc); /* harmless */ + + pThis->hReqPool = hReqPool; } else - AssertFailed(); + LogFlowFunc(("No thread pool.\n")); - LogFlowFunc(("Returning %Rrc\n", rc)); - return rc; + LogFlowFuncLeave(); + return VINF_SUCCESS; } + /** * Does the actual backend driver attaching and queries the backend's interface. * - * @return VBox status code. - * @param pThis Pointer to driver instance. - * @param fFlags Attach flags; see PDMDrvHlpAttach(). + * This is a worker for both drvAudioAttach and drvAudioConstruct. + * + * @returns VBox status code. + * @param pDrvIns The driver instance. + * @param pThis Pointer to driver instance. + * @param fFlags Attach flags; see PDMDrvHlpAttach(). */ -static int drvAudioDoAttachInternal(PDRVAUDIO pThis, uint32_t fFlags) +static int drvAudioDoAttachInternal(PPDMDRVINS pDrvIns, PDRVAUDIO pThis, uint32_t fFlags) { Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */ @@ -3323,181 +4428,129 @@ * Attach driver below and query its connector interface. */ PPDMIBASE pDownBase; - int rc = PDMDrvHlpAttach(pThis->pDrvIns, fFlags, &pDownBase); + int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase); if (RT_SUCCESS(rc)) { pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO); - if (!pThis->pHostDrvAudio) + if (pThis->pHostDrvAudio) + { + /* + * If everything went well, initialize the lower driver. + */ + rc = drvAudioHostInit(pThis); + if (RT_FAILURE(rc)) + pThis->pHostDrvAudio = NULL; + } + else { - LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->szName)); + LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->BackendCfg.szName)); rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW, - N_("Host audio backend missing or invalid")); + N_("The host audio driver does not implement PDMIHOSTAUDIO!")); } } - - if (RT_SUCCESS(rc)) - { - /* - * If everything went well, initialize the lower driver. - */ - AssertPtr(pThis->pCFGMNode); - rc = drvAudioHostInit(pThis, pThis->pCFGMNode); + /* + * If the host driver below us failed to construct for some beningn reason, + * we'll report it as a runtime error and replace it with the Null driver. + * + * Note! We do NOT change anything in PDM (or CFGM), so pDrvIns->pDownBase + * will remain NULL in this case. + */ + else if ( rc == VERR_AUDIO_BACKEND_INIT_FAILED + || rc == VERR_MODULE_NOT_FOUND + || rc == VERR_SYMBOL_NOT_FOUND + || rc == VERR_FILE_NOT_FOUND + || rc == VERR_PATH_NOT_FOUND) + { + /* Complain: */ + LogRel(("DrvAudio: Host audio driver '%s' init failed with %Rrc. Switching to the NULL driver for now.\n", + pThis->BackendCfg.szName, rc)); + PDMDrvHlpVMSetRuntimeError(pDrvIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("Host audio backend (%s) initialization has failed. Selecting the NULL audio backend with the consequence that no sound is audible"), + pThis->BackendCfg.szName); + + /* Replace with null audio: */ + pThis->pHostDrvAudio = (PPDMIHOSTAUDIO)&g_DrvHostAudioNull; + RTStrCopy(pThis->BackendCfg.szName, sizeof(pThis->BackendCfg.szName), "NULL"); + rc = drvAudioHostInit(pThis); + AssertRC(rc); } - LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc)); + LogFunc(("[%s] rc=%Rrc\n", pThis->BackendCfg.szName, rc)); return rc; } -/********************************************************************/ - /** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} + * Attach notification. + * + * @param pDrvIns The driver instance data. + * @param fFlags Attach flags. */ -static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags) { - LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID)); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + LogFunc(("%s\n", pThis->BackendCfg.szName)); - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + int rc = RTCritSectRwEnterExcl(&pThis->CritSectHotPlug); + AssertRCReturn(rc, rc); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector); + rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags); - return NULL; + RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug); + return rc; } + /** - * Power Off notification. + * Handles state changes for all audio streams. * - * @param pDrvIns The driver instance data. + * @param pDrvIns Pointer to driver instance. + * @param enmCmd Stream command to set for all streams. */ -static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns) +static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd) { + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd))); - LogFlowFuncEnter(); - - if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */ - return; + int rc2 = RTCritSectRwEnterShared(&pThis->CritSectGlobals); + AssertRCReturnVoid(rc2); - /* Just destroy the host stream on the backend side. - * The rest will either be destructed by the device emulation or - * in drvAudioDestruct(). */ - PPDMAUDIOSTREAM pStream; - RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node) + PDRVAUDIOSTREAM pStreamEx; + RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry) { - drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); - drvAudioStreamDestroyInternalBackend(pThis, pStream); + RTCritSectEnter(&pStreamEx->Core.CritSect); + drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd); + RTCritSectLeave(&pStreamEx->Core.CritSect); } - /* - * Last call for the driver below us. - * Let it know that we reached end of life. - */ - if (pThis->pHostDrvAudio->pfnShutdown) - pThis->pHostDrvAudio->pfnShutdown(pThis->pHostDrvAudio); - - pThis->pHostDrvAudio = NULL; - - LogFlowFuncLeave(); + RTCritSectRwLeaveShared(&pThis->CritSectGlobals); } + /** - * Constructs an audio driver instance. + * Resume notification. * - * @copydoc FNPDMDRVCONSTRUCT + * @param pDrvIns The driver instance data. */ -static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns) { - LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags)); - - PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - - RTListInit(&pThis->lstStreams); -#ifdef VBOX_WITH_AUDIO_CALLBACKS - RTListInit(&pThis->In.lstCB); - RTListInit(&pThis->Out.lstCB); -#endif - - /* - * Init the static parts. - */ - pThis->pDrvIns = pDrvIns; - /* IBase. */ - pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface; - /* IAudioConnector. */ - pThis->IAudioConnector.pfnEnable = drvAudioEnable; - pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled; - pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig; - pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus; - pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate; - pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy; - pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain; - pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease; - pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl; - pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead; - pThis->IAudioConnector.pfnStreamWrite = drvAudioStreamWrite; - pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate; - pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable; - pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable; - pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus; - pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume; - pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay; - pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture; -#ifdef VBOX_WITH_AUDIO_CALLBACKS - pThis->IAudioConnector.pfnRegisterCallbacks = drvAudioRegisterCallbacks; -#endif - - int rc = drvAudioInit(pDrvIns, pCfg); - if (RT_SUCCESS(rc)) - { -#ifdef VBOX_WITH_STATISTICS - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive", - STAMUNIT_COUNT, "Total active audio streams."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated", - STAMUNIT_COUNT, "Total created audio streams."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead", - STAMUNIT_COUNT, "Total frames read by device emulation."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesWritten, "TotalFramesWritten", - STAMUNIT_COUNT, "Total frames written by device emulation "); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedIn, "TotalFramesMixedIn", - STAMUNIT_COUNT, "Total input frames mixed."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedOut, "TotalFramesMixedOut", - STAMUNIT_COUNT, "Total output frames mixed."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostIn, "TotalFramesLostIn", - STAMUNIT_COUNT, "Total input frames lost."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostOut, "TotalFramesLostOut", - STAMUNIT_COUNT, "Total output frames lost."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesOut, "TotalFramesOut", - STAMUNIT_COUNT, "Total frames played by backend."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn", - STAMUNIT_COUNT, "Total frames captured by backend."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead", - STAMUNIT_BYTES, "Total bytes read."); - PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesWritten, "TotalBytesWritten", - STAMUNIT_BYTES, "Total bytes written."); - - PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayIn, "DelayIn", - STAMUNIT_NS_PER_CALL, "Profiling of input data processing."); - PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayOut, "DelayOut", - STAMUNIT_NS_PER_CALL, "Profiling of output data processing."); -#endif - } + drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME); +} - rc = drvAudioDoAttachInternal(pThis, fFlags); - if (RT_FAILURE(rc)) - { - /* No lower attached driver (yet)? Not a failure, might get attached later at runtime, just skip. */ - if (rc == VERR_PDM_NO_ATTACHED_DRIVER) - rc = VINF_SUCCESS; - } - LogFlowFuncLeaveRC(rc); - return rc; +/** + * Suspend notification. + * + * @param pDrvIns The driver instance data. + */ +static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns) +{ + drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE); } + /** * Destructs an audio driver instance. * @@ -3510,151 +4563,307 @@ LogFlowFuncEnter(); - int rc2; - - if (RTCritSectIsInitialized(&pThis->CritSect)) + /* + * We must start by setting pHostDrvAudio to NULL here as the anything below + * us has already been destroyed at this point. + */ + if (RTCritSectRwIsInitialized(&pThis->CritSectHotPlug)) { - rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); + RTCritSectRwEnterExcl(&pThis->CritSectHotPlug); + pThis->pHostDrvAudio = NULL; + RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug); + } + else + { + Assert(pThis->pHostDrvAudio == NULL); + pThis->pHostDrvAudio = NULL; } /* - * Note: No calls here to the driver below us anymore, - * as PDM already has destroyed it. - * If you need to call something from the host driver, - * do this in drvAudioPowerOff() instead. + * Make sure the thread pool is out of the picture before we terminate all the streams. */ - - /* Thus, NULL the pointer to the host audio driver first, - * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */ - pThis->pHostDrvAudio = NULL; - - PPDMAUDIOSTREAM pStream, pStreamNext; - RTListForEachSafe(&pThis->lstStreams, pStream, pStreamNext, PDMAUDIOSTREAM, Node) + if (pThis->hReqPool != NIL_RTREQPOOL) { - rc2 = drvAudioStreamUninitInternal(pThis, pStream); - if (RT_SUCCESS(rc2)) - { - RTListNodeRemove(&pStream->Node); - - drvAudioStreamFree(pStream); - pStream = NULL; - } + uint32_t cRefs = RTReqPoolRelease(pThis->hReqPool); + Assert(cRefs == 0); RT_NOREF(cRefs); + pThis->hReqPool = NIL_RTREQPOOL; } - /* Sanity. */ - Assert(RTListIsEmpty(&pThis->lstStreams)); - -#ifdef VBOX_WITH_AUDIO_CALLBACKS /* - * Destroy callbacks, if any. + * Destroy all streams. */ - PPDMAUDIOCBRECORD pCB, pCBNext; - RTListForEachSafe(&pThis->In.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node) - drvAudioCallbackDestroy(pCB); - - RTListForEachSafe(&pThis->Out.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node) - drvAudioCallbackDestroy(pCB); -#endif - - if (RTCritSectIsInitialized(&pThis->CritSect)) + if (RTCritSectRwIsInitialized(&pThis->CritSectGlobals)) { - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); + RTCritSectRwEnterExcl(&pThis->CritSectGlobals); - rc2 = RTCritSectDelete(&pThis->CritSect); - AssertRC(rc2); + PDRVAUDIOSTREAM pStreamEx, pStreamExNext; + RTListForEachSafe(&pThis->LstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry) + { + int rc = drvAudioStreamUninitInternal(pThis, pStreamEx); + if (RT_SUCCESS(rc)) + { + RTListNodeRemove(&pStreamEx->ListEntry); + drvAudioStreamFree(pStreamEx); + } + } + + RTCritSectRwLeaveExcl(&pThis->CritSectGlobals); + RTCritSectRwDelete(&pThis->CritSectGlobals); } -#ifdef VBOX_WITH_STATISTICS - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsActive); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsCreated); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesRead); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesWritten); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedIn); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedOut); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostIn); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostOut); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesOut); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesIn); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesRead); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesWritten); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayIn); - PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayOut); -#endif - LogFlowFuncLeave(); -} + /* Sanity. */ + Assert(RTListIsEmpty(&pThis->LstStreams)); -/** - * Suspend notification. - * - * @param pDrvIns The driver instance data. - */ -static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns) -{ - drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE); -} + if (RTCritSectRwIsInitialized(&pThis->CritSectHotPlug)) + RTCritSectRwDelete(&pThis->CritSectHotPlug); -/** - * Resume notification. - * - * @param pDrvIns The driver instance data. - */ -static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns) -{ - drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME); + PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, ""); + + LogFlowFuncLeave(); } + /** - * Attach notification. + * Constructs an audio driver instance. * - * @param pDrvIns The driver instance data. - * @param fFlags Attach flags. + * @copydoc FNPDMDRVCONSTRUCT */ -static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags) +static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) { - RT_NOREF(fFlags); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags)); - int rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); + /* + * Basic instance init. + */ + RTListInit(&pThis->LstStreams); + pThis->hReqPool = NIL_RTREQPOOL; - LogFunc(("%s\n", pThis->szName)); + /* + * Read configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, + "DriverName|" + "InputEnabled|" + "OutputEnabled|" + "DebugEnabled|" + "DebugPathOut|" + /* Deprecated: */ + "PCMSampleBitIn|" + "PCMSampleBitOut|" + "PCMSampleHzIn|" + "PCMSampleHzOut|" + "PCMSampleSignedIn|" + "PCMSampleSignedOut|" + "PCMSampleSwapEndianIn|" + "PCMSampleSwapEndianOut|" + "PCMSampleChannelsIn|" + "PCMSampleChannelsOut|" + "PeriodSizeMsIn|" + "PeriodSizeMsOut|" + "BufferSizeMsIn|" + "BufferSizeMsOut|" + "PreBufferSizeMsIn|" + "PreBufferSizeMsOut", + "In|Out"); + + int rc = CFGMR3QueryStringDef(pCfg, "DriverName", pThis->BackendCfg.szName, sizeof(pThis->BackendCfg.szName), "Untitled"); + AssertLogRelRCReturn(rc, rc); + + /* Neither input nor output by default for security reasons. */ + rc = CFGMR3QueryBoolDef(pCfg, "InputEnabled", &pThis->In.fEnabled, false); + AssertLogRelRCReturn(rc, rc); + + rc = CFGMR3QueryBoolDef(pCfg, "OutputEnabled", &pThis->Out.fEnabled, false); + AssertLogRelRCReturn(rc, rc); + + /* Debug stuff (same for both directions). */ + rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->CfgIn.Dbg.fEnabled, false); + AssertLogRelRCReturn(rc, rc); + + rc = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->CfgIn.Dbg.szPathOut, sizeof(pThis->CfgIn.Dbg.szPathOut), ""); + AssertLogRelRCReturn(rc, rc); + if (pThis->CfgIn.Dbg.szPathOut[0] == '\0') + { + rc = RTPathTemp(pThis->CfgIn.Dbg.szPathOut, sizeof(pThis->CfgIn.Dbg.szPathOut)); + if (RT_FAILURE(rc)) + { + LogRel(("Audio: Warning! Failed to retrieve temporary directory: %Rrc - disabling debugging.\n", rc)); + pThis->CfgIn.Dbg.szPathOut[0] = '\0'; + pThis->CfgIn.Dbg.fEnabled = false; + } + } + if (pThis->CfgIn.Dbg.fEnabled) + LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", + pThis->BackendCfg.szName, pThis->CfgIn.Dbg.szPathOut)); - int rc = drvAudioDoAttachInternal(pThis, fFlags); + /* Copy debug setup to the output direction. */ + pThis->CfgOut.Dbg = pThis->CfgIn.Dbg; - rc2 = RTCritSectLeave(&pThis->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; + LogRel2(("Audio: Verbose logging for driver '%s' is probably enabled too.\n", pThis->BackendCfg.szName)); + /* This ^^^^^^^ is the *WRONG* place for that kind of statement. Verbose logging might only be enabled for DrvAudio. */ + LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n", + pThis->BackendCfg.szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled")); - return rc; -} + /* + * Per direction configuration. A bit complicated as + * these wasn't originally in sub-nodes. + */ + for (unsigned iDir = 0; iDir < 2; iDir++) + { + char szNm[48]; + PDRVAUDIOCFG pAudioCfg = iDir == 0 ? &pThis->CfgIn : &pThis->CfgOut; + const char *pszDir = iDir == 0 ? "In" : "Out"; + +#define QUERY_VAL_RET(a_Width, a_szName, a_pValue, a_uDefault, a_ExprValid, a_szValidRange) \ + do { \ + rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pDirNode, strcpy(szNm, a_szName), a_pValue); \ + if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \ + { \ + rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pCfg, strcat(szNm, pszDir), a_pValue); \ + if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \ + { \ + *(a_pValue) = a_uDefault; \ + rc = VINF_SUCCESS; \ + } \ + else \ + LogRel(("DrvAudio: Warning! Please use '%s/" a_szName "' instead of '%s' for your VBoxInternal hacks\n", pszDir, szNm)); \ + } \ + AssertRCReturn(rc, PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, \ + N_("Configuration error: Failed to read %s config value '%s'"), pszDir, szNm)); \ + if (!(a_ExprValid)) \ + return PDMDrvHlpVMSetError(pDrvIns, VERR_OUT_OF_RANGE, RT_SRC_POS, \ + N_("Configuration error: Unsupported %s value %u. " a_szValidRange), szNm, *(a_pValue)); \ + } while (0) + + PCFGMNODE const pDirNode = CFGMR3GetChild(pCfg, pszDir); + rc = CFGMR3ValidateConfig(pDirNode, iDir == 0 ? "In/" : "Out/", + "PCMSampleBit|" + "PCMSampleHz|" + "PCMSampleSigned|" + "PCMSampleSwapEndian|" + "PCMSampleChannels|" + "PeriodSizeMs|" + "BufferSizeMs|" + "PreBufferSizeMs", + "", pDrvIns->pReg->szName, pDrvIns->iInstance); + AssertRCReturn(rc, rc); + + uint8_t cSampleBits = 0; + QUERY_VAL_RET(8, "PCMSampleBit", &cSampleBits, 0, + cSampleBits == 0 + || cSampleBits == 8 + || cSampleBits == 16 + || cSampleBits == 32 + || cSampleBits == 64, + "Must be either 0, 8, 16, 32 or 64"); + if (cSampleBits) + PDMAudioPropsSetSampleSize(&pAudioCfg->Props, cSampleBits / 8); + + uint8_t cChannels; + QUERY_VAL_RET(8, "PCMSampleChannels", &cChannels, 0, cChannels <= 16, "Max 16"); + if (cChannels) + PDMAudioPropsSetChannels(&pAudioCfg->Props, cChannels); + + QUERY_VAL_RET(32, "PCMSampleHz", &pAudioCfg->Props.uHz, 0, + pAudioCfg->Props.uHz == 0 || (pAudioCfg->Props.uHz >= 6000 && pAudioCfg->Props.uHz <= 768000), + "In the range 6000 thru 768000, or 0"); + + QUERY_VAL_RET(8, "PCMSampleSigned", &pAudioCfg->uSigned, UINT8_MAX, + pAudioCfg->uSigned == 0 || pAudioCfg->uSigned == 1 || pAudioCfg->uSigned == UINT8_MAX, + "Must be either 0, 1, or 255"); + + QUERY_VAL_RET(8, "PCMSampleSwapEndian", &pAudioCfg->uSwapEndian, UINT8_MAX, + pAudioCfg->uSwapEndian == 0 || pAudioCfg->uSwapEndian == 1 || pAudioCfg->uSwapEndian == UINT8_MAX, + "Must be either 0, 1, or 255"); + + QUERY_VAL_RET(32, "PeriodSizeMs", &pAudioCfg->uPeriodSizeMs, 0, + pAudioCfg->uPeriodSizeMs <= RT_MS_1SEC, "Max 1000"); + + QUERY_VAL_RET(32, "BufferSizeMs", &pAudioCfg->uBufferSizeMs, 0, + pAudioCfg->uBufferSizeMs <= RT_MS_5SEC, "Max 5000"); + + QUERY_VAL_RET(32, "PreBufferSizeMs", &pAudioCfg->uPreBufSizeMs, UINT32_MAX, + pAudioCfg->uPreBufSizeMs <= RT_MS_1SEC || pAudioCfg->uPreBufSizeMs == UINT32_MAX, + "Max 1000, or 0xffffffff"); +#undef QUERY_VAL_RET + } -/** - * Detach notification. - * - * @param pDrvIns The driver instance data. - * @param fFlags Detach flags. - */ -static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags) -{ - RT_NOREF(fFlags); + /* + * Init the rest of the driver instance data. + */ + rc = RTCritSectRwInit(&pThis->CritSectHotPlug); + AssertRCReturn(rc, rc); + rc = RTCritSectRwInit(&pThis->CritSectGlobals); + AssertRCReturn(rc, rc); +#ifdef VBOX_STRICT + /* Define locking order: */ + RTCritSectRwEnterExcl(&pThis->CritSectGlobals); + RTCritSectRwEnterExcl(&pThis->CritSectHotPlug); + RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug); + RTCritSectRwLeaveExcl(&pThis->CritSectGlobals); +#endif - PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); - PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + pThis->pDrvIns = pDrvIns; + /* IBase. */ + pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface; + /* IAudioConnector. */ + pThis->IAudioConnector.pfnEnable = drvAudioEnable; + pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled; + pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig; + pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus; + pThis->IAudioConnector.pfnStreamConfigHint = drvAudioStreamConfigHint; + pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate; + pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy; + pThis->IAudioConnector.pfnStreamReInit = drvAudioStreamReInit; + pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain; + pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease; + pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl; + pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate; + pThis->IAudioConnector.pfnStreamGetState = drvAudioStreamGetState; + pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable; + pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay; + pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable; + pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture; + /* IHostAudioPort */ + pThis->IHostAudioPort.pfnDoOnWorkerThread = drvAudioHostPort_DoOnWorkerThread; + pThis->IHostAudioPort.pfnNotifyDeviceChanged = drvAudioHostPort_NotifyDeviceChanged; + pThis->IHostAudioPort.pfnStreamNotifyPreparingDeviceSwitch = drvAudioHostPort_StreamNotifyPreparingDeviceSwitch; + pThis->IHostAudioPort.pfnStreamNotifyDeviceChanged = drvAudioHostPort_StreamNotifyDeviceChanged; + pThis->IHostAudioPort.pfnNotifyDevicesChanged = drvAudioHostPort_NotifyDevicesChanged; - int rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); +#ifdef VBOX_WITH_AUDIO_ENUM + /* + * Create a timer to trigger delayed device enumeration on device changes. + */ + RTStrPrintf(pThis->szEnumTimerName, sizeof(pThis->szEnumTimerName), "AudioEnum-%u", pDrvIns->iInstance); + rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvAudioEnumerateTimer, NULL /*pvUser*/, + 0 /*fFlags*/, pThis->szEnumTimerName, &pThis->hEnumTimer); + AssertRCReturn(rc, rc); +#endif - pThis->pHostDrvAudio = NULL; + /* + * Attach the host driver, if present. + */ + rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags); + if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + rc = VINF_SUCCESS; - LogFunc(("%s\n", pThis->szName)); + /* + * Statistics (afte driver attach for name). + */ + PDMDrvHlpSTAMRegister(pDrvIns, &pThis->BackendCfg.fFlags, STAMTYPE_U32, "BackendFlags", STAMUNIT_COUNT, pThis->BackendCfg.szName); /* Mainly for the name. */ + PDMDrvHlpSTAMRegister(pDrvIns, &pThis->cStreams, STAMTYPE_U32, "Streams", STAMUNIT_COUNT, "Current streams count."); + PDMDrvHlpSTAMRegCounter(pDrvIns, &pThis->StatTotalStreamsCreated, "TotalStreamsCreated", "Number of stream ever created."); + PDMDrvHlpSTAMRegister(pDrvIns, &pThis->In.fEnabled, STAMTYPE_BOOL, "InputEnabled", STAMUNIT_NONE, "Whether input is enabled or not."); + PDMDrvHlpSTAMRegister(pDrvIns, &pThis->In.cStreamsFree, STAMTYPE_U32, "InputStreamFree", STAMUNIT_COUNT, "Number of free input stream slots"); + PDMDrvHlpSTAMRegister(pDrvIns, &pThis->Out.fEnabled, STAMTYPE_BOOL, "OutputEnabled", STAMUNIT_NONE, "Whether output is enabled or not."); + PDMDrvHlpSTAMRegister(pDrvIns, &pThis->Out.cStreamsFree, STAMTYPE_U32, "OutputStreamFree", STAMUNIT_COUNT, "Number of free output stream slots"); - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); + LogFlowFuncLeaveRC(rc); + return rc; } /** diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvAudio.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvAudio.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvAudio.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvAudio.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,287 +0,0 @@ -/* $Id: DrvAudio.h $ */ -/** @file - * Intermediate audio driver header. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_DrvAudio_h -#define VBOX_INCLUDED_SRC_Audio_DrvAudio_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include - -#include -#include -#include -#include - -#include -#include -#include - -typedef enum -{ - AUD_OPT_INT, - AUD_OPT_FMT, - AUD_OPT_STR, - AUD_OPT_BOOL -} audio_option_tag_e; - -typedef struct audio_option -{ - const char *name; - audio_option_tag_e tag; - void *valp; - const char *descr; - int *overridenp; - int overriden; -} audio_option; - -#ifdef VBOX_WITH_STATISTICS -/** - * Structure for keeping stream statistics for the - * statistic manager (STAM). - */ -typedef struct DRVAUDIOSTATS -{ - STAMCOUNTER TotalStreamsActive; - STAMCOUNTER TotalStreamsCreated; - STAMCOUNTER TotalFramesRead; - STAMCOUNTER TotalFramesWritten; - STAMCOUNTER TotalFramesMixedIn; - STAMCOUNTER TotalFramesMixedOut; - STAMCOUNTER TotalFramesLostIn; - STAMCOUNTER TotalFramesLostOut; - STAMCOUNTER TotalFramesOut; - STAMCOUNTER TotalFramesIn; - STAMCOUNTER TotalBytesRead; - STAMCOUNTER TotalBytesWritten; - /** How much delay (in ms) for input processing. */ - STAMPROFILEADV DelayIn; - /** How much delay (in ms) for output processing. */ - STAMPROFILEADV DelayOut; -} DRVAUDIOSTATS, *PDRVAUDIOSTATS; -#endif - -/** - * Audio driver configuration data, tweakable via CFGM. - */ -typedef struct DRVAUDIOCFG -{ - /** Configures the period size (in ms). - * This value reflects the time in between each hardware interrupt on the - * backend (host) side. */ - uint32_t uPeriodSizeMs; - /** Configures the (ring) buffer size (in ms). Often is a multiple of uPeriodMs. */ - uint32_t uBufferSizeMs; - /** Configures the pre-buffering size (in ms). - * Time needed in buffer before the stream becomes active (pre buffering). - * The bigger this value is, the more latency for the stream will occur. - * Set to 0 to disable pre-buffering completely. - * By default set to UINT32_MAX if not set to a custom value. */ - uint32_t uPreBufSizeMs; - /** The driver's debugging configuration. */ - struct - { - /** Whether audio debugging is enabled or not. */ - bool fEnabled; - /** Where to store the debugging files. - * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH if not set. */ - char szPathOut[RTPATH_MAX]; - } Dbg; -} DRVAUDIOCFG, *PDRVAUDIOCFG; - -/** - * Audio driver instance data. - * - * @implements PDMIAUDIOCONNECTOR - */ -typedef struct DRVAUDIO -{ - /** Friendly name of the driver. */ - char szName[64]; - /** Critical section for serializing access. */ - RTCRITSECT CritSect; - /** Shutdown indicator. */ - bool fTerminate; - /** Our audio connector interface. */ - PDMIAUDIOCONNECTOR IAudioConnector; - /** Pointer to the driver instance. */ - PPDMDRVINS pDrvIns; - /** Pointer to audio driver below us. */ - PPDMIHOSTAUDIO pHostDrvAudio; - /** Pointer to CFGM configuration node of this driver. */ - PCFGMNODE pCFGMNode; - /** List of audio streams. */ - RTLISTANCHOR lstStreams; -#ifdef VBOX_WITH_AUDIO_ENUM - /** Flag indicating to perform an (re-)enumeration of the host audio devices. */ - bool fEnumerateDevices; -#endif - /** Audio configuration settings retrieved from the backend. */ - PDMAUDIOBACKENDCFG BackendCfg; -#ifdef VBOX_WITH_STATISTICS - /** Statistics for the statistics manager (STAM). */ - DRVAUDIOSTATS Stats; -#endif - struct - { - /** Whether this driver's input streams are enabled or not. - * This flag overrides all the attached stream statuses. */ - bool fEnabled; - /** Max. number of free input streams. - * UINT32_MAX for unlimited streams. */ - uint32_t cStreamsFree; -#ifdef VBOX_WITH_AUDIO_CALLBACKS - RTLISTANCHOR lstCB; -#endif - /** The driver's input confguration (tweakable via CFGM). */ - DRVAUDIOCFG Cfg; - } In; - struct - { - /** Whether this driver's output streams are enabled or not. - * This flag overrides all the attached stream statuses. */ - bool fEnabled; - /** Max. number of free output streams. - * UINT32_MAX for unlimited streams. */ - uint32_t cStreamsFree; -#ifdef VBOX_WITH_AUDIO_CALLBACKS - RTLISTANCHOR lstCB; -#endif - /** The driver's output confguration (tweakable via CFGM). */ - DRVAUDIOCFG Cfg; - } Out; -} DRVAUDIO, *PDRVAUDIO; - -/** Makes a PDRVAUDIO out of a PPDMIAUDIOCONNECTOR. */ -#define PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface) \ - ( (PDRVAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVAUDIO, IAudioConnector)) ) - -/** @name Audio format helper methods. - * @{ */ -const char *DrvAudioHlpAudDirToStr(PDMAUDIODIR enmDir); -const char *DrvAudioHlpAudFmtToStr(PDMAUDIOFMT enmFmt); -bool DrvAudioHlpAudFmtIsSigned(PDMAUDIOFMT enmFmt); -uint8_t DrvAudioHlpAudFmtToBits(PDMAUDIOFMT enmFmt); -/** @} */ - -/** @name Audio calculation helper methods. - * @{ */ -void DrvAudioHlpClearBuf(const PPDMAUDIOPCMPROPS pPCMProps, void *pvBuf, size_t cbBuf, uint32_t cFrames); -uint32_t DrvAudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels); -uint32_t DrvAudioHlpCalcBitrate(const PPDMAUDIOPCMPROPS pProps); -uint32_t DrvAudioHlpBytesAlign(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps); -bool DrvAudioHlpBytesIsAligned(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps); -uint32_t DrvAudioHlpBytesToFrames(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps); -uint64_t DrvAudioHlpBytesToMilli(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps); -uint64_t DrvAudioHlpBytesToMicro(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps); -uint64_t DrvAudioHlpBytesToNano(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps); -uint32_t DrvAudioHlpFramesToBytes(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps); -uint64_t DrvAudioHlpFramesToMilli(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps); -uint64_t DrvAudioHlpFramesToNano(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps); -uint32_t DrvAudioHlpMilliToBytes(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps); -uint32_t DrvAudioHlpNanoToBytes(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps); -uint32_t DrvAudioHlpMilliToFrames(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps); -uint32_t DrvAudioHlpNanoToFrames(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps); -/** @} */ - -/** @name Audio PCM properties helper methods. - * @{ */ -bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pPCMProps1, const PPDMAUDIOPCMPROPS pPCMProps2); -bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pPCMProps, const PPDMAUDIOSTREAMCFG pCfg); -bool DrvAudioHlpPCMPropsAreValid(const PPDMAUDIOPCMPROPS pProps); -uint32_t DrvAudioHlpPCMPropsBytesPerFrame(const PPDMAUDIOPCMPROPS pProps); -void DrvAudioHlpPCMPropsPrint(const PPDMAUDIOPCMPROPS pProps); -int DrvAudioHlpPCMPropsToStreamCfg(const PPDMAUDIOPCMPROPS pPCMProps, PPDMAUDIOSTREAMCFG pCfg); -/** @} */ - -/** @name Audio configuration helper methods. - * @{ */ -void DrvAudioHlpStreamCfgInit(PPDMAUDIOSTREAMCFG pCfg); -bool DrvAudioHlpStreamCfgIsValid(const PPDMAUDIOSTREAMCFG pCfg); -int DrvAudioHlpStreamCfgCopy(PPDMAUDIOSTREAMCFG pDstCfg, const PPDMAUDIOSTREAMCFG pSrcCfg); -PPDMAUDIOSTREAMCFG DrvAudioHlpStreamCfgDup(const PPDMAUDIOSTREAMCFG pCfg); -void DrvAudioHlpStreamCfgFree(PPDMAUDIOSTREAMCFG pCfg); -void DrvAudioHlpStreamCfgPrint(const PPDMAUDIOSTREAMCFG pCfg); -/** @} */ - -/** @name Audio stream command helper methods. - * @{ */ -const char *DrvAudioHlpStreamCmdToStr(PDMAUDIOSTREAMCMD enmCmd); -/** @} */ - -/** @name Audio stream status helper methods. - * @{ */ -bool DrvAudioHlpStreamStatusCanRead(PDMAUDIOSTREAMSTS fStatus); -bool DrvAudioHlpStreamStatusCanWrite(PDMAUDIOSTREAMSTS fStatus); -bool DrvAudioHlpStreamStatusIsReady(PDMAUDIOSTREAMSTS fStatus); -/** @} */ - -/** @name Audio file (name) helper methods. - * @{ */ -int DrvAudioHlpFileNameSanitize(char *pszPath, size_t cbPath); -int DrvAudioHlpFileNameGet(char *pszFile, size_t cchFile, const char *pszPath, const char *pszName, uint32_t uInstance, - PDMAUDIOFILETYPE enmType, uint32_t fFlags); -/** @} */ - -/** @name Audio device methods. - * @{ */ -PPDMAUDIODEVICE DrvAudioHlpDeviceAlloc(size_t cbData); -void DrvAudioHlpDeviceFree(PPDMAUDIODEVICE pDev); -PPDMAUDIODEVICE DrvAudioHlpDeviceDup(const PPDMAUDIODEVICE pDev, bool fCopyUserData); -/** @} */ - -/** @name Audio device enumartion methods. - * @{ */ -int DrvAudioHlpDeviceEnumInit(PPDMAUDIODEVICEENUM pDevEnm); -void DrvAudioHlpDeviceEnumFree(PPDMAUDIODEVICEENUM pDevEnm); -int DrvAudioHlpDeviceEnumAdd(PPDMAUDIODEVICEENUM pDevEnm, PPDMAUDIODEVICE pDev); -int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm, PDMAUDIODIR enmUsage); -int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm); -PPDMAUDIODEVICEENUM DrvAudioHlpDeviceEnumDup(const PPDMAUDIODEVICEENUM pDevEnm); -int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm); -int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm, PDMAUDIODIR enmUsage, bool fCopyUserData); -PPDMAUDIODEVICE DrvAudioHlpDeviceEnumGetDefaultDevice(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmDir); -uint16_t DrvAudioHlpDeviceEnumGetDeviceCount(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage); -void DrvAudioHlpDeviceEnumPrint(const char *pszDesc, const PPDMAUDIODEVICEENUM pDevEnm); -/** @} */ - -/** @name Audio string-ify methods. - * @{ */ -const char *DrvAudioHlpAudMixerCtlToStr(PDMAUDIOMIXERCTL enmMixerCtl); -const char *DrvAudioHlpPlaybackDstToStr(const PDMAUDIOPLAYBACKDST enmPlaybackDst); -const char *DrvAudioHlpRecSrcToStr(const PDMAUDIORECSRC enmRecSource); -PDMAUDIOFMT DrvAudioHlpStrToAudFmt(const char *pszFmt); -char *DrvAudioHlpAudDevFlagsToStrA(uint32_t fFlags); -/** @} */ - -/** @name Audio file methods. - * @{ */ -int DrvAudioHlpFileCreate(PDMAUDIOFILETYPE enmType, const char *pszFile, uint32_t fFlags, PPDMAUDIOFILE *ppFile); -void DrvAudioHlpFileDestroy(PPDMAUDIOFILE pFile); -int DrvAudioHlpFileOpen(PPDMAUDIOFILE pFile, uint32_t fOpen, const PPDMAUDIOPCMPROPS pProps); -int DrvAudioHlpFileClose(PPDMAUDIOFILE pFile); -int DrvAudioHlpFileDelete(PPDMAUDIOFILE pFile); -size_t DrvAudioHlpFileGetDataSize(PPDMAUDIOFILE pFile); -bool DrvAudioHlpFileIsOpen(PPDMAUDIOFILE pFile); -int DrvAudioHlpFileWrite(PPDMAUDIOFILE pFile, const void *pvBuf, size_t cbBuf, uint32_t fFlags); -/** @} */ - -#define AUDIO_MAKE_FOURCC(c0, c1, c2, c3) RT_H2LE_U32_C(RT_MAKE_U32_FROM_U8(c0, c1, c2, c3)) - -#endif /* !VBOX_INCLUDED_SRC_Audio_DrvAudio_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,1550 +0,0 @@ -/* $Id: DrvHostALSAAudio.cpp $ */ -/** @file - * ALSA audio driver. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - * -------------------------------------------------------------------- - * - * This code is based on: alsaaudio.c - * - * QEMU ALSA audio driver - * - * Copyright (c) 2005 Vassili Karpov (malc) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include -#include /* For PDMIBASE_2_PDMDRV. */ -#include - -RT_C_DECLS_BEGIN - #include "alsa_stubs.h" - #include "alsa_mangling.h" -RT_C_DECLS_END - -#include -#include /* For device enumeration. */ - -#include "DrvAudio.h" -#include "VBoxDD.h" - - -/********************************************************************************************************************************* -* Defines * -*********************************************************************************************************************************/ - -/** Makes DRVHOSTALSAAUDIO out of PDMIHOSTAUDIO. */ -#define PDMIHOSTAUDIO_2_DRVHOSTALSAAUDIO(pInterface) \ - ( (PDRVHOSTALSAAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTALSAAUDIO, IHostAudio)) ) - - -/********************************************************************************************************************************* -* Structures * -*********************************************************************************************************************************/ - -typedef struct ALSAAUDIOSTREAM -{ - /** The stream's acquired configuration. */ - PPDMAUDIOSTREAMCFG pCfg; - union - { - struct - { - } In; - struct - { - } Out; - }; - snd_pcm_t *phPCM; - void *pvBuf; - size_t cbBuf; -} ALSAAUDIOSTREAM, *PALSAAUDIOSTREAM; - -/* latency = period_size * periods / (rate * bytes_per_frame) */ - -static int alsaStreamRecover(snd_pcm_t *phPCM); - -/** - * Host Alsa audio driver instance data. - * @implements PDMIAUDIOCONNECTOR - */ -typedef struct DRVHOSTALSAAUDIO -{ - /** Pointer to the driver instance structure. */ - PPDMDRVINS pDrvIns; - /** Pointer to host audio interface. */ - PDMIHOSTAUDIO IHostAudio; - /** Error count for not flooding the release log. - * UINT32_MAX for unlimited logging. */ - uint32_t cLogErrors; -} DRVHOSTALSAAUDIO, *PDRVHOSTALSAAUDIO; - -/** Maximum number of tries to recover a broken pipe. */ -#define ALSA_RECOVERY_TRIES_MAX 5 - -typedef struct ALSAAUDIOSTREAMCFG -{ - unsigned int freq; - /** PCM sound format. */ - snd_pcm_format_t fmt; - /** PCM data access type. */ - snd_pcm_access_t access; - /** Whether resampling should be performed by alsalib or not. */ - int resample; - int nchannels; - /** Buffer size (in audio frames). */ - unsigned long buffer_size; - /** Periods (in audio frames). */ - unsigned long period_size; - /** For playback: Starting to play threshold (in audio frames). - * For Capturing: Starting to capture threshold (in audio frames). */ - unsigned long threshold; -} ALSAAUDIOSTREAMCFG, *PALSAAUDIOSTREAMCFG; - - - -static snd_pcm_format_t alsaAudioPropsToALSA(PPDMAUDIOPCMPROPS pProps) -{ - switch (pProps->cbSample) - { - case 1: - return pProps->fSigned ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8; - - case 2: - return pProps->fSigned ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U16_LE; - - case 4: - return pProps->fSigned ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_U32_LE; - - default: - break; - } - - AssertMsgFailed(("%RU8 bytes not supported\n", pProps->cbSample)); - return SND_PCM_FORMAT_U8; -} - - -static int alsaALSAToAudioProps(snd_pcm_format_t fmt, PPDMAUDIOPCMPROPS pProps) -{ - /** @todo r=bird: Why isn't this code setting fSwapEndian for every case? It - * seems to make some UNDOCUMENT ASSUMPTIONS about pProps. */ - switch (fmt) - { - case SND_PCM_FORMAT_S8: - pProps->cbSample = 1; - pProps->fSigned = true; - break; - - case SND_PCM_FORMAT_U8: - pProps->cbSample = 1; - pProps->fSigned = false; - break; - - case SND_PCM_FORMAT_S16_LE: - pProps->cbSample = 2; - pProps->fSigned = true; - break; - - case SND_PCM_FORMAT_U16_LE: - pProps->cbSample = 2; - pProps->fSigned = false; - break; - - case SND_PCM_FORMAT_S16_BE: - pProps->cbSample = 2; - pProps->fSigned = true; -#ifdef RT_LITTLE_ENDIAN - pProps->fSwapEndian = true; -#endif - break; - - case SND_PCM_FORMAT_U16_BE: - pProps->cbSample = 2; - pProps->fSigned = false; -#ifdef RT_LITTLE_ENDIAN - pProps->fSwapEndian = true; -#endif - break; - - case SND_PCM_FORMAT_S32_LE: - pProps->cbSample = 4; - pProps->fSigned = true; - break; - - case SND_PCM_FORMAT_U32_LE: - pProps->cbSample = 4; - pProps->fSigned = false; - break; - - case SND_PCM_FORMAT_S32_BE: - pProps->cbSample = 4; - pProps->fSigned = true; -#ifdef RT_LITTLE_ENDIAN - pProps->fSwapEndian = true; -#endif - break; - - case SND_PCM_FORMAT_U32_BE: - pProps->cbSample = 4; - pProps->fSigned = false; -#ifdef RT_LITTLE_ENDIAN - pProps->fSwapEndian = true; -#endif - break; - - default: - AssertMsgFailedReturn(("Format %d not supported\n", fmt), VERR_NOT_SUPPORTED); - } - - Assert(pProps->cbSample > 0); - Assert(pProps->cChannels > 0); - pProps->cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cbSample, pProps->cChannels); - - return VINF_SUCCESS; -} - - -static int alsaStreamSetSWParams(snd_pcm_t *phPCM, bool fIn, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt) -{ - if (fIn) /* For input streams there's nothing to do in here right now. */ - return VINF_SUCCESS; - - snd_pcm_sw_params_t *pSWParms = NULL; - snd_pcm_sw_params_alloca(&pSWParms); - if (!pSWParms) - return VERR_NO_MEMORY; - - int rc; - do - { - int err = snd_pcm_sw_params_current(phPCM, pSWParms); - if (err < 0) - { - LogRel(("ALSA: Failed to get current software parameters: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; - break; - } - - err = snd_pcm_sw_params_set_start_threshold(phPCM, pSWParms, pCfgReq->threshold); - if (err < 0) - { - LogRel(("ALSA: Failed to set software threshold to %ld: %s\n", pCfgReq->threshold, snd_strerror(err))); - rc = VERR_ACCESS_DENIED; - break; - } - - err = snd_pcm_sw_params_set_avail_min(phPCM, pSWParms, pCfgReq->period_size); - if (err < 0) - { - LogRel(("ALSA: Failed to set available minimum to %ld: %s\n", pCfgReq->threshold, snd_strerror(err))); - rc = VERR_ACCESS_DENIED; - break; - } - - err = snd_pcm_sw_params(phPCM, pSWParms); - if (err < 0) - { - LogRel(("ALSA: Failed to set new software parameters: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; - break; - } - - err = snd_pcm_sw_params_get_start_threshold(pSWParms, &pCfgObt->threshold); - if (err < 0) - { - LogRel(("ALSA: Failed to get start threshold\n")); - rc = VERR_ACCESS_DENIED; - break; - } - - LogFunc(("Setting threshold to %RU32 frames\n", pCfgObt->threshold)); - rc = VINF_SUCCESS; - } - while (0); - - return rc; -} - - -static int alsaStreamClose(snd_pcm_t **pphPCM) -{ - if (!pphPCM || !*pphPCM) - return VINF_SUCCESS; - - int rc; - int rc2 = snd_pcm_close(*pphPCM); - if (rc2) - { - LogRel(("ALSA: Closing PCM descriptor failed: %s\n", snd_strerror(rc2))); - rc = VERR_GENERAL_FAILURE; /** @todo */ - } - else - { - *pphPCM = NULL; - rc = VINF_SUCCESS; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int alsaStreamOpen(bool fIn, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt, snd_pcm_t **pphPCM) -{ - snd_pcm_t *phPCM = NULL; - - int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; - - unsigned int cChannels = pCfgReq->nchannels; - unsigned int uFreq = pCfgReq->freq; - snd_pcm_uframes_t obt_buffer_size; - - do - { - const char *pszDev = "default"; /** @todo Make this configurable through PALSAAUDIOSTREAMCFG. */ - if (!pszDev) - { - LogRel(("ALSA: Invalid or no %s device name set\n", fIn ? "input" : "output")); - rc = VERR_INVALID_PARAMETER; - break; - } - - LogRel(("ALSA: Using %s device \"%s\"\n", fIn ? "input" : "output", pszDev)); - - int err = snd_pcm_open(&phPCM, pszDev, - fIn ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, - SND_PCM_NONBLOCK); - if (err < 0) - { - LogRel(("ALSA: Failed to open \"%s\" as %s device: %s\n", pszDev, fIn ? "input" : "output", snd_strerror(err))); - break; - } - - err = snd_pcm_nonblock(phPCM, 1); - if (err < 0) - { - LogRel(("ALSA: Error setting output non-blocking mode: %s\n", snd_strerror(err))); - break; - } - - snd_pcm_hw_params_t *pHWParms; - snd_pcm_hw_params_alloca(&pHWParms); /** @todo Check for successful allocation? */ - err = snd_pcm_hw_params_any(phPCM, pHWParms); - if (err < 0) - { - LogRel(("ALSA: Failed to initialize hardware parameters: %s\n", snd_strerror(err))); - break; - } - - err = snd_pcm_hw_params_set_access(phPCM, pHWParms, SND_PCM_ACCESS_RW_INTERLEAVED); - if (err < 0) - { - LogRel(("ALSA: Failed to set access type: %s\n", snd_strerror(err))); - break; - } - - err = snd_pcm_hw_params_set_format(phPCM, pHWParms, pCfgReq->fmt); - if (err < 0) - { - LogRel(("ALSA: Failed to set audio format to %d: %s\n", pCfgReq->fmt, snd_strerror(err))); - break; - } - - err = snd_pcm_hw_params_set_rate_near(phPCM, pHWParms, &uFreq, 0); - if (err < 0) - { - LogRel(("ALSA: Failed to set frequency to %uHz: %s\n", pCfgReq->freq, snd_strerror(err))); - break; - } - - err = snd_pcm_hw_params_set_channels_near(phPCM, pHWParms, &cChannels); - if (err < 0) - { - LogRel(("ALSA: Failed to set number of channels to %d\n", pCfgReq->nchannels)); - break; - } - - if ( cChannels != 1 - && cChannels != 2) - { - LogRel(("ALSA: Number of audio channels (%u) not supported\n", cChannels)); - break; - } - - snd_pcm_uframes_t period_size_f = pCfgReq->period_size; - snd_pcm_uframes_t buffer_size_f = pCfgReq->buffer_size; - - snd_pcm_uframes_t minval = period_size_f; - - int dir = 0; - err = snd_pcm_hw_params_get_period_size_min(pHWParms, &minval, &dir); - if (err < 0) - { - LogRel(("ALSA: Could not determine minimal period size\n")); - break; - } - else - { - LogFunc(("Minimal period size is: %ld\n", minval)); - if (period_size_f < minval) - period_size_f = minval; - } - - err = snd_pcm_hw_params_set_period_size_near(phPCM, pHWParms, &period_size_f, 0); - LogFunc(("Period size is: %RU32\n", period_size_f)); - if (err < 0) - { - LogRel(("ALSA: Failed to set period size %d (%s)\n", period_size_f, snd_strerror(err))); - break; - } - - minval = buffer_size_f; - err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval); - if (err < 0) - { - LogRel(("ALSA: Could not retrieve minimal buffer size\n")); - break; - } - else - LogFunc(("Minimal buffer size is: %RU32\n", minval)); - - err = snd_pcm_hw_params_set_buffer_size_near(phPCM, pHWParms, &buffer_size_f); - if (err < 0) - { - LogRel(("ALSA: Failed to set near buffer size %RU32: %s\n", buffer_size_f, snd_strerror(err))); - break; - } - - err = snd_pcm_hw_params(phPCM, pHWParms); - if (err < 0) - { - LogRel(("ALSA: Failed to apply audio parameters\n")); - break; - } - - err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size); - if (err < 0) - { - LogRel(("ALSA: Failed to get buffer size\n")); - break; - } - - snd_pcm_uframes_t obt_period_size; - err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir); - if (err < 0) - { - LogRel(("ALSA: Failed to get period size\n")); - break; - } - - LogRel2(("ALSA: Frequency is %dHz, period size is %RU32 frames, buffer size is %RU32 frames\n", - pCfgReq->freq, obt_period_size, obt_buffer_size)); - - err = snd_pcm_prepare(phPCM); - if (err < 0) - { - LogRel(("ALSA: Could not prepare hPCM %p\n", (void *)phPCM)); - rc = VERR_AUDIO_BACKEND_INIT_FAILED; - break; - } - - rc = alsaStreamSetSWParams(phPCM, fIn, pCfgReq, pCfgObt); - if (RT_FAILURE(rc)) - break; - - pCfgObt->fmt = pCfgReq->fmt; - pCfgObt->nchannels = cChannels; - pCfgObt->freq = uFreq; - pCfgObt->period_size = obt_period_size; - pCfgObt->buffer_size = obt_buffer_size; - - rc = VINF_SUCCESS; - } - while (0); - - if (RT_SUCCESS(rc)) - { - *pphPCM = phPCM; - } - else - alsaStreamClose(&phPCM); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -#ifdef DEBUG -static void alsaDbgErrorHandler(const char *file, int line, const char *function, - int err, const char *fmt, ...) -{ - /** @todo Implement me! */ - RT_NOREF(file, line, function, err, fmt); -} -#endif - - -static int alsaStreamGetAvail(snd_pcm_t *phPCM, snd_pcm_sframes_t *pFramesAvail) -{ - AssertPtrReturn(phPCM, VERR_INVALID_POINTER); - /* pFramesAvail is optional. */ - - int rc; - - snd_pcm_sframes_t framesAvail = snd_pcm_avail_update(phPCM); - if (framesAvail < 0) - { - if (framesAvail == -EPIPE) - { - rc = alsaStreamRecover(phPCM); - if (RT_SUCCESS(rc)) - framesAvail = snd_pcm_avail_update(phPCM); - } - else - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - else - rc = VINF_SUCCESS; - - if (RT_SUCCESS(rc)) - { - if (pFramesAvail) - *pFramesAvail = framesAvail; - } - - LogFunc(("cFrames=%ld, rc=%Rrc\n", framesAvail, rc)); - return rc; -} - - -static int alsaStreamRecover(snd_pcm_t *phPCM) -{ - AssertPtrReturn(phPCM, VERR_INVALID_POINTER); - - int err = snd_pcm_prepare(phPCM); - if (err < 0) - { - LogFunc(("Failed to recover stream %p: %s\n", phPCM, snd_strerror(err))); - return VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - - return VINF_SUCCESS; -} - - -static int alsaStreamResume(snd_pcm_t *phPCM) -{ - AssertPtrReturn(phPCM, VERR_INVALID_POINTER); - - int err = snd_pcm_resume(phPCM); - if (err < 0) - { - LogFunc(("Failed to resume stream %p: %s\n", phPCM, snd_strerror(err))); - return VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} - */ -static DECLCALLBACK(int) drvHostAlsaAudioHA_Init(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); - - LogFlowFuncEnter(); - - int rc = audioLoadAlsaLib(); - if (RT_FAILURE(rc)) - LogRel(("ALSA: Failed to load the ALSA shared library, rc=%Rrc\n", rc)); - else - { -#ifdef DEBUG - snd_lib_error_set_handler(alsaDbgErrorHandler); -#endif - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} - */ -static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(uBufSize, VERR_INVALID_PARAMETER); - /* pcbRead is optional. */ - - PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; - - snd_pcm_sframes_t cAvail; - int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cAvail); - if (RT_FAILURE(rc)) - { - LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc)); - return rc; - } - - PPDMAUDIOSTREAMCFG pCfg = pStreamALSA->pCfg; - AssertPtr(pCfg); - - if (!cAvail) /* No data yet? */ - { - snd_pcm_state_t state = snd_pcm_state(pStreamALSA->phPCM); - switch (state) - { - case SND_PCM_STATE_PREPARED: - cAvail = PDMAUDIOSTREAMCFG_B2F(pCfg, uBufSize); - break; - - case SND_PCM_STATE_SUSPENDED: - { - rc = alsaStreamResume(pStreamALSA->phPCM); - if (RT_FAILURE(rc)) - break; - - LogFlow(("Resuming suspended input stream\n")); - break; - } - - default: - LogFlow(("No frames available, state=%d\n", state)); - break; - } - - if (!cAvail) - { - if (puRead) - *puRead = 0; - return VINF_SUCCESS; - } - } - - /* - * Check how much we can read from the capture device without overflowing - * the mixer buffer. - */ - size_t cbToRead = RT_MIN((size_t)PDMAUDIOSTREAMCFG_F2B(pCfg, cAvail), uBufSize); - - LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail)); - - uint32_t cbReadTotal = 0; - - snd_pcm_uframes_t cToRead; - snd_pcm_sframes_t cRead; - - while ( cbToRead - && RT_SUCCESS(rc)) - { - cToRead = RT_MIN(PDMAUDIOSTREAMCFG_B2F(pCfg, cbToRead), - PDMAUDIOSTREAMCFG_B2F(pCfg, pStreamALSA->cbBuf)); - AssertBreakStmt(cToRead, rc = VERR_NO_DATA); - cRead = snd_pcm_readi(pStreamALSA->phPCM, pStreamALSA->pvBuf, cToRead); - if (cRead <= 0) - { - switch (cRead) - { - case 0: - { - LogFunc(("No input frames available\n")); - rc = VERR_ACCESS_DENIED; - break; - } - - case -EAGAIN: - { - /* - * Don't set error here because EAGAIN means there are no further frames - * available at the moment, try later. As we might have read some frames - * already these need to be processed instead. - */ - cbToRead = 0; - break; - } - - case -EPIPE: - { - rc = alsaStreamRecover(pStreamALSA->phPCM); - if (RT_FAILURE(rc)) - break; - - LogFlowFunc(("Recovered from capturing\n")); - continue; - } - - default: - { - LogFunc(("Failed to read input frames: %s\n", snd_strerror(cRead))); - rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */ - break; - } - } - } - else - { - /* - * We should not run into a full mixer buffer or we loose samples and - * run into an endless loop if ALSA keeps producing samples ("null" - * capture device for example). - */ - uint32_t cbRead = PDMAUDIOSTREAMCFG_F2B(pCfg, cRead); - - memcpy(pvBuf, pStreamALSA->pvBuf, cbRead); - - Assert(cbToRead >= cbRead); - cbToRead -= cbRead; - cbReadTotal += cbRead; - } - } - - if (RT_SUCCESS(rc)) - { - if (puRead) - *puRead = cbReadTotal; - } - - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} - */ -static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(uBufSize, VERR_INVALID_PARAMETER); - /* puWritten is optional. */ - - PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; - - PPDMAUDIOSTREAMCFG pCfg = pStreamALSA->pCfg; - AssertPtr(pCfg); - - int rc; - - uint32_t cbWrittenTotal = 0; - - do - { - snd_pcm_sframes_t csAvail; - rc = alsaStreamGetAvail(pStreamALSA->phPCM, &csAvail); - if (RT_FAILURE(rc)) - { - LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc)); - break; - } - - if (!csAvail) - break; - - size_t cbToWrite = RT_MIN((unsigned)PDMAUDIOSTREAMCFG_F2B(pCfg, csAvail), pStreamALSA->cbBuf); - if (!cbToWrite) - break; - - /* Do not write more than available. */ - if (cbToWrite > uBufSize) - cbToWrite = uBufSize; - - memcpy(pStreamALSA->pvBuf, pvBuf, cbToWrite); - - snd_pcm_sframes_t csWritten = 0; - - /* Don't try infinitely on recoverable errors. */ - unsigned iTry; - for (iTry = 0; iTry < ALSA_RECOVERY_TRIES_MAX; iTry++) - { - csWritten = snd_pcm_writei(pStreamALSA->phPCM, pStreamALSA->pvBuf, - PDMAUDIOSTREAMCFG_B2F(pCfg, cbToWrite)); - if (csWritten <= 0) - { - switch (csWritten) - { - case 0: - { - LogFunc(("Failed to write %zu bytes\n", cbToWrite)); - rc = VERR_ACCESS_DENIED; - break; - } - - case -EPIPE: - { - rc = alsaStreamRecover(pStreamALSA->phPCM); - if (RT_FAILURE(rc)) - break; - - LogFlowFunc(("Recovered from playback\n")); - continue; - } - - case -ESTRPIPE: - { - /* Stream was suspended and waiting for a recovery. */ - rc = alsaStreamResume(pStreamALSA->phPCM); - if (RT_FAILURE(rc)) - { - LogRel(("ALSA: Failed to resume output stream\n")); - break; - } - - LogFlowFunc(("Resumed suspended output stream\n")); - continue; - } - - default: - LogFlowFunc(("Failed to write %RU32 bytes, error unknown\n", cbToWrite)); - rc = VERR_GENERAL_FAILURE; /** @todo */ - break; - } - } - else - break; - } /* For number of tries. */ - - if ( iTry == ALSA_RECOVERY_TRIES_MAX - && csWritten <= 0) - rc = VERR_BROKEN_PIPE; - - if (RT_FAILURE(rc)) - break; - - cbWrittenTotal = PDMAUDIOSTREAMCFG_F2B(pCfg, csWritten); - - } while (0); - - if (RT_SUCCESS(rc)) - { - if (puWritten) - *puWritten = cbWrittenTotal; - } - - return rc; -} - - -static int alsaDestroyStreamIn(PALSAAUDIOSTREAM pStreamALSA) -{ - alsaStreamClose(&pStreamALSA->phPCM); - - if (pStreamALSA->pvBuf) - { - RTMemFree(pStreamALSA->pvBuf); - pStreamALSA->pvBuf = NULL; - } - - return VINF_SUCCESS; -} - - -static int alsaDestroyStreamOut(PALSAAUDIOSTREAM pStreamALSA) -{ - alsaStreamClose(&pStreamALSA->phPCM); - - if (pStreamALSA->pvBuf) - { - RTMemFree(pStreamALSA->pvBuf); - pStreamALSA->pvBuf = NULL; - } - - return VINF_SUCCESS; -} - - -static int alsaCreateStreamOut(PALSAAUDIOSTREAM pStreamALSA, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - snd_pcm_t *phPCM = NULL; - - int rc; - - do - { - ALSAAUDIOSTREAMCFG req; - req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props); - req.freq = pCfgReq->Props.uHz; - req.nchannels = pCfgReq->Props.cChannels; - req.period_size = pCfgReq->Backend.cFramesPeriod; - req.buffer_size = pCfgReq->Backend.cFramesBufferSize; - req.threshold = pCfgReq->Backend.cFramesPreBuffering; - - ALSAAUDIOSTREAMCFG obt; - rc = alsaStreamOpen(false /* fIn */, &req, &obt, &phPCM); - if (RT_FAILURE(rc)) - break; - - pCfgAcq->Props.uHz = obt.freq; - pCfgAcq->Props.cChannels = obt.nchannels; - - rc = alsaALSAToAudioProps(obt.fmt, &pCfgAcq->Props); - if (RT_FAILURE(rc)) - break; - - pCfgAcq->Backend.cFramesPeriod = obt.period_size; - pCfgAcq->Backend.cFramesBufferSize = obt.buffer_size; - pCfgAcq->Backend.cFramesPreBuffering = obt.threshold; - - pStreamALSA->cbBuf = pCfgAcq->Backend.cFramesBufferSize * DrvAudioHlpPCMPropsBytesPerFrame(&pCfgAcq->Props); - pStreamALSA->pvBuf = RTMemAllocZ(pStreamALSA->cbBuf); - if (!pStreamALSA->pvBuf) - { - LogRel(("ALSA: Not enough memory for output DAC buffer (%zu frames)\n", pCfgAcq->Backend.cFramesBufferSize)); - rc = VERR_NO_MEMORY; - break; - } - - pStreamALSA->phPCM = phPCM; - } - while (0); - - if (RT_FAILURE(rc)) - alsaStreamClose(&phPCM); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int alsaCreateStreamIn(PALSAAUDIOSTREAM pStreamALSA, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - int rc; - - snd_pcm_t *phPCM = NULL; - - do - { - ALSAAUDIOSTREAMCFG req; - req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props); - req.freq = pCfgReq->Props.uHz; - req.nchannels = pCfgReq->Props.cChannels; - req.period_size = DrvAudioHlpMilliToFrames(50 /* ms */, &pCfgReq->Props); /** @todo Make this configurable. */ - req.buffer_size = req.period_size * 2; /** @todo Make this configurable. */ - req.threshold = req.period_size; - - ALSAAUDIOSTREAMCFG obt; - rc = alsaStreamOpen(true /* fIn */, &req, &obt, &phPCM); - if (RT_FAILURE(rc)) - break; - - pCfgAcq->Props.uHz = obt.freq; - pCfgAcq->Props.cChannels = obt.nchannels; - - rc = alsaALSAToAudioProps(obt.fmt, &pCfgAcq->Props); - if (RT_FAILURE(rc)) - break; - - pCfgAcq->Backend.cFramesPeriod = obt.period_size; - pCfgAcq->Backend.cFramesBufferSize = obt.buffer_size; - /* No pre-buffering. */ - - pStreamALSA->cbBuf = pCfgAcq->Backend.cFramesBufferSize * DrvAudioHlpPCMPropsBytesPerFrame(&pCfgAcq->Props); - pStreamALSA->pvBuf = RTMemAlloc(pStreamALSA->cbBuf); - if (!pStreamALSA->pvBuf) - { - LogRel(("ALSA: Not enough memory for input ADC buffer (%zu frames)\n", pCfgAcq->Backend.cFramesBufferSize)); - rc = VERR_NO_MEMORY; - break; - } - - pStreamALSA->phPCM = phPCM; - } - while (0); - - if (RT_FAILURE(rc)) - alsaStreamClose(&phPCM); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int alsaControlStreamIn(PALSAAUDIOSTREAM pStreamALSA, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - int rc = VINF_SUCCESS; - - int err; - - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - case PDMAUDIOSTREAMCMD_RESUME: - { - err = snd_pcm_prepare(pStreamALSA->phPCM); - if (err < 0) - { - LogRel(("ALSA: Error preparing input stream: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - else - { - Assert(snd_pcm_state(pStreamALSA->phPCM) == SND_PCM_STATE_PREPARED); - - /* Only start the PCM stream for input streams. */ - err = snd_pcm_start(pStreamALSA->phPCM); - if (err < 0) - { - LogRel(("ALSA: Error starting input stream: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - } - - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - { - err = snd_pcm_drop(pStreamALSA->phPCM); - if (err < 0) - { - LogRel(("ALSA: Error disabling input stream: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - break; - } - - case PDMAUDIOSTREAMCMD_PAUSE: - { - err = snd_pcm_drop(pStreamALSA->phPCM); - if (err < 0) - { - LogRel(("ALSA: Error pausing input stream: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int alsaControlStreamOut(PALSAAUDIOSTREAM pStreamALSA, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - int rc = VINF_SUCCESS; - - int err; - - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - case PDMAUDIOSTREAMCMD_RESUME: - { - err = snd_pcm_prepare(pStreamALSA->phPCM); - if (err < 0) - { - LogRel(("ALSA: Error preparing output stream: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - else - { - Assert(snd_pcm_state(pStreamALSA->phPCM) == SND_PCM_STATE_PREPARED); - } - - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - { - err = snd_pcm_drop(pStreamALSA->phPCM); - if (err < 0) - { - LogRel(("ALSA: Error disabling output stream: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - break; - } - - case PDMAUDIOSTREAMCMD_PAUSE: - { - err = snd_pcm_drop(pStreamALSA->phPCM); - if (err < 0) - { - LogRel(("ALSA: Error pausing output stream: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - break; - } - - case PDMAUDIOSTREAMCMD_DRAIN: - { - snd_pcm_state_t streamState = snd_pcm_state(pStreamALSA->phPCM); - Log2Func(("Stream state is: %d\n", streamState)); - - if ( streamState == SND_PCM_STATE_PREPARED - || streamState == SND_PCM_STATE_RUNNING) - { - err = snd_pcm_nonblock(pStreamALSA->phPCM, 0); - if (err < 0) - { - LogRel(("ALSA: Error disabling output non-blocking mode: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - break; - } - - err = snd_pcm_drain(pStreamALSA->phPCM); - if (err < 0) - { - LogRel(("ALSA: Error draining output: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - break; - } - - err = snd_pcm_nonblock(pStreamALSA->phPCM, 1); - if (err < 0) - { - LogRel(("ALSA: Error re-enabling output non-blocking mode: %s\n", snd_strerror(err))); - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - } - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} - */ -static DECLCALLBACK(int) drvHostAlsaAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); - - RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "ALSA"); - - pBackendCfg->cbStreamIn = sizeof(ALSAAUDIOSTREAM); - pBackendCfg->cbStreamOut = sizeof(ALSAAUDIOSTREAM); - - /* Enumerate sound devices. */ - char **pszHints; - int err = snd_device_name_hint(-1 /* All cards */, "pcm", (void***)&pszHints); - if (err == 0) - { - char** pszHintCur = pszHints; - while (*pszHintCur != NULL) - { - char *pszDev = snd_device_name_get_hint(*pszHintCur, "NAME"); - bool fSkip = !pszDev - || !RTStrICmp("null", pszDev); - if (fSkip) - { - if (pszDev) - free(pszDev); - pszHintCur++; - continue; - } - - char *pszIOID = snd_device_name_get_hint(*pszHintCur, "IOID"); - if (pszIOID) - { -#if 0 - if (!RTStrICmp("input", pszIOID)) - - else if (!RTStrICmp("output", pszIOID)) -#endif - } - else /* NULL means bidirectional, input + output. */ - { - } - - LogRel2(("ALSA: Found %s device: %s\n", pszIOID ? RTStrToLower(pszIOID) : "bidirectional", pszDev)); - - /* Special case for ALSAAudio. */ - if ( pszDev - && RTStrIStr("pulse", pszDev) != NULL) - LogRel2(("ALSA: ALSAAudio plugin in use\n")); - - if (pszIOID) - free(pszIOID); - - if (pszDev) - free(pszDev); - - pszHintCur++; - } - - snd_device_name_free_hint((void **)pszHints); - pszHints = NULL; - } - else - LogRel2(("ALSA: Error enumerating PCM devices: %Rrc (%d)\n", RTErrConvertFromErrno(err), err)); - - /* ALSA allows exactly one input and one output used at a time for the selected device(s). */ - pBackendCfg->cMaxStreamsIn = 1; - pBackendCfg->cMaxStreamsOut = 1; - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} - */ -static DECLCALLBACK(void) drvHostAlsaAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAlsaAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) -{ - RT_NOREF(enmDir); - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); - - return PDMAUDIOBACKENDSTS_RUNNING; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} - */ -static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; - - int rc; - if (pCfgReq->enmDir == PDMAUDIODIR_IN) - rc = alsaCreateStreamIn( pStreamALSA, pCfgReq, pCfgAcq); - else - rc = alsaCreateStreamOut(pStreamALSA, pCfgReq, pCfgAcq); - - if (RT_SUCCESS(rc)) - { - pStreamALSA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); - if (!pStreamALSA->pCfg) - rc = VERR_NO_MEMORY; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} - */ -static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; - - if (!pStreamALSA->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_IN) - rc = alsaDestroyStreamIn(pStreamALSA); - else - rc = alsaDestroyStreamOut(pStreamALSA); - - if (RT_SUCCESS(rc)) - { - DrvAudioHlpStreamCfgFree(pStreamALSA->pCfg); - pStreamALSA->pCfg = NULL; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} - */ -static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, - PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; - - if (!pStreamALSA->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_IN) - rc = alsaControlStreamIn (pStreamALSA, enmStreamCmd); - else - rc = alsaControlStreamOut(pStreamALSA, enmStreamCmd); - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} - */ -static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - - PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; - - uint32_t cbAvail = 0; - - snd_pcm_sframes_t cFramesAvail; - int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail); - if (RT_SUCCESS(rc)) - cbAvail = PDMAUDIOSTREAMCFG_F2B(pStreamALSA->pCfg, cFramesAvail); - - return cbAvail; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - - PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; - - uint32_t cbAvail = 0; - - snd_pcm_sframes_t cFramesAvail; - int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail); - if (RT_SUCCESS(rc)) - cbAvail = PDMAUDIOSTREAMCFG_F2B(pStreamALSA->pCfg, cFramesAvail); - - return cbAvail; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} - */ -static DECLCALLBACK(uint32_t) drvHostALSAStreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, 0); - - PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; - - snd_pcm_sframes_t cFramesDelay = 0; - snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->phPCM); - - int rc = VINF_SUCCESS; - - AssertPtr(pStreamALSA->pCfg); - if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_OUT) - { - /* Getting the delay (in audio frames) reports the time it will take - * to hear a new sample after all queued samples have been played out. */ - int rc2 = snd_pcm_delay(pStreamALSA->phPCM, &cFramesDelay); - if (RT_SUCCESS(rc)) - rc = rc2; - - /* Make sure to check the stream's status. - * If it's anything but SND_PCM_STATE_RUNNING, the delay is meaningless and therefore 0. */ - if (enmState != SND_PCM_STATE_RUNNING) - cFramesDelay = 0; - } - - /* Note: For input streams we never have pending data left. */ - - Log2Func(("cFramesDelay=%RI32, enmState=%d, rc=%d\n", cFramesDelay, enmState, rc)); - - return DrvAudioHlpFramesToBytes(cFramesDelay, &pStreamALSA->pCfg->Props); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} - */ -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostAlsaAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} - */ -static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - /* Nothing to do here for ALSA. */ - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) drvHostAlsaAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) -{ - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); - - return NULL; -} - - -/** - * Construct a DirectSound Audio driver instance. - * - * @copydoc FNPDMDRVCONSTRUCT - */ -static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) -{ - RT_NOREF(pCfg, fFlags); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO); - LogRel(("Audio: Initializing ALSA driver\n")); - - /* - * Init the static parts. - */ - pThis->pDrvIns = pDrvIns; - /* IBase */ - pDrvIns->IBase.pfnQueryInterface = drvHostAlsaAudioQueryInterface; - /* IHostAudio */ - pThis->IHostAudio.pfnInit = drvHostAlsaAudioHA_Init; - pThis->IHostAudio.pfnShutdown = drvHostAlsaAudioHA_Shutdown; - pThis->IHostAudio.pfnGetConfig = drvHostAlsaAudioHA_GetConfig; - pThis->IHostAudio.pfnGetStatus = drvHostAlsaAudioHA_GetStatus; - pThis->IHostAudio.pfnStreamCreate = drvHostAlsaAudioHA_StreamCreate; - pThis->IHostAudio.pfnStreamDestroy = drvHostAlsaAudioHA_StreamDestroy; - pThis->IHostAudio.pfnStreamControl = drvHostAlsaAudioHA_StreamControl; - pThis->IHostAudio.pfnStreamGetReadable = drvHostAlsaAudioHA_StreamGetReadable; - pThis->IHostAudio.pfnStreamGetWritable = drvHostAlsaAudioHA_StreamGetWritable; - pThis->IHostAudio.pfnStreamGetStatus = drvHostAlsaAudioHA_StreamGetStatus; - pThis->IHostAudio.pfnStreamIterate = drvHostAlsaAudioHA_StreamIterate; - pThis->IHostAudio.pfnStreamPlay = drvHostAlsaAudioHA_StreamPlay; - pThis->IHostAudio.pfnStreamCapture = drvHostAlsaAudioHA_StreamCapture; - pThis->IHostAudio.pfnSetCallback = NULL; - pThis->IHostAudio.pfnGetDevices = NULL; - pThis->IHostAudio.pfnStreamGetPending = drvHostALSAStreamGetPending; - pThis->IHostAudio.pfnStreamPlayBegin = NULL; - pThis->IHostAudio.pfnStreamPlayEnd = NULL; - pThis->IHostAudio.pfnStreamCaptureBegin = NULL; - pThis->IHostAudio.pfnStreamCaptureEnd = NULL; - - - return VINF_SUCCESS; -} - - -/** - * Char driver registration record. - */ -const PDMDRVREG g_DrvHostALSAAudio = -{ - /* u32Version */ - PDM_DRVREG_VERSION, - /* szName */ - "ALSAAudio", - /* szRCMod */ - "", - /* szR0Mod */ - "", - /* pszDescription */ - "ALSA host audio driver", - /* fFlags */ - PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, - /* fClass. */ - PDM_DRVREG_CLASS_AUDIO, - /* cMaxInstances */ - ~0U, - /* cbInstance */ - sizeof(DRVHOSTALSAAUDIO), - /* pfnConstruct */ - drvHostAlsaAudioConstruct, - /* pfnDestruct */ - NULL, - /* pfnRelocate */ - NULL, - /* pfnIOCtl */ - NULL, - /* pfnPowerOn */ - NULL, - /* pfnReset */ - NULL, - /* pfnSuspend */ - NULL, - /* pfnResume */ - NULL, - /* pfnAttach */ - NULL, - /* pfnDetach */ - NULL, - /* pfnPowerOff */ - NULL, - /* pfnSoftReset */ - NULL, - /* u32EndVersion */ - PDM_DRVREG_VERSION -}; - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,1585 @@ +/* $Id: DrvHostAudioAlsa.cpp $ */ +/** @file + * Host audio driver - Advanced Linux Sound Architecture (ALSA). + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * -------------------------------------------------------------------- + * + * This code is based on: alsaaudio.c + * + * QEMU ALSA audio driver + * + * Copyright (c) 2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include /* For PDMIBASE_2_PDMDRV. */ +#include +#include +#include + +#include "DrvHostAudioAlsaStubsMangling.h" +#include +#include /* For device enumeration. */ +#include +#include "DrvHostAudioAlsaStubs.h" + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Maximum number of tries to recover a broken pipe. */ +#define ALSA_RECOVERY_TRIES_MAX 5 + + +/********************************************************************************************************************************* +* Structures * +*********************************************************************************************************************************/ +/** + * ALSA host audio specific stream data. + */ +typedef struct DRVHSTAUDALSASTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + + /** Handle to the ALSA PCM stream. */ + snd_pcm_t *hPCM; + /** Internal stream offset (for debugging). */ + uint64_t offInternal; + + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; +} DRVHSTAUDALSASTREAM; +/** Pointer to the ALSA host audio specific stream data. */ +typedef DRVHSTAUDALSASTREAM *PDRVHSTAUDALSASTREAM; + + +/** + * Host Alsa audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHSTAUDALSA +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Error count for not flooding the release log. + * UINT32_MAX for unlimited logging. */ + uint32_t cLogErrors; + + /** Critical section protecting the default device strings. */ + RTCRITSECT CritSect; + /** Default input device name. */ + char szInputDev[256]; + /** Default output device name. */ + char szOutputDev[256]; + /** Upwards notification interface. */ + PPDMIHOSTAUDIOPORT pIHostAudioPort; +} DRVHSTAUDALSA; +/** Pointer to the instance data of an ALSA host audio driver. */ +typedef DRVHSTAUDALSA *PDRVHSTAUDALSA; + + + +/** + * Closes an ALSA stream + * + * @returns VBox status code. + * @param phPCM Pointer to the ALSA stream handle to close. Will be set to + * NULL. + */ +static int drvHstAudAlsaStreamClose(snd_pcm_t **phPCM) +{ + if (!phPCM || !*phPCM) + return VINF_SUCCESS; + + int rc; + int rc2 = snd_pcm_close(*phPCM); + if (rc2 == 0) + { + *phPCM = NULL; + rc = VINF_SUCCESS; + } + else + { + rc = RTErrConvertFromErrno(-rc2); + LogRel(("ALSA: Closing PCM descriptor failed: %s (%d, %Rrc)\n", snd_strerror(rc2), rc2, rc)); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +#ifdef DEBUG +static void drvHstAudAlsaDbgErrorHandler(const char *file, int line, const char *function, + int err, const char *fmt, ...) +{ + /** @todo Implement me! */ + RT_NOREF(file, line, function, err, fmt); +} +#endif + + +/** + * Tries to recover an ALSA stream. + * + * @returns VBox status code. + * @param hPCM ALSA stream handle. + */ +static int drvHstAudAlsaStreamRecover(snd_pcm_t *hPCM) +{ + AssertPtrReturn(hPCM, VERR_INVALID_POINTER); + + int rc = snd_pcm_prepare(hPCM); + if (rc >= 0) + { + LogFlowFunc(("Successfully recovered %p.\n", hPCM)); + return VINF_SUCCESS; + } + LogFunc(("Failed to recover stream %p: %s (%d)\n", hPCM, snd_strerror(rc), rc)); + return RTErrConvertFromErrno(-rc); +} + + +/** + * Resumes an ALSA stream. + * + * Used by drvHstAudAlsaHA_StreamPlay() and drvHstAudAlsaHA_StreamCapture(). + * + * @returns VBox status code. + * @param hPCM ALSA stream to resume. + */ +static int drvHstAudAlsaStreamResume(snd_pcm_t *hPCM) +{ + AssertPtrReturn(hPCM, VERR_INVALID_POINTER); + + int rc = snd_pcm_resume(hPCM); + if (rc >= 0) + { + LogFlowFunc(("Successfuly resumed %p.\n", hPCM)); + return VINF_SUCCESS; + } + LogFunc(("Failed to resume stream %p: %s (%d)\n", hPCM, snd_strerror(rc), rc)); + return RTErrConvertFromErrno(-rc); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "ALSA"); + pBackendCfg->cbStream = sizeof(DRVHSTAUDALSASTREAM); + pBackendCfg->fFlags = 0; + /* ALSA allows exactly one input and one output used at a time for the selected device(s). */ + pBackendCfg->cMaxStreamsIn = 1; + pBackendCfg->cMaxStreamsOut = 1; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum) +{ + RT_NOREF(pInterface); + PDMAudioHostEnumInit(pDeviceEnum); + + char **papszHints = NULL; + int rc = snd_device_name_hint(-1 /* All cards */, "pcm", (void***)&papszHints); + if (rc == 0) + { + rc = VINF_SUCCESS; + for (size_t iHint = 0; papszHints[iHint] != NULL && RT_SUCCESS(rc); iHint++) + { + /* + * Retrieve the available info: + */ + const char * const pszHint = papszHints[iHint]; + char * const pszDev = snd_device_name_get_hint(pszHint, "NAME"); + char * const pszInOutId = snd_device_name_get_hint(pszHint, "IOID"); + char * const pszDesc = snd_device_name_get_hint(pszHint, "DESC"); + + if (pszDev && RTStrICmpAscii(pszDev, "null") != 0) + { + /* Detect and log presence of pulse audio plugin. */ + if (RTStrIStr("pulse", pszDev) != NULL) + LogRel(("ALSA: The ALSAAudio plugin for pulse audio is being used (%s).\n", pszDev)); + + /* + * Add an entry to the enumeration result. + * We engage in some trickery here to deal with device names that + * are more than 63 characters long. + */ + size_t const cbId = pszDev ? strlen(pszDev) + 1 : 1; + size_t const cbName = pszDesc ? strlen(pszDesc) + 2 + 1 : cbId; + PPDMAUDIOHOSTDEV pDev = PDMAudioHostDevAlloc(sizeof(*pDev), cbName, cbId); + if (pDev) + { + RTStrCopy(pDev->pszId, cbId, pszDev); + if (pDev->pszId) + { + pDev->fFlags = PDMAUDIOHOSTDEV_F_NONE; + pDev->enmType = PDMAUDIODEVICETYPE_UNKNOWN; + + if (pszInOutId == NULL) + { + pDev->enmUsage = PDMAUDIODIR_DUPLEX; + pDev->cMaxInputChannels = 2; + pDev->cMaxOutputChannels = 2; + } + else if (RTStrICmpAscii(pszInOutId, "Input") == 0) + { + pDev->enmUsage = PDMAUDIODIR_IN; + pDev->cMaxInputChannels = 2; + pDev->cMaxOutputChannels = 0; + } + else + { + AssertMsg(RTStrICmpAscii(pszInOutId, "Output") == 0, ("%s (%s)\n", pszInOutId, pszHint)); + pDev->enmUsage = PDMAUDIODIR_OUT; + pDev->cMaxInputChannels = 0; + pDev->cMaxOutputChannels = 2; + } + + if (pszDesc && *pszDesc) + { + char *pszDesc2 = strchr(pszDesc, '\n'); + if (!pszDesc2) + RTStrCopy(pDev->pszName, cbName, pszDesc); + else + { + *pszDesc2++ = '\0'; + char *psz; + while ((psz = strchr(pszDesc2, '\n')) != NULL) + *psz = ' '; + RTStrPrintf(pDev->pszName, cbName, "%s (%s)", pszDesc2, pszDesc); + } + } + else + RTStrCopy(pDev->pszName, cbName, pszDev); + + PDMAudioHostEnumAppend(pDeviceEnum, pDev); + + LogRel2(("ALSA: Device #%u: '%s' enmDir=%s: %s\n", iHint, pszDev, + PDMAudioDirGetName(pDev->enmUsage), pszDesc)); + } + else + { + PDMAudioHostDevFree(pDev); + rc = VERR_NO_STR_MEMORY; + } + } + else + rc = VERR_NO_MEMORY; + } + + /* + * Clean up. + */ + if (pszInOutId) + free(pszInOutId); + if (pszDesc) + free(pszDesc); + if (pszDev) + free(pszDev); + } + + snd_device_name_free_hint((void **)papszHints); + + if (RT_FAILURE(rc)) + { + PDMAudioHostEnumDelete(pDeviceEnum); + PDMAudioHostEnumInit(pDeviceEnum); + } + } + else + { + int rc2 = RTErrConvertFromErrno(-rc); + LogRel2(("ALSA: Error enumerating PCM devices: %Rrc (%d)\n", rc2, rc)); + rc = rc2; + } + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId) +{ + PDRVHSTAUDALSA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDALSA, IHostAudio); + + /* + * Validate and normalize input. + */ + AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pszId, VERR_INVALID_POINTER); + if (!pszId || !*pszId) + pszId = "default"; + else + { + size_t cch = strlen(pszId); + AssertReturn(cch < sizeof(pThis->szInputDev), VERR_INVALID_NAME); + } + LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId)); + + /* + * Update input. + */ + if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX) + { + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + if (strcmp(pThis->szInputDev, pszId) == 0) + RTCritSectLeave(&pThis->CritSect); + else + { + LogRel(("ALSA: Changing input device: '%s' -> '%s'\n", pThis->szInputDev, pszId)); + RTStrCopy(pThis->szInputDev, sizeof(pThis->szInputDev), pszId); + PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort; + RTCritSectLeave(&pThis->CritSect); + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about input device change...\n")); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/); + } + } + } + + /* + * Update output. + */ + if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX) + { + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + if (strcmp(pThis->szOutputDev, pszId) == 0) + RTCritSectLeave(&pThis->CritSect); + else + { + LogRel(("ALSA: Changing output device: '%s' -> '%s'\n", pThis->szOutputDev, pszId)); + RTStrCopy(pThis->szOutputDev, sizeof(pThis->szOutputDev), pszId); + PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort; + RTCritSectLeave(&pThis->CritSect); + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about output device change...\n")); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/); + } + } + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudAlsaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * Converts internal audio PCM properties to an ALSA PCM format. + * + * @returns Converted ALSA PCM format. + * @param pProps Internal audio PCM configuration to convert. + */ +static snd_pcm_format_t alsaAudioPropsToALSA(PCPDMAUDIOPCMPROPS pProps) +{ + switch (PDMAudioPropsSampleSize(pProps)) + { + case 1: + return pProps->fSigned ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8; + + case 2: + if (PDMAudioPropsIsLittleEndian(pProps)) + return pProps->fSigned ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U16_LE; + return pProps->fSigned ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_U16_BE; + + case 4: + if (PDMAudioPropsIsLittleEndian(pProps)) + return pProps->fSigned ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_U32_LE; + return pProps->fSigned ? SND_PCM_FORMAT_S32_BE : SND_PCM_FORMAT_U32_BE; + + default: + AssertLogRelMsgFailed(("%RU8 bytes not supported\n", PDMAudioPropsSampleSize(pProps))); + return SND_PCM_FORMAT_UNKNOWN; + } +} + + +/** + * Sets the software parameters of an ALSA stream. + * + * @returns 0 on success, negative errno on failure. + * @param hPCM ALSA stream to set software parameters for. + * @param pCfgReq Requested stream configuration (PDM). + * @param pCfgAcq The actual stream configuration (PDM). Updated as + * needed. + */ +static int alsaStreamSetSWParams(snd_pcm_t *hPCM, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + if (pCfgReq->enmDir == PDMAUDIODIR_IN) /* For input streams there's nothing to do in here right now. */ + return 0; + + snd_pcm_sw_params_t *pSWParms = NULL; + snd_pcm_sw_params_alloca(&pSWParms); + AssertReturn(pSWParms, -ENOMEM); + + int err = snd_pcm_sw_params_current(hPCM, pSWParms); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to get current software parameters: %s\n", snd_strerror(err)), err); + + /* Under normal circumstance, we don't need to set a playback threshold + because DrvAudio will do the pre-buffering and hand us everything in + one continuous chunk when we should start playing. But since it is + configurable, we'll set a reasonable minimum of two DMA periods or + max 50 milliseconds (the pAlsaCfgReq->threshold value). + + Of course we also have to make sure the threshold is below the buffer + size, or ALSA will never start playing. */ + unsigned long const cFramesMax = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 50); + unsigned long cFramesThreshold = RT_MIN(pCfgAcq->Backend.cFramesPeriod * 2, cFramesMax); + if (cFramesThreshold >= pCfgAcq->Backend.cFramesBufferSize - pCfgAcq->Backend.cFramesBufferSize / 16) + cFramesThreshold = pCfgAcq->Backend.cFramesBufferSize - pCfgAcq->Backend.cFramesBufferSize / 16; + + err = snd_pcm_sw_params_set_start_threshold(hPCM, pSWParms, cFramesThreshold); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set software threshold to %lu: %s\n", cFramesThreshold, snd_strerror(err)), err); + + err = snd_pcm_sw_params_set_avail_min(hPCM, pSWParms, pCfgReq->Backend.cFramesPeriod); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set available minimum to %u: %s\n", + pCfgReq->Backend.cFramesPeriod, snd_strerror(err)), err); + + /* Commit the software parameters: */ + err = snd_pcm_sw_params(hPCM, pSWParms); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set new software parameters: %s\n", snd_strerror(err)), err); + + /* Get the actual parameters: */ + snd_pcm_uframes_t cFramesThresholdActual = cFramesThreshold; + err = snd_pcm_sw_params_get_start_threshold(pSWParms, &cFramesThresholdActual); + AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get start threshold: %s\n", snd_strerror(err)), + cFramesThresholdActual = cFramesThreshold); + + LogRel2(("ALSA: SW params: %lu frames threshold, %u frames avail minimum\n", + cFramesThresholdActual, pCfgAcq->Backend.cFramesPeriod)); + return 0; +} + + +/** + * Maps a PDM channel ID to an ASLA channel map position. + */ +static unsigned int drvHstAudAlsaPdmChToAlsa(PDMAUDIOCHANNELID enmId, uint8_t cChannels) +{ + switch (enmId) + { + case PDMAUDIOCHANNELID_UNKNOWN: return SND_CHMAP_UNKNOWN; + case PDMAUDIOCHANNELID_UNUSED_ZERO: return SND_CHMAP_NA; + case PDMAUDIOCHANNELID_UNUSED_SILENCE: return SND_CHMAP_NA; + + case PDMAUDIOCHANNELID_FRONT_LEFT: return SND_CHMAP_FL; + case PDMAUDIOCHANNELID_FRONT_RIGHT: return SND_CHMAP_FR; + case PDMAUDIOCHANNELID_FRONT_CENTER: return cChannels == 1 ? SND_CHMAP_MONO : SND_CHMAP_FC; + case PDMAUDIOCHANNELID_LFE: return SND_CHMAP_LFE; + case PDMAUDIOCHANNELID_REAR_LEFT: return SND_CHMAP_RL; + case PDMAUDIOCHANNELID_REAR_RIGHT: return SND_CHMAP_RR; + case PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER: return SND_CHMAP_FLC; + case PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER: return SND_CHMAP_FRC; + case PDMAUDIOCHANNELID_REAR_CENTER: return SND_CHMAP_RC; + case PDMAUDIOCHANNELID_SIDE_LEFT: return SND_CHMAP_SL; + case PDMAUDIOCHANNELID_SIDE_RIGHT: return SND_CHMAP_SR; + case PDMAUDIOCHANNELID_TOP_CENTER: return SND_CHMAP_TC; + case PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT: return SND_CHMAP_TFL; + case PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT: return SND_CHMAP_TFC; + case PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT: return SND_CHMAP_TFR; + case PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT: return SND_CHMAP_TRL; + case PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT: return SND_CHMAP_TRC; + case PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT: return SND_CHMAP_TRR; + + case PDMAUDIOCHANNELID_INVALID: + case PDMAUDIOCHANNELID_END: + case PDMAUDIOCHANNELID_32BIT_HACK: + break; + } + AssertFailed(); + return SND_CHMAP_NA; +} + + +/** + * Sets the hardware parameters of an ALSA stream. + * + * @returns 0 on success, negative errno on failure. + * @param hPCM ALSA stream to set software parameters for. + * @param enmAlsaFmt The ALSA format to use. + * @param pCfgReq Requested stream configuration (PDM). + * @param pCfgAcq The actual stream configuration (PDM). This is assumed + * to be a copy of pCfgReq on input, at least for + * properties handled here. On output some of the + * properties may be updated to match the actual stream + * configuration. + */ +static int alsaStreamSetHwParams(snd_pcm_t *hPCM, snd_pcm_format_t enmAlsaFmt, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + /* + * Get the current hardware parameters. + */ + snd_pcm_hw_params_t *pHWParms = NULL; + snd_pcm_hw_params_alloca(&pHWParms); + AssertReturn(pHWParms, -ENOMEM); + + int err = snd_pcm_hw_params_any(hPCM, pHWParms); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to initialize hardware parameters: %s\n", snd_strerror(err)), err); + + /* + * Modify them according to pAlsaCfgReq. + * We update pAlsaCfgObt as we go for parameters set by "near" methods. + */ + /* We'll use snd_pcm_writei/snd_pcm_readi: */ + err = snd_pcm_hw_params_set_access(hPCM, pHWParms, SND_PCM_ACCESS_RW_INTERLEAVED); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set access type: %s\n", snd_strerror(err)), err); + + /* Set the format and frequency. */ + err = snd_pcm_hw_params_set_format(hPCM, pHWParms, enmAlsaFmt); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set audio format to %d: %s\n", enmAlsaFmt, snd_strerror(err)), err); + + unsigned int uFreq = PDMAudioPropsHz(&pCfgReq->Props); + err = snd_pcm_hw_params_set_rate_near(hPCM, pHWParms, &uFreq, NULL /*dir*/); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set frequency to %uHz: %s\n", + PDMAudioPropsHz(&pCfgReq->Props), snd_strerror(err)), err); + pCfgAcq->Props.uHz = uFreq; + + /* Channel count currently does not change with the mapping translations, + as ALSA can express both silent and unknown channel positions. */ + union + { + snd_pcm_chmap_t Map; + unsigned int padding[1 + PDMAUDIO_MAX_CHANNELS]; + } u; + uint8_t aidSrcChannels[PDMAUDIO_MAX_CHANNELS]; + unsigned int *aidDstChannels = u.Map.pos; + unsigned int cChannels = u.Map.channels = PDMAudioPropsChannels(&pCfgReq->Props); + unsigned int iDst = 0; + for (unsigned int iSrc = 0; iSrc < cChannels; iSrc++) + { + uint8_t const idSrc = pCfgReq->Props.aidChannels[iSrc]; + aidSrcChannels[iDst] = idSrc; + aidDstChannels[iDst] = drvHstAudAlsaPdmChToAlsa((PDMAUDIOCHANNELID)idSrc, cChannels); + iDst++; + } + u.Map.channels = cChannels = iDst; + for (; iDst < PDMAUDIO_MAX_CHANNELS; iDst++) + { + aidSrcChannels[iDst] = PDMAUDIOCHANNELID_INVALID; + aidDstChannels[iDst] = SND_CHMAP_NA; + } + + err = snd_pcm_hw_params_set_channels_near(hPCM, pHWParms, &cChannels); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set number of channels to %d\n", PDMAudioPropsChannels(&pCfgReq->Props)), + err); + if (cChannels == PDMAudioPropsChannels(&pCfgReq->Props)) + memcpy(pCfgAcq->Props.aidChannels, aidSrcChannels, sizeof(pCfgAcq->Props.aidChannels)); + else + { + LogRel2(("ALSA: Requested %u channels, got %u\n", u.Map.channels, cChannels)); + AssertLogRelMsgReturn(cChannels > 0 && cChannels <= PDMAUDIO_MAX_CHANNELS, + ("ALSA: Unsupported channel count: %u (requested %d)\n", + cChannels, PDMAudioPropsChannels(&pCfgReq->Props)), -ERANGE); + PDMAudioPropsSetChannels(&pCfgAcq->Props, (uint8_t)cChannels); + /** @todo Can we somehow guess channel IDs? snd_pcm_get_chmap? */ + } + + /* The period size (reportedly frame count per hw interrupt): */ + int dir = 0; + snd_pcm_uframes_t minval = pCfgReq->Backend.cFramesPeriod; + err = snd_pcm_hw_params_get_period_size_min(pHWParms, &minval, &dir); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not determine minimal period size: %s\n", snd_strerror(err)), err); + + snd_pcm_uframes_t period_size_f = pCfgReq->Backend.cFramesPeriod; + if (period_size_f < minval) + period_size_f = minval; + err = snd_pcm_hw_params_set_period_size_near(hPCM, pHWParms, &period_size_f, 0); + LogRel2(("ALSA: Period size is: %lu frames (min %lu, requested %u)\n", period_size_f, minval, pCfgReq->Backend.cFramesPeriod)); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set period size %d (%s)\n", period_size_f, snd_strerror(err)), err); + + /* The buffer size: */ + minval = pCfgReq->Backend.cFramesBufferSize; + err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not retrieve minimal buffer size: %s\n", snd_strerror(err)), err); + + snd_pcm_uframes_t buffer_size_f = pCfgReq->Backend.cFramesBufferSize; + if (buffer_size_f < minval) + buffer_size_f = minval; + err = snd_pcm_hw_params_set_buffer_size_near(hPCM, pHWParms, &buffer_size_f); + LogRel2(("ALSA: Buffer size is: %lu frames (min %lu, requested %u)\n", buffer_size_f, minval, pCfgReq->Backend.cFramesBufferSize)); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set near buffer size %RU32: %s\n", buffer_size_f, snd_strerror(err)), err); + + /* + * Set the hardware parameters. + */ + err = snd_pcm_hw_params(hPCM, pHWParms); + AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to apply audio parameters: %s\n", snd_strerror(err)), err); + + /* + * Get relevant parameters and put them in the pAlsaCfgObt structure. + */ + snd_pcm_uframes_t obt_buffer_size = buffer_size_f; + err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size); + AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get buffer size: %s\n", snd_strerror(err)), obt_buffer_size = buffer_size_f); + pCfgAcq->Backend.cFramesBufferSize = obt_buffer_size; + + snd_pcm_uframes_t obt_period_size = period_size_f; + err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir); + AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get period size: %s\n", snd_strerror(err)), obt_period_size = period_size_f); + pCfgAcq->Backend.cFramesPeriod = obt_period_size; + + LogRel2(("ALSA: HW params: %u Hz, %u frames period, %u frames buffer, %u channel(s), enmAlsaFmt=%d\n", + PDMAudioPropsHz(&pCfgAcq->Props), pCfgAcq->Backend.cFramesPeriod, pCfgAcq->Backend.cFramesBufferSize, + PDMAudioPropsChannels(&pCfgAcq->Props), enmAlsaFmt)); + + /* + * Channel config (not fatal). + */ + if (PDMAudioPropsChannels(&pCfgAcq->Props) == PDMAudioPropsChannels(&pCfgReq->Props)) + { + err = snd_pcm_set_chmap(hPCM, &u.Map); + if (err < 0) + LogRel2(("ALSA: snd_pcm_set_chmap failed: %s (%d)\n", snd_strerror(err), err)); + } + + return 0; +} + + +/** + * Opens (creates) an ALSA stream. + * + * @returns VBox status code. + * @param pThis The alsa driver instance data. + * @param enmAlsaFmt The ALSA format to use. + * @param pCfgReq Requested configuration to create stream with (PDM). + * @param pCfgAcq The actual stream configuration (PDM). This is assumed + * to be a copy of pCfgReq on input, at least for + * properties handled here. On output some of the + * properties may be updated to match the actual stream + * configuration. + * @param phPCM Where to store the ALSA stream handle on success. + */ +static int alsaStreamOpen(PDRVHSTAUDALSA pThis, snd_pcm_format_t enmAlsaFmt, PCPDMAUDIOSTREAMCFG pCfgReq, + PPDMAUDIOSTREAMCFG pCfgAcq, snd_pcm_t **phPCM) +{ + /* + * Open the stream. + */ + int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + const char * const pszType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "input" : "output"; + const char * const pszDev = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->szInputDev : pThis->szOutputDev; + snd_pcm_stream_t enmType = pCfgReq->enmDir == PDMAUDIODIR_IN ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK; + + snd_pcm_t *hPCM = NULL; + LogRel(("ALSA: Using %s device \"%s\"\n", pszType, pszDev)); + int err = snd_pcm_open(&hPCM, pszDev, enmType, SND_PCM_NONBLOCK); + if (err >= 0) + { + err = snd_pcm_nonblock(hPCM, 1); + if (err >= 0) + { + /* + * Configure hardware stream parameters. + */ + err = alsaStreamSetHwParams(hPCM, enmAlsaFmt, pCfgReq, pCfgAcq); + if (err >= 0) + { + /* + * Prepare it. + */ + rc = VERR_AUDIO_BACKEND_INIT_FAILED; + err = snd_pcm_prepare(hPCM); + if (err >= 0) + { + /* + * Configure software stream parameters. + */ + rc = alsaStreamSetSWParams(hPCM, pCfgReq, pCfgAcq); + if (RT_SUCCESS(rc)) + { + *phPCM = hPCM; + return VINF_SUCCESS; + } + } + else + LogRel(("ALSA: snd_pcm_prepare failed: %s\n", snd_strerror(err))); + } + } + else + LogRel(("ALSA: Error setting non-blocking mode for %s stream: %s\n", pszType, snd_strerror(err))); + drvHstAudAlsaStreamClose(&hPCM); + } + else + LogRel(("ALSA: Failed to open \"%s\" as %s device: %s\n", pszDev, pszType, snd_strerror(err))); + *phPCM = NULL; + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHSTAUDALSA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDALSA, IHostAudio); + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgReq); + + int rc; + snd_pcm_format_t const enmFmt = alsaAudioPropsToALSA(&pCfgReq->Props); + if (enmFmt != SND_PCM_FORMAT_UNKNOWN) + { + rc = alsaStreamOpen(pThis, enmFmt, pCfgReq, pCfgAcq, &pStreamALSA->hPCM); + if (RT_SUCCESS(rc)) + { + /* We have no objections to the pre-buffering that DrvAudio applies, + only we need to adjust it relative to the actual buffer size. */ + pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering + * pCfgAcq->Backend.cFramesBufferSize + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + + PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgAcq); + LogFlowFunc(("returns success - hPCM=%p\n", pStreamALSA->hPCM)); + return rc; + } + } + else + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + LogFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate) +{ + RT_NOREF(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER); + RT_NOREF(fImmediate); + + /** @todo r=bird: It's not like we can do much with a bad status... Check + * what the caller does... */ + return drvHstAudAlsaStreamClose(&pStreamALSA->hPCM); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + + /* + * Prepare the stream. + */ + int rc = snd_pcm_prepare(pStreamALSA->hPCM); + if (rc >= 0) + { + Assert(snd_pcm_state(pStreamALSA->hPCM) == SND_PCM_STATE_PREPARED); + + /* + * Input streams should be started now, whereas output streams must + * pre-buffer sufficent data before starting. + */ + if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_IN) + { + rc = snd_pcm_start(pStreamALSA->hPCM); + if (rc >= 0) + rc = VINF_SUCCESS; + else + { + LogRel(("ALSA: Error starting input stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc)); + rc = RTErrConvertFromErrno(-rc); + } + } + else + rc = VINF_SUCCESS; + } + else + { + LogRel(("ALSA: Error preparing stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc)); + rc = RTErrConvertFromErrno(-rc); + } + LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + + int rc = snd_pcm_drop(pStreamALSA->hPCM); + if (rc >= 0) + rc = VINF_SUCCESS; + else + { + LogRel(("ALSA: Error stopping stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc)); + rc = RTErrConvertFromErrno(-rc); + } + LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + /* Same as disable. */ + /** @todo r=bird: Try use pause and fallback on disable/enable if it isn't + * supported or doesn't work. */ + return drvHstAudAlsaHA_StreamDisable(pInterface, pStream); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + /* Same as enable. */ + return drvHstAudAlsaHA_StreamEnable(pInterface, pStream); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + + snd_pcm_state_t const enmState = snd_pcm_state(pStreamALSA->hPCM); + LogFlowFunc(("Stream '%s' input state: %s (%d)\n", pStreamALSA->Cfg.szName, snd_pcm_state_name(enmState), enmState)); + + /* Only for output streams. */ + AssertReturn(pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER); + + int rc; + switch (enmState) + { + case SND_PCM_STATE_RUNNING: + case SND_PCM_STATE_PREPARED: /* not yet started */ + { + /* Do not change to blocking here! */ + rc = snd_pcm_drain(pStreamALSA->hPCM); + if (rc >= 0 || rc == -EAGAIN) + rc = VINF_SUCCESS; + else + { + snd_pcm_state_t const enmState2 = snd_pcm_state(pStreamALSA->hPCM); + if (rc == -EPIPE && enmState2 == enmState) + { + /* Not entirely sure, but possibly an underrun, so just disable the stream. */ + LogFunc(("snd_pcm_drain failed with -EPIPE, stopping stream (%s)\n", pStreamALSA->Cfg.szName)); + rc = snd_pcm_drop(pStreamALSA->hPCM); + if (rc >= 0) + rc = VINF_SUCCESS; + else + { + LogRel(("ALSA: Error draining/stopping stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc)); + rc = RTErrConvertFromErrno(-rc); + } + } + else + { + LogRel(("ALSA: Error draining output of '%s': %s (%d; %s -> %s)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), + rc, snd_pcm_state_name(enmState), snd_pcm_state_name(enmState2))); + rc = RTErrConvertFromErrno(-rc); + } + } + break; + } + + default: + rc = VINF_SUCCESS; + break; + } + LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudAlsaHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + AssertPtrReturn(pStreamALSA, PDMHOSTAUDIOSTREAMSTATE_INVALID); + + PDMHOSTAUDIOSTREAMSTATE enmStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY; + snd_pcm_state_t enmAlsaState = snd_pcm_state(pStreamALSA->hPCM); + if (enmAlsaState == SND_PCM_STATE_DRAINING) + { + /* We're operating in non-blocking mode, so we must (at least for a demux + config) call snd_pcm_drain again to drive it forward. Otherwise we + might be stuck in the drain state forever. */ + Log5Func(("Calling snd_pcm_drain again...\n")); + snd_pcm_drain(pStreamALSA->hPCM); + enmAlsaState = snd_pcm_state(pStreamALSA->hPCM); + } + + if (enmAlsaState == SND_PCM_STATE_DRAINING) + enmStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING; +#if (((SND_LIB_MAJOR) << 16) | ((SND_LIB_MAJOR) << 8) | (SND_LIB_SUBMINOR)) >= 0x10002 /* was added in 1.0.2 */ + else if (enmAlsaState == SND_PCM_STATE_DISCONNECTED) + enmStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; +#endif + + Log5Func(("Stream '%s': ALSA state=%s -> %s\n", + pStreamALSA->Cfg.szName, snd_pcm_state_name(enmAlsaState), PDMHostAudioStreamStateGetName(enmStreamState) )); + return enmStreamState; +} + + +/** + * Returns the available audio frames queued. + * + * @returns VBox status code. + * @param hPCM ALSA stream handle. + * @param pcFramesAvail Where to store the available frames. + */ +static int alsaStreamGetAvail(snd_pcm_t *hPCM, snd_pcm_sframes_t *pcFramesAvail) +{ + AssertPtr(hPCM); + AssertPtr(pcFramesAvail); + + int rc; + snd_pcm_sframes_t cFramesAvail = snd_pcm_avail_update(hPCM); + if (cFramesAvail > 0) + { + LogFunc(("cFramesAvail=%ld\n", cFramesAvail)); + *pcFramesAvail = cFramesAvail; + return VINF_SUCCESS; + } + + /* + * We can maybe recover from an EPIPE... + */ + if (cFramesAvail == -EPIPE) + { + rc = drvHstAudAlsaStreamRecover(hPCM); + if (RT_SUCCESS(rc)) + { + cFramesAvail = snd_pcm_avail_update(hPCM); + if (cFramesAvail >= 0) + { + LogFunc(("cFramesAvail=%ld\n", cFramesAvail)); + *pcFramesAvail = cFramesAvail; + return VINF_SUCCESS; + } + } + else + { + *pcFramesAvail = 0; + return rc; + } + } + + rc = RTErrConvertFromErrno(-(int)cFramesAvail); + LogFunc(("failed - cFramesAvail=%ld rc=%Rrc\n", cFramesAvail, rc)); + *pcFramesAvail = 0; + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} + */ +static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + AssertPtrReturn(pStreamALSA, 0); + + /* + * This is only relevant to output streams (input streams can't have + * any pending, unplayed data). + */ + uint32_t cbPending = 0; + if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT) + { + /* + * Getting the delay (in audio frames) reports the time it will take + * to hear a new sample after all queued samples have been played out. + * + * We use snd_pcm_avail_delay instead of snd_pcm_delay here as it will + * update the buffer positions, and we can use the extra value against + * the buffer size to double check since the delay value may include + * fixed built-in delays in the processing chain and hardware. + */ + snd_pcm_sframes_t cFramesAvail = 0; + snd_pcm_sframes_t cFramesDelay = 0; + int rc = snd_pcm_avail_delay(pStreamALSA->hPCM, &cFramesAvail, &cFramesDelay); + + /* + * We now also get the state as the pending value should be zero when + * we're not in a playing state. + */ + snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM); + switch (enmState) + { + case SND_PCM_STATE_RUNNING: + case SND_PCM_STATE_DRAINING: + if (rc >= 0) + { + if ((uint32_t)cFramesAvail >= pStreamALSA->Cfg.Backend.cFramesBufferSize) + cbPending = 0; + else + cbPending = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesDelay); + } + break; + + default: + break; + } + Log2Func(("returns %u (%#x) - cFramesBufferSize=%RU32 cFramesAvail=%ld cFramesDelay=%ld rc=%d; enmState=%s (%d) \n", + cbPending, cbPending, pStreamALSA->Cfg.Backend.cFramesBufferSize, cFramesAvail, cFramesDelay, rc, + snd_pcm_state_name(enmState), enmState)); + } + return cbPending; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + + uint32_t cbAvail = 0; + snd_pcm_sframes_t cFramesAvail = 0; + int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail); + if (RT_SUCCESS(rc)) + cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail); + + return cbAvail; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf, + snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName)); + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + else + { + /* Fend off draining calls. */ + *pcbWritten = 0; + return VINF_SUCCESS; + } + + /* + * Determine how much we can write (caller actually did this + * already, but we repeat it just to be sure or something). + */ + snd_pcm_sframes_t cFramesAvail; + int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail); + if (RT_SUCCESS(rc)) + { + Assert(cFramesAvail); + if (cFramesAvail) + { + PCPDMAUDIOPCMPROPS pProps = &pStreamALSA->Cfg.Props; + uint32_t cbToWrite = PDMAudioPropsFramesToBytes(pProps, (uint32_t)cFramesAvail); + if (cbToWrite) + { + if (cbToWrite > cbBuf) + cbToWrite = cbBuf; + + /* + * Try write the data. + */ + uint32_t cFramesToWrite = PDMAudioPropsBytesToFrames(pProps, cbToWrite); + snd_pcm_sframes_t cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite); + if (cFramesWritten > 0) + { + Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n", + cbToWrite, cFramesWritten, cFramesAvail)); + *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten); + pStreamALSA->offInternal += *pcbWritten; + return VINF_SUCCESS; + } + LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld]\n", cbToWrite, cFramesWritten, cFramesAvail)); + + + /* + * There are a couple of error we can recover from, try to do so. + * Only don't try too many times. + */ + for (unsigned iTry = 0; + (cFramesWritten == -EPIPE || cFramesWritten == -ESTRPIPE) && iTry < ALSA_RECOVERY_TRIES_MAX; + iTry++) + { + if (cFramesWritten == -EPIPE) + { + /* Underrun occurred. */ + rc = drvHstAudAlsaStreamRecover(pStreamALSA->hPCM); + if (RT_FAILURE(rc)) + break; + LogFlowFunc(("Recovered from playback (iTry=%u)\n", iTry)); + } + else + { + /* An suspended event occurred, needs resuming. */ + rc = drvHstAudAlsaStreamResume(pStreamALSA->hPCM); + if (RT_FAILURE(rc)) + { + LogRel(("ALSA: Failed to resume output stream (iTry=%u, rc=%Rrc)\n", iTry, rc)); + break; + } + LogFlowFunc(("Resumed suspended output stream (iTry=%u)\n", iTry)); + } + + cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite); + if (cFramesWritten > 0) + { + Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n", + cbToWrite, cFramesWritten, cFramesAvail)); + *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten); + pStreamALSA->offInternal += *pcbWritten; + return VINF_SUCCESS; + } + LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld, iTry=%d]\n", cbToWrite, cFramesWritten, cFramesAvail, iTry)); + } + + /* Make sure we return with an error status. */ + if (RT_SUCCESS_NP(rc)) + { + if (cFramesWritten == 0) + rc = VERR_ACCESS_DENIED; + else + { + rc = RTErrConvertFromErrno(-(int)cFramesWritten); + LogFunc(("Failed to write %RU32 bytes: %ld (%Rrc)\n", cbToWrite, cFramesWritten, rc)); + } + } + } + } + } + else + LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc)); + *pcbWritten = 0; + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + + uint32_t cbAvail = 0; + snd_pcm_sframes_t cFramesAvail = 0; + int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail); + if (RT_SUCCESS(rc)) + cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail); + + return cbAvail; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHstAudAlsaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF_PV(pInterface); + PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream; + AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf, + snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName)); + + /* + * Figure out how much we can read without trouble (we're doing + * non-blocking reads, but whatever). + */ + snd_pcm_sframes_t cAvail; + int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cAvail); + if (RT_SUCCESS(rc)) + { + if (!cAvail) /* No data yet? */ + { + snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM); + switch (enmState) + { + case SND_PCM_STATE_PREPARED: + /** @todo r=bird: explain the logic here... */ + cAvail = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbBuf); + break; + + case SND_PCM_STATE_SUSPENDED: + rc = drvHstAudAlsaStreamResume(pStreamALSA->hPCM); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Resumed suspended input stream.\n")); + break; + } + LogFunc(("Failed resuming suspended input stream: %Rrc\n", rc)); + return rc; + + default: + LogFlow(("No frames available: state=%s (%d)\n", snd_pcm_state_name(enmState), enmState)); + break; + } + if (!cAvail) + { + *pcbRead = 0; + return VINF_SUCCESS; + } + } + } + else + { + LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc)); + return rc; + } + + size_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cAvail); + cbToRead = RT_MIN(cbToRead, cbBuf); + LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail)); + + /* + * Read loop. + */ + uint32_t cbReadTotal = 0; + while (cbToRead > 0) + { + /* + * Do the reading. + */ + snd_pcm_uframes_t const cFramesToRead = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbToRead); + AssertBreakStmt(cFramesToRead > 0, rc = VERR_NO_DATA); + + snd_pcm_sframes_t cFramesRead = snd_pcm_readi(pStreamALSA->hPCM, pvBuf, cFramesToRead); + if (cFramesRead > 0) + { + /* + * We should not run into a full mixer buffer or we lose samples and + * run into an endless loop if ALSA keeps producing samples ("null" + * capture device for example). + */ + uint32_t const cbRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesRead); + Assert(cbRead <= cbToRead); + + cbToRead -= cbRead; + cbReadTotal += cbRead; + pvBuf = (uint8_t *)pvBuf + cbRead; + pStreamALSA->offInternal += cbRead; + } + else + { + /* + * Try recover from overrun and re-try. + * Other conditions/errors we cannot and will just quit the loop. + */ + if (cFramesRead == -EPIPE) + { + rc = drvHstAudAlsaStreamRecover(pStreamALSA->hPCM); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Successfully recovered from overrun\n")); + continue; + } + LogFunc(("Failed to recover from overrun: %Rrc\n", rc)); + } + else if (cFramesRead == -EAGAIN) + LogFunc(("No input frames available (EAGAIN)\n")); + else if (cFramesRead == 0) + LogFunc(("No input frames available (0)\n")); + else + { + rc = RTErrConvertFromErrno(-(int)cFramesRead); + LogFunc(("Failed to read input frames: %s (%ld, %Rrc)\n", snd_strerror(cFramesRead), cFramesRead, rc)); + } + + /* If we've read anything, suppress the error. */ + if (RT_FAILURE(rc) && cbReadTotal > 0) + { + LogFunc(("Suppressing %Rrc because %#x bytes has been read already\n", rc, cbReadTotal)); + rc = VINF_SUCCESS; + } + break; + } + } + + LogFlowFunc(("returns %Rrc and %#x (%d) bytes (%u bytes left); state %s\n", + rc, cbReadTotal, cbReadTotal, cbToRead, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)))); + *pcbRead = cbReadTotal; + return rc; +} + + +/********************************************************************************************************************************* +* PDMIBASE * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHstAudAlsaQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMDRVREG,pfnDestruct, + * Destructs an ALSA host audio driver instance.} + */ +static DECLCALLBACK(void) drvHstAudAlsaDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA); + LogFlowFuncEnter(); + + if (RTCritSectIsInitialized(&pThis->CritSect)) + { + RTCritSectEnter(&pThis->CritSect); + pThis->pIHostAudioPort = NULL; + RTCritSectLeave(&pThis->CritSect); + RTCritSectDelete(&pThis->CritSect); + } + + LogFlowFuncLeave(); +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnConstruct, + * Construct an ALSA host audio driver instance.} + */ +static DECLCALLBACK(int) drvHstAudAlsaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA); + LogRel(("Audio: Initializing ALSA driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + int rc = RTCritSectInit(&pThis->CritSect); + AssertRCReturn(rc, rc); + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHstAudAlsaQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHstAudAlsaHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = drvHstAudAlsaHA_GetDevices; + pThis->IHostAudio.pfnSetDevice = drvHstAudAlsaHA_SetDevice; + pThis->IHostAudio.pfnGetStatus = drvHstAudAlsaHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHstAudAlsaHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHstAudAlsaHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHstAudAlsaHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHstAudAlsaHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHstAudAlsaHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHstAudAlsaHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHstAudAlsaHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetPending = drvHstAudAlsaHA_StreamGetPending; + pThis->IHostAudio.pfnStreamGetState = drvHstAudAlsaHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetWritable = drvHstAudAlsaHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvHstAudAlsaHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvHstAudAlsaHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvHstAudAlsaHA_StreamCapture; + + /* + * Read configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "OutputDeviceID|InputDeviceID", ""); + + rc = CFGMR3QueryStringDef(pCfg, "InputDeviceID", pThis->szInputDev, sizeof(pThis->szInputDev), "default"); + AssertRCReturn(rc, rc); + rc = CFGMR3QueryStringDef(pCfg, "OutputDeviceID", pThis->szOutputDev, sizeof(pThis->szOutputDev), "default"); + AssertRCReturn(rc, rc); + + /* + * Init the alsa library. + */ + rc = audioLoadAlsaLib(); + if (RT_FAILURE(rc)) + { + LogRel(("ALSA: Failed to load the ALSA shared library: %Rrc\n", rc)); + return rc; + } + + /* + * Query the notification interface from the driver/device above us. + */ + pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT); + AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE); + +#ifdef DEBUG + /* + * Some debug stuff we don't use for anything at all. + */ + snd_lib_error_set_handler(drvHstAudAlsaDbgErrorHandler); +#endif + return VINF_SUCCESS; +} + + +/** + * ALSA audio driver registration record. + */ +const PDMDRVREG g_DrvHostALSAAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "ALSAAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "ALSA host audio driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHSTAUDALSA), + /* pfnConstruct */ + drvHstAudAlsaConstruct, + /* pfnDestruct */ + drvHstAudAlsaDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,294 @@ +/* $Id: DrvHostAudioAlsaStubs.cpp $ */ +/** @file + * Stubs for libasound. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include +#include +#include + +#include +#include + +#include "DrvHostAudioAlsaStubs.h" + +#define VBOX_ALSA_LIB "libasound.so.2" + +#define PROXY_STUB(function, rettype, signature, shortsig) \ + static rettype (*pfn_ ## function) signature; \ + \ + extern "C" rettype VBox_##function signature; \ + rettype VBox_##function signature \ + { \ + return pfn_ ## function shortsig; \ + } + +PROXY_STUB(snd_lib_error_set_handler, int, (snd_lib_error_handler_t handler), + (handler)) +PROXY_STUB(snd_strerror, const char *, (int errnum), (errnum)) + +PROXY_STUB(snd_device_name_hint, int, + (int card, const char *iface, void ***hints), + (card, iface, hints)) +PROXY_STUB(snd_device_name_free_hint, int, + (void **hints), + (hints)) +PROXY_STUB(snd_device_name_get_hint, char *, + (const void *hint, const char *id), + (hint, id)) + +static int fallback_snd_device_name_hint(int card, const char *iface, void ***hints) +{ + RT_NOREF(card, iface); + *hints = NULL; + return -ENOSYS; +} + +static int fallback_snd_device_name_free_hint(void **hints) +{ + RT_NOREF(hints); + return 0; +} + +static char *fallback_snd_device_name_get_hint(const void *hint, const char *id) +{ + RT_NOREF(hint, id); + return NULL; +} + +/* + * PCM + */ + +PROXY_STUB(snd_pcm_avail_update, snd_pcm_sframes_t, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_avail_delay, int, + (snd_pcm_t *pcm, snd_pcm_sframes_t *availp, snd_pcm_sframes_t *delayp), + (pcm, availp, delayp)) +PROXY_STUB(snd_pcm_close, int, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_delay, int, (snd_pcm_t *pcm, snd_pcm_sframes_t *delayp), (pcm, delayp)) +PROXY_STUB(snd_pcm_nonblock, int, (snd_pcm_t *pcm, int *onoff), + (pcm, onoff)) +PROXY_STUB(snd_pcm_drain, int, (snd_pcm_t *pcm), + (pcm)) +PROXY_STUB(snd_pcm_drop, int, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_open, int, + (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode), + (pcm, name, stream, mode)) +PROXY_STUB(snd_pcm_prepare, int, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_readi, snd_pcm_sframes_t, + (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size), + (pcm, buffer, size)) +PROXY_STUB(snd_pcm_resume, int, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_set_chmap, int, (snd_pcm_t *pcm, snd_pcm_chmap_t const *map), (pcm, map)) +PROXY_STUB(snd_pcm_state, snd_pcm_state_t, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_state_name, const char *, (snd_pcm_state_t state), (state)) +PROXY_STUB(snd_pcm_writei, snd_pcm_sframes_t, + (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size), + (pcm, buffer, size)) +PROXY_STUB(snd_pcm_start, int, (snd_pcm_t *pcm), (pcm)) + +static int fallback_snd_pcm_avail_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *availp, snd_pcm_sframes_t *delayp) +{ + *availp = pfn_snd_pcm_avail_update(pcm); + int ret = pfn_snd_pcm_delay(pcm, delayp); + if (ret >= 0 && *availp < 0) + ret = (int)*availp; + return ret; +} + +static int fallback_snd_pcm_set_chmap(snd_pcm_t *pcm, snd_pcm_chmap_t const *map) +{ + RT_NOREF(pcm, map); + return 0; +} + +/* + * HW + */ + +PROXY_STUB(snd_pcm_hw_params, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params), + (pcm, params)) +PROXY_STUB(snd_pcm_hw_params_any, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params), + (pcm, params)) +PROXY_STUB(snd_pcm_hw_params_get_buffer_size, int, + (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), + (params, val)) +PROXY_STUB(snd_pcm_hw_params_get_buffer_size_min, int, + (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), + (params, val)) +PROXY_STUB(snd_pcm_hw_params_get_period_size, int, + (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir), + (params, frames, dir)) +PROXY_STUB(snd_pcm_hw_params_get_period_size_min, int, + (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir), + (params, frames, dir)) +PROXY_STUB(snd_pcm_hw_params_set_rate_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), + (pcm, params, val, dir)) +PROXY_STUB(snd_pcm_hw_params_set_access, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access), + (pcm, params, _access)) +PROXY_STUB(snd_pcm_hw_params_set_buffer_time_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), + (pcm, params, val, dir)) +PROXY_STUB(snd_pcm_hw_params_set_buffer_size_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), + (pcm, params, val)) +PROXY_STUB(snd_pcm_hw_params_set_channels_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val), + (pcm, params, val)) +PROXY_STUB(snd_pcm_hw_params_set_period_size_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir), + (pcm, params, val, dir)) +PROXY_STUB(snd_pcm_hw_params_set_period_time_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), + (pcm, params, val, dir)) +PROXY_STUB(snd_pcm_hw_params_sizeof, size_t, (void), ()) +PROXY_STUB(snd_pcm_hw_params_set_format, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val), + (pcm, params, val)) + +/* + * SW + */ + +PROXY_STUB(snd_pcm_sw_params, int, + (snd_pcm_t *pcm, snd_pcm_sw_params_t *params), + (pcm, params)) +PROXY_STUB(snd_pcm_sw_params_current, int, + (snd_pcm_t *pcm, snd_pcm_sw_params_t *params), + (pcm, params)) +PROXY_STUB(snd_pcm_sw_params_get_start_threshold, int, + (const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val), + (params, val)) +PROXY_STUB(snd_pcm_sw_params_set_avail_min, int, + (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val), + (pcm, params, val)) +PROXY_STUB(snd_pcm_sw_params_set_start_threshold, int, + (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val), + (pcm, params, val)) +PROXY_STUB(snd_pcm_sw_params_sizeof, size_t, (void), ()) + +typedef struct +{ + const char *name; + void (**pfn)(void); + void (*pfnFallback)(void); +} SHARED_FUNC; + +#define ELEMENT(function) { #function , (void (**)(void)) & pfn_ ## function, NULL } +#define ELEMENT_FALLBACK(function) { #function , (void (**)(void)) & pfn_ ## function, (void (*)(void))fallback_ ## function } +static SHARED_FUNC SharedFuncs[] = +{ + ELEMENT(snd_lib_error_set_handler), + ELEMENT(snd_strerror), + + ELEMENT_FALLBACK(snd_device_name_hint), + ELEMENT_FALLBACK(snd_device_name_get_hint), + ELEMENT_FALLBACK(snd_device_name_free_hint), + + ELEMENT(snd_pcm_avail_update), + ELEMENT_FALLBACK(snd_pcm_avail_delay), + ELEMENT(snd_pcm_close), + ELEMENT(snd_pcm_delay), + ELEMENT(snd_pcm_drain), + ELEMENT(snd_pcm_drop), + ELEMENT(snd_pcm_nonblock), + ELEMENT(snd_pcm_open), + ELEMENT(snd_pcm_prepare), + ELEMENT(snd_pcm_resume), + ELEMENT_FALLBACK(snd_pcm_set_chmap), + ELEMENT(snd_pcm_state), + ELEMENT(snd_pcm_state_name), + + ELEMENT(snd_pcm_readi), + ELEMENT(snd_pcm_start), + ELEMENT(snd_pcm_writei), + + ELEMENT(snd_pcm_hw_params), + ELEMENT(snd_pcm_hw_params_any), + ELEMENT(snd_pcm_hw_params_sizeof), + ELEMENT(snd_pcm_hw_params_get_buffer_size), + ELEMENT(snd_pcm_hw_params_get_buffer_size_min), + ELEMENT(snd_pcm_hw_params_get_period_size_min), + ELEMENT(snd_pcm_hw_params_set_access), + ELEMENT(snd_pcm_hw_params_set_buffer_size_near), + ELEMENT(snd_pcm_hw_params_set_buffer_time_near), + ELEMENT(snd_pcm_hw_params_set_channels_near), + ELEMENT(snd_pcm_hw_params_set_format), + ELEMENT(snd_pcm_hw_params_get_period_size), + ELEMENT(snd_pcm_hw_params_set_period_size_near), + ELEMENT(snd_pcm_hw_params_set_period_time_near), + ELEMENT(snd_pcm_hw_params_set_rate_near), + + ELEMENT(snd_pcm_sw_params), + ELEMENT(snd_pcm_sw_params_current), + ELEMENT(snd_pcm_sw_params_get_start_threshold), + ELEMENT(snd_pcm_sw_params_set_avail_min), + ELEMENT(snd_pcm_sw_params_set_start_threshold), + ELEMENT(snd_pcm_sw_params_sizeof), +}; +#undef ELEMENT + +/** Init once. */ +static RTONCE g_AlsaLibInitOnce = RTONCE_INITIALIZER; + + +/** @callback_method_impl{FNRTONCE} */ +static DECLCALLBACK(int32_t) drvHostAudioAlsaLibInitOnce(void *pvUser) +{ + RT_NOREF(pvUser); + LogFlowFunc(("\n")); + + RTLDRMOD hMod = NIL_RTLDRMOD; + int rc = RTLdrLoadSystemEx(VBOX_ALSA_LIB, RTLDRLOAD_FLAGS_NO_UNLOAD, &hMod); + if (RT_SUCCESS(rc)) + { + for (uintptr_t i = 0; i < RT_ELEMENTS(SharedFuncs); i++) + { + rc = RTLdrGetSymbol(hMod, SharedFuncs[i].name, (void **)SharedFuncs[i].pfn); + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (SharedFuncs[i].pfnFallback && rc == VERR_SYMBOL_NOT_FOUND) + *SharedFuncs[i].pfn = SharedFuncs[i].pfnFallback; + else + { + LogRelFunc(("Failed to load library %s: Getting symbol %s failed: %Rrc\n", VBOX_ALSA_LIB, SharedFuncs[i].name, rc)); + return rc; + } + } + } + else + LogRelFunc(("Failed to load library %s (%Rrc)\n", VBOX_ALSA_LIB, rc)); + return rc; +} + + +/** + * Try to dynamically load the ALSA libraries. + * + * @returns VBox status code. + */ +int audioLoadAlsaLib(void) +{ + LogFlowFunc(("\n")); + return RTOnce(&g_AlsaLibInitOnce, drvHostAudioAlsaLibInitOnce, NULL); +} + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,62 @@ +/* $Id: DrvHostAudioAlsaStubs.h $ */ +/** @file + * Stubs for libasound. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubs_h +#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include + +#define VBOX_ALSA_MAKE_VER(a,b,c) ( ((a) << 24) | ((b) << 16) | (c) ) +#define VBOX_ALSA_VER VBOX_ALSA_MAKE_VER(SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR) + +RT_C_DECLS_BEGIN +extern int audioLoadAlsaLib(void); + +#if VBOX_ALSA_VER < VBOX_ALSA_MAKE_VER(1,0,18) /* added in 1.0.18 */ +extern int snd_pcm_avail_delay(snd_pcm_t *, snd_pcm_sframes_t *, snd_pcm_sframes_t *); +#endif + +#if VBOX_ALSA_VER < VBOX_ALSA_MAKE_VER(1,0,14) /* added in 1.0.14a */ +extern int snd_device_name_hint(int, const char *, void ***); +extern int snd_device_name_free_hint(void **); +extern char *snd_device_name_get_hint(const void *, const char *); +#endif + +#if VBOX_ALSA_VER < VBOX_ALSA_MAKE_VER(1,0,27) /* added in 1.0.27 */ +enum snd_pcm_chmap_position { SND_CHMAP_UNKNOWN = 0, SND_CHMAP_NA, SND_CHMAP_MONO, SND_CHMAP_FL, SND_CHMAP_FR, + SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_SL, SND_CHMAP_SR, SND_CHMAP_RC, + SND_CHMAP_FLC, SND_CHMAP_FRC, SND_CHMAP_RLC, SND_CHMAP_RRC, SND_CHMAP_FLW, SND_CHMAP_FRW, SND_CHMAP_FLH, + SND_CHMAP_FCH, SND_CHMAP_FRH, SND_CHMAP_TC, SND_CHMAP_TFL, SND_CHMAP_TFR, SND_CHMAP_TFC, SND_CHMAP_TRL, + SND_CHMAP_TRR, SND_CHMAP_TRC, SND_CHMAP_TFLC, SND_CHMAP_TFRC, SND_CHMAP_TSL, SND_CHMAP_TSR, SND_CHMAP_LLFE, + SND_CHMAP_RLFE, SND_CHMAP_BC, SND_CHMAP_BLC, SND_CHMAP_BRC }; +typedef struct snd_pcm_chmap +{ + unsigned int channels; + RT_GCC_EXTENSION + unsigned int pos[RT_FLEXIBLE_ARRAY_IN_NESTED_UNION]; +} snd_pcm_chmap_t; +extern int snd_pcm_set_chmap(snd_pcm_t *, snd_pcm_chmap_t const *); +#endif + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubs_h */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubsMangling.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubsMangling.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubsMangling.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioAlsaStubsMangling.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,75 @@ +/* $Id: DrvHostAudioAlsaStubsMangling.h $ */ +/** @file + * Mangle libasound symbols. + * + * This is necessary on hosts which don't support the -fvisibility gcc switch. + */ + +/* + * Copyright (C) 2013-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubsMangling_h +#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubsMangling_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define ALSA_MANGLER(symbol) VBox_##symbol + +#define snd_lib_error_set_handler ALSA_MANGLER(snd_lib_error_set_handler) +#define snd_strerror ALSA_MANGLER(snd_strerror) + +#define snd_device_name_hint ALSA_MANGLER(snd_device_name_hint) +#define snd_device_name_get_hint ALSA_MANGLER(snd_device_name_get_hint) +#define snd_device_name_free_hint ALSA_MANGLER(snd_device_name_free_hint) + +#define snd_pcm_avail_update ALSA_MANGLER(snd_pcm_avail_update) +#define snd_pcm_close ALSA_MANGLER(snd_pcm_close) +#define snd_pcm_avail_delay ALSA_MANGLER(snd_pcm_avail_delay) +#define snd_pcm_delay ALSA_MANGLER(snd_pcm_delay) +#define snd_pcm_drain ALSA_MANGLER(snd_pcm_drain) +#define snd_pcm_drop ALSA_MANGLER(snd_pcm_drop) +#define snd_pcm_nonblock ALSA_MANGLER(snd_pcm_nonblock) +#define snd_pcm_open ALSA_MANGLER(snd_pcm_open) +#define snd_pcm_prepare ALSA_MANGLER(snd_pcm_prepare) +#define snd_pcm_readi ALSA_MANGLER(snd_pcm_readi) +#define snd_pcm_resume ALSA_MANGLER(snd_pcm_resume) +#define snd_pcm_set_chmap ALSA_MANGLER(snd_pcm_set_chmap) +#define snd_pcm_start ALSA_MANGLER(snd_pcm_start) +#define snd_pcm_state ALSA_MANGLER(snd_pcm_state) +#define snd_pcm_state_name ALSA_MANGLER(snd_pcm_state_name) +#define snd_pcm_writei ALSA_MANGLER(snd_pcm_writei) + +#define snd_pcm_hw_params ALSA_MANGLER(snd_pcm_hw_params) +#define snd_pcm_hw_params_any ALSA_MANGLER(snd_pcm_hw_params_any) +#define snd_pcm_hw_params_sizeof ALSA_MANGLER(snd_pcm_hw_params_sizeof) +#define snd_pcm_hw_params_get_buffer_size ALSA_MANGLER(snd_pcm_hw_params_get_buffer_size) +#define snd_pcm_hw_params_get_period_size_min ALSA_MANGLER(snd_pcm_hw_params_get_period_size_min) +#define snd_pcm_hw_params_set_rate_near ALSA_MANGLER(snd_pcm_hw_params_set_rate_near) +#define snd_pcm_hw_params_set_access ALSA_MANGLER(snd_pcm_hw_params_set_access) +#define snd_pcm_hw_params_set_buffer_time_near ALSA_MANGLER(snd_pcm_hw_params_set_buffer_time_near) +#define snd_pcm_hw_params_set_buffer_size_near ALSA_MANGLER(snd_pcm_hw_params_set_buffer_size_near) +#define snd_pcm_hw_params_get_buffer_size_min ALSA_MANGLER(snd_pcm_hw_params_get_buffer_size_min) +#define snd_pcm_hw_params_set_channels_near ALSA_MANGLER(snd_pcm_hw_params_set_channels_near) +#define snd_pcm_hw_params_set_format ALSA_MANGLER(snd_pcm_hw_params_set_format) +#define snd_pcm_hw_params_get_period_size ALSA_MANGLER(snd_pcm_hw_params_get_period_size) +#define snd_pcm_hw_params_set_period_size_near ALSA_MANGLER(snd_pcm_hw_params_set_period_size_near) +#define snd_pcm_hw_params_set_period_time_near ALSA_MANGLER(snd_pcm_hw_params_set_period_time_near) + +#define snd_pcm_sw_params ALSA_MANGLER(snd_pcm_sw_params) +#define snd_pcm_sw_params_current ALSA_MANGLER(snd_pcm_sw_params_current) +#define snd_pcm_sw_params_get_start_threshold ALSA_MANGLER(snd_pcm_sw_params_get_start_threshold) +#define snd_pcm_sw_params_set_avail_min ALSA_MANGLER(snd_pcm_sw_params_set_avail_min) +#define snd_pcm_sw_params_set_start_threshold ALSA_MANGLER(snd_pcm_sw_params_set_start_threshold) +#define snd_pcm_sw_params_sizeof ALSA_MANGLER(snd_pcm_sw_params_sizeof) + +#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubsMangling_h */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioCoreAudioAuth.mm virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioCoreAudioAuth.mm --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioCoreAudioAuth.mm 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioCoreAudioAuth.mm 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,140 @@ +/* $Id: DrvHostAudioCoreAudioAuth.mm $ */ +/** @file + * Host audio driver - Mac OS X CoreAudio, authorization helpers for Mojave+. + */ + +/* + * Copyright (C) 2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include + +#include +#include + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 +# import +# import +#endif +#import + + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 + +/* HACK ALERT! It's there in the 10.13 SDK, but only for iOS 7.0+. Deploying CPP trickery to shut up warnings/errors. */ +# if MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 +# define AVAuthorizationStatus OurAVAuthorizationStatus +# define AVAuthorizationStatusNotDetermined OurAVAuthorizationStatusNotDetermined +# define AVAuthorizationStatusRestricted OurAVAuthorizationStatusRestricted +# define AVAuthorizationStatusDenied OurAVAuthorizationStatusDenied +# define AVAuthorizationStatusAuthorized OurAVAuthorizationStatusAuthorized +# endif + +/** + * The authorization status enum. + * + * Starting macOS 10.14 we need to request permissions in order to use any audio input device + * but as we build against an older SDK where this is not available we have to duplicate + * AVAuthorizationStatus and do everything dynmically during runtime, sigh... + */ +typedef enum AVAuthorizationStatus +# if RT_CPLUSPLUS_PREREQ(201100) + : NSInteger +# endif +{ + AVAuthorizationStatusNotDetermined = 0, + AVAuthorizationStatusRestricted = 1, + AVAuthorizationStatusDenied = 2, + AVAuthorizationStatusAuthorized = 3 +} AVAuthorizationStatus; + +#endif + + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 /** @todo need some better fix/whatever for AudioTest */ + +/** + * Requests camera permissions for Mojave and onwards. + * + * @returns VBox status code. + */ +static int coreAudioInputPermissionRequest(void) +{ + __block RTSEMEVENT hEvt = NIL_RTSEMEVENT; + __block int rc = RTSemEventCreate(&hEvt); + if (RT_SUCCESS(rc)) + { + /* Perform auth request. */ + [AVCaptureDevice performSelector: @selector(requestAccessForMediaType: completionHandler:) + withObject: (id)AVMediaTypeAudio + withObject: (id)^(BOOL granted) + { + if (!granted) + { + LogRel(("CoreAudio: Access denied!\n")); + rc = VERR_ACCESS_DENIED; + } + RTSemEventSignal(hEvt); + }]; + + rc = RTSemEventWait(hEvt, RT_MS_10SEC); + RTSemEventDestroy(hEvt); + } + + return rc; +} + +#endif + +/** + * Checks permission for capturing devices on Mojave and onwards. + * + * @returns VBox status code. + */ +DECLHIDDEN(int) coreAudioInputPermissionCheck(void) +{ + int rc = VINF_SUCCESS; + + if (NSFoundationVersionNumber >= 10.14) + { + /* + * Because we build with an older SDK where the authorization APIs are not available + * (introduced with Mojave 10.14) we have to resort to resolving the APIs dynamically. + */ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 /** @todo need some better fix/whatever for AudioTest */ + LogRel(("CoreAudio: macOS 10.14+ detected, checking audio input permissions\n")); + + if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)]) + { + AVAuthorizationStatus enmAuthSts + = (AVAuthorizationStatus)(NSInteger)[AVCaptureDevice performSelector: @selector(authorizationStatusForMediaType:) + withObject: (id)AVMediaTypeAudio]; + if (enmAuthSts == AVAuthorizationStatusNotDetermined) + rc = coreAudioInputPermissionRequest(); + else if ( enmAuthSts == AVAuthorizationStatusRestricted + || enmAuthSts == AVAuthorizationStatusDenied) + { + LogRel(("CoreAudio: Access denied!\n")); + rc = VERR_ACCESS_DENIED; + } + } +#else + LogRel(("CoreAudio: WARNING! macOS 10.14+ detected. Audio input probably wont work as this app was compiled using a too old SDK.\n")); +#endif + } + + return rc; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,2923 @@ +/* $Id: DrvHostAudioCoreAudio.cpp $ */ +/** @file + * Host audio driver - Mac OS X CoreAudio. + * + * For relevant Apple documentation, here are some starters: + * - Core Audio Essentials + * https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html + * - TN2097: Playing a sound file using the Default Output Audio Unit + * https://developer.apple.com/library/archive/technotes/tn2097/ + * - TN2091: Device input using the HAL Output Audio Unit + * https://developer.apple.com/library/archive/technotes/tn2091/ + * - Audio Component Services + * https://developer.apple.com/documentation/audiounit/audio_component_services?language=objc + * - QA1533: How to handle kAudioUnitProperty_MaximumFramesPerSlice + * https://developer.apple.com/library/archive/qa/qa1533/ + * - QA1317: Signaling the end of data when using AudioConverterFillComplexBuffer + * https://developer.apple.com/library/archive/qa/qa1317/ + */ + +/* + * Copyright (C) 2010-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include + +#include "VBoxDD.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090 /* possibly 1080 */ +# define kAudioHardwarePropertyTranslateUIDToDevice (AudioObjectPropertySelector)'uidd' +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The max number of queue buffers we'll use. */ +#define COREAUDIO_MAX_BUFFERS 1024 +/** The minimum number of queue buffers. */ +#define COREAUDIO_MIN_BUFFERS 4 + +/** Enables the worker thread. + * This saves CoreAudio from creating an additional thread upon queue + * creation. (It does not help with the slow AudioQueueDispose fun.) */ +#define CORE_AUDIO_WITH_WORKER_THREAD +#if 0 +/** Enables the AudioQueueDispose breakpoint timer (debugging help). */ +# define CORE_AUDIO_WITH_BREAKPOINT_TIMER +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the instance data for a Core Audio driver instance. */ +typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO; +/** Pointer to the Core Audio specific backend data for an audio stream. */ +typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM; + +/** + * Core Audio device entry (enumeration). + * + * @note This is definitely not safe to just copy! + */ +typedef struct COREAUDIODEVICEDATA +{ + /** The core PDM structure. */ + PDMAUDIOHOSTDEV Core; + + /** The audio device ID of the currently used device (UInt32 typedef). */ + AudioDeviceID idDevice; +} COREAUDIODEVICEDATA; +/** Pointer to a Core Audio device entry (enumeration). */ +typedef COREAUDIODEVICEDATA *PCOREAUDIODEVICEDATA; + + +/** + * Audio device information. + * + * We do not use COREAUDIODEVICEDATA here as it contains lots more than what we + * need and care to query. We also don't want to depend on DrvAudio making + * PDMIHOSTAUDIO::pfnGetDevices callbacks to keep this information up to date. + */ +typedef struct DRVHSTAUDCADEVICE +{ + /** The audio device ID. kAudioDeviceUnknown if not available. */ + AudioObjectID idDevice; + /** Indicates whether we've registered device change listener. */ + bool fRegisteredListeners; + /** The UID string (must release). NULL if not available. */ + CFStringRef hStrUid; + /** The UID string for a specific device, NULL if we're using the default device. */ + char *pszSpecific; +} DRVHSTAUDCADEVICE; +/** Pointer to info about a default device. */ +typedef DRVHSTAUDCADEVICE *PDRVHSTAUDCADEVICE; + + +/** + * Core Audio stream state. + */ +typedef enum COREAUDIOINITSTATE +{ + /** The device is uninitialized. */ + COREAUDIOINITSTATE_UNINIT = 0, + /** The device is currently initializing. */ + COREAUDIOINITSTATE_IN_INIT, + /** The device is initialized. */ + COREAUDIOINITSTATE_INIT, + /** The device is currently uninitializing. */ + COREAUDIOINITSTATE_IN_UNINIT, + /** The usual 32-bit hack. */ + COREAUDIOINITSTATE_32BIT_HACK = 0x7fffffff +} COREAUDIOINITSTATE; + + +/** + * Core audio buffer tracker. + * + * For output buffer we'll be using AudioQueueBuffer::mAudioDataByteSize to + * track how much we've written. When a buffer is full, or if we run low on + * queued bufferes, it will be queued. + * + * For input buffer we'll be using offRead to track how much we've read. + * + * The queued/not-queued state is stored in the first bit of + * AudioQueueBuffer::mUserData. While bits 8 and up holds the index into + * COREAUDIOSTREAM::paBuffers. + */ +typedef struct COREAUDIOBUF +{ + /** The buffer. */ + AudioQueueBufferRef pBuf; + /** The buffer read offset (input only). */ + uint32_t offRead; +} COREAUDIOBUF; +/** Pointer to a core audio buffer tracker. */ +typedef COREAUDIOBUF *PCOREAUDIOBUF; + + +/** + * Core Audio specific data for an audio stream. + */ +typedef struct COREAUDIOSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** List node for the device's stream list. */ + RTLISTNODE Node; + /** The acquired (final) audio format for this stream. + * @note This what the device requests, we don't alter anything. */ + AudioStreamBasicDescription BasicStreamDesc; + /** The actual audio queue being used. */ + AudioQueueRef hAudioQueue; + + /** Number of buffers. */ + uint32_t cBuffers; + /** The array of buffer. */ + PCOREAUDIOBUF paBuffers; + + /** Initialization status tracker, actually COREAUDIOINITSTATE. + * Used when some of the device parameters or the device itself is changed + * during the runtime. */ + volatile uint32_t enmInitState; + /** The current buffer being written to / read from. */ + uint32_t idxBuffer; + /** Set if the stream is enabled. */ + bool fEnabled; + /** Set if the stream is started (playing/capturing). */ + bool fStarted; + /** Set if the stream is draining (output only). */ + bool fDraining; + /** Set if we should restart the stream on resume (saved pause state). */ + bool fRestartOnResume; +// /** Set if we're switching to a new output/input device. */ +// bool fSwitchingDevice; + /** Internal stream offset (bytes). */ + uint64_t offInternal; + /** The RTTimeMilliTS() at the end of the last transfer. */ + uint64_t msLastTransfer; + + /** Critical section for serializing access between thread + callbacks. */ + RTCRITSECT CritSect; + /** Buffer that drvHstAudCaStreamStatusString uses. */ + char szStatus[64]; +} COREAUDIOSTREAM; + + +/** + * Instance data for a Core Audio host audio driver. + * + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTCOREAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** The input device. */ + DRVHSTAUDCADEVICE InputDevice; + /** The output device. */ + DRVHSTAUDCADEVICE OutputDevice; + /** Upwards notification interface. */ + PPDMIHOSTAUDIOPORT pIHostAudioPort; + /** Indicates whether we've registered default input device change listener. */ + bool fRegisteredDefaultInputListener; + /** Indicates whether we've registered default output device change listener. */ + bool fRegisteredDefaultOutputListener; + +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + /** @name Worker Thread For Queue callbacks and stuff. + * @{ */ + /** The worker thread. */ + RTTHREAD hThread; + /** The runloop of the worker thread. */ + CFRunLoopRef hThreadRunLoop; + /** The message port we use to talk to the thread. + * @note While we don't currently use the port, it is necessary to prevent + * the thread from spinning or stopping prematurely because of + * CFRunLoopRunInMode returning kCFRunLoopRunFinished. */ + CFMachPortRef hThreadPort; + /** Runloop source for hThreadPort. */ + CFRunLoopSourceRef hThreadPortSrc; + /** @} */ +#endif + + /** Critical section to serialize access. */ + RTCRITSECT CritSect; +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + /** Timder for debugging AudioQueueDispose slowness. */ + RTTIMERLR hBreakpointTimer; +#endif +} DRVHOSTCOREAUDIO; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify); + +/* DrvHostAudioCoreAudioAuth.mm: */ +DECLHIDDEN(int) coreAudioInputPermissionCheck(void); + + +#ifdef LOG_ENABLED +/** + * Gets the stream status. + * + * @returns Pointer to stream status string. + * @param pStreamCA The stream to get the status for. + */ +static const char *drvHstAudCaStreamStatusString(PCOREAUDIOSTREAM pStreamCA) +{ + static RTSTRTUPLE const s_aInitState[5] = + { + { RT_STR_TUPLE("UNINIT") }, + { RT_STR_TUPLE("IN_INIT") }, + { RT_STR_TUPLE("INIT") }, + { RT_STR_TUPLE("IN_UNINIT") }, + { RT_STR_TUPLE("BAD") }, + }; + uint32_t enmInitState = pStreamCA->enmInitState; + PCRTSTRTUPLE pTuple = &s_aInitState[RT_MIN(enmInitState, RT_ELEMENTS(s_aInitState) - 1)]; + memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch); + size_t off = pTuple->cch; + + static RTSTRTUPLE const s_aEnable[2] = + { + { RT_STR_TUPLE("DISABLED") }, + { RT_STR_TUPLE("ENABLED ") }, + }; + pTuple = &s_aEnable[pStreamCA->fEnabled]; + memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch); + off += pTuple->cch; + + static RTSTRTUPLE const s_aStarted[2] = + { + { RT_STR_TUPLE(" STOPPED") }, + { RT_STR_TUPLE(" STARTED") }, + }; + pTuple = &s_aStarted[pStreamCA->fStarted]; + memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch); + off += pTuple->cch; + + static RTSTRTUPLE const s_aDraining[2] = + { + { RT_STR_TUPLE("") }, + { RT_STR_TUPLE(" DRAINING") }, + }; + pTuple = &s_aDraining[pStreamCA->fDraining]; + memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch); + off += pTuple->cch; + + Assert(off < sizeof(pStreamCA->szStatus)); + pStreamCA->szStatus[off] = '\0'; + return pStreamCA->szStatus; +} +#endif /*LOG_ENABLED*/ + + + + +#if 0 /* unused */ +static int drvHstAudCaCFStringToCString(const CFStringRef pCFString, char **ppszString) +{ + CFIndex cLen = CFStringGetLength(pCFString) + 1; + char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char)); + if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8)) + { + RTMemFree(pszResult); + return VERR_NOT_FOUND; + } + + *ppszString = pszResult; + return VINF_SUCCESS; +} + +static AudioDeviceID drvHstAudCaDeviceUIDtoID(const char* pszUID) +{ + /* Create a CFString out of our CString. */ + CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman); + + /* Fill the translation structure. */ + AudioDeviceID deviceID; + + AudioValueTranslation translation; + translation.mInputData = &strUID; + translation.mInputDataSize = sizeof(CFStringRef); + translation.mOutputData = &deviceID; + translation.mOutputDataSize = sizeof(AudioDeviceID); + + /* Fetch the translation from the UID to the device ID. */ + AudioObjectPropertyAddress PropAddr = + { + kAudioHardwarePropertyDeviceForUID, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + UInt32 uSize = sizeof(AudioValueTranslation); + OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, 0, NULL, &uSize, &translation); + + /* Release the temporary CFString */ + CFRelease(strUID); + + if (RT_LIKELY(err == noErr)) + return deviceID; + + /* Return the unknown device on error. */ + return kAudioDeviceUnknown; +} +#endif /* unused */ + + +/** + * Wrapper around AudioObjectGetPropertyData and AudioObjectGetPropertyDataSize. + * + * @returns Pointer to temp heap allocation with the data on success, free using + * RTMemTmpFree. NULL on failure, fully logged. + */ +static void *drvHstAudCaGetPropertyDataEx(AudioObjectID idObject, AudioObjectPropertySelector enmSelector, + AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement, + const char *pszWhat, UInt32 *pcb) +{ + AudioObjectPropertyAddress const PropAddr = + { + /*.mSelector = */ enmSelector, + /*.mScope = */ enmScope, + /*.mElement = */ enmElement + }; + + /* + * Have to retry here in case the size isn't stable (like if a new device/whatever is added). + */ + for (uint32_t iTry = 0; ; iTry++) + { + UInt32 cb = 0; + OSStatus orc = AudioObjectGetPropertyDataSize(idObject, &PropAddr, 0, NULL, &cb); + if (orc == noErr) + { + cb = RT_MAX(cb, 1); /* we're allergic to zero allocations. */ + void *pv = RTMemTmpAllocZ(cb); + if (pv) + { + orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv); + if (orc == noErr) + { + Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n", + idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb)); + if (pcb) + *pcb = cb; + return pv; + } + + RTMemTmpFree(pv); + LogFunc(("AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) -> %#x, iTry=%d\n", + idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc, iTry)); + if (iTry < 3) + continue; + LogRelMax(32, ("CoreAudio: AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) failed: %#x\n", + idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc)); + } + else + LogRelMax(32, ("CoreAudio: Failed to allocate %#x bytes (to get %s for %s).\n", cb, pszWhat, idObject)); + } + else + LogRelMax(32, ("CoreAudio: Failed to get %s for %u: %#x\n", pszWhat, idObject, orc)); + if (pcb) + *pcb = 0; + return NULL; + } +} + + +/** + * Wrapper around AudioObjectGetPropertyData. + * + * @returns Success indicator. Failures (@c false) are fully logged. + */ +static bool drvHstAudCaGetPropertyData(AudioObjectID idObject, AudioObjectPropertySelector enmSelector, + AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement, + const char *pszWhat, void *pv, UInt32 cb) +{ + AudioObjectPropertyAddress const PropAddr = + { + /*.mSelector = */ enmSelector, + /*.mScope = */ enmScope, + /*.mElement = */ enmElement + }; + + OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv); + if (orc == noErr) + { + Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n", idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb)); + return true; + } + LogRelMax(64, ("CoreAudio: Failed to query %s (%u/%#x/%#x/%x, cb=%#x): %#x\n", + pszWhat, idObject, enmSelector, enmScope, enmElement, cb, orc)); + return false; +} + + +/** + * Count the number of channels in one direction. + * + * @returns Channel count. + */ +static uint32_t drvHstAudCaEnumCountChannels(AudioObjectID idObject, AudioObjectPropertyScope enmScope) +{ + uint32_t cChannels = 0; + + AudioBufferList *pBufs + = (AudioBufferList *)drvHstAudCaGetPropertyDataEx(idObject, kAudioDevicePropertyStreamConfiguration, + enmScope, kAudioObjectPropertyElementMaster, "stream config", NULL); + if (pBufs) + { + UInt32 idxBuf = pBufs->mNumberBuffers; + while (idxBuf-- > 0) + { + Log9Func(("%u/%#x[%u]: %u\n", idObject, enmScope, idxBuf, pBufs->mBuffers[idxBuf].mNumberChannels)); + cChannels += pBufs->mBuffers[idxBuf].mNumberChannels; + } + + RTMemTmpFree(pBufs); + } + + return cChannels; +} + + +/** + * Translates a UID to an audio device ID. + * + * @returns Audio device ID on success, kAudioDeviceUnknown on failure. + * @param hStrUid The UID string to convert. + * @param pszUid The C-string vresion of @a hStrUid. + * @param pszWhat What we're converting (for logging). + */ +static AudioObjectID drvHstAudCaDeviceUidToId(CFStringRef hStrUid, const char *pszUid, const char *pszWhat) +{ + AudioObjectPropertyAddress const PropAddr = + { + /*.mSelector = */ kAudioHardwarePropertyTranslateUIDToDevice, + /*.mScope = */ kAudioObjectPropertyScopeGlobal, + /*.mElement = */ kAudioObjectPropertyElementMaster + }; + AudioObjectID idDevice = 0; + UInt32 cb = sizeof(idDevice); + OSStatus orc = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, + sizeof(hStrUid), &hStrUid, &cb, &idDevice); + if (orc == noErr) + { + Log9Func(("%s device UID '%s' -> %RU32\n", pszWhat, pszUid, idDevice)); + return idDevice; + } + /** @todo test on < 10.9, see which status code and do a fallback using the + * enumeration code. */ + LogRelMax(64, ("CoreAudio: Failed to translate %s device UID '%s' to audio device ID: %#x\n", pszWhat, pszUid, orc)); + return kAudioDeviceUnknown; +} + + +/** + * Copies a CFString to a buffer (UTF-8). + * + * @returns VBox status code. In the case of a buffer overflow, the buffer will + * contain data and be correctly terminated (provided @a cbDst is not + * zero.) + */ +static int drvHstAudCaCFStringToBuf(CFStringRef hStr, char *pszDst, size_t cbDst) +{ + AssertReturn(cbDst > 0, VERR_BUFFER_OVERFLOW); + + if (CFStringGetCString(hStr, pszDst, cbDst, kCFStringEncodingUTF8)) + return VINF_SUCCESS; + + /* First fallback: */ + const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8); + if (pszSrc) + return RTStrCopy(pszDst, cbDst, pszSrc); + + /* Second fallback: */ + CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1; + AssertReturn(cbMax > 0, VERR_INVALID_UTF8_ENCODING); + AssertReturn(cbMax < (CFIndex)_16M, VERR_OUT_OF_RANGE); + + char *pszTmp = (char *)RTMemTmpAlloc(cbMax); + AssertReturn(pszTmp, VERR_NO_TMP_MEMORY); + + int rc; + if (CFStringGetCString(hStr, pszTmp, cbMax, kCFStringEncodingUTF8)) + rc = RTStrCopy(pszDst, cbDst, pszTmp); + else + { + *pszDst = '\0'; + rc = VERR_INVALID_UTF8_ENCODING; + } + + RTMemTmpFree(pszTmp); + return rc; +} + + +/** + * Copies a CFString to a heap buffer (UTF-8). + * + * @returns Pointer to the heap buffer on success, NULL if out of heap or some + * conversion/extraction problem + */ +static char *drvHstAudCaCFStringToHeap(CFStringRef hStr) +{ + const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8); + if (pszSrc) + return RTStrDup(pszSrc); + + /* Fallback: */ + CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1; + AssertReturn(cbMax > 0, NULL); + AssertReturn(cbMax < (CFIndex)_16M, NULL); + + char *pszDst = RTStrAlloc(cbMax); + if (pszDst) + { + AssertReturnStmt(CFStringGetCString(hStr, pszDst, cbMax, kCFStringEncodingUTF8), RTStrFree(pszDst), NULL); + size_t const cchDst = strlen(pszDst); + if (cbMax - cchDst > 32) + RTStrRealloc(&pszDst, cchDst + 1); + } + return pszDst; +} + + +/********************************************************************************************************************************* +* Device Change Notification Callbacks * +*********************************************************************************************************************************/ + +#ifdef LOG_ENABLED +/** + * Called when the kAudioDevicePropertyNominalSampleRate or + * kAudioDeviceProcessorOverload properties changes on a default device. + * + * Registered on default devices after device enumeration. + * Not sure on which thread/runloop this runs. + * + * (See AudioObjectPropertyListenerProc in the SDK headers.) + */ +static OSStatus drvHstAudCaDevicePropertyChangedCallback(AudioObjectID idObject, UInt32 cAddresses, + const AudioObjectPropertyAddress paAddresses[], void *pvUser) +{ + LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pvUser=%p\n", idObject, idObject, cAddresses, pvUser)); + for (UInt32 idx = 0; idx < cAddresses; idx++) + LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n", + idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement)); + +/** @todo r=bird: What's the plan here exactly? I've changed it to + * LOG_ENABLED only for now, as this has no other purpose. */ + switch (idObject) + { + case kAudioDeviceProcessorOverload: + LogFunc(("Processor overload detected!\n")); + break; + case kAudioDevicePropertyNominalSampleRate: + LogFunc(("kAudioDevicePropertyNominalSampleRate!\n")); + break; + default: + /* Just skip. */ + break; + } + + return noErr; +} +#endif /* LOG_ENABLED */ + + +/** + * Called when the kAudioDevicePropertyDeviceIsAlive property changes on a + * default device. + * + * The purpose is mainly to log the event. There isn't much we can do about + * active streams or future one, other than waiting for a default device change + * notification callback. In the mean time, active streams should start failing + * to work and new ones fail on creation. This is the same for when we're + * configure to use specific devices, only we don't get any device change + * callback like for default ones. + * + * Not sure on which thread/runloop this runs. + * + * (See AudioObjectPropertyListenerProc in the SDK headers.) + */ +static OSStatus drvHstAudCaDeviceIsAliveChangedCallback(AudioObjectID idObject, UInt32 cAddresses, + const AudioObjectPropertyAddress paAddresses[], void *pvUser) +{ + PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser; + AssertPtr(pThis); + RT_NOREF(cAddresses, paAddresses); + + /* + * Log everything. + */ + LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses)); + for (UInt32 idx = 0; idx < cAddresses; idx++) + LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n", + idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement)); + + /* + * Check which devices are affected. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, noErr); /* could be a destruction race */ + + for (unsigned i = 0; i < 2; i++) + { + if (idObject == (i == 0 ? pThis->InputDevice.idDevice : pThis->OutputDevice.idDevice)) + { + AudioObjectPropertyAddress const PropAddr = + { + kAudioDevicePropertyDeviceIsAlive, + i == 0 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + UInt32 fAlive = 0; + UInt32 cb = sizeof(fAlive); + OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, &fAlive); + if ( orc == kAudioHardwareBadDeviceError + || (orc == noErr && !fAlive)) + { + LogRel(("CoreAudio: The default %s device (%u) stopped functioning.\n", idObject, i == 0 ? "input" : "output")); +#if 0 /* This will only cause an extra re-init (in addition to the default device change) and likely do no good even if that + default device change callback doesn't arrive. So, don't do it! (bird) */ + PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort; + if (pIHostAudioPort) + { + RTCritSectLeave(&pThis->CritSect); + + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, i == 0 ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL); + + rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, noErr); /* could be a destruction race */ + } +#endif + } + } + } + + RTCritSectLeave(&pThis->CritSect); + return noErr; +} + + +/** + * Called when the default recording or playback device has changed. + * + * Registered by the constructor. Not sure on which thread/runloop this runs. + * + * (See AudioObjectPropertyListenerProc in the SDK headers.) + */ +static OSStatus drvHstAudCaDefaultDeviceChangedCallback(AudioObjectID idObject, UInt32 cAddresses, + const AudioObjectPropertyAddress *paAddresses, void *pvUser) + +{ + PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser; + AssertPtr(pThis); + RT_NOREF(idObject, cAddresses, paAddresses); + + /* + * Log everything. + */ + LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses)); + for (UInt32 idx = 0; idx < cAddresses; idx++) + LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n", + idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement)); + + /* + * Update the default devices and notify parent driver if anything actually changed. + */ + drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/); + drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/); + + return noErr; +} + + +/** + * Registers callbacks for a specific Core Audio device. + * + * @returns true if idDevice isn't kAudioDeviceUnknown and callbacks were + * registered, otherwise false. + * @param pThis The core audio driver instance data. + * @param idDevice The device ID to deregister callbacks for. + */ +static bool drvHstAudCaDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice) +{ + if (idDevice != kAudioDeviceUnknown) + { + LogFunc(("idDevice=%RU32\n", idDevice)); + AudioObjectPropertyAddress PropAddr = + { + kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + OSStatus orc; + orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis); + unsigned cRegistrations = orc == noErr; + if ( orc != noErr + && orc != kAudioHardwareIllegalOperationError) + LogRel(("CoreAudio: Failed to add the recording device state changed listener (%#x)\n", orc)); + +#ifdef LOG_ENABLED + PropAddr.mSelector = kAudioDeviceProcessorOverload; + PropAddr.mScope = kAudioUnitScope_Global; + orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis); + cRegistrations += orc == noErr; + if (orc != noErr) + LogRel(("CoreAudio: Failed to register processor overload listener (%#x)\n", orc)); + + PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate; + PropAddr.mScope = kAudioUnitScope_Global; + orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis); + cRegistrations += orc == noErr; + if (orc != noErr) + LogRel(("CoreAudio: Failed to register sample rate changed listener (%#x)\n", orc)); +#endif + return cRegistrations > 0; + } + return false; +} + + +/** + * Undoes what drvHstAudCaDeviceRegisterCallbacks() did. + * + * @param pThis The core audio driver instance data. + * @param idDevice The device ID to deregister callbacks for. + */ +static void drvHstAudCaDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice) +{ + if (idDevice != kAudioDeviceUnknown) + { + LogFunc(("idDevice=%RU32\n", idDevice)); + AudioObjectPropertyAddress PropAddr = + { + kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + OSStatus orc; + orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the device alive listener (%#x)\n", orc)); + +#ifdef LOG_ENABLED + PropAddr.mSelector = kAudioDeviceProcessorOverload; + orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%#x)\n", orc)); + + PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate; + orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%#x)\n", orc)); +#endif + } +} + + +/** + * Updates the default device for one direction. + * + * @param pThis The core audio driver instance data. + * @param pDevice The device information to update. + * @param fInput Set if input device, clear if output. + * @param fNotify Whether to notify the parent driver if something + * changed. + */ +static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify) +{ + /* + * Skip if there is a specific device we should use for this direction. + */ + if (pDevice->pszSpecific) + return; + + /* + * Get the information before we enter the critical section. + * + * (Yeah, this may make us get things wrong if the defaults changes really + * fast and we get notifications in parallel on multiple threads. However, + * the first is a don't-do-that situation and the latter is unlikely.) + */ + AudioDeviceID idDefaultDev = kAudioDeviceUnknown; + if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, + fInput ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster, + fInput ? "default input device" : "default output device", + &idDefaultDev, sizeof(idDefaultDev))) + idDefaultDev = kAudioDeviceUnknown; + + CFStringRef hStrUid = NULL; + if (idDefaultDev != kAudioDeviceUnknown) + { + if (!drvHstAudCaGetPropertyData(idDefaultDev, kAudioDevicePropertyDeviceUID, + fInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster, + fInput ? "default input device UID" : "default output device UID", + &hStrUid, sizeof(hStrUid))) + hStrUid = NULL; + } + char szUid[128]; + if (hStrUid) + drvHstAudCaCFStringToBuf(hStrUid, szUid, sizeof(szUid)); + else + szUid[0] = '\0'; + + /* + * Grab the lock and do the updating. + * + * We're a little paranoid wrt the locking in case there turn out to be some kind + * of race around destruction (there really can't be, but better play safe). + */ + PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL; + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (idDefaultDev != pDevice->idDevice) + { + if (idDefaultDev != kAudioDeviceUnknown) + { + LogRel(("CoreAudio: Default %s device: %u (was %u), ID '%s'\n", + fInput ? "input" : "output", idDefaultDev, pDevice->idDevice, szUid)); + pIHostAudioPort = fNotify ? pThis->pIHostAudioPort : NULL; /* (only if there is a new device) */ + } + else + LogRel(("CoreAudio: Default %s device is gone (was %u)\n", fInput ? "input" : "output", pDevice->idDevice)); + + if (pDevice->hStrUid) + CFRelease(pDevice->hStrUid); + if (pDevice->fRegisteredListeners) + drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice); + pDevice->hStrUid = hStrUid; + pDevice->idDevice = idDefaultDev; + pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice); + hStrUid = NULL; + } + RTCritSectLeave(&pThis->CritSect); + } + + if (hStrUid != NULL) + CFRelease(hStrUid); + + /* + * Notify parent driver to trigger a re-init of any associated streams. + */ + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about %s default device change...\n", fInput ? "input" : "output")); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/); + } +} + + +/** + * Sets the device to use in one or the other direction (@a fInput). + * + * @returns VBox status code. + * @param pThis The core audio driver instance data. + * @param pDevice The device info structure to update. + * @param fInput Set if input, clear if output. + * @param fNotify Whether to notify the parent driver if something + * changed. + * @param pszUid The UID string for the device to use. NULL or empty + * string if default should be used. + */ +static int drvHstAudCaSetDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify, + const char *pszUid) +{ + if (!pszUid || !*pszUid) + { + /* + * Use default. Always refresh the given default device. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + + if (pDevice->pszSpecific) + { + LogRel(("CoreAudio: Changing %s device from '%s' to default.\n", fInput ? "input" : "output", pDevice->pszSpecific)); + RTStrFree(pDevice->pszSpecific); + pDevice->pszSpecific = NULL; + } + + RTCritSectLeave(&pThis->CritSect); + + drvHstAudCaUpdateOneDefaultDevice(pThis, pDevice, fInput, fNotify); + } + else + { + /* + * Use device specified by pszUid. If not change, search for the device + * again if idDevice is unknown. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + + bool fSkip = false; + bool fSame = false; + if (pDevice->pszSpecific) + { + if (strcmp(pszUid, pDevice->pszSpecific) != 0) + { + LogRel(("CoreAudio: Changing %s device from '%s' to '%s'.\n", + fInput ? "input" : "output", pDevice->pszSpecific, pszUid)); + RTStrFree(pDevice->pszSpecific); + pDevice->pszSpecific = NULL; + } + else + { + fSkip = pDevice->idDevice != kAudioDeviceUnknown; + fSame = true; + } + } + else + LogRel(("CoreAudio: Changing %s device from default to '%s'.\n", fInput ? "input" : "output", pszUid)); + + /* + * Allocate and swap the strings. This is the bit that might fail. + */ + if (!fSame) + { + CFStringRef hStrUid = CFStringCreateWithBytes(NULL /*allocator*/, (UInt8 const *)pszUid, (CFIndex)strlen(pszUid), + kCFStringEncodingUTF8, false /*isExternalRepresentation*/); + char *pszSpecific = RTStrDup(pszUid); + if (hStrUid && pszSpecific) + { + if (pDevice->hStrUid) + CFRelease(pDevice->hStrUid); + pDevice->hStrUid = hStrUid; + RTStrFree(pDevice->pszSpecific); + pDevice->pszSpecific = pszSpecific; + } + else + { + RTCritSectLeave(&pThis->CritSect); + + LogFunc(("returns VERR_NO_STR_MEMORY!\n")); + if (hStrUid) + CFRelease(hStrUid); + RTStrFree(pszSpecific); + return VERR_NO_STR_MEMORY; + } + + if (pDevice->fRegisteredListeners) + { + drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice); + pDevice->fRegisteredListeners = false; + } + } + + /* + * Locate the device ID corresponding to the UID string. + */ + if (!fSkip) + { + pDevice->idDevice = drvHstAudCaDeviceUidToId(pDevice->hStrUid, pszUid, fInput ? "input" : "output"); + pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice); + } + + PPDMIHOSTAUDIOPORT pIHostAudioPort = fNotify && !fSame ? pThis->pIHostAudioPort : NULL; + RTCritSectLeave(&pThis->CritSect); + + /* + * Notify parent driver to trigger a re-init of any associated streams. + */ + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about %s device change...\n", fInput ? "input" : "output")); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/); + } + } + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Worker Thread * +*********************************************************************************************************************************/ +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + +/** + * Message handling callback for CFMachPort. + */ +static void drvHstAudCaThreadPortCallback(CFMachPortRef hPort, void *pvMsg, CFIndex cbMsg, void *pvUser) +{ + RT_NOREF(hPort, pvMsg, cbMsg, pvUser); + LogFunc(("hPort=%p pvMsg=%p cbMsg=%#x pvUser=%p\n", hPort, pvMsg, cbMsg, pvUser)); +} + + +/** + * @callback_method_impl{FNRTTHREAD, Worker thread for buffer callbacks.} + */ +static DECLCALLBACK(int) drvHstAudCaThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser; + + /* + * Get the runloop, add the mach port to it and signal the constructor thread that we're ready. + */ + pThis->hThreadRunLoop = CFRunLoopGetCurrent(); + CFRetain(pThis->hThreadRunLoop); + + CFRunLoopAddSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode); + + int rc = RTThreadUserSignal(hThreadSelf); + AssertRCReturn(rc, rc); + + /* + * Do work. + */ + for (;;) + { + SInt32 rcRunLoop = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 30.0, TRUE); + Log8Func(("CFRunLoopRunInMode -> %d\n", rcRunLoop)); + Assert(rcRunLoop != kCFRunLoopRunFinished); + if (rcRunLoop != kCFRunLoopRunStopped && rcRunLoop != kCFRunLoopRunFinished) + { /* likely */ } + else + break; + } + + /* + * Clean up. + */ + CFRunLoopRemoveSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode); + LogFunc(("The thread quits!\n")); + return VINF_SUCCESS; +} + +#endif /* CORE_AUDIO_WITH_WORKER_THREAD */ + + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHstAudCaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio"); + pBackendCfg->cbStream = sizeof(COREAUDIOSTREAM); + pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY; + + RTCritSectEnter(&pThis->CritSect); +#if 0 /** @todo r=bird: This looks like complete utter non-sense to me. */ + /* For Core Audio we provide one stream per device for now. */ + pBackendCfg->cMaxStreamsIn = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_IN); + pBackendCfg->cMaxStreamsOut = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_OUT); +#else + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; +#endif + RTCritSectLeave(&pThis->CritSect); + + LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS)); + return VINF_SUCCESS; +} + + +/** + * Creates an enumeration of the host's playback and capture devices. + * + * @returns VBox status code. + * @param pDevEnm Where to store the enumerated devices. Caller is + * expected to clean this up on failure, if so desired. + * + * @note Handling of out-of-memory conditions isn't perhaps as good as it + * could be, but it was done so to make the drvHstAudCaGetPropertyData* + * functions as uncomplicated as possible. + */ +static int drvHstAudCaDevicesEnumerateAll(PPDMAUDIOHOSTENUM pDevEnm) +{ + AssertPtr(pDevEnm); + + /* + * First get the UIDs for the default devices. + */ + AudioDeviceID idDefaultDevIn = kAudioDeviceUnknown; + if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster, + "default input device", &idDefaultDevIn, sizeof(idDefaultDevIn))) + idDefaultDevIn = kAudioDeviceUnknown; + if (idDefaultDevIn == kAudioDeviceUnknown) + LogFunc(("No default input device\n")); + + AudioDeviceID idDefaultDevOut = kAudioDeviceUnknown; + if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster, + "default output device", &idDefaultDevOut, sizeof(idDefaultDevOut))) + idDefaultDevOut = kAudioDeviceUnknown; + if (idDefaultDevOut == kAudioDeviceUnknown) + LogFunc(("No default output device\n")); + + /* + * Get a list of all audio devices. + * (We have to retry as the we may race new devices being inserted.) + */ + UInt32 cDevices = 0; + AudioDeviceID *paidDevices + = (AudioDeviceID *)drvHstAudCaGetPropertyDataEx(kAudioObjectSystemObject, kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster, + "devices", &cDevices); + cDevices /= sizeof(paidDevices[0]); + + /* + * Try get details on each device and try add them to the enumeration result. + */ + for (uint32_t i = 0; i < cDevices; i++) + { + AudioDeviceID const idDevice = paidDevices[i]; + + /* + * Allocate a new device entry and populate it. + * + * The only relevant information here is channel counts and the UID(s), + * everything else is just extras we can live without. + */ + PCOREAUDIODEVICEDATA pDevEntry = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevEntry), 0, 0); + AssertReturnStmt(pDevEntry, RTMemTmpFree(paidDevices), VERR_NO_MEMORY); + + pDevEntry->idDevice = idDevice; + if (idDevice != kAudioDeviceUnknown) + { + if (idDevice == idDefaultDevIn) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_IN; + if (idDevice == idDefaultDevOut) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_OUT; + } + + /* Count channels and determin the usage. */ + pDevEntry->Core.cMaxInputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeInput); + pDevEntry->Core.cMaxOutputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeOutput); + if (pDevEntry->Core.cMaxInputChannels > 0 && pDevEntry->Core.cMaxOutputChannels > 0) + pDevEntry->Core.enmUsage = PDMAUDIODIR_DUPLEX; + else if (pDevEntry->Core.cMaxInputChannels > 0) + pDevEntry->Core.enmUsage = PDMAUDIODIR_IN; + else if (pDevEntry->Core.cMaxOutputChannels > 0) + pDevEntry->Core.enmUsage = PDMAUDIODIR_OUT; + else + { + pDevEntry->Core.enmUsage = PDMAUDIODIR_UNKNOWN; + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE; + /** @todo drop & skip? */ + } + + /* Get the device UID. (We ASSUME this is the same for both input and + output sides of the device.) */ + CFStringRef hStrUid; + if (!drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceUID, kAudioDevicePropertyDeviceUID, + kAudioObjectPropertyElementMaster, + "device UID", &hStrUid, sizeof(hStrUid))) + hStrUid = NULL; + + if (hStrUid) + { + pDevEntry->Core.pszId = drvHstAudCaCFStringToHeap(hStrUid); + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_ID_ALLOC; + } + else + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE; + + /* Get the device name (ignore failures). */ + CFStringRef hStrName = NULL; + if (drvHstAudCaGetPropertyData(idDevice, kAudioObjectPropertyName, + pDevEntry->Core.enmUsage == PDMAUDIODIR_IN + ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster, "device name", &hStrName, sizeof(hStrName))) + { + pDevEntry->Core.pszName = drvHstAudCaCFStringToHeap(hStrName); + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_NAME_ALLOC; + CFRelease(hStrName); + } + + /* Check if the device is alive for the intended usage. For duplex + devices we'll flag it as dead if either of the directions are dead, + as there is no convenient way of saying otherwise. It's acadmic as + nobody currently 2021-05-22) uses the flag for anything. */ + UInt32 fAlive = 0; + if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive, + pDevEntry->Core.enmUsage == PDMAUDIODIR_IN + ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive))) + if (!fAlive) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD; + fAlive = 0; + if ( pDevEntry->Core.enmUsage == PDMAUDIODIR_DUPLEX + && !(pDevEntry->Core.fFlags == PDMAUDIOHOSTDEV_F_DEAD) + && drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive, kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive))) + if (!fAlive) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD; + + /* Check if the device is being hogged by someone else. */ + pid_t pidHogger = -2; + if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyHogMode, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster, "hog-mode", &pidHogger, sizeof(pidHogger))) + if (pidHogger >= 0) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_LOCKED; + + /* + * Try make sure we've got a name... Only add it to the enumeration if we have one. + */ + if (!pDevEntry->Core.pszName) + { + pDevEntry->Core.pszName = pDevEntry->Core.pszId; + pDevEntry->Core.fFlags &= ~PDMAUDIOHOSTDEV_F_NAME_ALLOC; + } + + if (pDevEntry->Core.pszName) + PDMAudioHostEnumAppend(pDevEnm, &pDevEntry->Core); + else + PDMAudioHostDevFree(&pDevEntry->Core); + } + + RTMemTmpFree(paidDevices); + + LogFunc(("Returning %u devices\n", pDevEnm->cDevices)); + PDMAudioHostEnumLog(pDevEnm, "Core Audio"); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} + */ +static DECLCALLBACK(int) drvHstAudCaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); + + PDMAudioHostEnumInit(pDeviceEnum); + int rc = drvHstAudCaDevicesEnumerateAll(pDeviceEnum); + if (RT_FAILURE(rc)) + PDMAudioHostEnumDelete(pDeviceEnum); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice} + */ +static DECLCALLBACK(int) drvHstAudCaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId) +{ + PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio); + AssertPtrNullReturn(pszId, VERR_INVALID_POINTER); + if (pszId && !*pszId) + pszId = NULL; + AssertMsgReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, + ("enmDir=%d\n", enmDir, pszId), VERR_INVALID_PARAMETER); + + /* + * Make the change. + */ + int rc = VINF_SUCCESS; + if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX) + rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/, pszId); + if (enmDir == PDMAUDIODIR_OUT || (enmDir == PDMAUDIODIR_DUPLEX && RT_SUCCESS(rc))) + rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/, pszId); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudCaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * Marks the given buffer as queued or not-queued. + * + * @returns Old queued value. + * @param pAudioBuffer The buffer. + * @param fQueued The new queued state. + */ +DECLINLINE(bool) drvHstAudCaSetBufferQueued(AudioQueueBufferRef pAudioBuffer, bool fQueued) +{ + if (fQueued) + return ASMAtomicBitTestAndSet(&pAudioBuffer->mUserData, 0); + return ASMAtomicBitTestAndClear(&pAudioBuffer->mUserData, 0); +} + + +/** + * Gets the queued state of the buffer. + * @returns true if queued, false if not. + * @param pAudioBuffer The buffer. + */ +DECLINLINE(bool) drvHstAudCaIsBufferQueued(AudioQueueBufferRef pAudioBuffer) +{ + return ((uintptr_t)pAudioBuffer->mUserData & 1) == 1; +} + + +/** + * Output audio queue buffer callback. + * + * Called whenever an audio queue is done processing a buffer. This routine + * will set the data fill size to zero and mark it as unqueued so that + * drvHstAudCaHA_StreamPlay knowns it can use it. + * + * @param pvUser User argument. + * @param hAudioQueue Audio queue to process output data for. + * @param pAudioBuffer Audio buffer to store output data in. + * + * @thread queue thread. + */ +static void drvHstAudCaOutputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue, AudioQueueBufferRef pAudioBuffer) +{ +#if defined(VBOX_STRICT) || defined(LOG_ENABLED) + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser; + AssertPtr(pStreamCA); + Assert(pStreamCA->hAudioQueue == hAudioQueue); + + uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8; + Log4Func(("Got back buffer #%zu (%p)\n", idxBuf, pAudioBuffer)); + AssertReturnVoid( idxBuf < pStreamCA->cBuffers + && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer); +#endif + + pAudioBuffer->mAudioDataByteSize = 0; + bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/); + Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer)); + Assert(fWasQueued); RT_NOREF(fWasQueued); + + RT_NOREF(pvUser, hAudioQueue); +} + + +/** + * Input audio queue buffer callback. + * + * Called whenever input data from the audio queue becomes available. This + * routine will mark the buffer unqueued so that drvHstAudCaHA_StreamCapture can + * read the data from it. + * + * @param pvUser User argument. + * @param hAudioQueue Audio queue to process input data from. + * @param pAudioBuffer Audio buffer to process input data from. + * @param pAudioTS Audio timestamp. + * @param cPacketDesc Number of packet descriptors. + * @param paPacketDesc Array of packet descriptors. + */ +static void drvHstAudCaInputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue, + AudioQueueBufferRef pAudioBuffer, const AudioTimeStamp *pAudioTS, + UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc) +{ +#if defined(VBOX_STRICT) || defined(LOG_ENABLED) + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser; + AssertPtr(pStreamCA); + Assert(pStreamCA->hAudioQueue == hAudioQueue); + + uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8; + Log4Func(("Got back buffer #%zu (%p) with %#x bytes\n", idxBuf, pAudioBuffer, pAudioBuffer->mAudioDataByteSize)); + AssertReturnVoid( idxBuf < pStreamCA->cBuffers + && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer); +#endif + + bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/); + Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer)); + Assert(fWasQueued); RT_NOREF(fWasQueued); + + RT_NOREF(pvUser, hAudioQueue, pAudioTS, cPacketDesc, paPacketDesc); +} + + +static void drvHstAudCaLogAsbd(const char *pszDesc, const AudioStreamBasicDescription *pASBD) +{ + LogRel2(("CoreAudio: %s description:\n", pszDesc)); + LogRel2(("CoreAudio: Format ID: %#RX32 (%c%c%c%c)\n", pASBD->mFormatID, + RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID), + RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID))); + LogRel2(("CoreAudio: Flags: %#RX32", pASBD->mFormatFlags)); + if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat) + LogRel2((" Float")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian) + LogRel2((" BigEndian")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger) + LogRel2((" SignedInteger")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked) + LogRel2((" Packed")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh) + LogRel2((" AlignedHigh")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved) + LogRel2((" NonInterleaved")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable) + LogRel2((" NonMixable")); + if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear) + LogRel2((" AllClear")); + LogRel2(("\n")); + LogRel2(("CoreAudio: SampleRate : %RU64.%02u Hz\n", + (uint64_t)pASBD->mSampleRate, (unsigned)(pASBD->mSampleRate * 100) % 100)); + LogRel2(("CoreAudio: ChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame)); + LogRel2(("CoreAudio: FramesPerPacket : %RU32\n", pASBD->mFramesPerPacket)); + LogRel2(("CoreAudio: BitsPerChannel : %RU32\n", pASBD->mBitsPerChannel)); + LogRel2(("CoreAudio: BytesPerFrame : %RU32\n", pASBD->mBytesPerFrame)); + LogRel2(("CoreAudio: BytesPerPacket : %RU32\n", pASBD->mBytesPerPacket)); +} + + +static void drvHstAudCaPropsToAsbd(PCPDMAUDIOPCMPROPS pProps, AudioStreamBasicDescription *pASBD) +{ + AssertPtrReturnVoid(pProps); + AssertPtrReturnVoid(pASBD); + + RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription)); + + pASBD->mFormatID = kAudioFormatLinearPCM; + pASBD->mFormatFlags = kAudioFormatFlagIsPacked; + if (pProps->fSigned) + pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger; + if (PDMAudioPropsIsBigEndian(pProps)) + pASBD->mFormatFlags |= kAudioFormatFlagIsBigEndian; + pASBD->mSampleRate = PDMAudioPropsHz(pProps); + pASBD->mChannelsPerFrame = PDMAudioPropsChannels(pProps); + pASBD->mBitsPerChannel = PDMAudioPropsSampleBits(pProps); + pASBD->mBytesPerFrame = PDMAudioPropsFrameSize(pProps); + pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */ + pASBD->mBytesPerPacket = PDMAudioPropsFrameSize(pProps) * pASBD->mFramesPerPacket; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + int rc; + + /** @todo This takes too long. Stats indicates it may take up to 200 ms. + * Knoppix guest resets the stream and we hear nada because the + * draining is aborted when the stream is destroyed. Should try use + * async init for parts (much) of this. */ + + /* + * Permission check for input devices before we start. + */ + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + { + rc = coreAudioInputPermissionCheck(); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Do we have a device for the requested stream direction? + */ + RTCritSectEnter(&pThis->CritSect); + CFStringRef hDevUidStr = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->InputDevice.hStrUid : pThis->OutputDevice.hStrUid; + if (hDevUidStr) + CFRetain(hDevUidStr); + RTCritSectLeave(&pThis->CritSect); + +#ifdef LOG_ENABLED + char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX]; +#endif + LogFunc(("hDevUidStr=%p *pCfgReq: %s\n", hDevUidStr, PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)) )); + if (hDevUidStr) + { + /* + * Basic structure init. + */ + pStreamCA->fEnabled = false; + pStreamCA->fStarted = false; + pStreamCA->fDraining = false; + pStreamCA->fRestartOnResume = false; + pStreamCA->offInternal = 0; + pStreamCA->idxBuffer = 0; + pStreamCA->enmInitState = COREAUDIOINITSTATE_IN_INIT; + + rc = RTCritSectInit(&pStreamCA->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Do format conversion and create the circular buffer we use to shuffle + * data to/from the queue thread. + */ + PDMAudioStrmCfgCopy(&pStreamCA->Cfg, pCfgReq); + drvHstAudCaPropsToAsbd(&pCfgReq->Props, &pStreamCA->BasicStreamDesc); + /** @todo Do some validation? */ + drvHstAudCaLogAsbd(pCfgReq->enmDir == PDMAUDIODIR_IN ? "Capturing queue format" : "Playback queue format", + &pStreamCA->BasicStreamDesc); + /* + * Create audio queue. + * + * Documentation says the callbacks will be run on some core audio + * related thread if we don't specify a runloop here. That's simpler. + */ +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + CFRunLoopRef const hRunLoop = pThis->hThreadRunLoop; + CFStringRef const hRunLoopMode = kCFRunLoopDefaultMode; +#else + CFRunLoopRef const hRunLoop = NULL; + CFStringRef const hRunLoopMode = NULL; +#endif + OSStatus orc; + if (pCfgReq->enmDir == PDMAUDIODIR_OUT) + orc = AudioQueueNewOutput(&pStreamCA->BasicStreamDesc, drvHstAudCaOutputQueueBufferCallback, pStreamCA, + hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue); + else + orc = AudioQueueNewInput(&pStreamCA->BasicStreamDesc, drvHstAudCaInputQueueBufferCallback, pStreamCA, + hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueueNew%s -> %#x\n", pCfgReq->enmDir == PDMAUDIODIR_OUT ? "Output" : "Input", orc)); + if (orc == noErr) + { + /* + * Assign device to the queue. + */ + UInt32 uSize = sizeof(hDevUidStr); + orc = AudioQueueSetProperty(pStreamCA->hAudioQueue, kAudioQueueProperty_CurrentDevice, &hDevUidStr, uSize); + LogFlowFunc(("AudioQueueSetProperty -> %#x\n", orc)); + if (orc == noErr) + { + /* + * Sanity-adjust the requested buffer size. + */ + uint32_t cFramesBufferSizeMax = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 2 * RT_MS_1SEC); + uint32_t cFramesBufferSize = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 32 /*ms*/); + cFramesBufferSize = RT_MAX(cFramesBufferSize, pCfgReq->Backend.cFramesBufferSize); + cFramesBufferSize = RT_MIN(cFramesBufferSize, cFramesBufferSizeMax); + + /* + * The queue buffers size is based on cMsSchedulingHint so that we're likely to + * have a new one ready/done after each guest DMA transfer. We must however + * make sure we don't end up with too may or too few. + */ + Assert(pCfgReq->Device.cMsSchedulingHint > 0); + uint32_t cFramesQueueBuffer = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, + pCfgReq->Device.cMsSchedulingHint > 0 + ? pCfgReq->Device.cMsSchedulingHint : 10); + uint32_t cQueueBuffers; + if (cFramesQueueBuffer * COREAUDIO_MIN_BUFFERS <= cFramesBufferSize) + { + cQueueBuffers = cFramesBufferSize / cFramesQueueBuffer; + if (cQueueBuffers > COREAUDIO_MAX_BUFFERS) + { + cQueueBuffers = COREAUDIO_MAX_BUFFERS; + cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MAX_BUFFERS; + } + } + else + { + cQueueBuffers = COREAUDIO_MIN_BUFFERS; + cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MIN_BUFFERS; + } + + cFramesBufferSize = cQueueBuffers * cFramesBufferSize; + + /* + * Allocate the audio queue buffers. + */ + pStreamCA->paBuffers = (PCOREAUDIOBUF)RTMemAllocZ(sizeof(pStreamCA->paBuffers[0]) * cQueueBuffers); + if (pStreamCA->paBuffers != NULL) + { + pStreamCA->cBuffers = cQueueBuffers; + + const size_t cbQueueBuffer = PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, cFramesQueueBuffer); + LogFlowFunc(("Allocating %u, each %#x bytes / %u frames\n", cQueueBuffers, cbQueueBuffer, cFramesQueueBuffer)); + cFramesBufferSize = 0; + for (uint32_t iBuf = 0; iBuf < cQueueBuffers; iBuf++) + { + AudioQueueBufferRef pBuf = NULL; + orc = AudioQueueAllocateBuffer(pStreamCA->hAudioQueue, cbQueueBuffer, &pBuf); + if (RT_LIKELY(orc == noErr)) + { + pBuf->mUserData = (void *)(uintptr_t)(iBuf << 8); /* bit zero is the queued-indicator. */ + pStreamCA->paBuffers[iBuf].pBuf = pBuf; + cFramesBufferSize += PDMAudioPropsBytesToFrames(&pStreamCA->Cfg.Props, + pBuf->mAudioDataBytesCapacity); + Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, pBuf->mAudioDataBytesCapacity)); + } + else + { + LogRel(("CoreAudio: Out of memory (buffer %#x out of %#x, %#x bytes)\n", + iBuf, cQueueBuffers, cbQueueBuffer)); + while (iBuf-- > 0) + { + AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf); + pStreamCA->paBuffers[iBuf].pBuf = NULL; + } + break; + } + } + if (orc == noErr) + { + /* + * Update the stream config. + */ + pStreamCA->Cfg.Backend.cFramesBufferSize = cFramesBufferSize; + pStreamCA->Cfg.Backend.cFramesPeriod = cFramesQueueBuffer; /* whatever */ + pStreamCA->Cfg.Backend.cFramesPreBuffering = pStreamCA->Cfg.Backend.cFramesPreBuffering + * pStreamCA->Cfg.Backend.cFramesBufferSize + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + + PDMAudioStrmCfgCopy(pCfgAcq, &pStreamCA->Cfg); + + ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_INIT); + + LogFunc(("returns VINF_SUCCESS\n")); + CFRelease(hDevUidStr); + return VINF_SUCCESS; + } + + RTMemFree(pStreamCA->paBuffers); + } + else + rc = VERR_NO_MEMORY; + } + else + LogRelMax(64, ("CoreAudio: Failed to associate device with queue: %#x (%d)\n", orc, orc)); + AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate*/); + } + else + LogRelMax(64, ("CoreAudio: Failed to create audio queue: %#x (%d)\n", orc, orc)); + RTCritSectDelete(&pStreamCA->CritSect); + } + else + LogRel(("CoreAudio: Failed to initialize critical section for stream: %Rrc\n", rc)); + CFRelease(hDevUidStr); + } + else + { + LogRelMax(64, ("CoreAudio: No device for %s stream.\n", PDMAudioDirGetName(pCfgReq->enmDir))); + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + LogFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + LogFunc(("%p: %s fImmediate=%RTbool\n", pStreamCA, pStreamCA->Cfg.szName, fImmediate)); +#ifdef LOG_ENABLED + uint64_t const nsStart = RTTimeNanoTS(); +#endif + + /* + * Never mind if the status isn't INIT (it should always be, though). + */ + COREAUDIOINITSTATE const enmInitState = (COREAUDIOINITSTATE)ASMAtomicReadU32(&pStreamCA->enmInitState); + AssertMsg(enmInitState == COREAUDIOINITSTATE_INIT, ("%d\n", enmInitState)); + if (enmInitState == COREAUDIOINITSTATE_INIT) + { + Assert(RTCritSectIsInitialized(&pStreamCA->CritSect)); + + /* + * Change the stream state and stop the stream (just to be sure). + */ + OSStatus orc; + ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_IN_UNINIT); + if (pStreamCA->hAudioQueue) + { + orc = AudioQueueStop(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/); + LogFlowFunc(("AudioQueueStop -> %#x\n", orc)); + } + + /* + * Enter and leave the critsect afterwards for paranoid reasons. + */ + RTCritSectEnter(&pStreamCA->CritSect); + RTCritSectLeave(&pStreamCA->CritSect); + + /* + * Free the queue buffers and the queue. + * + * This may take a while. The AudioQueueReset call seems to helps + * reducing time stuck in AudioQueueDispose. + */ +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + LogRel(("Queue-destruction timer starting...\n")); + PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio); + RTTimerLRStart(pThis->hBreakpointTimer, RT_NS_100MS); + uint64_t nsStart = RTTimeNanoTS(); +#endif + +#if 0 /* This seems to work even when doing a non-immediate stop&dispose. However, it doesn't make sense conceptually. */ + if (pStreamCA->hAudioQueue /*&& fImmediate*/) + { + LogFlowFunc(("Calling AudioQueueReset ...\n")); + orc = AudioQueueReset(pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueueReset -> %#x\n", orc)); + } +#endif + + if (pStreamCA->paBuffers && fImmediate) + { + LogFlowFunc(("Freeing %u buffers ...\n", pStreamCA->cBuffers)); + for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++) + { + orc = AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf); + AssertMsg(orc == noErr, ("AudioQueueFreeBuffer(#%u) -> orc=%#x\n", iBuf, orc)); + pStreamCA->paBuffers[iBuf].pBuf = NULL; + } + } + + if (pStreamCA->hAudioQueue) + { + LogFlowFunc(("Disposing of the queue ...\n")); + orc = AudioQueueDispose(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/); /* may take some time */ + LogFlowFunc(("AudioQueueDispose -> %#x (%d)\n", orc, orc)); + AssertMsg(orc == noErr, ("AudioQueueDispose -> orc=%#x\n", orc)); + pStreamCA->hAudioQueue = NULL; + } + + /* We should get no further buffer callbacks at this point according to the docs. */ + if (pStreamCA->paBuffers) + { + RTMemFree(pStreamCA->paBuffers); + pStreamCA->paBuffers = NULL; + } + pStreamCA->cBuffers = 0; + +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + RTTimerLRStop(pThis->hBreakpointTimer); + LogRel(("Queue-destruction: %'RU64\n", RTTimeNanoTS() - nsStart)); +#endif + + /* + * Delete the critsect and we're done. + */ + RTCritSectDelete(&pStreamCA->CritSect); + + ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_UNINIT); + } + else + LogFunc(("Wrong stream init state for %p: %d - leaking it\n", pStream, enmInitState)); + + LogFunc(("returns (took %'RU64 ns)\n", RTTimeNanoTS() - nsStart)); + return VINF_SUCCESS; +} + + +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER +/** @callback_method_impl{FNRTTIMERLR, For debugging things that takes too long.} */ +static DECLCALLBACK(void) drvHstAudCaBreakpointTimer(RTTIMERLR hTimer, void *pvUser, uint64_t iTick) +{ + LogFlowFunc(("Queue-destruction timeout! iTick=%RU64\n", iTick)); + RT_NOREF(hTimer, pvUser, iTick); + RTLogFlush(NULL); + RT_BREAKPOINT(); +} +#endif + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA))); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + Assert(!pStreamCA->fEnabled); + Assert(!pStreamCA->fStarted); + + /* + * We always reset the buffer before enabling the stream (normally never necessary). + */ + OSStatus orc = AudioQueueReset(pStreamCA->hAudioQueue); + if (orc != noErr) + LogRelMax(64, ("CoreAudio: Stream reset failed when enabling '%s': %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + Assert(orc == noErr); + for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++) + Assert(!drvHstAudCaIsBufferQueued(pStreamCA->paBuffers[iBuf].pBuf)); + + pStreamCA->offInternal = 0; + pStreamCA->fDraining = false; + pStreamCA->fEnabled = true; + pStreamCA->fRestartOnResume = false; + pStreamCA->idxBuffer = 0; + + /* + * Input streams will start capturing, while output streams will only start + * playing once we get some audio data to play (see drvHstAudCaHA_StreamPlay). + */ + int rc = VINF_SUCCESS; + if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN) + { + /* Zero (probably not needed) and submit all the buffers first. */ + for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++) + { + AudioQueueBufferRef pBuf = pStreamCA->paBuffers[iBuf].pBuf; + + RT_BZERO(pBuf->mAudioData, pBuf->mAudioDataBytesCapacity); + pBuf->mAudioDataByteSize = 0; + drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/); + + orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/); + AssertLogRelMsgBreakStmt(orc == noErr, ("CoreAudio: AudioQueueEnqueueBuffer(#%u) -> %#x (%d) - stream '%s'\n", + iBuf, orc, orc, pStreamCA->Cfg.szName), + drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/)); + } + + /* Start the stream. */ + if (orc == noErr) + { + LogFlowFunc(("Start input stream '%s'...\n", pStreamCA->Cfg.szName)); + orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/); + AssertLogRelMsgStmt(orc == noErr, ("CoreAudio: AudioQueueStart(%s) -> %#x (%d) \n", pStreamCA->Cfg.szName, orc, orc), + rc = VERR_AUDIO_STREAM_NOT_READY); + pStreamCA->fStarted = orc == noErr; + } + else + rc = VERR_AUDIO_STREAM_NOT_READY; + } + else + Assert(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT); + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1, + pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + /* + * Always stop it (draining or no). + */ + pStreamCA->fEnabled = false; + pStreamCA->fRestartOnResume = false; + Assert(!pStreamCA->fDraining || pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT); + + int rc = VINF_SUCCESS; + if (pStreamCA->fStarted) + { +#if 0 + OSStatus orc2 = AudioQueueReset(pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueueReset(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2); + orc2 = AudioQueueFlush(pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueueFlush(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2); +#endif + + OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate*/); + LogFlowFunc(("AudioQueueStop(%s,TRUE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc != noErr) + { + LogRelMax(64, ("CoreAudio: Stopping '%s' failed (disable): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + pStreamCA->fStarted = false; + pStreamCA->fDraining = false; + } + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA))); + return rc; +} + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1, + pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + /* + * Unless we're draining the stream, pause it if it has started. + */ + int rc = VINF_SUCCESS; + if (pStreamCA->fStarted && !pStreamCA->fDraining) + { + pStreamCA->fRestartOnResume = true; + + OSStatus orc = AudioQueuePause(pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueuePause(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc != noErr) + { + LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + pStreamCA->fStarted = false; + } + else + { + pStreamCA->fRestartOnResume = false; + if (pStreamCA->fDraining) + { + LogFunc(("Stream '%s' is draining\n", pStreamCA->Cfg.szName)); + Assert(pStreamCA->fStarted); + } + } + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA))); + return rc; +} + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA))); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + /* + * Resume according to state saved by drvHstAudCaHA_StreamPause. + */ + int rc = VINF_SUCCESS; + if (pStreamCA->fRestartOnResume) + { + OSStatus orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/); + LogFlowFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc != noErr) + { + LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + rc = VERR_AUDIO_STREAM_NOT_READY; + } + } + pStreamCA->fRestartOnResume = false; + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA))); + return rc; +} + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertReturn(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1, + pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + /* + * The AudioQueueStop function has both an immediate and a drain mode, + * so we'll obviously use the latter here. For checking draining progress, + * we will just check if all buffers have been returned or not. + */ + int rc = VINF_SUCCESS; + if (pStreamCA->fStarted) + { + if (!pStreamCA->fDraining) + { + OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, FALSE /*inImmediate*/); + LogFlowFunc(("AudioQueueStop(%s, FALSE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc == noErr) + pStreamCA->fDraining = true; + else + { + LogRelMax(64, ("CoreAudio: Stopping '%s' failed (drain): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + } + else + LogFlowFunc(("Already draining '%s' ...\n", pStreamCA->Cfg.szName)); + } + else + { + LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamCA->Cfg.szName)); + AssertStmt(!pStreamCA->fDraining, pStreamCA->fDraining = false); + } + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0); + + uint32_t cbReadable = 0; + if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN) + { + RTCritSectEnter(&pStreamCA->CritSect); + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uint32_t const cBuffers = pStreamCA->cBuffers; + uint32_t const idxStart = pStreamCA->idxBuffer; + uint32_t idxBuffer = idxStart; + AudioQueueBufferRef pBuf; + + if ( cBuffers > 0 + && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf)) + { + do + { + uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity; + uint32_t cbFill = pBuf->mAudioDataByteSize; + AssertStmt(cbFill <= cbTotal, cbFill = cbTotal); + uint32_t off = paBuffers[idxBuffer].offRead; + AssertStmt(off < cbFill, off = cbFill); + + cbReadable += cbFill - off; + + /* Advance. */ + idxBuffer++; + if (idxBuffer < cBuffers) + { /* likely */ } + else + idxBuffer = 0; + } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf)); + } + + RTCritSectLeave(&pStreamCA->CritSect); + } + Log2Func(("returns %#x for '%s'\n", cbReadable, pStreamCA->Cfg.szName)); + return cbReadable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0); + + uint32_t cbWritable = 0; + if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT) + { + RTCritSectEnter(&pStreamCA->CritSect); + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uint32_t const cBuffers = pStreamCA->cBuffers; + uint32_t const idxStart = pStreamCA->idxBuffer; + uint32_t idxBuffer = idxStart; + AudioQueueBufferRef pBuf; + + if ( cBuffers > 0 + && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf)) + { + do + { + uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity; + uint32_t cbUsed = pBuf->mAudioDataByteSize; + AssertStmt(cbUsed <= cbTotal, paBuffers[idxBuffer].pBuf->mAudioDataByteSize = cbUsed = cbTotal); + + cbWritable += cbTotal - cbUsed; + + /* Advance. */ + idxBuffer++; + if (idxBuffer < cBuffers) + { /* likely */ } + else + idxBuffer = 0; + } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf)); + } + + RTCritSectLeave(&pStreamCA->CritSect); + } + Log2Func(("returns %#x for '%s'\n", cbWritable, pStreamCA->Cfg.szName)); + return cbWritable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudCaHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, PDMHOSTAUDIOSTREAMSTATE_INVALID); + + if (ASMAtomicReadU32(&pStreamCA->enmInitState) == COREAUDIOINITSTATE_INIT) + { + if (!pStreamCA->fDraining) + { /* likely */ } + else + { + /* + * If we're draining, we're done when we've got all the buffers back. + */ + RTCritSectEnter(&pStreamCA->CritSect); + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uintptr_t idxBuffer = pStreamCA->cBuffers; + while (idxBuffer-- > 0) + if (!drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf)) + { /* likely */ } + else + { +#ifdef LOG_ENABLED + uint32_t cQueued = 1; + while (idxBuffer-- > 0) + cQueued += drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf); + LogFunc(("Still done draining '%s': %u queued buffers\n", pStreamCA->Cfg.szName, cQueued)); +#endif + RTCritSectLeave(&pStreamCA->CritSect); + return PDMHOSTAUDIOSTREAMSTATE_DRAINING; + } + + LogFunc(("Done draining '%s'\n", pStreamCA->Cfg.szName)); + pStreamCA->fDraining = false; + pStreamCA->fEnabled = false; + pStreamCA->fStarted = false; + RTCritSectLeave(&pStreamCA->CritSect); + } + + return PDMHOSTAUDIOSTREAMSTATE_OKAY; + } + return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; /** @todo ?? */ +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf)); + AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbWritten = 0, VERR_AUDIO_STREAM_NOT_READY); + + RTCritSectEnter(&pStreamCA->CritSect); + if (pStreamCA->fEnabled) + { /* likely */ } + else + { + RTCritSectLeave(&pStreamCA->CritSect); + *pcbWritten = 0; + LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + return VINF_SUCCESS; + } + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + + /* + * Transfer loop. + */ + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uint32_t const cBuffers = pStreamCA->cBuffers; + AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers), + RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY); + + uint32_t idxBuffer = pStreamCA->idxBuffer; + AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers); + + int rc = VINF_SUCCESS; + uint32_t cbWritten = 0; + while (cbBuf > 0) + { + AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY); + + /* + * Check out how much we can put into the current buffer. + */ + AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf; + if (!drvHstAudCaIsBufferQueued(pBuf)) + { /* likely */ } + else + { + LogFunc(("@%#RX64: Warning! Out of buffer space! (%#x bytes unwritten)\n", pStreamCA->offInternal, cbBuf)); + /** @todo stats */ + break; + } + + AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2); + uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity; + uint32_t cbUsed = pBuf->mAudioDataByteSize; + AssertStmt(cbUsed < cbTotal, cbUsed = cbTotal); + uint32_t const cbAvail = cbTotal - cbUsed; + + /* + * Copy over the data. + */ + if (cbBuf < cbAvail) + { + Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x only - leaving unqueued {%s}\n", + pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbBuf); + pBuf->mAudioDataByteSize = cbUsed + cbBuf; + cbWritten += cbBuf; + pStreamCA->offInternal += cbBuf; + /** @todo Maybe queue it anyway if it's almost full or we haven't got a lot of + * buffers queued. */ + break; + } + + Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x - will queue {%s}\n", + pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbAvail); + pBuf->mAudioDataByteSize = cbTotal; + cbWritten += cbAvail; + pStreamCA->offInternal += cbAvail; + drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/); + + OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/); + if (orc == noErr) + { /* likely */ } + else + { + LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n", + pStreamCA->Cfg.szName, idxBuffer, orc, orc)); + drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/); + pBuf->mAudioDataByteSize -= PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, 1); /* avoid assertions above */ + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + /* + * Advance. + */ + idxBuffer += 1; + if (idxBuffer < cBuffers) + { /* likely */ } + else + idxBuffer = 0; + pStreamCA->idxBuffer = idxBuffer; + + pvBuf = (const uint8_t *)pvBuf + cbAvail; + cbBuf -= cbAvail; + } + + /* + * Start the stream if we haven't do so yet. + */ + if ( pStreamCA->fStarted + || cbWritten == 0 + || RT_FAILURE_NP(rc)) + { /* likely */ } + else + { + UInt32 cFramesPrepared = 0; +#if 0 /* taking too long? */ + OSStatus orc = AudioQueuePrime(pStreamCA->hAudioQueue, 0 /*inNumberOfFramesToPrepare*/, &cFramesPrepared); + LogFlowFunc(("AudioQueuePrime(%s, 0,) returns %#x (%d) and cFramesPrepared=%u (offInternal=%#RX64)\n", + pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal)); + AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc)); +#else + OSStatus orc; +#endif + orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/); + LogFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc == noErr) + pStreamCA->fStarted = true; + else + { + LogRelMax(128, ("CoreAudio: Starting '%s' failed: %#x (%d) - %u frames primed, %#x bytes queued\n", + pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal)); + rc = VERR_AUDIO_STREAM_NOT_READY; + } + } + + /* + * Done. + */ +#ifdef LOG_ENABLED + uint64_t const msPrev = pStreamCA->msLastTransfer; +#endif + uint64_t const msNow = RTTimeMilliTS(); + if (cbWritten) + pStreamCA->msLastTransfer = msNow; + + RTCritSectLeave(&pStreamCA->CritSect); + + *pcbWritten = cbWritten; + if (RT_SUCCESS(rc) || !cbWritten) + { } + else + { + LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten)); + rc = VINF_SUCCESS; + } + LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbWritten, + msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) )); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, 0); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf)); + AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbRead = 0, VERR_AUDIO_STREAM_NOT_READY); + + RTCritSectEnter(&pStreamCA->CritSect); + if (pStreamCA->fEnabled) + { /* likely */ } + else + { + RTCritSectLeave(&pStreamCA->CritSect); + *pcbRead = 0; + LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA))); + return VINF_SUCCESS; + } + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + + + /* + * Transfer loop. + */ + uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamCA->Cfg.Props); + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uint32_t const cBuffers = pStreamCA->cBuffers; + AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers), + RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY); + + uint32_t idxBuffer = pStreamCA->idxBuffer; + AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers); + + int rc = VINF_SUCCESS; + uint32_t cbRead = 0; + while (cbBuf > cbFrame) + { + AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY); + + /* + * Check out how much we can read from the current buffer (if anything at all). + */ + AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf; + if (!drvHstAudCaIsBufferQueued(pBuf)) + { /* likely */ } + else + { + LogFunc(("@%#RX64: Warning! Underrun! (%#x bytes unread)\n", pStreamCA->offInternal, cbBuf)); + /** @todo stats */ + break; + } + + AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2); + uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity; + uint32_t cbValid = pBuf->mAudioDataByteSize; + AssertStmt(cbValid < cbTotal, cbValid = cbTotal); + uint32_t offRead = paBuffers[idxBuffer].offRead; + uint32_t const cbLeft = cbValid - offRead; + + /* + * Copy over the data. + */ + if (cbBuf < cbLeft) + { + Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want %#x - leaving unqueued {%s}\n", + pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbBuf); + paBuffers[idxBuffer].offRead = offRead + cbBuf; + cbRead += cbBuf; + pStreamCA->offInternal += cbBuf; + break; + } + + Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want all (%#x) - will queue {%s}\n", + pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbLeft); + cbRead += cbLeft; + pStreamCA->offInternal += cbLeft; + + RT_BZERO(pBuf->mAudioData, cbTotal); /* paranoia */ + paBuffers[idxBuffer].offRead = 0; + pBuf->mAudioDataByteSize = 0; + drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/); + + OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/); + if (orc == noErr) + { /* likely */ } + else + { + LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n", + pStreamCA->Cfg.szName, idxBuffer, orc, orc)); + drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/); + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + /* + * Advance. + */ + idxBuffer += 1; + if (idxBuffer < cBuffers) + { /* likely */ } + else + idxBuffer = 0; + pStreamCA->idxBuffer = idxBuffer; + + pvBuf = (uint8_t *)pvBuf + cbLeft; + cbBuf -= cbLeft; + } + + /* + * Done. + */ +#ifdef LOG_ENABLED + uint64_t const msPrev = pStreamCA->msLastTransfer; +#endif + uint64_t const msNow = RTTimeMilliTS(); + if (cbRead) + pStreamCA->msLastTransfer = msNow; + + RTCritSectLeave(&pStreamCA->CritSect); + + *pcbRead = cbRead; + if (RT_SUCCESS(rc) || !cbRead) + { } + else + { + LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead)); + rc = VINF_SUCCESS; + } + LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbRead, + msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) )); + return rc; +} + + +/********************************************************************************************************************************* +* PDMIBASE * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHstAudCaQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG * +*********************************************************************************************************************************/ + +/** + * Worker for the power off and destructor callbacks. + */ +static void drvHstAudCaRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis) +{ + /* + * Unregister system callbacks. + */ + AudioObjectPropertyAddress PropAddr = + { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + OSStatus orc; + if (pThis->fRegisteredDefaultInputListener) + { + orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, + drvHstAudCaDefaultDeviceChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc)); + pThis->fRegisteredDefaultInputListener = false; + } + + if (pThis->fRegisteredDefaultOutputListener) + { + PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, + drvHstAudCaDefaultDeviceChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc)); + pThis->fRegisteredDefaultOutputListener = false; + } + + /* + * Unregister device callbacks. + */ + RTCritSectEnter(&pThis->CritSect); + + drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->InputDevice.idDevice); + pThis->InputDevice.idDevice = kAudioDeviceUnknown; + + drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->OutputDevice.idDevice); + pThis->OutputDevice.idDevice = kAudioDeviceUnknown; + + RTCritSectLeave(&pThis->CritSect); + + LogFlowFuncEnter(); +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOff} + */ +static DECLCALLBACK(void) drvHstAudCaPowerOff(PPDMDRVINS pDrvIns) +{ + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + drvHstAudCaRemoveDefaultDeviceListners(pThis); +} + + +/** + * @callback_method_impl{FNPDMDRVDESTRUCT} + */ +static DECLCALLBACK(void) drvHstAudCaDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + + if (RTCritSectIsInitialized(&pThis->CritSect)) + drvHstAudCaRemoveDefaultDeviceListners(pThis); + +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + if (pThis->hThread != NIL_RTTHREAD) + { + for (unsigned iLoop = 0; iLoop < 60; iLoop++) + { + if (pThis->hThreadRunLoop) + CFRunLoopStop(pThis->hThreadRunLoop); + if (iLoop > 10) + RTThreadPoke(pThis->hThread); + int rc = RTThreadWait(pThis->hThread, 500 /*ms*/, NULL /*prcThread*/); + if (RT_SUCCESS(rc)) + break; + AssertMsgBreak(rc == VERR_TIMEOUT, ("RTThreadWait -> %Rrc\n",rc)); + } + pThis->hThread = NIL_RTTHREAD; + } + if (pThis->hThreadPortSrc) + { + CFRelease(pThis->hThreadPortSrc); + pThis->hThreadPortSrc = NULL; + } + if (pThis->hThreadPort) + { + CFMachPortInvalidate(pThis->hThreadPort); + CFRelease(pThis->hThreadPort); + pThis->hThreadPort = NULL; + } + if (pThis->hThreadRunLoop) + { + CFRelease(pThis->hThreadRunLoop); + pThis->hThreadRunLoop = NULL; + } +#endif + +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + if (pThis->hBreakpointTimer != NIL_RTTIMERLR) + { + RTTimerLRDestroy(pThis->hBreakpointTimer); + pThis->hBreakpointTimer = NIL_RTTIMERLR; + } +#endif + + if (RTCritSectIsInitialized(&pThis->CritSect)) + { + int rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + } + + LogFlowFuncLeave(); +} + + +/** + * @callback_method_impl{FNPDMDRVCONSTRUCT, + * Construct a Core Audio driver instance.} + */ +static DECLCALLBACK(int) drvHstAudCaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + LogRel(("Audio: Initializing Core Audio driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + pThis->hThread = NIL_RTTHREAD; +#endif +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + pThis->hBreakpointTimer = NIL_RTTIMERLR; +#endif + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHstAudCaQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHstAudCaHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = drvHstAudCaHA_GetDevices; + pThis->IHostAudio.pfnSetDevice = drvHstAudCaHA_SetDevice; + pThis->IHostAudio.pfnGetStatus = drvHstAudCaHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHstAudCaHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHstAudCaHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHstAudCaHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHstAudCaHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHstAudCaHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHstAudCaHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHstAudCaHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetReadable = drvHstAudCaHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamGetWritable = drvHstAudCaHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetState = drvHstAudCaHA_StreamGetState; + pThis->IHostAudio.pfnStreamPlay = drvHstAudCaHA_StreamPlay; + pThis->IHostAudio.pfnStreamCapture = drvHstAudCaHA_StreamCapture; + + int rc = RTCritSectInit(&pThis->CritSect); + AssertRCReturn(rc, rc); + + /* + * Validate and read configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "InputDeviceID|OutputDeviceID", ""); + + char *pszTmp = NULL; + rc = CFGMR3QueryStringAlloc(pCfg, "InputDeviceID", &pszTmp); + if (RT_SUCCESS(rc)) + { + rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotify*/, pszTmp); + MMR3HeapFree(pszTmp); + } + else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT) + return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'InputDeviceID'"); + + rc = CFGMR3QueryStringAlloc(pCfg, "OutputDeviceID", &pszTmp); + if (RT_SUCCESS(rc)) + { + rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotify*/, pszTmp); + MMR3HeapFree(pszTmp); + } + else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT) + return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'OutputDeviceID'"); + + /* + * Query the notification interface from the driver/device above us. + */ + pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT); + AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE); + +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + /* + * Create worker thread for running callbacks on. + */ + CFMachPortContext PortCtx; + PortCtx.version = 0; + PortCtx.info = pThis; + PortCtx.retain = NULL; + PortCtx.release = NULL; + PortCtx.copyDescription = NULL; + pThis->hThreadPort = CFMachPortCreate(NULL /*allocator*/, drvHstAudCaThreadPortCallback, &PortCtx, NULL); + AssertLogRelReturn(pThis->hThreadPort != NULL, VERR_NO_MEMORY); + + pThis->hThreadPortSrc = CFMachPortCreateRunLoopSource(NULL, pThis->hThreadPort, 0 /*order*/); + AssertLogRelReturn(pThis->hThreadPortSrc != NULL, VERR_NO_MEMORY); + + rc = RTThreadCreateF(&pThis->hThread, drvHstAudCaThread, pThis, 0, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "CaAud-%u", pDrvIns->iInstance); + AssertLogRelMsgReturn(RT_SUCCESS(rc), ("RTThreadCreateF failed: %Rrc\n", rc), rc); + + RTThreadUserWait(pThis->hThread, RT_MS_10SEC); + AssertLogRel(pThis->hThreadRunLoop); +#endif + +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + /* + * Create a IPRT timer. The TM timers won't necessarily work as EMT is probably busy. + */ + rc = RTTimerLRCreateEx(&pThis->hBreakpointTimer, 0 /*no interval*/, 0, drvHstAudCaBreakpointTimer, pThis); + AssertRCReturn(rc, rc); +#endif + + /* + * Determin the default devices. + */ + drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotifty*/); + drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotifty*/); + + /* + * Register callbacks for default device input and output changes. + * (We just ignore failures here as there isn't much we can do about it, + * and it isn't 100% critical.) + */ + AudioObjectPropertyAddress PropAddr = + { + /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice, + /* .mScope = */ kAudioObjectPropertyScopeGlobal, + /* .mElement = */ kAudioObjectPropertyElementMaster + }; + + OSStatus orc; + orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis); + pThis->fRegisteredDefaultInputListener = orc == noErr; + if ( orc != noErr + && orc != kAudioHardwareIllegalOperationError) + LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc)); + + PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis); + pThis->fRegisteredDefaultOutputListener = orc == noErr; + if ( orc != noErr + && orc != kAudioHardwareIllegalOperationError) + LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc)); + + /* + * Cleanup debug dumps from previous run. + */ +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm"); + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm"); +#endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostCoreAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "CoreAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Core Audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTCOREAUDIO), + /* pfnConstruct */ + drvHstAudCaConstruct, + /* pfnDestruct */ + drvHstAudCaDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + drvHstAudCaPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioDebug.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioDebug.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioDebug.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioDebug.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,526 @@ +/* $Id: DrvHostAudioDebug.cpp $ */ +/** @file + * Host audio driver - Debug - For dumping and injecting audio data from/to the device emulation. + */ + +/* + * Copyright (C) 2016-2021 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include + +#include +#include /* For PDMIBASE_2_PDMDRV. */ + +#define _USE_MATH_DEFINES +#include /* sin, M_PI */ + +#include "AudioHlp.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Debug host audio stream. + */ +typedef struct DRVHSTAUDDEBUGSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** Audio file to dump output to or read input from. */ + PAUDIOHLPFILE pFile; + union + { + struct + { + /** Current sample index for generate the sine wave. */ + uint64_t uSample; + /** The fixed portion of the sin() input. */ + double rdFixed; + /** Timestamp of last captured samples. */ + uint64_t tsLastCaptured; + /** Frequency (in Hz) of the sine wave to generate. */ + double rdFreqHz; + } In; + }; +} DRVHSTAUDDEBUGSTREAM; +/** Pointer to a debug host audio stream. */ +typedef DRVHSTAUDDEBUGSTREAM *PDRVHSTAUDDEBUGSTREAM; + +/** + * Debug audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHSTAUDDEBUG +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; +} DRVHSTAUDDEBUG; +/** Pointer to a debug host audio driver. */ +typedef DRVHSTAUDDEBUG *PDRVHSTAUDDEBUG; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Frequency selection for input streams. */ +static const double s_ardInputFreqsHz[] = +{ + 349.2282 /*F4*/, + 440.0000 /*A4*/, + 523.2511 /*C5*/, + 698.4565 /*F5*/, + 880.0000 /*A5*/, + 1046.502 /*C6*/, + 1174.659 /*D6*/, + 1396.913 /*F6*/, + 1760.0000 /*A6*/ +}; + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "DebugAudio"); + pBackendCfg->cbStream = sizeof(DRVHSTAUDDEBUGSTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsOut = 1; /* Output; writing to a file. */ + pBackendCfg->cMaxStreamsIn = 1; /* Input; generates a sine wave. */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudDebugHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHSTAUDDEBUG pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDDEBUG, IHostAudio); + PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream; + AssertPtrReturn(pStreamDbg, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PDMAudioStrmCfgCopy(&pStreamDbg->Cfg, pCfgAcq); + + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + { + /* Pick a frequency from our selection, so that every time a recording starts + we'll hopfully generate a different note. */ + pStreamDbg->In.rdFreqHz = s_ardInputFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_ardInputFreqsHz) - 1)]; + pStreamDbg->In.rdFixed = 2.0 * M_PI * pStreamDbg->In.rdFreqHz / PDMAudioPropsHz(&pStreamDbg->Cfg.Props); + pStreamDbg->In.uSample = 0; + } + + int rc = AudioHlpFileCreateAndOpenEx(&pStreamDbg->pFile, AUDIOHLPFILETYPE_WAV, NULL /*use temp dir*/, + pThis->pDrvIns->iInstance, AUDIOHLPFILENAME_FLAGS_NONE, AUDIOHLPFILE_FLAGS_NONE, + &pCfgReq->Props, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE, + pCfgReq->enmDir == PDMAUDIODIR_IN ? "DebugAudioIn" : "DebugAudioOut"); + if (RT_FAILURE(rc)) + LogRel(("DebugAudio: Failed to creating debug file for %s stream '%s' in the temp directory: %Rrc\n", + pCfgReq->enmDir == PDMAUDIODIR_IN ? "input" : "output", pCfgReq->szName, rc)); + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fImmediate) +{ + RT_NOREF(pInterface, fImmediate); + PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream; + AssertPtrReturn(pStreamDbg, VERR_INVALID_POINTER); + + if (pStreamDbg->pFile) + { + AudioHlpFileDestroy(pStreamDbg->pFile); + pStreamDbg->pFile = NULL; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudDebugHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID); + return PDMHOSTAUDIOSTREAMSTATE_OKAY; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} + */ +static DECLCALLBACK(uint32_t) drvHstAudDebugHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return 0; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHstAudDebugHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + RT_NOREF(pInterface); + PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream; + + int rc = AudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cbBuf); + if (RT_SUCCESS(rc)) + *pcbWritten = cbBuf; + else + LogRelMax(32, ("DebugAudio: Writing output failed with %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHstAudDebugHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream; + + return PDMAudioPropsMilliToBytes(&pStreamDbg->Cfg.Props, 10 /*ms*/); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHstAudDebugHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface); + PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream; +/** @todo rate limit this? */ + + /* + * Clear the buffer first so we don't need to thing about additional channels. + */ + uint32_t cFrames = PDMAudioPropsBytesToFrames(&pStreamDbg->Cfg.Props, cbBuf); + PDMAudioPropsClearBuffer(&pStreamDbg->Cfg.Props, pvBuf, cbBuf, cFrames); + + /* + * Generate the select sin wave in the first channel: + */ + uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamDbg->Cfg.Props); + double const rdFixed = pStreamDbg->In.rdFixed; + uint64_t iSrcFrame = pStreamDbg->In.uSample; + switch (PDMAudioPropsSampleSize(&pStreamDbg->Cfg.Props)) + { + case 1: + /* untested */ + if (PDMAudioPropsIsSigned(&pStreamDbg->Cfg.Props)) + { + int8_t *piSample = (int8_t *)pvBuf; + while (cFrames-- > 0) + { + *piSample = 126 /*Amplitude*/ * sin(rdFixed * iSrcFrame); + iSrcFrame++; + piSample += cbFrame; + } + } + else + { + /* untested */ + uint16_t *pbSample = (uint16_t *)pvBuf; + while (cFrames-- > 0) + { + *pbSample = 126 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x80; + iSrcFrame++; + pbSample += cbFrame; + } + } + break; + + case 2: + if (PDMAudioPropsIsSigned(&pStreamDbg->Cfg.Props)) + { + int16_t *piSample = (int16_t *)pvBuf; + while (cFrames-- > 0) + { + *piSample = 32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame); + iSrcFrame++; + piSample = (int16_t *)((uint8_t *)piSample + cbFrame); + } + } + else + { + /* untested */ + uint16_t *puSample = (uint16_t *)pvBuf; + while (cFrames-- > 0) + { + *puSample = 32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x8000; + iSrcFrame++; + puSample = (uint16_t *)((uint8_t *)puSample + cbFrame); + } + } + break; + + case 4: + /* untested */ + if (PDMAudioPropsIsSigned(&pStreamDbg->Cfg.Props)) + { + int32_t *piSample = (int32_t *)pvBuf; + while (cFrames-- > 0) + { + *piSample = (32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame); + iSrcFrame++; + piSample = (int32_t *)((uint8_t *)piSample + cbFrame); + } + } + else + { + uint32_t *puSample = (uint32_t *)pvBuf; + while (cFrames-- > 0) + { + *puSample = (32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame) + UINT32_C(0x80000000); + iSrcFrame++; + puSample = (uint32_t *)((uint8_t *)puSample + cbFrame); + } + } + break; + + default: + AssertFailed(); + } + pStreamDbg->In.uSample = iSrcFrame; + + /* + * Write it. + */ + int rc = AudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cbBuf); + if (RT_SUCCESS(rc)) + *pcbRead = cbBuf; + else + LogRelMax(32, ("DebugAudio: Writing input failed with %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHstAudDebugQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHSTAUDDEBUG pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDDEBUG); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/** + * Constructs a Null audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHstAudDebugConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHSTAUDDEBUG pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDDEBUG); + LogRel(("Audio: Initializing DEBUG driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHstAudDebugQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHstAudDebugHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = NULL; + pThis->IHostAudio.pfnSetDevice = NULL; + pThis->IHostAudio.pfnGetStatus = drvHstAudDebugHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHstAudDebugHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHstAudDebugHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHstAudDebugHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHstAudDebugHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHstAudDebugHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHstAudDebugHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHstAudDebugHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetState = drvHstAudDebugHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetPending = drvHstAudDebugHA_StreamGetPending; + pThis->IHostAudio.pfnStreamGetWritable = drvHstAudDebugHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvHstAudDebugHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvHstAudDebugHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvHstAudDebugHA_StreamCapture; + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "AudioDebugOutput.pcm"); +#endif + + return VINF_SUCCESS; +} + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostDebugAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "DebugAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Debug audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHSTAUDDEBUG), + /* pfnConstruct */ + drvHstAudDebugConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,2876 @@ +/* $Id: DrvHostAudioDSound.cpp $ */ +/** @file + * Host audio driver - DirectSound (Windows). + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#define INITGUID +#include +#include +#include +#include +#include +#include /* WAVEFORMATEXTENSIBLE */ + +#include +#include +#include +#include + +#include +#include + +#include "VBoxDD.h" + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT +# include /* For bad_alloc. */ +# include "DrvHostAudioDSoundMMNotifClient.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* + * Optional release logging, which a user can turn on with the + * 'VBoxManage debugvm' command. + * Debug logging still uses the common Log* macros from VBox. + * Messages which always should go to the release log use LogRel. + * + * @deprecated Use LogRelMax, LogRel2 and LogRel3 directly. + */ +/** General code behavior. */ +#define DSLOG(a) do { LogRel2(a); } while(0) +/** Something which produce a lot of logging during playback/recording. */ +#define DSLOGF(a) do { LogRel3(a); } while(0) +/** Important messages like errors. Limited in the default release log to avoid log flood. */ +#define DSLOGREL(a) \ + do { \ + static int8_t s_cLogged = 0; \ + if (s_cLogged < 8) { \ + ++s_cLogged; \ + LogRel(a); \ + } else DSLOG(a); \ + } while (0) + +/** Maximum number of attempts to restore the sound buffer before giving up. */ +#define DRV_DSOUND_RESTORE_ATTEMPTS_MAX 3 +#if 0 /** @todo r=bird: What are these for? Nobody is using them... */ +/** Default input latency (in ms). */ +#define DRV_DSOUND_DEFAULT_LATENCY_MS_IN 50 +/** Default output latency (in ms). */ +#define DRV_DSOUND_DEFAULT_LATENCY_MS_OUT 50 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/* Dynamically load dsound.dll. */ +typedef HRESULT WINAPI FNDIRECTSOUNDENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext); +typedef FNDIRECTSOUNDENUMERATEW *PFNDIRECTSOUNDENUMERATEW; +typedef HRESULT WINAPI FNDIRECTSOUNDCAPTUREENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext); +typedef FNDIRECTSOUNDCAPTUREENUMERATEW *PFNDIRECTSOUNDCAPTUREENUMERATEW; +typedef HRESULT WINAPI FNDIRECTSOUNDCAPTURECREATE8(LPCGUID lpcGUID, LPDIRECTSOUNDCAPTURE8 *lplpDSC, LPUNKNOWN pUnkOuter); +typedef FNDIRECTSOUNDCAPTURECREATE8 *PFNDIRECTSOUNDCAPTURECREATE8; + +#define VBOX_DSOUND_MAX_EVENTS 3 + +typedef enum DSOUNDEVENT +{ + DSOUNDEVENT_NOTIFY = 0, + DSOUNDEVENT_INPUT, + DSOUNDEVENT_OUTPUT, +} DSOUNDEVENT; + +typedef struct DSOUNDHOSTCFG +{ + RTUUID uuidPlay; + LPCGUID pGuidPlay; + RTUUID uuidCapture; + LPCGUID pGuidCapture; +} DSOUNDHOSTCFG, *PDSOUNDHOSTCFG; + +typedef struct DSOUNDSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** Entry in DRVHOSTDSOUND::HeadStreams. */ + RTLISTNODE ListEntry; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** Buffer alignment. */ + uint8_t uAlign; + /** Whether this stream is in an enable state on the DirectSound side. */ + bool fEnabled; + bool afPadding[2]; + /** Size (in bytes) of the DirectSound buffer. */ + DWORD cbBufSize; + union + { + struct + { + /** The actual DirectSound Buffer (DSB) used for the capturing. + * This is a secondary buffer and is used as a streaming buffer. */ + LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB; + /** Current read offset (in bytes) within the DSB. */ + DWORD offReadPos; + /** Number of buffer overruns happened. Used for logging. */ + uint8_t cOverruns; + } In; + struct + { + /** The actual DirectSound Buffer (DSB) used for playback. + * This is a secondary buffer and is used as a streaming buffer. */ + LPDIRECTSOUNDBUFFER8 pDSB; + /** Current write offset (in bytes) within the DSB. + * @note This is needed as the current write position as kept by direct sound + * will move ahead if we're too late. */ + DWORD offWritePos; + /** Offset of last play cursor within the DSB when checked for pending. */ + DWORD offPlayCursorLastPending; + /** Offset of last play cursor within the DSB when last played. */ + DWORD offPlayCursorLastPlayed; + /** Total amount (in bytes) written to our internal ring buffer. */ + uint64_t cbWritten; + /** Total amount (in bytes) played (to the DirectSound buffer). */ + uint64_t cbTransferred; + /** Flag indicating whether playback was just (re)started. */ + bool fFirstTransfer; + /** Flag indicating whether this stream is in draining mode, e.g. no new + * data is being written to it but DirectSound still needs to be able to + * play its remaining (buffered) data. */ + bool fDrain; + /** How much (in bytes) the last transfer from the internal buffer + * to the DirectSound buffer was. */ + uint32_t cbLastTransferred; + /** The RTTimeMilliTS() deadline for the draining of this stream. */ + uint64_t msDrainDeadline; + } Out; + }; + /** Timestamp (in ms) of the last transfer from the internal buffer to/from the + * DirectSound buffer. */ + uint64_t msLastTransfer; + /** The stream's critical section for synchronizing access. */ + RTCRITSECT CritSect; + /** Used for formatting the current DSound status. */ + char szStatus[127]; + /** Fixed zero terminator. */ + char const chStateZero; +} DSOUNDSTREAM, *PDSOUNDSTREAM; + +/** + * DirectSound-specific device entry. + */ +typedef struct DSOUNDDEV +{ + PDMAUDIOHOSTDEV Core; + /** The GUID if handy. */ + GUID Guid; + /** The GUID as a string (empty if default). */ + char szGuid[RTUUID_STR_LENGTH]; +} DSOUNDDEV; +/** Pointer to a DirectSound device entry. */ +typedef DSOUNDDEV *PDSOUNDDEV; + +/** + * Structure for holding a device enumeration context. + */ +typedef struct DSOUNDENUMCBCTX +{ + /** Enumeration flags. */ + uint32_t fFlags; + /** Pointer to device list to populate. */ + PPDMAUDIOHOSTENUM pDevEnm; +} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX; + +typedef struct DRVHOSTDSOUND +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Our audio host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Critical section to serialize access. */ + RTCRITSECT CritSect; + /** DirectSound configuration options. */ + DSOUNDHOSTCFG Cfg; + /** List of devices of last enumeration. */ + PDMAUDIOHOSTENUM DeviceEnum; + /** Whether this backend supports any audio input. + * @todo r=bird: This is not actually used for anything. */ + bool fEnabledIn; + /** Whether this backend supports any audio output. + * @todo r=bird: This is not actually used for anything. */ + bool fEnabledOut; + /** The Direct Sound playback interface. */ + LPDIRECTSOUND8 pDS; + /** The Direct Sound capturing interface. */ + LPDIRECTSOUNDCAPTURE8 pDSC; + /** List of streams (DSOUNDSTREAM). + * Requires CritSect ownership. */ + RTLISTANCHOR HeadStreams; + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + DrvHostAudioDSoundMMNotifClient *m_pNotificationClient; +#endif +} DRVHOSTDSOUND, *PDRVHOSTDSOUND; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB); +static int drvHostDSoundStreamStopPlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fReset); + +static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PDMAUDIOHOSTENUM pDevEnm, uint32_t fEnum); + +static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis); + + +#if defined(LOG_ENABLED) || defined(RTLOG_REL_ENABLED) +/** + * Gets the stream status as a string for logging purposes. + * + * @returns Status string (pStreamDS->szStatus). + * @param pStreamDS The stream to get the status for. + */ +static const char *drvHostDSoundStreamStatusString(PDSOUNDSTREAM pStreamDS) +{ + /* + * Out internal stream status first. + */ + size_t off; + if (pStreamDS->fEnabled) + { + memcpy(pStreamDS->szStatus, RT_STR_TUPLE("ENABLED ")); + off = sizeof("ENABLED ") - 1; + } + else + { + memcpy(pStreamDS->szStatus, RT_STR_TUPLE("DISABLED")); + off = sizeof("DISABLED") - 1; + } + + /* + * Direction specific stuff, returning with a status DWORD and string mappings for it. + */ + typedef struct DRVHOSTDSOUNDSFLAGS2STR + { + const char *pszMnemonic; + uint32_t cchMnemonic; + uint32_t fFlag; + } DRVHOSTDSOUNDSFLAGS2STR; + static const DRVHOSTDSOUNDSFLAGS2STR s_aCaptureFlags[] = + { + { RT_STR_TUPLE(" CAPTURING"), DSCBSTATUS_CAPTURING }, + { RT_STR_TUPLE(" LOOPING"), DSCBSTATUS_LOOPING }, + }; + static const DRVHOSTDSOUNDSFLAGS2STR s_aPlaybackFlags[] = + { + { RT_STR_TUPLE(" PLAYING"), DSBSTATUS_PLAYING }, + { RT_STR_TUPLE(" BUFFERLOST"), DSBSTATUS_BUFFERLOST }, + { RT_STR_TUPLE(" LOOPING"), DSBSTATUS_LOOPING }, + { RT_STR_TUPLE(" LOCHARDWARE"), DSBSTATUS_LOCHARDWARE }, + { RT_STR_TUPLE(" LOCSOFTWARE"), DSBSTATUS_LOCSOFTWARE }, + { RT_STR_TUPLE(" TERMINATED"), DSBSTATUS_TERMINATED }, + }; + DRVHOSTDSOUNDSFLAGS2STR const *paMappings = NULL; + size_t cMappings = 0; + DWORD fStatus = 0; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + if (pStreamDS->In.pDSCB) + { + HRESULT hrc = pStreamDS->In.pDSCB->GetStatus(&fStatus); + if (SUCCEEDED(hrc)) + { + paMappings = s_aCaptureFlags; + cMappings = RT_ELEMENTS(s_aCaptureFlags); + } + else + RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "GetStatus->%Rhrc", hrc); + } + else + RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "NO-DSCB"); + } + else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) + { + if (pStreamDS->Out.fDrain) + { + memcpy(&pStreamDS->szStatus[off], RT_STR_TUPLE(" DRAINING")); + off += sizeof(" DRAINING") - 1; + } + + if (pStreamDS->Out.fFirstTransfer) + { + memcpy(&pStreamDS->szStatus[off], RT_STR_TUPLE(" NOXFER")); + off += sizeof(" NOXFER") - 1; + } + + if (pStreamDS->Out.pDSB) + { + HRESULT hrc = pStreamDS->Out.pDSB->GetStatus(&fStatus); + if (SUCCEEDED(hrc)) + { + paMappings = s_aPlaybackFlags; + cMappings = RT_ELEMENTS(s_aPlaybackFlags); + } + else + RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "GetStatus->%Rhrc", hrc); + } + else + RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "NO-DSB"); + } + else + RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "BAD-DIR"); + + /* Format flags. */ + if (paMappings) + { + if (fStatus == 0) + RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, " 0"); + else + { + for (size_t i = 0; i < cMappings; i++) + if (fStatus & paMappings[i].fFlag) + { + memcpy(&pStreamDS->szStatus[off], paMappings[i].pszMnemonic, paMappings[i].cchMnemonic); + off += paMappings[i].cchMnemonic; + + fStatus &= ~paMappings[i].fFlag; + if (!fStatus) + break; + } + if (fStatus != 0) + off += RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, " %#x", fStatus); + } + } + + /* + * Finally, terminate the string. By postponing it this long, it won't be + * a big deal if two threads go thru here at the same time as long as the + * status is the same. + */ + Assert(off < sizeof(pStreamDS->szStatus)); + pStreamDS->szStatus[off] = '\0'; + + return pStreamDS->szStatus; +} +#endif /* LOG_ENABLED || RTLOG_REL_ENABLED */ + + +static DWORD dsoundRingDistance(DWORD offEnd, DWORD offBegin, DWORD cSize) +{ + AssertReturn(offEnd <= cSize, 0); + AssertReturn(offBegin <= cSize, 0); + + return offEnd >= offBegin ? offEnd - offBegin : cSize - offBegin + offEnd; +} + + +static char *dsoundGUIDToUtf8StrA(LPCGUID pGUID) +{ + if (pGUID) + { + LPOLESTR lpOLEStr; + HRESULT hr = StringFromCLSID(*pGUID, &lpOLEStr); + if (SUCCEEDED(hr)) + { + char *pszGUID; + int rc = RTUtf16ToUtf8(lpOLEStr, &pszGUID); + CoTaskMemFree(lpOLEStr); + + return RT_SUCCESS(rc) ? pszGUID : NULL; + } + } + + return RTStrDup("{Default device}"); +} + + +static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB) +{ + RT_NOREF(pThis); + HRESULT hr = IDirectSoundBuffer8_Restore(pDSB); + if (FAILED(hr)) + DSLOG(("DSound: Restoring playback buffer\n")); + else + DSLOGREL(("DSound: Restoring playback buffer failed with %Rhrc\n", hr)); + + return hr; +} + + +static HRESULT directSoundPlayUnlock(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, + PVOID pv1, PVOID pv2, + DWORD cb1, DWORD cb2) +{ + RT_NOREF(pThis); + HRESULT hr = IDirectSoundBuffer8_Unlock(pDSB, pv1, cb1, pv2, cb2); + if (FAILED(hr)) + DSLOGREL(("DSound: Unlocking playback buffer failed with %Rhrc\n", hr)); + return hr; +} + + +static HRESULT directSoundPlayLock(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, + DWORD dwOffset, DWORD dwBytes, + PVOID *ppv1, PVOID *ppv2, + DWORD *pcb1, DWORD *pcb2, + DWORD dwFlags) +{ + AssertReturn(dwBytes, VERR_INVALID_PARAMETER); + + HRESULT hr = E_FAIL; + AssertCompile(DRV_DSOUND_RESTORE_ATTEMPTS_MAX > 0); + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + PVOID pv1, pv2; + DWORD cb1, cb2; + hr = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, dwOffset, dwBytes, &pv1, &cb1, &pv2, &cb2, dwFlags); + if (SUCCEEDED(hr)) + { + if ( (!pv1 || !(cb1 & pStreamDS->uAlign)) + && (!pv2 || !(cb2 & pStreamDS->uAlign))) + { + if (ppv1) + *ppv1 = pv1; + if (ppv2) + *ppv2 = pv2; + if (pcb1) + *pcb1 = cb1; + if (pcb2) + *pcb2 = cb2; + return S_OK; + } + DSLOGREL(("DSound: Locking playback buffer returned misaligned buffer: cb1=%#RX32, cb2=%#RX32 (alignment: %#RX32)\n", + *pcb1, *pcb2, pStreamDS->uAlign)); + directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); + return E_FAIL; + } + + if (hr != DSERR_BUFFERLOST) + break; + + LogFlowFunc(("Locking failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + } + + DSLOGREL(("DSound: Locking playback buffer failed with %Rhrc (dwOff=%ld, dwBytes=%ld)\n", hr, dwOffset, dwBytes)); + return hr; +} + + +static HRESULT directSoundCaptureUnlock(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB, + PVOID pv1, PVOID pv2, + DWORD cb1, DWORD cb2) +{ + HRESULT hr = IDirectSoundCaptureBuffer8_Unlock(pDSCB, pv1, cb1, pv2, cb2); + if (FAILED(hr)) + DSLOGREL(("DSound: Unlocking capture buffer failed with %Rhrc\n", hr)); + return hr; +} + + +static HRESULT directSoundCaptureLock(PDSOUNDSTREAM pStreamDS, + DWORD dwOffset, DWORD dwBytes, + PVOID *ppv1, PVOID *ppv2, + DWORD *pcb1, DWORD *pcb2, + DWORD dwFlags) +{ + PVOID pv1 = NULL; + PVOID pv2 = NULL; + DWORD cb1 = 0; + DWORD cb2 = 0; + + HRESULT hr = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, dwOffset, dwBytes, + &pv1, &cb1, &pv2, &cb2, dwFlags); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Locking capture buffer failed with %Rhrc\n", hr)); + return hr; + } + + if ( (pv1 && (cb1 & pStreamDS->uAlign)) + || (pv2 && (cb2 & pStreamDS->uAlign))) + { + DSLOGREL(("DSound: Locking capture buffer returned misaligned buffer: cb1=%RI32, cb2=%RI32 (alignment: %RU32)\n", + cb1, cb2, pStreamDS->uAlign)); + directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); + return E_FAIL; + } + + *ppv1 = pv1; + *ppv2 = pv2; + *pcb1 = cb1; + *pcb2 = cb2; + + return S_OK; +} + + +/* + * DirectSound playback + */ + +/** + * Creates a DirectSound playback instance. + * + * @return HRESULT + * @param pGUID GUID of device to create the playback interface for. NULL + * for the default device. + * @param ppDS Where to return the interface to the created instance. + */ +static HRESULT drvHostDSoundCreateDSPlaybackInstance(LPCGUID pGUID, LPDIRECTSOUND8 *ppDS) +{ + LogFlowFuncEnter(); + + LPDIRECTSOUND8 pDS = NULL; + HRESULT hrc = CoCreateInstance(CLSID_DirectSound8, NULL, CLSCTX_ALL, IID_IDirectSound8, (void **)&pDS); + if (SUCCEEDED(hrc)) + { + hrc = IDirectSound8_Initialize(pDS, pGUID); + if (SUCCEEDED(hrc)) + { + HWND hWnd = GetDesktopWindow(); + hrc = IDirectSound8_SetCooperativeLevel(pDS, hWnd, DSSCL_PRIORITY); + if (SUCCEEDED(hrc)) + { + *ppDS = pDS; + LogFlowFunc(("LEAVE S_OK\n")); + return S_OK; + } + LogRelMax(64, ("DSound: Setting cooperative level for (hWnd=%p) failed: %Rhrc\n", hWnd, hrc)); + } + else if (hrc == DSERR_NODRIVER) /* Usually means that no playback devices are attached. */ + LogRelMax(64, ("DSound: DirectSound playback is currently unavailable\n")); + else + LogRelMax(64, ("DSound: DirectSound playback initialization failed: %Rhrc\n", hrc)); + + IDirectSound8_Release(pDS); + } + else + LogRelMax(64, ("DSound: Creating playback instance failed: %Rhrc\n", hrc)); + + LogFlowFunc(("LEAVE %Rhrc\n", hrc)); + return hrc; +} + + +#if 0 /* not used */ +static HRESULT directSoundPlayGetStatus(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, DWORD *pdwStatus) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pDSB, E_POINTER); + + AssertPtrNull(pdwStatus); + + DWORD dwStatus = 0; + HRESULT hr = E_FAIL; + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + hr = IDirectSoundBuffer8_GetStatus(pDSB, &dwStatus); + if ( hr == DSERR_BUFFERLOST + || ( SUCCEEDED(hr) + && (dwStatus & DSBSTATUS_BUFFERLOST))) + { + LogFlowFunc(("Getting status failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pDSB); + } + else + break; + } + + if (SUCCEEDED(hr)) + { + if (pdwStatus) + *pdwStatus = dwStatus; + } + else + DSLOGREL(("DSound: Retrieving playback status failed with %Rhrc\n", hr)); + + return hr; +} +#endif + + +/* + * DirectSoundCapture + */ + +#if 0 /* unused */ +static LPCGUID dsoundCaptureSelectDevice(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, NULL); + AssertPtrReturn(pCfg, NULL); + + int rc = VINF_SUCCESS; + + LPCGUID pGUID = pThis->Cfg.pGuidCapture; + if (!pGUID) + { + PDSOUNDDEV pDev = NULL; + switch (pCfg->enmPath) + { + case PDMAUDIOPATH_IN_LINE: + /* + * At the moment we're only supporting line-in in the HDA emulation, + * and line-in + mic-in in the AC'97 emulation both are expected + * to use the host's mic-in as well. + * + * So the fall through here is intentional for now. + */ + case PDMAUDIOPATH_IN_MIC: + pDev = (PDSOUNDDEV)DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->DeviceEnum, PDMAUDIODIR_IN); + break; + + default: + AssertFailedStmt(rc = VERR_NOT_SUPPORTED); + break; + } + + if ( RT_SUCCESS(rc) + && pDev) + { + DSLOG(("DSound: Guest source '%s' is using host recording device '%s'\n", + PDMAudioPathGetName(pCfg->enmPath), pDev->Core.szName)); + pGUID = &pDev->Guid; + } + if (RT_FAILURE(rc)) + { + LogRel(("DSound: Selecting recording device failed with %Rrc\n", rc)); + return NULL; + } + } + + /* This always has to be in the release log. */ + char *pszGUID = dsoundGUIDToUtf8StrA(pGUID); + LogRel(("DSound: Guest source '%s' is using host recording device with GUID '%s'\n", + PDMAudioPathGetName(pCfg->enmPath), pszGUID ? pszGUID: "{?}")); + RTStrFree(pszGUID); + + return pGUID; +} +#endif + + +/** + * Creates a DirectSound capture instance. + * + * @returns HRESULT + * @param pGUID GUID of device to create the capture interface for. NULL + * for default. + * @param ppDSC Where to return the interface to the created instance. + */ +static HRESULT drvHostDSoundCreateDSCaptureInstance(LPCGUID pGUID, LPDIRECTSOUNDCAPTURE8 *ppDSC) +{ + LogFlowFuncEnter(); + + LPDIRECTSOUNDCAPTURE8 pDSC = NULL; + HRESULT hrc = CoCreateInstance(CLSID_DirectSoundCapture8, NULL, CLSCTX_ALL, IID_IDirectSoundCapture8, (void **)&pDSC); + if (SUCCEEDED(hrc)) + { + hrc = IDirectSoundCapture_Initialize(pDSC, pGUID); + if (SUCCEEDED(hrc)) + { + *ppDSC = pDSC; + LogFlowFunc(("LEAVE S_OK\n")); + return S_OK; + } + if (hrc == DSERR_NODRIVER) /* Usually means that no capture devices are attached. */ + LogRelMax(64, ("DSound: Capture device currently is unavailable\n")); + else + LogRelMax(64, ("DSound: Initializing capturing device failed: %Rhrc\n", hrc)); + IDirectSoundCapture_Release(pDSC); + } + else + LogRelMax(64, ("DSound: Creating capture instance failed: %Rhrc\n", hrc)); + + LogFlowFunc(("LEAVE %Rhrc\n", hrc)); + return hrc; +} + + +/** + * Updates this host driver's internal status, according to the global, overall input/output + * state and all connected (native) audio streams. + * + * @todo r=bird: This is a 'ing waste of 'ing time! We're doing this everytime + * an 'ing stream is created and we doesn't 'ing use the information here + * for any darn thing! Given the reported slowness of enumeration and + * issues with the 'ing code the only appropriate response is: + * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARG!!!!!!! + * + * @param pThis Host audio driver instance. + */ +static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis) +{ +#if 0 /** @todo r=bird: This isn't doing *ANYTHING* useful. So, I've just disabled it. */ + AssertPtrReturnVoid(pThis); + LogFlowFuncEnter(); + + PDMAudioHostEnumDelete(&pThis->DeviceEnum); + int rc = dsoundDevicesEnumerate(pThis, &pThis->DeviceEnum); + if (RT_SUCCESS(rc)) + { +#if 0 + if ( pThis->fEnabledOut != RT_BOOL(cbCtx.cDevOut) + || pThis->fEnabledIn != RT_BOOL(cbCtx.cDevIn)) + { + /** @todo Use a registered callback to the audio connector (e.g "OnConfigurationChanged") to + * let the connector know that something has changed within the host backend. */ + } +#endif + pThis->fEnabledIn = PDMAudioHostEnumCountMatching(&pThis->DeviceEnum, PDMAUDIODIR_IN) != 0; + pThis->fEnabledOut = PDMAudioHostEnumCountMatching(&pThis->DeviceEnum, PDMAUDIODIR_OUT) != 0; + } + + LogFlowFuncLeaveRC(rc); +#else + RT_NOREF(pThis); +#endif +} + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostDSoundHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "DirectSound"); + pBackendCfg->cbStream = sizeof(DSOUNDSTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + + return VINF_SUCCESS; +} + + +/** + * Callback for the playback device enumeration. + * + * @return TRUE if continuing enumeration, FALSE if not. + * @param pGUID Pointer to GUID of enumerated device. Can be NULL. + * @param pwszDescription Pointer to (friendly) description of enumerated device. + * @param pwszModule Pointer to module name of enumerated device. + * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information. + * + * @note Carbon copy of drvHostDSoundEnumOldStyleCaptureCallback with OUT direction. + */ +static BOOL CALLBACK drvHostDSoundEnumOldStylePlaybackCallback(LPGUID pGUID, LPCWSTR pwszDescription, + LPCWSTR pwszModule, PVOID lpContext) +{ + PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX)lpContext; + AssertPtrReturn(pEnumCtx, FALSE); + + PPDMAUDIOHOSTENUM pDevEnm = pEnumCtx->pDevEnm; + AssertPtrReturn(pDevEnm, FALSE); + + AssertPtrNullReturn(pGUID, FALSE); /* pGUID can be NULL for default device(s). */ + AssertPtrReturn(pwszDescription, FALSE); + RT_NOREF(pwszModule); /* Do not care about pwszModule. */ + + int rc; + size_t const cbName = RTUtf16CalcUtf8Len(pwszDescription) + 1; + PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0); + if (pDev) + { + pDev->Core.enmUsage = PDMAUDIODIR_OUT; + pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN; + + if (pGUID == NULL) + pDev->Core.fFlags = PDMAUDIOHOSTDEV_F_DEFAULT_OUT; + + rc = RTUtf16ToUtf8Ex(pwszDescription, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL); + if (RT_SUCCESS(rc)) + { + if (!pGUID) + pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_OUT; + else + { + memcpy(&pDev->Guid, pGUID, sizeof(pDev->Guid)); + rc = RTUuidToStr((PCRTUUID)pGUID, pDev->szGuid, sizeof(pDev->szGuid)); + AssertRC(rc); + } + pDev->Core.pszId = &pDev->szGuid[0]; + + PDMAudioHostEnumAppend(pDevEnm, &pDev->Core); + + /* Note: Querying the actual device information will be done at some + * later point in time outside this enumeration callback to prevent + * DSound hangs. */ + return TRUE; + } + PDMAudioHostDevFree(&pDev->Core); + } + else + rc = VERR_NO_MEMORY; + + LogRel(("DSound: Error enumeration playback device '%ls': rc=%Rrc\n", pwszDescription, rc)); + return FALSE; /* Abort enumeration. */ +} + + +/** + * Callback for the capture device enumeration. + * + * @return TRUE if continuing enumeration, FALSE if not. + * @param pGUID Pointer to GUID of enumerated device. Can be NULL. + * @param pwszDescription Pointer to (friendly) description of enumerated device. + * @param pwszModule Pointer to module name of enumerated device. + * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information. + * + * @note Carbon copy of drvHostDSoundEnumOldStylePlaybackCallback with IN direction. + */ +static BOOL CALLBACK drvHostDSoundEnumOldStyleCaptureCallback(LPGUID pGUID, LPCWSTR pwszDescription, + LPCWSTR pwszModule, PVOID lpContext) +{ + PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX )lpContext; + AssertPtrReturn(pEnumCtx, FALSE); + + PPDMAUDIOHOSTENUM pDevEnm = pEnumCtx->pDevEnm; + AssertPtrReturn(pDevEnm, FALSE); + + AssertPtrNullReturn(pGUID, FALSE); /* pGUID can be NULL for default device(s). */ + AssertPtrReturn(pwszDescription, FALSE); + RT_NOREF(pwszModule); /* Do not care about pwszModule. */ + + int rc; + size_t const cbName = RTUtf16CalcUtf8Len(pwszDescription) + 1; + PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0); + if (pDev) + { + pDev->Core.enmUsage = PDMAUDIODIR_IN; + pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN; + + rc = RTUtf16ToUtf8Ex(pwszDescription, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL); + if (RT_SUCCESS(rc)) + { + if (!pGUID) + pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_IN; + else + { + memcpy(&pDev->Guid, pGUID, sizeof(pDev->Guid)); + rc = RTUuidToStr((PCRTUUID)pGUID, pDev->szGuid, sizeof(pDev->szGuid)); + AssertRC(rc); + } + pDev->Core.pszId = &pDev->szGuid[0]; + + PDMAudioHostEnumAppend(pDevEnm, &pDev->Core); + + /* Note: Querying the actual device information will be done at some + * later point in time outside this enumeration callback to prevent + * DSound hangs. */ + return TRUE; + } + PDMAudioHostDevFree(&pDev->Core); + } + else + rc = VERR_NO_MEMORY; + + LogRel(("DSound: Error enumeration capture device '%ls', rc=%Rrc\n", pwszDescription, rc)); + return FALSE; /* Abort enumeration. */ +} + + +/** + * Queries information for a given (DirectSound) device. + * + * @returns VBox status code. + * @param pDev Audio device to query information for. + */ +static int drvHostDSoundEnumOldStyleQueryDeviceInfo(PDSOUNDDEV pDev) +{ + AssertPtr(pDev); + int rc; + + if (pDev->Core.enmUsage == PDMAUDIODIR_OUT) + { + LPDIRECTSOUND8 pDS; + HRESULT hr = drvHostDSoundCreateDSPlaybackInstance(&pDev->Guid, &pDS); + if (SUCCEEDED(hr)) + { + DSCAPS DSCaps; + RT_ZERO(DSCaps); + DSCaps.dwSize = sizeof(DSCAPS); + hr = IDirectSound_GetCaps(pDS, &DSCaps); + if (SUCCEEDED(hr)) + { + pDev->Core.cMaxOutputChannels = DSCaps.dwFlags & DSCAPS_PRIMARYSTEREO ? 2 : 1; + + DWORD dwSpeakerCfg; + hr = IDirectSound_GetSpeakerConfig(pDS, &dwSpeakerCfg); + if (SUCCEEDED(hr)) + { + unsigned uSpeakerCount = 0; + switch (DSSPEAKER_CONFIG(dwSpeakerCfg)) + { + case DSSPEAKER_MONO: uSpeakerCount = 1; break; + case DSSPEAKER_HEADPHONE: uSpeakerCount = 2; break; + case DSSPEAKER_STEREO: uSpeakerCount = 2; break; + case DSSPEAKER_QUAD: uSpeakerCount = 4; break; + case DSSPEAKER_SURROUND: uSpeakerCount = 4; break; + case DSSPEAKER_5POINT1: uSpeakerCount = 6; break; + case DSSPEAKER_5POINT1_SURROUND: uSpeakerCount = 6; break; + case DSSPEAKER_7POINT1: uSpeakerCount = 8; break; + case DSSPEAKER_7POINT1_SURROUND: uSpeakerCount = 8; break; + default: break; + } + + if (uSpeakerCount) /* Do we need to update the channel count? */ + pDev->Core.cMaxOutputChannels = uSpeakerCount; + + rc = VINF_SUCCESS; + } + else + { + LogRel(("DSound: Error retrieving playback device speaker config, hr=%Rhrc\n", hr)); + rc = VERR_ACCESS_DENIED; /** @todo Fudge! */ + } + } + else + { + LogRel(("DSound: Error retrieving playback device capabilities, hr=%Rhrc\n", hr)); + rc = VERR_ACCESS_DENIED; /** @todo Fudge! */ + } + + IDirectSound8_Release(pDS); + } + else + rc = VERR_GENERAL_FAILURE; + } + else if (pDev->Core.enmUsage == PDMAUDIODIR_IN) + { + LPDIRECTSOUNDCAPTURE8 pDSC; + HRESULT hr = drvHostDSoundCreateDSCaptureInstance(&pDev->Guid, &pDSC); + if (SUCCEEDED(hr)) + { + DSCCAPS DSCCaps; + RT_ZERO(DSCCaps); + DSCCaps.dwSize = sizeof(DSCCAPS); + hr = IDirectSoundCapture_GetCaps(pDSC, &DSCCaps); + if (SUCCEEDED(hr)) + { + pDev->Core.cMaxInputChannels = DSCCaps.dwChannels; + rc = VINF_SUCCESS; + } + else + { + LogRel(("DSound: Error retrieving capture device capabilities, hr=%Rhrc\n", hr)); + rc = VERR_ACCESS_DENIED; /** @todo Fudge! */ + } + + IDirectSoundCapture_Release(pDSC); + } + else + rc = VERR_GENERAL_FAILURE; + } + else + AssertFailedStmt(rc = VERR_NOT_SUPPORTED); + + return rc; +} + + +/** + * Queries information for @a pDevice and adds an entry to the enumeration. + * + * @returns VBox status code. + * @param pDevEnm The enumeration to add the device to. + * @param pDevice The device. + * @param enmType The type of device. + * @param fDefault Whether it's the default device. + */ +static int drvHostDSoundEnumNewStyleAdd(PPDMAUDIOHOSTENUM pDevEnm, IMMDevice *pDevice, EDataFlow enmType, bool fDefault) +{ + int rc = VINF_SUCCESS; /* ignore most errors */ + + /* + * Gather the necessary properties. + */ + IPropertyStore *pProperties = NULL; + HRESULT hrc = pDevice->OpenPropertyStore(STGM_READ, &pProperties); + if (SUCCEEDED(hrc)) + { + /* Get the friendly name. */ + PROPVARIANT VarName; + PropVariantInit(&VarName); + hrc = pProperties->GetValue(PKEY_Device_FriendlyName, &VarName); + if (SUCCEEDED(hrc)) + { + /* Get the DirectSound GUID. */ + PROPVARIANT VarGUID; + PropVariantInit(&VarGUID); + hrc = pProperties->GetValue(PKEY_AudioEndpoint_GUID, &VarGUID); + if (SUCCEEDED(hrc)) + { + /* Get the device format. */ + PROPVARIANT VarFormat; + PropVariantInit(&VarFormat); + hrc = pProperties->GetValue(PKEY_AudioEngine_DeviceFormat, &VarFormat); + if (SUCCEEDED(hrc)) + { + WAVEFORMATEX const * const pFormat = (WAVEFORMATEX const *)VarFormat.blob.pBlobData; + AssertPtr(pFormat); + + /* + * Create a enumeration entry for it. + */ + size_t const cbName = RTUtf16CalcUtf8Len(VarName.pwszVal) + 1; + PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0); + if (pDev) + { + pDev->Core.enmUsage = enmType == eRender ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN; + pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN; + if (fDefault) + pDev->Core.fFlags |= enmType == eRender + ? PDMAUDIOHOSTDEV_F_DEFAULT_OUT : PDMAUDIOHOSTDEV_F_DEFAULT_IN; + if (enmType == eRender) + pDev->Core.cMaxOutputChannels = pFormat->nChannels; + else + pDev->Core.cMaxInputChannels = pFormat->nChannels; + + //if (fDefault) + rc = RTUuidFromUtf16((PRTUUID)&pDev->Guid, VarGUID.pwszVal); + if (RT_SUCCESS(rc)) + { + rc = RTUuidToStr((PCRTUUID)&pDev->Guid, pDev->szGuid, sizeof(pDev->szGuid)); + AssertRC(rc); + pDev->Core.pszId = &pDev->szGuid[0]; + + rc = RTUtf16ToUtf8Ex(VarName.pwszVal, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL); + if (RT_SUCCESS(rc)) + PDMAudioHostEnumAppend(pDevEnm, &pDev->Core); + else + PDMAudioHostDevFree(&pDev->Core); + } + else + { + LogFunc(("RTUuidFromUtf16(%ls): %Rrc\n", VarGUID.pwszVal, rc)); + PDMAudioHostDevFree(&pDev->Core); + } + } + else + rc = VERR_NO_MEMORY; + PropVariantClear(&VarFormat); + } + else + LogFunc(("Failed to get PKEY_AudioEngine_DeviceFormat: %Rhrc\n", hrc)); + PropVariantClear(&VarGUID); + } + else + LogFunc(("Failed to get PKEY_AudioEndpoint_GUID: %Rhrc\n", hrc)); + PropVariantClear(&VarName); + } + else + LogFunc(("Failed to get PKEY_Device_FriendlyName: %Rhrc\n", hrc)); + pProperties->Release(); + } + else + LogFunc(("OpenPropertyStore failed: %Rhrc\n", hrc)); + + if (hrc == E_OUTOFMEMORY && RT_SUCCESS_NP(rc)) + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Does a (Re-)enumeration of the host's playback + capturing devices. + * + * @return VBox status code. + * @param pDevEnm Where to store the enumerated devices. + */ +static int drvHostDSoundEnumerateDevices(PPDMAUDIOHOSTENUM pDevEnm) +{ + DSLOG(("DSound: Enumerating devices ...\n")); + + /* + * Use the Vista+ API. + */ + IMMDeviceEnumerator *pEnumerator; + HRESULT hrc = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, + __uuidof(IMMDeviceEnumerator), (void **)&pEnumerator); + if (SUCCEEDED(hrc)) + { + int rc = VINF_SUCCESS; + for (unsigned idxPass = 0; idxPass < 2 && RT_SUCCESS(rc); idxPass++) + { + EDataFlow const enmType = idxPass == 0 ? /*EDataFlow::*/eRender : /*EDataFlow::*/eCapture; + + /* Get the default device first. */ + IMMDevice *pDefaultDevice = NULL; + hrc = pEnumerator->GetDefaultAudioEndpoint(enmType, eMultimedia, &pDefaultDevice); + if (SUCCEEDED(hrc)) + rc = drvHostDSoundEnumNewStyleAdd(pDevEnm, pDefaultDevice, enmType, true); + else + pDefaultDevice = NULL; + + /* Enumerate the devices. */ + IMMDeviceCollection *pCollection = NULL; + hrc = pEnumerator->EnumAudioEndpoints(enmType, DEVICE_STATE_ACTIVE /*| DEVICE_STATE_UNPLUGGED?*/, &pCollection); + if (SUCCEEDED(hrc) && pCollection != NULL) + { + UINT cDevices = 0; + hrc = pCollection->GetCount(&cDevices); + if (SUCCEEDED(hrc)) + { + for (UINT idxDevice = 0; idxDevice < cDevices && RT_SUCCESS(rc); idxDevice++) + { + IMMDevice *pDevice = NULL; + hrc = pCollection->Item(idxDevice, &pDevice); + if (SUCCEEDED(hrc) && pDevice) + { + if (pDevice != pDefaultDevice) + rc = drvHostDSoundEnumNewStyleAdd(pDevEnm, pDevice, enmType, false); + pDevice->Release(); + } + } + } + pCollection->Release(); + } + else + LogRelMax(10, ("EnumAudioEndpoints(%s) failed: %Rhrc\n", idxPass == 0 ? "output" : "input", hrc)); + + if (pDefaultDevice) + pDefaultDevice->Release(); + } + pEnumerator->Release(); + if (pDevEnm->cDevices > 0 || RT_FAILURE(rc)) + { + DSLOG(("DSound: Enumerating devices done - %u device (%Rrc)\n", pDevEnm->cDevices, rc)); + return rc; + } + } + + /* + * Fall back to dsound. + */ + /* Resolve symbols once. */ + static PFNDIRECTSOUNDENUMERATEW volatile s_pfnDirectSoundEnumerateW = NULL; + static PFNDIRECTSOUNDCAPTUREENUMERATEW volatile s_pfnDirectSoundCaptureEnumerateW = NULL; + + PFNDIRECTSOUNDENUMERATEW pfnDirectSoundEnumerateW = s_pfnDirectSoundEnumerateW; + PFNDIRECTSOUNDCAPTUREENUMERATEW pfnDirectSoundCaptureEnumerateW = s_pfnDirectSoundCaptureEnumerateW; + if (!pfnDirectSoundEnumerateW || !pfnDirectSoundCaptureEnumerateW) + { + RTLDRMOD hModDSound = NIL_RTLDRMOD; + int rc = RTLdrLoadSystem("dsound.dll", true /*fNoUnload*/, &hModDSound); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hModDSound, "DirectSoundEnumerateW", (void **)&pfnDirectSoundEnumerateW); + if (RT_SUCCESS(rc)) + s_pfnDirectSoundEnumerateW = pfnDirectSoundEnumerateW; + else + LogRel(("DSound: Failed to get dsound.dll export DirectSoundEnumerateW: %Rrc\n", rc)); + + rc = RTLdrGetSymbol(hModDSound, "DirectSoundCaptureEnumerateW", (void **)&pfnDirectSoundCaptureEnumerateW); + if (RT_SUCCESS(rc)) + s_pfnDirectSoundCaptureEnumerateW = pfnDirectSoundCaptureEnumerateW; + else + LogRel(("DSound: Failed to get dsound.dll export DirectSoundCaptureEnumerateW: %Rrc\n", rc)); + RTLdrClose(hModDSound); + } + else + LogRel(("DSound: Unable to load dsound.dll for enumerating devices: %Rrc\n", rc)); + if (!pfnDirectSoundEnumerateW && !pfnDirectSoundCaptureEnumerateW) + return rc; + } + + /* Common callback context for both playback and capture enumerations: */ + DSOUNDENUMCBCTX EnumCtx; + EnumCtx.fFlags = 0; + EnumCtx.pDevEnm = pDevEnm; + + /* Enumerate playback devices. */ + if (pfnDirectSoundEnumerateW) + { + DSLOG(("DSound: Enumerating playback devices ...\n")); + HRESULT hr = pfnDirectSoundEnumerateW(&drvHostDSoundEnumOldStylePlaybackCallback, &EnumCtx); + if (FAILED(hr)) + LogRel(("DSound: Error enumerating host playback devices: %Rhrc\n", hr)); + } + + /* Enumerate capture devices. */ + if (pfnDirectSoundCaptureEnumerateW) + { + DSLOG(("DSound: Enumerating capture devices ...\n")); + HRESULT hr = pfnDirectSoundCaptureEnumerateW(&drvHostDSoundEnumOldStyleCaptureCallback, &EnumCtx); + if (FAILED(hr)) + LogRel(("DSound: Error enumerating host capture devices: %Rhrc\n", hr)); + } + + /* + * Query Information for all enumerated devices. + * Note! This is problematic to do from the enumeration callbacks. + */ + PDSOUNDDEV pDev; + RTListForEach(&pDevEnm->LstDevices, pDev, DSOUNDDEV, Core.ListEntry) + { + drvHostDSoundEnumOldStyleQueryDeviceInfo(pDev); /* ignore rc */ + } + + DSLOG(("DSound: Enumerating devices done\n")); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} + */ +static DECLCALLBACK(int) drvHostDSoundHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); + + PDMAudioHostEnumInit(pDeviceEnum); + int rc = drvHostDSoundEnumerateDevices(pDeviceEnum); + if (RT_FAILURE(rc)) + PDMAudioHostEnumDelete(pDeviceEnum); + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDSoundHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * Converts from PDM stream config to windows WAVEFORMATEXTENSIBLE struct. + * + * @param pCfg The PDM audio stream config to convert from. + * @param pFmt The windows structure to initialize. + */ +static void dsoundWaveFmtFromCfg(PCPDMAUDIOSTREAMCFG pCfg, PWAVEFORMATEXTENSIBLE pFmt) +{ + RT_ZERO(*pFmt); + pFmt->Format.wFormatTag = WAVE_FORMAT_PCM; + pFmt->Format.nChannels = PDMAudioPropsChannels(&pCfg->Props); + pFmt->Format.wBitsPerSample = PDMAudioPropsSampleBits(&pCfg->Props); + pFmt->Format.nSamplesPerSec = PDMAudioPropsHz(&pCfg->Props); + pFmt->Format.nBlockAlign = PDMAudioPropsFrameSize(&pCfg->Props); + pFmt->Format.nAvgBytesPerSec = PDMAudioPropsFramesToBytes(&pCfg->Props, PDMAudioPropsHz(&pCfg->Props)); + pFmt->Format.cbSize = 0; /* No extra data specified. */ + + /* + * We need to use the extensible structure if there are more than two channels + * or if the channels have non-standard assignments. + */ + if ( pFmt->Format.nChannels > 2 + || ( pFmt->Format.nChannels == 1 + ? pCfg->Props.aidChannels[0] != PDMAUDIOCHANNELID_MONO + : pCfg->Props.aidChannels[0] != PDMAUDIOCHANNELID_FRONT_LEFT + || pCfg->Props.aidChannels[1] != PDMAUDIOCHANNELID_FRONT_RIGHT)) + { + pFmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pFmt->Format.cbSize = sizeof(*pFmt) - sizeof(pFmt->Format); + pFmt->Samples.wValidBitsPerSample = PDMAudioPropsSampleBits(&pCfg->Props); + pFmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + pFmt->dwChannelMask = 0; + unsigned const cSrcChannels = pFmt->Format.nChannels; + for (unsigned i = 0; i < cSrcChannels; i++) + if ( pCfg->Props.aidChannels[i] >= PDMAUDIOCHANNELID_FIRST_STANDARD + && pCfg->Props.aidChannels[i] < PDMAUDIOCHANNELID_END_STANDARD) + pFmt->dwChannelMask |= RT_BIT_32(pCfg->Props.aidChannels[i] - PDMAUDIOCHANNELID_FIRST_STANDARD); + else + pFmt->Format.nChannels -= 1; + } +} + + +/** + * Resets the state of a DirectSound stream, clearing the buffer content. + * + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to reset state for. + */ +static void drvHostDSoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + RT_NOREF(pThis); + LogFunc(("Resetting %s\n", pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback")); + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + /* + * Input streams. + */ + LogFunc(("Resetting capture stream '%s'\n", pStreamDS->Cfg.szName)); + + /* Reset the state: */ + pStreamDS->msLastTransfer = 0; +/** @todo r=bird: We set the read position to zero here, but shouldn't we query it + * from the buffer instead given that there isn't any interface for repositioning + * to the start of the buffer as with playback buffers? */ + pStreamDS->In.offReadPos = 0; + pStreamDS->In.cOverruns = 0; + + /* Clear the buffer content: */ + AssertPtr(pStreamDS->In.pDSCB); + if (pStreamDS->In.pDSCB) + { + PVOID pv1 = NULL; + DWORD cb1 = 0; + PVOID pv2 = NULL; + DWORD cb2 = 0; + HRESULT hrc = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, 0, pStreamDS->cbBufSize, + &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/); + if (SUCCEEDED(hrc)) + { + PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1)); + if (pv2 && cb2) + PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2)); + hrc = IDirectSoundCaptureBuffer8_Unlock(pStreamDS->In.pDSCB, pv1, cb1, pv2, cb2); + if (FAILED(hrc)) + LogRelMaxFunc(64, ("DSound: Unlocking capture buffer '%s' after reset failed: %Rhrc\n", + pStreamDS->Cfg.szName, hrc)); + } + else + LogRelMaxFunc(64, ("DSound: Locking capture buffer '%s' for reset failed: %Rhrc\n", + pStreamDS->Cfg.szName, hrc)); + } + } + else + { + /* + * Output streams. + */ + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + LogFunc(("Resetting playback stream '%s'\n", pStreamDS->Cfg.szName)); + + /* If draining was enagaged, make sure dsound has stopped playing: */ + if (pStreamDS->Out.fDrain && pStreamDS->Out.pDSB) + pStreamDS->Out.pDSB->Stop(); + + /* Reset the internal state: */ + pStreamDS->msLastTransfer = 0; + pStreamDS->Out.fFirstTransfer = true; + pStreamDS->Out.fDrain = false; + pStreamDS->Out.cbLastTransferred = 0; + pStreamDS->Out.cbTransferred = 0; + pStreamDS->Out.cbWritten = 0; + pStreamDS->Out.offWritePos = 0; + pStreamDS->Out.offPlayCursorLastPending = 0; + pStreamDS->Out.offPlayCursorLastPlayed = 0; + + /* Reset the buffer content and repositioning the buffer to the start of the buffer. */ + AssertPtr(pStreamDS->Out.pDSB); + if (pStreamDS->Out.pDSB) + { + HRESULT hrc = IDirectSoundBuffer8_SetCurrentPosition(pStreamDS->Out.pDSB, 0); + if (FAILED(hrc)) + LogRelMaxFunc(64, ("DSound: Failed to set buffer position for '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + + PVOID pv1 = NULL; + DWORD cb1 = 0; + PVOID pv2 = NULL; + DWORD cb2 = 0; + hrc = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, 0, pStreamDS->cbBufSize, &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/); + if (hrc == DSERR_BUFFERLOST) + { + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + hrc = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, 0, pStreamDS->cbBufSize, &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/); + } + if (SUCCEEDED(hrc)) + { + PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1)); + if (pv2 && cb2) + PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2)); + + hrc = IDirectSoundBuffer8_Unlock(pStreamDS->Out.pDSB, pv1, cb1, pv2, cb2); + if (FAILED(hrc)) + LogRelMaxFunc(64, ("DSound: Unlocking playback buffer '%s' after reset failed: %Rhrc\n", + pStreamDS->Cfg.szName, hrc)); + } + else + LogRelMaxFunc(64, ("DSound: Locking playback buffer '%s' for reset failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + } +} + + +/** + * Worker for drvHostDSoundHA_StreamCreate that creates caputre stream. + * + * @returns Windows COM status code. + * @param pThis The DSound instance data. + * @param pStreamDS The stream instance data. + * @param pCfgReq The requested stream config (input). + * @param pCfgAcq Where to return the actual stream config. This is a + * copy of @a *pCfgReq when called. + * @param pWaveFmtExt On input the requested stream format. Updated to the + * actual stream format on successful return. + */ +static HRESULT drvHostDSoundStreamCreateCapture(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PCPDMAUDIOSTREAMCFG pCfgReq, + PPDMAUDIOSTREAMCFG pCfgAcq, WAVEFORMATEXTENSIBLE *pWaveFmtExt) +{ + Assert(pStreamDS->In.pDSCB == NULL); + HRESULT hrc; + + /* + * Create, initialize and set up a IDirectSoundCapture instance the first time + * we go thru here. + */ + /** @todo bird: Or should we rather just throw this away after we've gotten the + * capture buffer? Old code would just leak it... */ + if (pThis->pDSC == NULL) + { + hrc = drvHostDSoundCreateDSCaptureInstance(pThis->Cfg.pGuidCapture, &pThis->pDSC); + if (FAILED(hrc)) + return hrc; /* The worker has complained to the release log already. */ + } + + /* + * Create the capture buffer. + */ + DSCBUFFERDESC BufferDesc = + { + /*.dwSize = */ sizeof(BufferDesc), + /*.dwFlags = */ 0, + /*.dwBufferBytes =*/ PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize), + /*.dwReserved = */ 0, + /*.lpwfxFormat = */ &pWaveFmtExt->Format, + /*.dwFXCount = */ 0, + /*.lpDSCFXDesc = */ NULL + }; + + LogRel2(("DSound: Requested capture buffer is %#x B / %u B / %RU64 ms\n", BufferDesc.dwBufferBytes, BufferDesc.dwBufferBytes, + PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferDesc.dwBufferBytes))); + + LPDIRECTSOUNDCAPTUREBUFFER pLegacyDSCB = NULL; + hrc = IDirectSoundCapture_CreateCaptureBuffer(pThis->pDSC, &BufferDesc, &pLegacyDSCB, NULL); + if (FAILED(hrc)) + { + LogRelMax(64, ("DSound: Creating capture buffer for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + return hrc; + } + + /* Get the IDirectSoundCaptureBuffer8 version of the interface. */ + hrc = IDirectSoundCaptureBuffer_QueryInterface(pLegacyDSCB, IID_IDirectSoundCaptureBuffer8, (void **)&pStreamDS->In.pDSCB); + IDirectSoundCaptureBuffer_Release(pLegacyDSCB); + if (FAILED(hrc)) + { + LogRelMax(64, ("DSound: Querying IID_IDirectSoundCaptureBuffer8 for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + return hrc; + } + + /* + * Query the actual stream configuration. + */ +#if 0 /** @todo r=bird: WTF was this for? */ + DWORD offByteReadPos = 0; + hrc = IDirectSoundCaptureBuffer8_GetCurrentPosition(pStreamDS->In.pDSCB, NULL, &offByteReadPos); + if (FAILED(hrc)) + { + offByteReadPos = 0; + DSLOGREL(("DSound: Getting capture position failed with %Rhrc\n", hr)); + } +#endif + RT_ZERO(*pWaveFmtExt); + hrc = IDirectSoundCaptureBuffer8_GetFormat(pStreamDS->In.pDSCB, &pWaveFmtExt->Format, sizeof(*pWaveFmtExt), NULL); + if (SUCCEEDED(hrc)) + { + /** @todo r=bird: We aren't converting/checking the pWaveFmtX content... */ + + DSCBCAPS BufferCaps = { /*.dwSize = */ sizeof(BufferCaps), 0, 0, 0 }; + hrc = IDirectSoundCaptureBuffer8_GetCaps(pStreamDS->In.pDSCB, &BufferCaps); + if (SUCCEEDED(hrc)) + { + LogRel2(("DSound: Acquired capture buffer capabilities for '%s':\n" + "DSound: dwFlags = %#RX32\n" + "DSound: dwBufferBytes = %#RX32 B / %RU32 B / %RU64 ms\n" + "DSound: dwReserved = %#RX32\n", + pCfgReq->szName, BufferCaps.dwFlags, BufferCaps.dwBufferBytes, BufferCaps.dwBufferBytes, + PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferCaps.dwBufferBytes), BufferCaps.dwReserved )); + + /* Update buffer related stuff: */ + pStreamDS->In.offReadPos = 0; /** @todo shouldn't we use offBytReadPos here to "read at the initial capture position"? */ + pStreamDS->cbBufSize = BufferCaps.dwBufferBytes; + pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, BufferCaps.dwBufferBytes); + +#if 0 /** @todo r=bird: uAlign isn't set anywhere, so this hasn't been checking anything for a while... */ + if (bc.dwBufferBytes & pStreamDS->uAlign) + DSLOGREL(("DSound: Capture GetCaps returned misaligned buffer: size %RU32, alignment %RU32\n", + bc.dwBufferBytes, pStreamDS->uAlign + 1)); +#endif + LogFlow(("returns S_OK\n")); + return S_OK; + } + LogRelMax(64, ("DSound: Getting capture buffer capabilities for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + } + else + LogRelMax(64, ("DSound: Getting capture format for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + + IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB); + pStreamDS->In.pDSCB = NULL; + LogFlowFunc(("returns %Rhrc\n", hrc)); + return hrc; +} + + +/** + * Worker for drvHostDSoundHA_StreamCreate that creates playback stream. + * + * @returns Windows COM status code. + * @param pThis The DSound instance data. + * @param pStreamDS The stream instance data. + * @param pCfgReq The requested stream config (input). + * @param pCfgAcq Where to return the actual stream config. This is a + * copy of @a *pCfgReq when called. + * @param pWaveFmtExt On input the requested stream format. + * Updated to the actual stream format on successful + * return. + */ +static HRESULT drvHostDSoundStreamCreatePlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PCPDMAUDIOSTREAMCFG pCfgReq, + PPDMAUDIOSTREAMCFG pCfgAcq, WAVEFORMATEXTENSIBLE *pWaveFmtExt) +{ + Assert(pStreamDS->Out.pDSB == NULL); + HRESULT hrc; + + /* + * Create, initialize and set up a DirectSound8 instance the first time + * we go thru here. + */ + /** @todo bird: Or should we rather just throw this away after we've gotten the + * sound buffer? Old code would just leak it... */ + if (pThis->pDS == NULL) + { + hrc = drvHostDSoundCreateDSPlaybackInstance(pThis->Cfg.pGuidPlay, &pThis->pDS); + if (FAILED(hrc)) + return hrc; /* The worker has complained to the release log already. */ + } + + /* + * As we reuse our (secondary) buffer for playing out data as it comes in, + * we're using this buffer as a so-called streaming buffer. + * + * See https://msdn.microsoft.com/en-us/library/windows/desktop/ee419014(v=vs.85).aspx + * + * However, as we do not want to use memory on the sound device directly + * (as most modern audio hardware on the host doesn't have this anyway), + * we're *not* going to use DSBCAPS_STATIC for that. + * + * Instead we're specifying DSBCAPS_LOCSOFTWARE, as this fits the bill + * of copying own buffer data to our secondary's Direct Sound buffer. + */ + DSBUFFERDESC BufferDesc = + { + /*.dwSize = */ sizeof(BufferDesc), + /*.dwFlags = */ DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE, + /*.dwBufferBytes = */ PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize), + /*.dwReserved = */ 0, + /*.lpwfxFormat = */ &pWaveFmtExt->Format, + /*.guid3DAlgorithm = {0, 0, 0, {0,0,0,0, 0,0,0,0}} */ + }; + LogRel2(("DSound: Requested playback buffer is %#x B / %u B / %RU64 ms\n", BufferDesc.dwBufferBytes, BufferDesc.dwBufferBytes, + PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferDesc.dwBufferBytes))); + + LPDIRECTSOUNDBUFFER pLegacyDSB = NULL; + hrc = IDirectSound8_CreateSoundBuffer(pThis->pDS, &BufferDesc, &pLegacyDSB, NULL); + if (FAILED(hrc)) + { + LogRelMax(64, ("DSound: Creating playback sound buffer for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + return hrc; + } + + /* Get the IDirectSoundBuffer8 version of the interface. */ + hrc = IDirectSoundBuffer_QueryInterface(pLegacyDSB, IID_IDirectSoundBuffer8, (PVOID *)&pStreamDS->Out.pDSB); + IDirectSoundBuffer_Release(pLegacyDSB); + if (FAILED(hrc)) + { + LogRelMax(64, ("DSound: Querying IID_IDirectSoundBuffer8 for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + return hrc; + } + + /* + * Query the actual stream parameters, they may differ from what we requested. + */ + RT_ZERO(*pWaveFmtExt); + hrc = IDirectSoundBuffer8_GetFormat(pStreamDS->Out.pDSB, &pWaveFmtExt->Format, sizeof(*pWaveFmtExt), NULL); + if (SUCCEEDED(hrc)) + { + /** @todo r=bird: We aren't converting/checking the pWaveFmtX content... */ + + DSBCAPS BufferCaps = { /*.dwSize = */ sizeof(BufferCaps), 0, 0, 0, 0 }; + hrc = IDirectSoundBuffer8_GetCaps(pStreamDS->Out.pDSB, &BufferCaps); + if (SUCCEEDED(hrc)) + { + LogRel2(("DSound: Acquired playback buffer capabilities for '%s':\n" + "DSound: dwFlags = %#RX32\n" + "DSound: dwBufferBytes = %#RX32 B / %RU32 B / %RU64 ms\n" + "DSound: dwUnlockTransferRate = %RU32 KB/s\n" + "DSound: dwPlayCpuOverhead = %RU32%%\n", + pCfgReq->szName, BufferCaps.dwFlags, BufferCaps.dwBufferBytes, BufferCaps.dwBufferBytes, + PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferCaps.dwBufferBytes), + BufferCaps.dwUnlockTransferRate, BufferCaps.dwPlayCpuOverhead)); + + /* Update buffer related stuff: */ + pStreamDS->cbBufSize = BufferCaps.dwBufferBytes; + pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, BufferCaps.dwBufferBytes); + pCfgAcq->Backend.cFramesPeriod = pCfgAcq->Backend.cFramesBufferSize / 4; /* total fiction */ + pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pCfgAcq->Backend.cFramesBufferSize + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + +#if 0 /** @todo r=bird: uAlign isn't set anywhere, so this hasn't been checking anything for a while... */ + if (bc.dwBufferBytes & pStreamDS->uAlign) + DSLOGREL(("DSound: Playback capabilities returned misaligned buffer: size %RU32, alignment %RU32\n", + bc.dwBufferBytes, pStreamDS->uAlign + 1)); +#endif + LogFlow(("returns S_OK\n")); + return S_OK; + } + LogRelMax(64, ("DSound: Getting playback buffer capabilities for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + } + else + LogRelMax(64, ("DSound: Getting playback format for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + + IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB); + pStreamDS->Out.pDSB = NULL; + LogFlowFunc(("returns %Rhrc\n", hrc)); + return hrc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq)); + + const char * const pszStreamType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "capture" : "playback"; RT_NOREF(pszStreamType); + LogFlowFunc(("enmPath=%s '%s'\n", PDMAudioPathGetName(pCfgReq->enmPath), pCfgReq->szName)); + RTListInit(&pStreamDS->ListEntry); /* paranoia */ + + /* For whatever reason: */ + dsoundUpdateStatusInternal(pThis); + + /* + * DSound has different COM interfaces for working with input and output + * streams, so we'll quickly part ways here after some common format + * specification setup and logging. + */ +#if defined(RTLOG_REL_ENABLED) || defined(LOG_ENABLED) + char szTmp[64]; +#endif + LogRel2(("DSound: Opening %s stream '%s' (%s)\n", pCfgReq->szName, pszStreamType, + PDMAudioPropsToString(&pCfgReq->Props, szTmp, sizeof(szTmp)))); + + WAVEFORMATEXTENSIBLE WaveFmtExt; + dsoundWaveFmtFromCfg(pCfgReq, &WaveFmtExt); + LogRel2(("DSound: Requested %s format for '%s':\n" + "DSound: wFormatTag = %RU16\n" + "DSound: nChannels = %RU16\n" + "DSound: nSamplesPerSec = %RU32\n" + "DSound: nAvgBytesPerSec = %RU32\n" + "DSound: nBlockAlign = %RU16\n" + "DSound: wBitsPerSample = %RU16\n" + "DSound: cbSize = %RU16\n", + pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels, + WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign, + WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize)); + if (WaveFmtExt.Format.cbSize != 0) + LogRel2(("DSound: dwChannelMask = %#RX32\n" + "DSound: wValidBitsPerSample = %RU16\n", + WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample)); + + HRESULT hrc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + hrc = drvHostDSoundStreamCreateCapture(pThis, pStreamDS, pCfgReq, pCfgAcq, &WaveFmtExt); + else + hrc = drvHostDSoundStreamCreatePlayback(pThis, pStreamDS, pCfgReq, pCfgAcq, &WaveFmtExt); + int rc; + if (SUCCEEDED(hrc)) + { + LogRel2(("DSound: Acquired %s format for '%s':\n" + "DSound: wFormatTag = %RU16\n" + "DSound: nChannels = %RU16\n" + "DSound: nSamplesPerSec = %RU32\n" + "DSound: nAvgBytesPerSec = %RU32\n" + "DSound: nBlockAlign = %RU16\n" + "DSound: wBitsPerSample = %RU16\n" + "DSound: cbSize = %RU16\n", + pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels, + WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign, + WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize)); + if (WaveFmtExt.Format.cbSize != 0) + { + LogRel2(("DSound: dwChannelMask = %#RX32\n" + "DSound: wValidBitsPerSample = %RU16\n", + WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample)); + + /* Update the channel count and map here. */ + PDMAudioPropsSetChannels(&pCfgAcq->Props, WaveFmtExt.Format.nChannels); + uint8_t idCh = 0; + for (unsigned iBit = 0; iBit < 32 && idCh < WaveFmtExt.Format.nChannels; iBit++) + if (WaveFmtExt.dwChannelMask & RT_BIT_32(iBit)) + { + pCfgAcq->Props.aidChannels[idCh] = (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD + iBit; + idCh++; + } + Assert(idCh == WaveFmtExt.Format.nChannels); + } + + /* + * Copy the acquired config and reset the stream (clears the buffer). + */ + PDMAudioStrmCfgCopy(&pStreamDS->Cfg, pCfgAcq); + drvHostDSoundStreamReset(pThis, pStreamDS); + + RTCritSectEnter(&pThis->CritSect); + RTListAppend(&pThis->HeadStreams, &pStreamDS->ListEntry); + RTCritSectLeave(&pThis->CritSect); + + rc = VINF_SUCCESS; + } + else + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fImmediate) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER); + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + RT_NOREF(fImmediate); + + RTCritSectEnter(&pThis->CritSect); + RTListNodeRemove(&pStreamDS->ListEntry); + RTCritSectLeave(&pThis->CritSect); + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + /* + * Input. + */ + if (pStreamDS->In.pDSCB) + { + HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB); + if (FAILED(hrc)) + LogFunc(("IDirectSoundCaptureBuffer_Stop failed: %Rhrc\n", hrc)); + + drvHostDSoundStreamReset(pThis, pStreamDS); + + IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB); + pStreamDS->In.pDSCB = NULL; + } + } + else + { + /* + * Output. + */ + if (pStreamDS->Out.pDSB) + { + drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/); + + IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB); + pStreamDS->Out.pDSB = NULL; + } + } + + if (RTCritSectIsInitialized(&pStreamDS->CritSect)) + RTCritSectDelete(&pStreamDS->CritSect); + + return VINF_SUCCESS; +} + + +/** + * Worker for drvHostDSoundHA_StreamEnable and drvHostDSoundHA_StreamResume. + * + * This will try re-open the capture device if we're having trouble starting it. + * + * @returns VBox status code. + * @param pThis The DSound host audio driver instance data. + * @param pStreamDS The stream instance data. + */ +static int drvHostDSoundStreamCaptureStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + /* + * Check the stream status first. + */ + int rc = VERR_AUDIO_STREAM_NOT_READY; + if (pStreamDS->In.pDSCB) + { + DWORD fStatus = 0; + HRESULT hrc = IDirectSoundCaptureBuffer8_GetStatus(pStreamDS->In.pDSCB, &fStatus); + if (SUCCEEDED(hrc)) + { + /* + * Try start capturing if it's not already doing so. + */ + if (!(fStatus & DSCBSTATUS_CAPTURING)) + { + LogRel2(("DSound: Starting capture on '%s' ... \n", pStreamDS->Cfg.szName)); + hrc = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, DSCBSTART_LOOPING); + if (SUCCEEDED(hrc)) + rc = VINF_SUCCESS; + else + { + /* + * Failed to start, try re-create the capture buffer. + */ + LogRelMax(64, ("DSound: Starting to capture on '%s' failed: %Rhrc - will try re-open it ...\n", + pStreamDS->Cfg.szName, hrc)); + + IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB); + pStreamDS->In.pDSCB = NULL; + + PDMAUDIOSTREAMCFG CfgReq = pStreamDS->Cfg; + PDMAUDIOSTREAMCFG CfgAcq = pStreamDS->Cfg; + WAVEFORMATEXTENSIBLE WaveFmtExt; + dsoundWaveFmtFromCfg(&pStreamDS->Cfg, &WaveFmtExt); + hrc = drvHostDSoundStreamCreateCapture(pThis, pStreamDS, &CfgReq, &CfgAcq, &WaveFmtExt); + if (SUCCEEDED(hrc)) + { + PDMAudioStrmCfgCopy(&pStreamDS->Cfg, &CfgAcq); + + /* + * Try starting capture again. + */ + LogRel2(("DSound: Starting capture on re-opened '%s' ... \n", pStreamDS->Cfg.szName)); + hrc = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, DSCBSTART_LOOPING); + if (SUCCEEDED(hrc)) + rc = VINF_SUCCESS; + else + LogRelMax(64, ("DSound: Starting to capture on re-opened '%s' failed: %Rhrc\n", + pStreamDS->Cfg.szName, hrc)); + } + else + LogRelMax(64, ("DSound: Re-opening '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + } + else + { + LogRel2(("DSound: Already capturing (%#x)\n", fStatus)); + AssertFailed(); + } + } + else + LogRelMax(64, ("DSound: Retrieving capture status for '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + + /* + * We always reset the buffer before enabling the stream (normally never necessary). + */ + drvHostDSoundStreamReset(pThis, pStreamDS); + pStreamDS->fEnabled = true; + + /* + * Input streams will start capturing, while output streams will only start + * playing once we get some audio data to play. + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS); + else + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * Worker for drvHostDSoundHA_StreamDestroy, drvHostDSoundHA_StreamDisable and + * drvHostDSoundHA_StreamPause. + * + * @returns VBox status code. + * @param pThis The DSound host audio driver instance data. + * @param pStreamDS The stream instance data. + * @param fReset Whether to reset the buffer and state. + */ +static int drvHostDSoundStreamStopPlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fReset) +{ + if (!pStreamDS->Out.pDSB) + return VINF_SUCCESS; + + LogRel2(("DSound: Stopping playback of '%s'...\n", pStreamDS->Cfg.szName)); + HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (FAILED(hrc)) + { + LogFunc(("IDirectSoundBuffer8_Stop -> %Rhrc; will attempt restoring the stream...\n", hrc)); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (FAILED(hrc)) + LogRelMax(64, ("DSound: %s playback of '%s' failed: %Rhrc\n", fReset ? "Stopping" : "Pausing", + pStreamDS->Cfg.szName, hrc)); + } + LogRel2(("DSound: Stopped playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + + if (fReset) + drvHostDSoundStreamReset(pThis, pStreamDS); + return SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_AUDIO_STREAM_NOT_READY; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1, + pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * Change the state. + */ + pStreamDS->fEnabled = false; + + /* + * Stop the stream and maybe reset the buffer. + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + if (pStreamDS->In.pDSCB) + { + HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB); + if (SUCCEEDED(hrc)) + LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName)); + else + { + LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + /* Don't report errors up to the caller, as it might just be a capture device change. */ + } + + /* This isn't strictly speaking necessary since StreamEnable does it too... */ + drvHostDSoundStreamReset(pThis, pStreamDS); + } + } + else + { + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + if (pStreamDS->Out.pDSB) + { + rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/); + if (RT_SUCCESS(rc)) + LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName)); + } + } + + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + * + * @note Basically the same as drvHostDSoundHA_StreamDisable, just w/o the + * buffer resetting and fEnabled change. + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1, + pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * Stop the stream and maybe reset the buffer. + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + if (pStreamDS->In.pDSCB) + { + HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB); + if (SUCCEEDED(hrc)) + LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName)); + else + { + LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + /* Don't report errors up to the caller, as it might just be a capture device change. */ + } + } + } + else + { + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + if (pStreamDS->Out.pDSB) + { + /* Don't stop draining buffers, we won't be resuming them right. + They'll stop by themselves anyway. */ + if (pStreamDS->Out.fDrain) + LogFunc(("Stream '%s' is draining\n", pStreamDS->Cfg.szName)); + else + { + rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, false /*fReset*/); + if (RT_SUCCESS(rc)) + LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName)); + } + } + } + + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS))); + return rc; +} + + +/** + * Worker for drvHostDSoundHA_StreamResume and drvHostDSoundHA_StreamPlay that + * starts playing the DirectSound Buffer. + * + * @returns VBox status code. + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to start playing. + */ +static int directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + if (!pStreamDS->Out.pDSB) + return VERR_AUDIO_STREAM_NOT_READY; + + LogRel2(("DSound: Starting playback of '%s' ...\n", pStreamDS->Cfg.szName)); + HRESULT hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING); + if (SUCCEEDED(hrc)) + return VINF_SUCCESS; + + for (unsigned i = 0; hrc == DSERR_BUFFERLOST && i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + + hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING); + if (SUCCEEDED(hrc)) + return VINF_SUCCESS; + } + + LogRelMax(64, ("DSound: Failed to start playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + return VERR_AUDIO_STREAM_NOT_READY; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + + /* + * Input streams will start capturing, while output streams will only start + * playing if we're past the pre-buffering state. + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS); + else + { + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + if (!pStreamDS->Out.fFirstTransfer) + rc = directSoundPlayStart(pThis, pStreamDS); + } + + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertReturn(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1, + pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * We've started the buffer in looping mode, try switch to non-looping... + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Out.pDSB && !pStreamDS->Out.fDrain) + { + LogRel2(("DSound: Switching playback stream '%s' to drain mode...\n", pStreamDS->Cfg.szName)); + HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (SUCCEEDED(hrc)) + { + hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, 0); + if (SUCCEEDED(hrc)) + { + uint64_t const msNow = RTTimeMilliTS(); + pStreamDS->Out.msDrainDeadline = PDMAudioPropsBytesToMilli(&pStreamDS->Cfg.Props, pStreamDS->cbBufSize) + msNow; + pStreamDS->Out.fDrain = true; + } + else + LogRelMax(64, ("DSound: Failed to restart '%s' in drain mode: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + else + { + Log2Func(("drain: IDirectSoundBuffer8_Stop failed: %Rhrc\n", hrc)); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + + HRESULT hrc2 = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (SUCCEEDED(hrc2)) + LogFunc(("Successfully stopped the stream after restoring it. (hrc=%Rhrc)\n", hrc)); + else + { + LogRelMax(64, ("DSound: Failed to stop playback stream '%s' for putting into drain mode: %Rhrc (initial), %Rhrc (after restore)\n", + pStreamDS->Cfg.szName, hrc, hrc2)); + rc = VERR_AUDIO_STREAM_NOT_READY; + } + } + } + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS))); + return rc; +} + + +/** + * Retrieves the number of free bytes available for writing to a DirectSound output stream. + * + * @return VBox status code. VERR_NOT_AVAILABLE if unable to determine or the + * buffer was not recoverable. + * @param pThis Host audio driver instance. + * @param pStreamDS DirectSound output stream to retrieve number for. + * @param pdwFree Where to return the free amount on success. + * @param poffPlayCursor Where to return the play cursor offset. + */ +static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree, DWORD *poffPlayCursor) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER); + AssertPtrReturn(pdwFree, VERR_INVALID_POINTER); + AssertPtr(poffPlayCursor); + + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */ + + LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB; + AssertPtrReturn(pDSB, VERR_INVALID_POINTER); + + HRESULT hr = S_OK; + + /* Get the current play position which is used for calculating the free space in the buffer. */ + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + DWORD offPlayCursor = 0; + DWORD offWriteCursor = 0; + hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor); + if (SUCCEEDED(hr)) + { + int32_t cbDiff = offWriteCursor - offPlayCursor; + if (cbDiff < 0) + cbDiff += pStreamDS->cbBufSize; + + int32_t cbFree = offPlayCursor - pStreamDS->Out.offWritePos; + if (cbFree < 0) + cbFree += pStreamDS->cbBufSize; + + if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff) + { + /** @todo count/log these. */ + pStreamDS->Out.offWritePos = offWriteCursor; + cbFree = pStreamDS->cbBufSize - cbDiff; + } + + /* When starting to use a DirectSound buffer, offPlayCursor and offWriteCursor + * both point at position 0, so we won't be able to detect how many bytes + * are writable that way. + * + * So use our per-stream written indicator to see if we just started a stream. */ + if (pStreamDS->Out.cbWritten == 0) + cbFree = pStreamDS->cbBufSize; + + LogRel3(("DSound: offPlayCursor=%RU32, offWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n", + offPlayCursor, offWriteCursor, pStreamDS->Out.offWritePos, cbFree)); + + *pdwFree = cbFree; + *poffPlayCursor = offPlayCursor; + return VINF_SUCCESS; + } + + if (hr != DSERR_BUFFERLOST) /** @todo MSDN doesn't state this error for GetCurrentPosition(). */ + break; + + LogFunc(("Getting playing position failed due to lost buffer, restoring ...\n")); + + directSoundPlayRestore(pThis, pDSB); + } + + if (hr != DSERR_BUFFERLOST) /* Avoid log flooding if the error is still there. */ + DSLOGREL(("DSound: Getting current playback position failed with %Rhrc\n", hr)); + + LogFunc(("Failed with %Rhrc\n", hr)); + + *poffPlayCursor = pStreamDS->cbBufSize; + return VERR_NOT_AVAILABLE; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostDSoundHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, PDMHOSTAUDIOSTREAMSTATE_INVALID); + + if ( pStreamDS->Cfg.enmDir != PDMAUDIODIR_OUT + || !pStreamDS->Out.fDrain) + { + LogFlowFunc(("returns OKAY for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + return PDMHOSTAUDIOSTREAMSTATE_OKAY; + } + LogFlowFunc(("returns DRAINING for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + return PDMHOSTAUDIOSTREAMSTATE_DRAINING; +} + +#if 0 /* This isn't working as the write cursor is more a function of time than what we do. + Previously we only reported the pre-buffering status anyway, so no harm. */ +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) + { + /* This is a similar calculation as for StreamGetReadable, only for an output buffer. */ + AssertPtr(pStreamDS->In.pDSCB); + DWORD offPlayCursor = 0; + DWORD offWriteCursor = 0; + HRESULT hrc = IDirectSoundBuffer8_GetCurrentPosition(pStreamDS->Out.pDSB, &offPlayCursor, &offWriteCursor); + if (SUCCEEDED(hrc)) + { + uint32_t cbPending = dsoundRingDistance(offWriteCursor, offPlayCursor, pStreamDS->cbBufSize); + Log3Func(("cbPending=%RU32\n", cbPending)); + return cbPending; + } + AssertMsgFailed(("hrc=%Rhrc\n", hrc)); + } + /* else: For input streams we never have any pending data. */ + + return 0; +} +#endif + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + + DWORD cbFree = 0; + DWORD offIgn = 0; + int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbFree, &offIgn); + AssertRCReturn(rc, 0); + + return cbFree; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + + if (pStreamDS->fEnabled) + AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2); + else + { + Log2Func(("Skipping disabled stream {%s}\n", drvHostDSoundStreamStatusString(pStreamDS))); + return VINF_SUCCESS; + } + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + +/** @todo Any condition under which we should call dsoundUpdateStatusInternal(pThis) here? + * The old code thought it did so in case of failure, only it couldn't ever fails, so it never did. */ + + /* + * Transfer loop. + */ + uint32_t cbWritten = 0; + while (cbBuf > 0) + { + /* + * Figure out how much we can possibly write. + */ + DWORD offPlayCursor = 0; + DWORD cbWritable = 0; + int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbWritable, &offPlayCursor); + AssertRCReturn(rc, rc); + if (cbWritable < pStreamDS->Cfg.Props.cbFrame) + break; + + uint32_t const cbToWrite = RT_MIN(cbWritable, cbBuf); + Log3Func(("offPlay=%#x offWritePos=%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offPlayCursor, pStreamDS->Out.offWritePos, + cbWritable, cbToWrite, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * Lock that amount of buffer. + */ + PVOID pv1 = NULL; + DWORD cb1 = 0; + PVOID pv2 = NULL; + DWORD cb2 = 0; + HRESULT hrc = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, cbToWrite, + &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/); + AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */ + //AssertMsg(cb1 + cb2 == cbToWrite, ("%#x + %#x vs %#x\n", cb1, cb2, cbToWrite)); + + /* + * Copy over the data. + */ + memcpy(pv1, pvBuf, cb1); + pvBuf = (uint8_t *)pvBuf + cb1; + cbBuf -= cb1; + cbWritten += cb1; + + if (pv2) + { + memcpy(pv2, pvBuf, cb2); + pvBuf = (uint8_t *)pvBuf + cb2; + cbBuf -= cb2; + cbWritten += cb2; + } + + /* + * Unlock and update the write position. + */ + directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); /** @todo r=bird: pThis + pDSB parameters here for Unlock, but only pThis for Lock. Why? */ + pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize; + + /* + * If this was the first chunk, kick off playing. + */ + if (!pStreamDS->Out.fFirstTransfer) + { /* likely */ } + else + { + *pcbWritten = cbWritten; + rc = directSoundPlayStart(pThis, pStreamDS); + AssertRCReturn(rc, rc); + pStreamDS->Out.fFirstTransfer = false; + } + } + + /* + * Done. + */ + *pcbWritten = cbWritten; + + pStreamDS->Out.cbTransferred += cbWritten; + if (cbWritten) + { + uint64_t const msPrev = pStreamDS->msLastTransfer; RT_NOREF(msPrev); + pStreamDS->Out.cbLastTransferred = cbWritten; + pStreamDS->msLastTransfer = RTTimeMilliTS(); + LogFlowFunc(("cbLastTransferred=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n", + cbWritten, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0, + drvHostDSoundStreamStatusString(pStreamDS) )); + } + else if ( pStreamDS->Out.fDrain + && RTTimeMilliTS() >= pStreamDS->Out.msDrainDeadline) + { + LogRel2(("DSound: Stopping draining of '%s' {%s} ...\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + if (pStreamDS->Out.pDSB) + { + HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (FAILED(hrc)) + LogRelMax(64, ("DSound: Failed to stop draining stream '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + pStreamDS->Out.fDrain = false; + pStreamDS->fEnabled = false; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN); + + if (pStreamDS->fEnabled) + { + /* This is the same calculation as for StreamGetPending. */ + AssertPtr(pStreamDS->In.pDSCB); + DWORD offCaptureCursor = 0; + DWORD offReadCursor = 0; + HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor); + if (SUCCEEDED(hrc)) + { + uint32_t cbPending = dsoundRingDistance(offCaptureCursor, offReadCursor, pStreamDS->cbBufSize); + Log3Func(("cbPending=%RU32\n", cbPending)); + return cbPending; + } + AssertMsgFailed(("hrc=%Rhrc\n", hrc)); + } + + return 0; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);*/ RT_NOREF(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + +#if 0 /** @todo r=bird: shouldn't we do the same check as for output streams? */ + if (pStreamDS->fEnabled) + AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2); + else + { + Log2Func(("Stream disabled, skipping\n")); + return VINF_SUCCESS; + } +#endif + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * Read loop. + */ + uint32_t cbRead = 0; + while (cbBuf > 0) + { + /* + * Figure out how much we can read. + */ + DWORD offCaptureCursor = 0; + DWORD offReadCursor = 0; + HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor); + AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */ + //AssertMsg(offReadCursor == pStreamDS->In.offReadPos, ("%#x %#x\n", offReadCursor, pStreamDS->In.offReadPos)); + + uint32_t const cbReadable = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize); + + if (cbReadable >= pStreamDS->Cfg.Props.cbFrame) + { /* likely */ } + else + { + if (cbRead > 0) + { /* likely */ } + else if (pStreamDS->In.cOverruns < 32) + { + pStreamDS->In.cOverruns++; + DSLOG(("DSound: Warning: Buffer full (size is %zu bytes), skipping to record data (overflow #%RU32)\n", + pStreamDS->cbBufSize, pStreamDS->In.cOverruns)); + } + break; + } + + uint32_t const cbToRead = RT_MIN(cbReadable, cbBuf); + Log3Func(("offCapture=%#x offRead=%#x/%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offCaptureCursor, offReadCursor, + pStreamDS->In.offReadPos, cbReadable, cbToRead, drvHostDSoundStreamStatusString(pStreamDS))); + + /* + * Lock that amount of buffer. + */ + PVOID pv1 = NULL; + DWORD cb1 = 0; + PVOID pv2 = NULL; + DWORD cb2 = 0; + hrc = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, cbToRead, &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/); + AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */ + AssertMsg(cb1 + cb2 == cbToRead, ("%#x + %#x vs %#x\n", cb1, cb2, cbToRead)); + + /* + * Copy over the data. + */ + memcpy(pvBuf, pv1, cb1); + pvBuf = (uint8_t *)pvBuf + cb1; + cbBuf -= cb1; + cbRead += cb1; + + if (pv2) + { + memcpy(pvBuf, pv2, cb2); + pvBuf = (uint8_t *)pvBuf + cb2; + cbBuf -= cb2; + cbRead += cb2; + } + + /* + * Unlock and update the write position. + */ + directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); /** @todo r=bird: pDSB parameter here for Unlock, but pStreamDS for Lock. Why? */ + pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize; + } + + /* + * Done. + */ + *pcbRead = cbRead; + if (cbRead) + { + uint64_t const msPrev = pStreamDS->msLastTransfer; RT_NOREF(msPrev); + pStreamDS->msLastTransfer = RTTimeMilliTS(); + LogFlowFunc(("cbRead=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n", + cbRead, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0, + drvHostDSoundStreamStatusString(pStreamDS) )); + } + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH + if (cbRead) + { + RTFILE hFile; + int rc2 = RTFileOpen(&hFile, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "dsoundCapture.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc2)) + { + RTFileWrite(hFile, (uint8_t *)pvBuf - cbRead, cbRead, NULL); + RTFileClose(hFile); + } + } +#endif + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* PDMDRVINS::IBase Interface * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG Interface * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct} + */ +static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns) +{ + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + LogFlowFuncEnter(); + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + if (pThis->m_pNotificationClient) + { + pThis->m_pNotificationClient->Unregister(); + pThis->m_pNotificationClient->Release(); + + pThis->m_pNotificationClient = NULL; + } +#endif + + PDMAudioHostEnumDelete(&pThis->DeviceEnum); + + int rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + + LogFlowFuncLeave(); +} + + +static LPCGUID dsoundConfigQueryGUID(PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid) +{ + LPCGUID pGuid = NULL; + + char *pszGuid = NULL; + int rc = CFGMR3QueryStringAlloc(pCfg, pszName, &pszGuid); + if (RT_SUCCESS(rc)) + { + rc = RTUuidFromStr(pUuid, pszGuid); + if (RT_SUCCESS(rc)) + pGuid = (LPCGUID)&pUuid; + else + DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc)); + + RTStrFree(pszGuid); + } + + return pGuid; +} + + +static void dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg) +{ + pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay); + pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture); + + DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n", + &pThis->Cfg.uuidPlay, + &pThis->Cfg.uuidCapture)); +} + + +/** + * @callback_method_impl{FNPDMDRVCONSTRUCT, + * Construct a DirectSound Audio driver instance.} + */ +static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + RT_NOREF(fFlags); + LogRel(("Audio: Initializing DirectSound audio driver\n")); + + /* + * Init basic data members and interfaces. + */ + RTListInit(&pThis->HeadStreams); + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHostDSoundHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = drvHostDSoundHA_GetDevices; + pThis->IHostAudio.pfnSetDevice = NULL; + pThis->IHostAudio.pfnGetStatus = drvHostDSoundHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHostDSoundHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHostDSoundHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHostDSoundHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHostDSoundHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHostDSoundHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHostDSoundHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHostDSoundHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetState = drvHostDSoundHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetWritable = drvHostDSoundHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvHostDSoundHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvHostDSoundHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvHostDSoundHA_StreamCapture; + + /* + * Init the static parts. + */ + PDMAudioHostEnumInit(&pThis->DeviceEnum); + + pThis->fEnabledIn = false; + pThis->fEnabledOut = false; + + /* + * Verify that IDirectSound is available. + */ + LPDIRECTSOUND pDirectSound = NULL; + HRESULT hrc = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound); + if (SUCCEEDED(hrc)) + IDirectSound_Release(pDirectSound); + else + { + LogRel(("DSound: DirectSound not available: %Rhrc\n", hrc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + /* + * Set up WASAPI device change notifications (Vista+). + */ + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)) + { + /* Get the notification interface (from DrvAudio). */ +# ifdef VBOX_WITH_AUDIO_CALLBACKS + PPDMIHOSTAUDIOPORT pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT); + Assert(pIHostAudioPort); +# else + PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL; +# endif + try + { + pThis->m_pNotificationClient = new DrvHostAudioDSoundMMNotifClient(pIHostAudioPort, + pThis->Cfg.pGuidCapture == NULL, + pThis->Cfg.pGuidPlay == NULL); + } + catch (std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + hrc = pThis->m_pNotificationClient->Initialize(); + if (SUCCEEDED(hrc)) + { + hrc = pThis->m_pNotificationClient->Register(); + if (SUCCEEDED(hrc)) + LogRel2(("DSound: Notification client is enabled (ver %#RX64)\n", RTSystemGetNtVersion())); + else + { + LogRel(("DSound: Notification client registration failed: %Rhrc\n", hrc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + } + else + { + LogRel(("DSound: Notification client initialization failed: %Rhrc\n", hrc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + } + else + LogRel2(("DSound: Notification client is disabled (ver %#RX64)\n", RTSystemGetNtVersion())); +#endif + + /* + * Initialize configuration values and critical section. + */ + dsoundConfigInit(pThis, pCfg); + return RTCritSectInit(&pThis->CritSect); +} + + +/** + * PDM driver registration. + */ +const PDMDRVREG g_DrvHostDSound = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "DSoundAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "DirectSound Audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTDSOUND), + /* pfnConstruct */ + drvHostDSoundConstruct, + /* pfnDestruct */ + drvHostDSoundDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,227 @@ +/* $Id: DrvHostAudioDSoundMMNotifClient.cpp $ */ +/** @file + * Host audio driver - DSound - Implementation of the IMMNotificationClient interface to detect audio endpoint changes. + */ + +/* + * Copyright (C) 2017-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "DrvHostAudioDSoundMMNotifClient.h" + +#include +#include +#include +#include + +#ifdef LOG_GROUP /** @todo r=bird: wtf? Put it before all other includes like you're supposed to. */ +# undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include + + +DrvHostAudioDSoundMMNotifClient::DrvHostAudioDSoundMMNotifClient(PPDMIHOSTAUDIOPORT pInterface, bool fDefaultIn, bool fDefaultOut) + : m_fDefaultIn(fDefaultIn) + , m_fDefaultOut(fDefaultOut) + , m_fRegisteredClient(false) + , m_cRef(1) + , m_pIAudioNotifyFromHost(pInterface) +{ +} + +DrvHostAudioDSoundMMNotifClient::~DrvHostAudioDSoundMMNotifClient(void) +{ +} + +/** + * Registers the mulitmedia notification client implementation. + */ +HRESULT DrvHostAudioDSoundMMNotifClient::Register(void) +{ + HRESULT hr = m_pEnum->RegisterEndpointNotificationCallback(this); + if (SUCCEEDED(hr)) + m_fRegisteredClient = true; + + return hr; +} + +/** + * Unregisters the mulitmedia notification client implementation. + */ +void DrvHostAudioDSoundMMNotifClient::Unregister(void) +{ + if (m_fRegisteredClient) + { + m_pEnum->UnregisterEndpointNotificationCallback(this); + + m_fRegisteredClient = false; + } +} + +/** + * Initializes the mulitmedia notification client implementation. + * + * @return HRESULT + */ +HRESULT DrvHostAudioDSoundMMNotifClient::Initialize(void) +{ + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), + (void **)&m_pEnum); + + LogFunc(("Returning %Rhrc\n", hr)); + return hr; +} + +/** + * Handler implementation which is called when an audio device state + * has been changed. + * + * @return HRESULT + * @param pwstrDeviceId Device ID the state is announced for. + * @param dwNewState New state the device is now in. + */ +STDMETHODIMP DrvHostAudioDSoundMMNotifClient::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) +{ + char *pszState = "unknown"; + + switch (dwNewState) + { + case DEVICE_STATE_ACTIVE: + pszState = "active"; + break; + case DEVICE_STATE_DISABLED: + pszState = "disabled"; + break; + case DEVICE_STATE_NOTPRESENT: + pszState = "not present"; + break; + case DEVICE_STATE_UNPLUGGED: + pszState = "unplugged"; + break; + default: + break; + } + + LogRel(("Audio: Device '%ls' has changed state to '%s'\n", pwstrDeviceId, pszState)); + + if (m_pIAudioNotifyFromHost) + m_pIAudioNotifyFromHost->pfnNotifyDevicesChanged(m_pIAudioNotifyFromHost); + + return S_OK; +} + +/** + * Handler implementation which is called when a new audio device has been added. + * + * @return HRESULT + * @param pwstrDeviceId Device ID which has been added. + */ +STDMETHODIMP DrvHostAudioDSoundMMNotifClient::OnDeviceAdded(LPCWSTR pwstrDeviceId) +{ + LogRel(("Audio: Device '%ls' has been added\n", pwstrDeviceId)); + /* Note! It is hard to properly support non-default devices when the backend is DSound, + as DSound talks GUID where-as the pwszDeviceId string we get here is something + completely different. So, ignorining that edge case here. The WasApi backend + supports this, though. */ + if (m_pIAudioNotifyFromHost) + m_pIAudioNotifyFromHost->pfnNotifyDevicesChanged(m_pIAudioNotifyFromHost); + return S_OK; +} + +/** + * Handler implementation which is called when an audio device has been removed. + * + * @return HRESULT + * @param pwstrDeviceId Device ID which has been removed. + */ +STDMETHODIMP DrvHostAudioDSoundMMNotifClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId) +{ + LogRel(("Audio: Device '%ls' has been removed\n", pwstrDeviceId)); + if (m_pIAudioNotifyFromHost) + m_pIAudioNotifyFromHost->pfnNotifyDevicesChanged(m_pIAudioNotifyFromHost); + return S_OK; +} + +/** + * Handler implementation which is called when the device audio device has been + * changed. + * + * @return HRESULT + * @param eFlow Flow direction of the new default device. + * @param eRole Role of the new default device. + * @param pwstrDefaultDeviceId ID of the new default device. + */ +STDMETHODIMP DrvHostAudioDSoundMMNotifClient::OnDefaultDeviceChanged(EDataFlow eFlow, ERole eRole, LPCWSTR pwstrDefaultDeviceId) +{ + /* When the user triggers a default device change, we'll typically get two or + three notifications. Just pick up the one for the multimedia role for now + (dunno if DSound default equals eMultimedia or eConsole, and whether it make + any actual difference). */ + if (eRole == eMultimedia) + { + PDMAUDIODIR enmDir = PDMAUDIODIR_INVALID; + char *pszRole = "unknown"; + if (eFlow == eRender) + { + pszRole = "output"; + if (m_fDefaultOut) + enmDir = PDMAUDIODIR_OUT; + } + else if (eFlow == eCapture) + { + pszRole = "input"; + if (m_fDefaultIn) + enmDir = PDMAUDIODIR_IN; + } + + LogRel(("Audio: Default %s device has been changed to '%ls'\n", pszRole, pwstrDefaultDeviceId)); + + if (m_pIAudioNotifyFromHost) + { + if (enmDir != PDMAUDIODIR_INVALID) + m_pIAudioNotifyFromHost->pfnNotifyDeviceChanged(m_pIAudioNotifyFromHost, enmDir, NULL); + m_pIAudioNotifyFromHost->pfnNotifyDevicesChanged(m_pIAudioNotifyFromHost); + } + } + return S_OK; +} + +STDMETHODIMP DrvHostAudioDSoundMMNotifClient::QueryInterface(REFIID interfaceID, void **ppvInterface) +{ + const IID MY_IID_IMMNotificationClient = __uuidof(IMMNotificationClient); + + if ( IsEqualIID(interfaceID, IID_IUnknown) + || IsEqualIID(interfaceID, MY_IID_IMMNotificationClient)) + { + *ppvInterface = static_cast(this); + AddRef(); + return S_OK; + } + + *ppvInterface = NULL; + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) DrvHostAudioDSoundMMNotifClient::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) DrvHostAudioDSoundMMNotifClient::Release(void) +{ + long lRef = InterlockedDecrement(&m_cRef); + if (lRef == 0) + delete this; + + return lRef; +} + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,77 @@ +/* $Id: DrvHostAudioDSoundMMNotifClient.h $ */ +/** @file + * Host audio driver - DSound - Implementation of the IMMNotificationClient interface to detect audio endpoint changes. + */ + +/* + * Copyright (C) 2017-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioDSoundMMNotifClient_h +#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioDSoundMMNotifClient_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include + +#include + + +class DrvHostAudioDSoundMMNotifClient : public IMMNotificationClient +{ +public: + + DrvHostAudioDSoundMMNotifClient(PPDMIHOSTAUDIOPORT pInterface, bool fDefaultIn, bool fDefaultOut); + virtual ~DrvHostAudioDSoundMMNotifClient(); + + HRESULT Initialize(); + + HRESULT Register(void); + void Unregister(void); + + /** @name IUnknown interface + * @{ */ + IFACEMETHODIMP_(ULONG) Release(); + /** @} */ + +private: + + bool m_fDefaultIn; + bool m_fDefaultOut; + bool m_fRegisteredClient; + IMMDeviceEnumerator *m_pEnum; + IMMDevice *m_pEndpoint; + + long m_cRef; + + PPDMIHOSTAUDIOPORT m_pIAudioNotifyFromHost; + + /** @name IMMNotificationClient interface + * @{ */ + IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState); + IFACEMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId); + IFACEMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId); + IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId); + IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) { return S_OK; } + /** @} */ + + /** @name IUnknown interface + * @{ */ + IFACEMETHODIMP QueryInterface(const IID& iid, void** ppUnk); + IFACEMETHODIMP_(ULONG) AddRef(); + /** @} */ +}; + +#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioDSoundMMNotifClient_h */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioNull.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioNull.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioNull.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioNull.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,318 @@ +/* $Id: DrvHostAudioNull.cpp $ */ +/** @file + * Host audio driver - NULL (bitbucket). + * + * This also acts as a fallback if no other backend is available. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include /* For PDMIBASE_2_PDMDRV. */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Null audio stream. */ +typedef struct DRVHSTAUDNULLSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; +} DRVHSTAUDNULLSTREAM; +/** Pointer to a null audio stream. */ +typedef DRVHSTAUDNULLSTREAM *PDRVHSTAUDNULLSTREAM; + + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHstAudNullHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "NULL audio"); + pBackendCfg->cbStream = sizeof(DRVHSTAUDNULLSTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsOut = 1; /* Output */ + pBackendCfg->cMaxStreamsIn = 2; /* Line input + microphone input. */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudNullHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHstAudNullHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pInterface); + PDRVHSTAUDNULLSTREAM pStreamNull = (PDRVHSTAUDNULLSTREAM)pStream; + AssertPtrReturn(pStreamNull, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PDMAudioStrmCfgCopy(&pStreamNull->Cfg, pCfgAcq); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHstAudNullHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate) +{ + RT_NOREF(pInterface, pStream, fImmediate); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHstAudNullHA_StreamControlStub(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudNullHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID); +#if 0 + /* Bit bucket appraoch where we ignore the output and silence the input buffers. */ + return PDMHOSTAUDIOSTREAMSTATE_OKAY; +#else + /* Approach where the mixer in the devices skips us and saves a few CPU cycles. */ + return PDMHOSTAUDIOSTREAMSTATE_INACTIVE; +#endif +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} + */ +static DECLCALLBACK(uint32_t) drvHstAudNullHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return 0; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHstAudNullHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHstAudNullHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + RT_NOREF(pInterface, pStream, pvBuf); + + /* The bitbucket never overflows. */ + *pcbWritten = cbBuf; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHstAudNullHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + /** @todo rate limit this? */ + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHstAudNullHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface); + PDRVHSTAUDNULLSTREAM pStreamNull = (PDRVHSTAUDNULLSTREAM)pStream; + + /** @todo rate limit this? */ + + /* Return silence. */ + PDMAudioPropsClearBuffer(&pStreamNull->Cfg.Props, pvBuf, cbBuf, + PDMAudioPropsBytesToFrames(&pStreamNull->Cfg.Props, cbBuf)); + *pcbRead = cbBuf; + return VINF_SUCCESS; +} + + +/** + * This is used directly by DrvAudio when a backend fails to initialize in a + * non-fatal manner. + */ +DECL_HIDDEN_CONST(PDMIHOSTAUDIO) const g_DrvHostAudioNull = +{ + /* .pfnGetConfig =*/ drvHstAudNullHA_GetConfig, + /* .pfnGetDevices =*/ NULL, + /* .pfnSetDevice =*/ NULL, + /* .pfnGetStatus =*/ drvHstAudNullHA_GetStatus, + /* .pfnDoOnWorkerThread =*/ NULL, + /* .pfnStreamConfigHint =*/ NULL, + /* .pfnStreamCreate =*/ drvHstAudNullHA_StreamCreate, + /* .pfnStreamInitAsync =*/ NULL, + /* .pfnStreamDestroy =*/ drvHstAudNullHA_StreamDestroy, + /* .pfnStreamNotifyDeviceChanged =*/ NULL, + /* .pfnStreamEnable =*/ drvHstAudNullHA_StreamControlStub, + /* .pfnStreamDisable =*/ drvHstAudNullHA_StreamControlStub, + /* .pfnStreamPause =*/ drvHstAudNullHA_StreamControlStub, + /* .pfnStreamResume =*/ drvHstAudNullHA_StreamControlStub, + /* .pfnStreamDrain =*/ drvHstAudNullHA_StreamControlStub, + /* .pfnStreamGetState =*/ drvHstAudNullHA_StreamGetState, + /* .pfnStreamGetPending =*/ drvHstAudNullHA_StreamGetPending, + /* .pfnStreamGetWritable =*/ drvHstAudNullHA_StreamGetWritable, + /* .pfnStreamPlay =*/ drvHstAudNullHA_StreamPlay, + /* .pfnStreamGetReadable =*/ drvHstAudNullHA_StreamGetReadable, + /* .pfnStreamCapture =*/ drvHstAudNullHA_StreamCapture, +}; + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHstAudNullQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PPDMIHOSTAUDIO pThis = PDMINS_2_DATA(pDrvIns, PPDMIHOSTAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, pThis); + return NULL; +} + + +/** + * Constructs a Null audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHstAudNullConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PPDMIHOSTAUDIO pThis = PDMINS_2_DATA(pDrvIns, PPDMIHOSTAUDIO); + RT_NOREF(pCfg, fFlags); + LogRel(("Audio: Initializing NULL driver\n")); + + /* + * Init the static parts. + */ + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHstAudNullQueryInterface; + /* IHostAudio */ + *pThis = g_DrvHostAudioNull; + + return VINF_SUCCESS; +} + + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostNullAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "NullAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "NULL audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(PDMIHOSTAUDIO), + /* pfnConstruct */ + drvHstAudNullConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioOss.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioOss.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioOss.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioOss.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,1017 @@ +/* $Id: DrvHostAudioOss.cpp $ */ +/** @file + * Host audio driver - OSS (Open Sound System). + */ + +/* + * Copyright (C) 2014-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include /* For PDMIBASE_2_PDMDRV. */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ +#if ((SOUND_VERSION > 360) && (defined(OSS_SYSINFO))) +/* OSS > 3.6 has a new syscall available for querying a bit more detailed information + * about OSS' audio capabilities. This is handy for e.g. Solaris. */ +# define VBOX_WITH_AUDIO_OSS_SYSINFO 1 +#endif + + +/********************************************************************************************************************************* +* Structures * +*********************************************************************************************************************************/ +/** + * OSS audio stream configuration. + */ +typedef struct DRVHSTAUDOSSSTREAMCFG +{ + PDMAUDIOPCMPROPS Props; + uint16_t cFragments; + /** The log2 of cbFragment. */ + uint16_t cbFragmentLog2; + uint32_t cbFragment; +} DRVHSTAUDOSSSTREAMCFG; +/** Pointer to an OSS audio stream configuration. */ +typedef DRVHSTAUDOSSSTREAMCFG *PDRVHSTAUDOSSSTREAMCFG; + +/** + * OSS audio stream. + */ +typedef struct DRVHSTAUDOSSSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** The file descriptor. */ + int hFile; + /** Buffer alignment. */ + uint8_t uAlign; + /** Set if we're draining the stream (output only). */ + bool fDraining; + /** Internal stream byte offset. */ + uint64_t offInternal; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** The acquired OSS configuration. */ + DRVHSTAUDOSSSTREAMCFG OssCfg; + /** Handle to the thread draining output streams. */ + RTTHREAD hThreadDrain; +} DRVHSTAUDOSSSTREAM; +/** Pointer to an OSS audio stream. */ +typedef DRVHSTAUDOSSSTREAM *PDRVHSTAUDOSSSTREAM; + +/** + * OSS host audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHSTAUDOSS +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Error count for not flooding the release log. + * UINT32_MAX for unlimited logging. */ + uint32_t cLogErrors; +} DRVHSTAUDOSS; +/** Pointer to the instance data for an OSS host audio driver. */ +typedef DRVHSTAUDOSS *PDRVHSTAUDOSS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The path to the output OSS device. */ +static char g_szPathOutputDev[] = "/dev/dsp"; +/** The path to the input OSS device. */ +static char g_szPathInputDev[] = "/dev/dsp"; + + + +static int drvHstAudOssToPdmAudioProps(PPDMAUDIOPCMPROPS pProps, int fmt, int cChannels, int uHz) +{ + switch (fmt) + { + case AFMT_S8: + PDMAudioPropsInit(pProps, 1 /*8-bit*/, true /*signed*/, cChannels, uHz); + break; + + case AFMT_U8: + PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz); + break; + + case AFMT_S16_LE: + PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/); + break; + + case AFMT_U16_LE: + PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, false /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/); + break; + + case AFMT_S16_BE: + PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/); + break; + + case AFMT_U16_BE: + PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, false /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/); + break; + + default: + AssertMsgFailedReturn(("Format %d not supported\n", fmt), VERR_NOT_SUPPORTED); + } + + return VINF_SUCCESS; +} + + +static int drvHstAudOssStreamClose(int *phFile) +{ + if (!phFile || !*phFile || *phFile == -1) + return VINF_SUCCESS; + + int rc; + if (close(*phFile)) + { + rc = RTErrConvertFromErrno(errno); + LogRel(("OSS: Closing stream failed: %s / %Rrc\n", strerror(errno), rc)); + } + else + { + *phFile = -1; + rc = VINF_SUCCESS; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHstAudOssHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "OSS"); + pBackendCfg->cbStream = sizeof(DRVHSTAUDOSSSTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsIn = 0; + pBackendCfg->cMaxStreamsOut = 0; + + int hFile = open("/dev/dsp", O_WRONLY | O_NONBLOCK, 0); + if (hFile == -1) + { + /* Try opening the mixing device instead. */ + hFile = open("/dev/mixer", O_RDONLY | O_NONBLOCK, 0); + } + if (hFile != -1) + { + int ossVer = -1; + int err = ioctl(hFile, OSS_GETVERSION, &ossVer); + if (err == 0) + { + LogRel2(("OSS: Using version: %d\n", ossVer)); +#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO + oss_sysinfo ossInfo; + RT_ZERO(ossInfo); + err = ioctl(hFile, OSS_SYSINFO, &ossInfo); + if (err == 0) + { + LogRel2(("OSS: Number of DSPs: %d\n", ossInfo.numaudios)); + LogRel2(("OSS: Number of mixers: %d\n", ossInfo.nummixers)); + + int cDev = ossInfo.nummixers; + if (!cDev) + cDev = ossInfo.numaudios; + + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + } + else +#endif + { + /* Since we cannot query anything, assume that we have at least + * one input and one output if we found "/dev/dsp" or "/dev/mixer". */ + + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + } + } + else + LogRel(("OSS: Unable to determine installed version: %s (%d)\n", strerror(err), err)); + close(hFile); + } + else + LogRel(("OSS: No devices found, audio is not available\n")); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudOssHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + RT_NOREF(enmDir); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +static int drvHstAudOssStreamConfigure(int hFile, bool fInput, PDRVHSTAUDOSSSTREAMCFG pOSSReq, PDRVHSTAUDOSSSTREAMCFG pOSSAcq) +{ + /* + * Format. + */ + int iFormat; + switch (PDMAudioPropsSampleSize(&pOSSReq->Props)) + { + case 1: + iFormat = pOSSReq->Props.fSigned ? AFMT_S8 : AFMT_U8; + break; + + case 2: + if (PDMAudioPropsIsLittleEndian(&pOSSReq->Props)) + iFormat = pOSSReq->Props.fSigned ? AFMT_S16_LE : AFMT_U16_LE; + else + iFormat = pOSSReq->Props.fSigned ? AFMT_S16_BE : AFMT_U16_BE; + break; + + default: + LogRel2(("OSS: Unsupported sample size: %u\n", PDMAudioPropsSampleSize(&pOSSReq->Props))); + return VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SAMPLESIZE, &iFormat) >= 0, + ("OSS: Failed to set audio format to %d: %s (%d)\n", iFormat, strerror(errno), errno), + RTErrConvertFromErrno(errno)); + + /* + * Channel count. + */ + int cChannels = PDMAudioPropsChannels(&pOSSReq->Props); + AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_CHANNELS, &cChannels) >= 0, + ("OSS: Failed to set number of audio channels (%RU8): %s (%d)\n", + PDMAudioPropsChannels(&pOSSReq->Props), strerror(errno), errno), + RTErrConvertFromErrno(errno)); + + /* + * Frequency. + */ + int iFrequenc = pOSSReq->Props.uHz; + AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SPEED, &iFrequenc) >= 0, + ("OSS: Failed to set audio frequency to %d Hz: %s (%d)\n", pOSSReq->Props.uHz, strerror(errno), errno), + RTErrConvertFromErrno(errno)); + + /* + * Set fragment size and count. + */ + LogRel2(("OSS: Requested %RU16 %s fragments, %RU32 bytes each\n", + pOSSReq->cFragments, fInput ? "input" : "output", pOSSReq->cbFragment)); + + int mmmmssss = (pOSSReq->cFragments << 16) | pOSSReq->cbFragmentLog2; + AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SETFRAGMENT, &mmmmssss) >= 0, + ("OSS: Failed to set %RU16 fragments to %RU32 bytes each: %s (%d)\n", + pOSSReq->cFragments, pOSSReq->cbFragment, strerror(errno), errno), + RTErrConvertFromErrno(errno)); + + /* + * Get parameters and popuplate pOSSAcq. + */ + audio_buf_info BufInfo = { 0, 0, 0, 0 }; + AssertLogRelMsgReturn(ioctl(hFile, fInput ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &BufInfo) >= 0, + ("OSS: Failed to retrieve %s buffer length: %s (%d)\n", + fInput ? "input" : "output", strerror(errno), errno), + RTErrConvertFromErrno(errno)); + + int rc = drvHstAudOssToPdmAudioProps(&pOSSAcq->Props, iFormat, cChannels, iFrequenc); + if (RT_SUCCESS(rc)) + { + pOSSAcq->cFragments = BufInfo.fragstotal; + pOSSAcq->cbFragment = BufInfo.fragsize; + pOSSAcq->cbFragmentLog2 = ASMBitFirstSetU32(BufInfo.fragsize) - 1; + Assert(RT_BIT_32(pOSSAcq->cbFragmentLog2) == pOSSAcq->cbFragment); + + LogRel2(("OSS: Got %RU16 %s fragments, %RU32 bytes each\n", + pOSSAcq->cFragments, fInput ? "input" : "output", pOSSAcq->cbFragment)); + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtr(pInterface); RT_NOREF(pInterface); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + pStreamOSS->hThreadDrain = NIL_RTTHREAD; + + /* + * Open the device + */ + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + pStreamOSS->hFile = open(g_szPathInputDev, O_RDONLY); + else + pStreamOSS->hFile = open(g_szPathOutputDev, O_WRONLY); + if (pStreamOSS->hFile >= 0) + { + /* + * Configure it. + * + * Note! We limit the output channels to mono or stereo for now just + * to keep things simple and avoid wasting time here. If the + * channel count isn't a power of two, our code below trips up + * on the fragment size. We'd also need to try report/get + * channel mappings and whatnot. + */ + DRVHSTAUDOSSSTREAMCFG ReqOssCfg; + RT_ZERO(ReqOssCfg); + + memcpy(&ReqOssCfg.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS)); + if (PDMAudioPropsChannels(&ReqOssCfg.Props) > 2) + { + LogRel2(("OSS: Limiting output to two channels, requested %u.\n", PDMAudioPropsChannels(&ReqOssCfg.Props) )); + PDMAudioPropsSetChannels(&ReqOssCfg.Props, 2); + } + + ReqOssCfg.cbFragmentLog2 = 12; + ReqOssCfg.cbFragment = RT_BIT_32(ReqOssCfg.cbFragmentLog2); + uint32_t const cbBuffer = PDMAudioPropsFramesToBytes(&ReqOssCfg.Props, pCfgReq->Backend.cFramesBufferSize); + ReqOssCfg.cFragments = cbBuffer >> ReqOssCfg.cbFragmentLog2; + AssertLogRelStmt(cbBuffer < ((uint32_t)0x7ffe << ReqOssCfg.cbFragmentLog2), ReqOssCfg.cFragments = 0x7ffe); + + rc = drvHstAudOssStreamConfigure(pStreamOSS->hFile, pCfgReq->enmDir == PDMAUDIODIR_IN, &ReqOssCfg, &pStreamOSS->OssCfg); + if (RT_SUCCESS(rc)) + { + pStreamOSS->uAlign = 0; /** @todo r=bird: Where did the correct assignment of this go? */ + + /* + * Complete the stream structure and fill in the pCfgAcq bits. + */ + if ((pStreamOSS->OssCfg.cFragments * pStreamOSS->OssCfg.cbFragment) & pStreamOSS->uAlign) + LogRel(("OSS: Warning: Misaligned playback buffer: Size = %zu, Alignment = %u\n", + pStreamOSS->OssCfg.cFragments * pStreamOSS->OssCfg.cbFragment, pStreamOSS->uAlign + 1)); + + memcpy(&pCfgAcq->Props, &pStreamOSS->OssCfg.Props, sizeof(PDMAUDIOPCMPROPS)); + pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamOSS->OssCfg.cbFragment); + pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * pStreamOSS->OssCfg.cFragments; + pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering + * pCfgAcq->Backend.cFramesBufferSize + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + + /* + * Copy the stream config and we're done! + */ + PDMAudioStrmCfgCopy(&pStreamOSS->Cfg, pCfgAcq); + return VINF_SUCCESS; + } + drvHstAudOssStreamClose(&pStreamOSS->hFile); + } + else + { + rc = RTErrConvertFromErrno(errno); + LogRel(("OSS: Failed to open '%s': %s (%d) / %Rrc\n", + pCfgReq->enmDir == PDMAUDIODIR_IN ? g_szPathInputDev : g_szPathOutputDev, strerror(errno), errno, rc)); + } + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate) +{ + RT_NOREF(pInterface, fImmediate); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER); + + drvHstAudOssStreamClose(&pStreamOSS->hFile); + + if (pStreamOSS->hThreadDrain != NIL_RTTHREAD) + { + int rc = RTThreadWait(pStreamOSS->hThreadDrain, 1, NULL); + AssertRC(rc); + pStreamOSS->hThreadDrain = NIL_RTTHREAD; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + int rc; + + /* + * This is most probably untested... + */ + if (pStreamOSS->fDraining) + { + LogFlowFunc(("Still draining...\n")); + rc = RTThreadWait(pStreamOSS->hThreadDrain, 0 /*ms*/, NULL); + if (RT_FAILURE(rc)) + { + LogFlowFunc(("Resetting...\n")); + ioctl(pStreamOSS->hFile, SNDCTL_DSP_RESET, NULL); + rc = RTThreadWait(pStreamOSS->hThreadDrain, 0 /*ms*/, NULL); + if (RT_FAILURE(rc)) + { + LogFlowFunc(("Poking...\n")); + RTThreadPoke(pStreamOSS->hThreadDrain); + rc = RTThreadWait(pStreamOSS->hThreadDrain, 1 /*ms*/, NULL); + } + } + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Done draining.\n")); + pStreamOSS->hThreadDrain = NIL_RTTHREAD; + } + else + LogFlowFunc(("No, still draining...\n")); + pStreamOSS->fDraining = false; + } + + /* + * Enable the stream. + */ + int fMask = pStreamOSS->Cfg.enmDir == PDMAUDIODIR_IN ? PCM_ENABLE_INPUT : PCM_ENABLE_OUTPUT; + if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0) + rc = VINF_SUCCESS; + else + { + LogRel(("OSS: Failed to enable output stream: %s (%d)\n", strerror(errno), errno)); + rc = RTErrConvertFromErrno(errno); + } + + LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + LogFlowFunc(("Stream '%s'\n", pStreamOSS->Cfg.szName)); + int rc; + + /* + * If we're still draining, try kick the thread before we try disable the stream. + */ + if (pStreamOSS->fDraining) + { + LogFlowFunc(("Trying to cancel draining...\n")); + if (pStreamOSS->hThreadDrain != NIL_RTTHREAD) + { + RTThreadPoke(pStreamOSS->hThreadDrain); + rc = RTThreadWait(pStreamOSS->hThreadDrain, 1 /*ms*/, NULL); + if (RT_SUCCESS(rc) || rc == VERR_INVALID_HANDLE) + pStreamOSS->fDraining = false; + else + LogFunc(("Failed to cancel draining (%Rrc)\n", rc)); + } + else + { + LogFlowFunc(("Thread handle is NIL, so we can't be draining\n")); + pStreamOSS->fDraining = false; + } + } + + /* + * The Official documentation says this isn't the right way to stop + * playback. It may work in some implementations but fail in all others... + * Suggest SNDCTL_DSP_RESET / SNDCTL_DSP_HALT. + * + * So, let's do both and see how that works out... + */ + rc = VINF_SUCCESS; + int fMask = 0; + if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0) + LogFlowFunc(("SNDCTL_DSP_SETTRIGGER succeeded\n")); + else + { + LogRel(("OSS: Failed to clear triggers for stream '%s': %s (%d)\n", pStreamOSS->Cfg.szName, strerror(errno), errno)); + rc = RTErrConvertFromErrno(errno); + } + + if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_RESET, NULL) >= 0) + LogFlowFunc(("SNDCTL_DSP_RESET succeeded\n")); + else + { + LogRel(("OSS: Failed to reset stream '%s': %s (%d)\n", pStreamOSS->Cfg.szName, strerror(errno), errno)); + rc = RTErrConvertFromErrno(errno); + } + + LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + return drvHstAudOssHA_StreamDisable(pInterface, pStream); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + return drvHstAudOssHA_StreamEnable(pInterface, pStream); +} + + +/** + * @callback_method_impl{FNRTTHREAD, + * Thread for calling SNDCTL_DSP_SYNC (blocking) on an output stream.} + */ +static DECLCALLBACK(int) drvHstAudOssDrainThread(RTTHREAD ThreadSelf, void *pvUser) +{ + RT_NOREF(ThreadSelf); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pvUser; + int rc; + + /* Make it blocking (for Linux). */ + int fOrgFlags = fcntl(pStreamOSS->hFile, F_GETFL, 0); + LogFunc(("F_GETFL -> %#x\n", fOrgFlags)); + Assert(fOrgFlags != -1); + if (fOrgFlags != -1 && (fOrgFlags & O_NONBLOCK)) + { + rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags & ~O_NONBLOCK); + AssertStmt(rc != -1, fOrgFlags = -1); + } + + /* Drain it. */ + LogFunc(("Calling SNDCTL_DSP_SYNC now...\n")); + rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SYNC, NULL); + LogFunc(("SNDCTL_DSP_SYNC returned %d / errno=%d\n", rc, errno)); RT_NOREF(rc); + + /* Re-enable non-blocking mode and disable it. */ + if (fOrgFlags != -1) + { + rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags); + Assert(rc != -1); + + int fMask = 0; + rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask); + Assert(rc >= 0); + + pStreamOSS->fDraining = false; + LogFunc(("Restored non-block mode and cleared the trigger mask\n")); + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHSTAUDOSS pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDOSS, IHostAudio); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + AssertReturn(pStreamOSS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER); + + pStreamOSS->fDraining = true; + + /* + * Because the SNDCTL_DSP_SYNC call is blocking on real OSS, + * we kick off a thread to deal with it as we're probably on EMT + * and cannot block for extended periods. + */ + if (pStreamOSS->hThreadDrain != NIL_RTTHREAD) + { + int rc = RTThreadWait(pStreamOSS->hThreadDrain, 0, NULL); + if (RT_SUCCESS(rc)) + { + pStreamOSS->hThreadDrain = NIL_RTTHREAD; + LogFunc(("Cleaned up stale thread handle.\n")); + } + else + { + LogFunc(("Drain thread already running (%Rrc).\n", rc)); + AssertMsg(rc == VERR_TIMEOUT, ("%Rrc\n", rc)); + return rc == VERR_TIMEOUT ? VINF_SUCCESS : rc; + } + } + + int rc = RTThreadCreateF(&pStreamOSS->hThreadDrain, drvHstAudOssDrainThread, pStreamOSS, 0, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ossdrai%u", pThis->pDrvIns->iInstance); + LogFunc(("Started drain thread: %Rrc\n", rc)); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudOssHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + AssertPtrReturn(pStreamOSS, PDMHOSTAUDIOSTREAMSTATE_INVALID); + if (!pStreamOSS->fDraining) + return PDMHOSTAUDIOSTREAMSTATE_OKAY; + return PDMHOSTAUDIOSTREAMSTATE_DRAINING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHstAudOssHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + AssertPtr(pStreamOSS); + + /* + * The logic here must match what StreamPlay does. + * + * Note! We now use 'bytes' rather than the fragments * fragsize as we used + * to do (up to 2021), as these are documented as obsolete. + */ + audio_buf_info BufInfo = { 0, 0, 0, 0 }; + int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo); + AssertMsgReturn(rc2 >= 0, ("SNDCTL_DSP_GETOSPACE failed: %s (%d)\n", strerror(errno), errno), 0); + + /* Try use the size. */ + uint32_t cbRet; + uint32_t const cbBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments; + if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbBuf) + cbRet = BufInfo.bytes; + else + { + AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes)); + AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0); + AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0); + cbRet = (uint32_t)(BufInfo.fragments * BufInfo.fragsize); + AssertMsgStmt(cbRet <= cbBuf, ("fragsize*fragments: %d, cbBuf=%#x\n", cbRet, cbBuf), 0); + } + + Log4Func(("returns %#x (%u) [cbBuf=%#x]\n", cbRet, cbRet, cbBuf)); + return cbRet; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + RT_NOREF(pInterface); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER); + + /* + * Return immediately if this is a draining service call. + * + * Otherwise the ioctl below will race the drain thread and sometimes fail, + * triggering annoying assertion and release logging. + */ + if (cbBuf || !pStreamOSS->fDraining) + { /* likely */ } + else + { + *pcbWritten = 0; + return VINF_SUCCESS; + } + + /* + * Figure out now much to write (same as drvHstAudOssHA_StreamGetWritable, + * must match exactly). + */ + audio_buf_info BufInfo; + int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo); + AssertLogRelMsgReturn(rc2 >= 0, ("OSS: Failed to retrieve current playback buffer: %s (%d, hFile=%d, rc2=%d)\n", + strerror(errno), errno, pStreamOSS->hFile, rc2), + RTErrConvertFromErrno(errno)); + + uint32_t cbToWrite; + uint32_t const cbStreamBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments; + if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbStreamBuf) + cbToWrite = BufInfo.bytes; + else + { + AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes)); + AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0); + AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0); + cbToWrite = (uint32_t)(BufInfo.fragments * BufInfo.fragsize); + AssertMsgStmt(cbToWrite <= cbStreamBuf, ("fragsize*fragments: %d, cbStreamBuf=%#x\n", cbToWrite, cbStreamBuf), 0); + } + + cbToWrite = RT_MIN(cbToWrite, cbBuf); + Log3Func(("@%#RX64 cbBuf=%#x BufInfo: fragments=%#x fragstotal=%#x fragsize=%#x bytes=%#x %s cbToWrite=%#x\n", + pStreamOSS->offInternal, cbBuf, BufInfo.fragments, BufInfo.fragstotal, BufInfo.fragsize, BufInfo.bytes, + pStreamOSS->Cfg.szName, cbToWrite)); + + /* + * Write. + */ + uint8_t const *pbBuf = (uint8_t const *)pvBuf; + uint32_t cbChunk = cbToWrite; + uint32_t offChunk = 0; + while (cbChunk > 0) + { + ssize_t cbWritten = write(pStreamOSS->hFile, &pbBuf[offChunk], RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment)); + if (cbWritten > 0) + { + AssertLogRelMsg(!(cbWritten & pStreamOSS->uAlign), + ("OSS: Misaligned write (written %#zx, alignment %#x)\n", cbWritten, pStreamOSS->uAlign)); + + Assert((uint32_t)cbWritten <= cbChunk); + offChunk += (uint32_t)cbWritten; + cbChunk -= (uint32_t)cbWritten; + pStreamOSS->offInternal += cbWritten; + } + else if (cbWritten == 0) + { + LogFunc(("@%#RX64 write(%#x) returned zeroed (previously wrote %#x bytes)!\n", + pStreamOSS->offInternal, RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment), cbWritten)); + break; + } + else + { + LogRel(("OSS: Failed writing output data: %s (%d)\n", strerror(errno), errno)); + return RTErrConvertFromErrno(errno); + } + } + + *pcbWritten = offChunk; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHstAudOssHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + AssertPtr(pStreamOSS); + + /* + * Use SNDCTL_DSP_GETISPACE to see how much we can read. + * + * Note! We now use 'bytes' rather than the fragments * fragsize as we used + * to do (up to 2021), as these are documented as obsolete. + */ + audio_buf_info BufInfo = { 0, 0, 0, 0 }; + int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETISPACE, &BufInfo); + AssertMsgReturn(rc2 >= 0, ("SNDCTL_DSP_GETISPACE failed: %s (%d)\n", strerror(errno), errno), 0); + + uint32_t cbRet; + uint32_t const cbBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments; + if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbBuf) + cbRet = BufInfo.bytes; + else + { + AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes)); + AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0); + AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0); + cbRet = (uint32_t)(BufInfo.fragments * BufInfo.fragsize); + AssertMsgStmt(cbRet <= cbBuf, ("fragsize*fragments: %d, cbBuf=%#x\n", cbRet, cbBuf), 0); + } + + /* + * HACK ALERT! To force the stream to start recording, we read a frame + * here if we get back that there are zero bytes available + * and we're at the start of the stream. (We cannot just + * return a frame size, we have to read it, as pre-buffering + * would prevent it from being read.) + */ + if (BufInfo.bytes > 0 || pStreamOSS->offInternal != 0) + { /* likely */ } + else + { + uint32_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamOSS->Cfg.Props, 1); + uint8_t abFrame[256]; + Assert(cbToRead < sizeof(abFrame)); + ssize_t cbRead = read(pStreamOSS->hFile, abFrame, cbToRead); + RT_NOREF(cbRead); + LogFunc(("Dummy read for '%s' returns %zd (errno=%d)\n", pStreamOSS->Cfg.szName, cbRead, errno)); + } + + Log4Func(("returns %#x (%u) [cbBuf=%#x]\n", cbRet, cbRet, cbBuf)); + return cbRet; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHstAudOssHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface); + PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream; + AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER); + Log3Func(("@%#RX64 cbBuf=%#x %s\n", pStreamOSS->offInternal, cbBuf, pStreamOSS->Cfg.szName)); + + size_t cbToRead = cbBuf; + uint8_t * const pbDst = (uint8_t *)pvBuf; + size_t offWrite = 0; + while (cbToRead > 0) + { + ssize_t cbRead = read(pStreamOSS->hFile, &pbDst[offWrite], cbToRead); + if (cbRead > 0) + { + LogFlowFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu\n", cbRead, offWrite, cbToRead)); + Assert((ssize_t)cbToRead >= cbRead); + cbToRead -= cbRead; + offWrite += cbRead; + pStreamOSS->offInternal += cbRead; + } + else + { + LogFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu errno=%d\n", cbRead, offWrite, cbToRead, errno)); + + /* Don't complain about errors if we've retrieved some audio data already. */ + if (cbRead < 0 && offWrite == 0 && errno != EINTR && errno != EAGAIN) + { + AssertStmt(errno != 0, errno = EACCES); + int rc = RTErrConvertFromErrno(errno); + LogFunc(("Failed to read %zu input frames, errno=%d rc=%Rrc\n", cbToRead, errno, rc)); + return rc; + } + break; + } + } + + *pcbRead = offWrite; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHstAudOssQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHSTAUDOSS pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDOSS); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnConstruct, + * Constructs an OSS audio driver instance.} + */ +static DECLCALLBACK(int) drvHstAudOssConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHSTAUDOSS pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDOSS); + LogRel(("Audio: Initializing OSS driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHstAudOssQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHstAudOssHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = NULL; + pThis->IHostAudio.pfnSetDevice = NULL; + pThis->IHostAudio.pfnGetStatus = drvHstAudOssHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHstAudOssHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHstAudOssHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHstAudOssHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHstAudOssHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHstAudOssHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHstAudOssHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHstAudOssHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetState = drvHstAudOssHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetWritable = drvHstAudOssHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvHstAudOssHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvHstAudOssHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvHstAudOssHA_StreamCapture; + + return VINF_SUCCESS; +} + + +/** + * OSS driver registration record. + */ +const PDMDRVREG g_DrvHostOSSAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "OSSAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "OSS audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHSTAUDOSS), + /* pfnConstruct */ + drvHstAudOssConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,2405 @@ +/* $Id: DrvHostAudioPulseAudio.cpp $ */ +/** @file + * Host audio driver - Pulse Audio. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "DrvHostAudioPulseAudioStubsMangling.h" +#include "DrvHostAudioPulseAudioStubs.h" + +#include +#ifndef PA_STREAM_NOFLAGS +# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */ +#endif +#ifndef PA_CONTEXT_NOFLAGS +# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */ +#endif + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ +/** Max number of errors reported by drvHstAudPaError per instance. + * @todo Make this configurable thru driver config. */ +#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 99 + + +/** @name DRVHSTAUDPAENUMCB_F_XXX + * @{ */ +/** No flags specified. */ +#define DRVHSTAUDPAENUMCB_F_NONE 0 +/** (Release) log found devices. */ +#define DRVHSTAUDPAENUMCB_F_LOG RT_BIT(0) +/** Only do default devices. */ +#define DRVHSTAUDPAENUMCB_F_DEFAULT_ONLY RT_BIT(1) +/** @} */ + + +/********************************************************************************************************************************* +* Structures * +*********************************************************************************************************************************/ +/** Pointer to the instance data for a pulse audio host audio driver. */ +typedef struct DRVHSTAUDPA *PDRVHSTAUDPA; + + +/** + * Callback context for the server init context state changed callback. + */ +typedef struct DRVHSTAUDPASTATECHGCTX +{ + /** The event semaphore. */ + RTSEMEVENT hEvtInit; + /** The returned context state. */ + pa_context_state_t volatile enmCtxState; +} DRVHSTAUDPASTATECHGCTX; +/** Pointer to a server init context state changed callback context. */ +typedef DRVHSTAUDPASTATECHGCTX *PDRVHSTAUDPASTATECHGCTX; + + +/** + * Enumeration callback context used by the pfnGetConfig code. + */ +typedef struct DRVHSTAUDPAENUMCBCTX +{ + /** Pointer to PulseAudio's threaded main loop. */ + pa_threaded_mainloop *pMainLoop; + /** Enumeration flags, DRVHSTAUDPAENUMCB_F_XXX. */ + uint32_t fFlags; + /** VBox status code for the operation. + * The caller sets this to VERR_AUDIO_ENUMERATION_FAILED, the callback never + * uses that status code. */ + int32_t rcEnum; + /** Name of default sink being used. Must be free'd using RTStrFree(). */ + char *pszDefaultSink; + /** Name of default source being used. Must be free'd using RTStrFree(). */ + char *pszDefaultSource; + /** The device enumeration to fill, NULL if pfnGetConfig context. */ + PPDMAUDIOHOSTENUM pDeviceEnum; +} DRVHSTAUDPAENUMCBCTX; +/** Pointer to an enumeration callback context. */ +typedef DRVHSTAUDPAENUMCBCTX *PDRVHSTAUDPAENUMCBCTX; + + +/** + * Pulse audio device enumeration entry. + */ +typedef struct DRVHSTAUDPADEVENTRY +{ + /** The part we share with others. */ + PDMAUDIOHOSTDEV Core; +} DRVHSTAUDPADEVENTRY; +/** Pointer to a pulse audio device enumeration entry. */ +typedef DRVHSTAUDPADEVENTRY *PDRVHSTAUDPADEVENTRY; + + +/** + * Pulse audio stream data. + */ +typedef struct DRVHSTAUDPASTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** Pointer to driver instance. */ + PDRVHSTAUDPA pDrv; + /** Pointer to opaque PulseAudio stream. */ + pa_stream *pStream; + /** Input: Pointer to Pulse sample peek buffer. */ + const uint8_t *pbPeekBuf; + /** Input: Current size (in bytes) of peeked data in buffer. */ + size_t cbPeekBuf; + /** Input: Our offset (in bytes) in peek data buffer. */ + size_t offPeekBuf; + /** Output: Asynchronous drain operation. This is used as an indicator of + * whether we're currently draining the stream (will be cleaned up before + * resume/re-enable). */ + pa_operation *pDrainOp; + /** Asynchronous cork/uncork operation. + * (This solely for cancelling before destroying the stream, so the callback + * won't do any after-freed accesses.) */ + pa_operation *pCorkOp; + /** Asynchronous trigger operation. + * (This solely for cancelling before destroying the stream, so the callback + * won't do any after-freed accesses.) */ + pa_operation *pTriggerOp; + /** Internal byte offset. */ + uint64_t offInternal; +#ifdef LOG_ENABLED + /** Creation timestamp (in microsecs) of stream playback / recording. */ + pa_usec_t tsStartUs; + /** Timestamp (in microsecs) when last read from / written to the stream. */ + pa_usec_t tsLastReadWrittenUs; +#endif + /** Number of occurred audio data underflows. */ + uint32_t cUnderflows; + /** Pulse sample format and attribute specification. */ + pa_sample_spec SampleSpec; + /** Channel map. */ + pa_channel_map ChannelMap; + /** Pulse playback and buffer metrics. */ + pa_buffer_attr BufAttr; +} DRVHSTAUDPASTREAM; +/** Pointer to pulse audio stream data. */ +typedef DRVHSTAUDPASTREAM *PDRVHSTAUDPASTREAM; + + +/** + * Pulse audio host audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHSTAUDPA +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to PulseAudio's threaded main loop. */ + pa_threaded_mainloop *pMainLoop; + /** + * Pointer to our PulseAudio context. + * @note We use a pMainLoop in a separate thread (pContext). + * So either use callback functions or protect these functions + * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock(). + */ + pa_context *pContext; + /** Shutdown indicator. */ + volatile bool fAbortLoop; + /** Error count for not flooding the release log. + * Specify UINT32_MAX for unlimited logging. */ + uint32_t cLogErrors; + /** Don't want to put this on the stack... */ + DRVHSTAUDPASTATECHGCTX InitStateChgCtx; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Upwards notification interface. */ + PPDMIHOSTAUDIOPORT pIHostAudioPort; + + /** The stream (base) name. + * This is needed for distinguishing streams in the PulseAudio mixer controls if + * multiple VMs are running at the same time. */ + char szStreamName[64]; + /** The name of the input device to use. Empty string for default. */ + char szInputDev[256]; + /** The name of the output device to use. Empty string for default. */ + char szOutputDev[256]; + + /** Number of buffer underruns (for all streams). */ + STAMCOUNTER StatUnderruns; + /** Number of buffer overruns (for all streams). */ + STAMCOUNTER StatOverruns; +} DRVHSTAUDPA; + + + +/* + * Glue to make the code work systems with PulseAudio < 0.9.11. + */ +#if !defined(PA_CONTEXT_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */ +DECLINLINE(bool) PA_CONTEXT_IS_GOOD(pa_context_state_t enmState) +{ + return enmState == PA_CONTEXT_CONNECTING + || enmState == PA_CONTEXT_AUTHORIZING + || enmState == PA_CONTEXT_SETTING_NAME + || enmState == PA_CONTEXT_READY; +} +#endif + +#if !defined(PA_STREAM_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */ +DECLINLINE(bool) PA_STREAM_IS_GOOD(pa_stream_state_t enmState) +{ + return enmState == PA_STREAM_CREATING + || enmState == PA_STREAM_READY; +} +#endif + + +/** + * Converts a pulse audio error to a VBox status. + * + * @returns VBox status code. + * @param rcPa The error code to convert. + */ +static int drvHstAudPaErrorToVBox(int rcPa) +{ + /** @todo Implement some PulseAudio -> VBox mapping here. */ + RT_NOREF(rcPa); + return VERR_GENERAL_FAILURE; +} + + +/** + * Logs a pulse audio (from context) and converts it to VBox status. + * + * @returns VBox status code. + * @param pThis Our instance data. + * @param pszFormat The format string for the release log (no newline) . + * @param ... Format string arguments. + */ +static int drvHstAudPaError(PDRVHSTAUDPA pThis, const char *pszFormat, ...) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtr(pszFormat); + + int const rcPa = pa_context_errno(pThis->pContext); + int const rcVBox = drvHstAudPaErrorToVBox(rcPa); + + if ( pThis->cLogErrors < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS + && LogRelIs2Enabled()) + { + va_list va; + va_start(va, pszFormat); + LogRel(("PulseAudio: %N: %s (%d, %Rrc)\n", pszFormat, &va, pa_strerror(rcPa), rcPa, rcVBox)); + va_end(va); + + if (++pThis->cLogErrors == VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS) + LogRel(("PulseAudio: muting errors (max %u)\n", VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)); + } + + return rcVBox; +} + + +/** + * Signal the main loop to abort. Just signalling isn't sufficient as the + * mainloop might not have been entered yet. + */ +static void drvHstAudPaSignalWaiter(PDRVHSTAUDPA pThis) +{ + if (pThis) + { + pThis->fAbortLoop = true; + pa_threaded_mainloop_signal(pThis->pMainLoop, 0); + } +} + + +/** + * Wrapper around pa_threaded_mainloop_wait(). + */ +static void drvHstAudPaMainloopWait(PDRVHSTAUDPA pThis) +{ + /** @todo r=bird: explain this logic. */ + if (!pThis->fAbortLoop) + pa_threaded_mainloop_wait(pThis->pMainLoop); + pThis->fAbortLoop = false; +} + + +/** + * Pulse audio callback for context status changes, init variant. + */ +static void drvHstAudPaCtxCallbackStateChanged(pa_context *pCtx, void *pvUser) +{ + AssertPtrReturnVoid(pCtx); + + PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser; + AssertPtrReturnVoid(pThis); + + switch (pa_context_get_state(pCtx)) + { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + drvHstAudPaSignalWaiter(pThis); + break; + + default: + break; + } +} + + +/** + * Synchronously wait until an operation completed. + * + * This will consume the pOperation reference. + */ +static int drvHstAudPaWaitForEx(PDRVHSTAUDPA pThis, pa_operation *pOperation, RTMSINTERVAL cMsTimeout) +{ + AssertPtrReturn(pOperation, VERR_INVALID_POINTER); + + uint64_t const msStart = RTTimeMilliTS(); + pa_operation_state_t enmOpState; + while ((enmOpState = pa_operation_get_state(pOperation)) == PA_OPERATION_RUNNING) + { + if (!pThis->fAbortLoop) /** @todo r=bird: I do _not_ get the logic behind this fAbortLoop mechanism, it looks more + * than a little mixed up and too much generalized see drvHstAudPaSignalWaiter. */ + { + AssertPtr(pThis->pMainLoop); + pa_threaded_mainloop_wait(pThis->pMainLoop); + if ( !pThis->pContext + || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY) + { + pa_operation_cancel(pOperation); + pa_operation_unref(pOperation); + LogRel(("PulseAudio: pa_context_get_state context not ready\n")); + return VERR_INVALID_STATE; + } + } + pThis->fAbortLoop = false; + + /* + * Note! This timeout business is a bit bogus as pa_threaded_mainloop_wait is indefinite. + */ + if (RTTimeMilliTS() - msStart >= cMsTimeout) + { + enmOpState = pa_operation_get_state(pOperation); + if (enmOpState != PA_OPERATION_RUNNING) + break; + pa_operation_cancel(pOperation); + pa_operation_unref(pOperation); + return VERR_TIMEOUT; + } + } + + pa_operation_unref(pOperation); + if (enmOpState == PA_OPERATION_DONE) + return VINF_SUCCESS; + return VERR_CANCELLED; +} + + +static int drvHstAudPaWaitFor(PDRVHSTAUDPA pThis, pa_operation *pOP) +{ + return drvHstAudPaWaitForEx(pThis, pOP, 10 * RT_MS_1SEC); +} + + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO * +*********************************************************************************************************************************/ + +/** + * Worker for drvHstAudPaEnumSourceCallback() and + * drvHstAudPaEnumSinkCallback() that adds an entry to the enumeration + * result. + */ +static void drvHstAudPaEnumAddDevice(PDRVHSTAUDPAENUMCBCTX pCbCtx, PDMAUDIODIR enmDir, const char *pszName, + const char *pszDesc, uint8_t cChannelsInput, uint8_t cChannelsOutput, + const char *pszDefaultName) +{ + size_t const cbId = strlen(pszName) + 1; + size_t const cbName = pszDesc && *pszDesc ? strlen(pszDesc) + 1 : cbId; + PDRVHSTAUDPADEVENTRY pDev = (PDRVHSTAUDPADEVENTRY)PDMAudioHostDevAlloc(sizeof(*pDev), cbName, cbId); + if (pDev != NULL) + { + pDev->Core.enmUsage = enmDir; + pDev->Core.enmType = RTStrIStr(pszDesc, "built-in") != NULL + ? PDMAUDIODEVICETYPE_BUILTIN : PDMAUDIODEVICETYPE_UNKNOWN; + if (RTStrCmp(pszName, pszDefaultName) != 0) + pDev->Core.fFlags = PDMAUDIOHOSTDEV_F_NONE; + else + pDev->Core.fFlags = enmDir == PDMAUDIODIR_IN ? PDMAUDIOHOSTDEV_F_DEFAULT_IN : PDMAUDIOHOSTDEV_F_DEFAULT_OUT; + pDev->Core.cMaxInputChannels = cChannelsInput; + pDev->Core.cMaxOutputChannels = cChannelsOutput; + + int rc = RTStrCopy(pDev->Core.pszId, cbId, pszName); + AssertRC(rc); + + rc = RTStrCopy(pDev->Core.pszName, cbName, pszDesc && *pszDesc ? pszDesc : pszName); + AssertRC(rc); + + PDMAudioHostEnumAppend(pCbCtx->pDeviceEnum, &pDev->Core); + } + else + pCbCtx->rcEnum = VERR_NO_MEMORY; +} + + +/** + * Enumeration callback - source info. + * + * @param pCtx The context (DRVHSTAUDPA::pContext). + * @param pInfo The info. NULL when @a eol is not zero. + * @param eol Error-or-last indicator or something like that: + * - 0: Normal call with info. + * - 1: End of list, no info. + * - -1: Error callback, no info. + * @param pvUserData Pointer to our DRVHSTAUDPAENUMCBCTX structure. + */ +static void drvHstAudPaEnumSourceCallback(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData) +{ + LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData)); + PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData; + AssertPtrReturnVoid(pCbCtx); + Assert((pInfo == NULL) == (eol != 0)); + RT_NOREF(pCtx); + + if (eol == 0 && pInfo != NULL) + { + LogRel2(("Pulse Audio: Source #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n", + pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format, + pInfo->name, pInfo->description, pInfo->driver, pInfo->flags)); + drvHstAudPaEnumAddDevice(pCbCtx, PDMAUDIODIR_IN, pInfo->name, pInfo->description, + pInfo->sample_spec.channels, 0 /*cChannelsOutput*/, pCbCtx->pszDefaultSource); + } + else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED) + pCbCtx->rcEnum = VINF_SUCCESS; + + /* Wake up the calling thread when done: */ + if (eol != 0) + pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0); +} + + +/** + * Enumeration callback - sink info. + * + * @param pCtx The context (DRVHSTAUDPA::pContext). + * @param pInfo The info. NULL when @a eol is not zero. + * @param eol Error-or-last indicator or something like that: + * - 0: Normal call with info. + * - 1: End of list, no info. + * - -1: Error callback, no info. + * @param pvUserData Pointer to our DRVHSTAUDPAENUMCBCTX structure. + */ +static void drvHstAudPaEnumSinkCallback(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData) +{ + LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData)); + PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData; + AssertPtrReturnVoid(pCbCtx); + Assert((pInfo == NULL) == (eol != 0)); + RT_NOREF(pCtx); + + if (eol == 0 && pInfo != NULL) + { + LogRel2(("Pulse Audio: Sink #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n", + pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format, + pInfo->name, pInfo->description, pInfo->driver, pInfo->flags)); + drvHstAudPaEnumAddDevice(pCbCtx, PDMAUDIODIR_OUT, pInfo->name, pInfo->description, + 0 /*cChannelsInput*/, pInfo->sample_spec.channels, pCbCtx->pszDefaultSink); + } + else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED) + pCbCtx->rcEnum = VINF_SUCCESS; + + /* Wake up the calling thread when done: */ + if (eol != 0) + pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0); +} + + +/** + * Enumeration callback - service info. + * + * Copy down the default names. + */ +static void drvHstAudPaEnumServerCallback(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData) +{ + LogFlowFunc(("pCtx=%p pInfo=%p pvUserData=%p\n", pCtx, pInfo, pvUserData)); + PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData; + AssertPtrReturnVoid(pCbCtx); + RT_NOREF(pCtx); + + if (pInfo) + { + LogRel2(("PulseAudio: Server info: user=%s host=%s ver=%s name=%s defsink=%s defsrc=%s spec: %d %uHz %uch\n", + pInfo->user_name, pInfo->host_name, pInfo->server_version, pInfo->server_name, + pInfo->default_sink_name, pInfo->default_source_name, + pInfo->sample_spec.format, pInfo->sample_spec.rate, pInfo->sample_spec.channels)); + + Assert(!pCbCtx->pszDefaultSink); + Assert(!pCbCtx->pszDefaultSource); + Assert(pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED); + pCbCtx->rcEnum = VINF_SUCCESS; + + if (pInfo->default_sink_name) + { + Assert(RTStrIsValidEncoding(pInfo->default_sink_name)); + pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name); + AssertStmt(pCbCtx->pszDefaultSink, pCbCtx->rcEnum = VERR_NO_STR_MEMORY); + } + + if (pInfo->default_source_name) + { + Assert(RTStrIsValidEncoding(pInfo->default_source_name)); + pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name); + AssertStmt(pCbCtx->pszDefaultSource, pCbCtx->rcEnum = VERR_NO_STR_MEMORY); + } + } + else + pCbCtx->rcEnum = VERR_INVALID_POINTER; + + pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0); +} + + +/** + * @note Called with the PA main loop locked. + */ +static int drvHstAudPaEnumerate(PDRVHSTAUDPA pThis, uint32_t fEnum, PPDMAUDIOHOSTENUM pDeviceEnum) +{ + DRVHSTAUDPAENUMCBCTX CbCtx = { pThis->pMainLoop, fEnum, VERR_AUDIO_ENUMERATION_FAILED, NULL, NULL, pDeviceEnum }; + bool const fLog = (fEnum & DRVHSTAUDPAENUMCB_F_LOG); + bool const fOnlyDefault = (fEnum & DRVHSTAUDPAENUMCB_F_DEFAULT_ONLY); + int rc; + + /* + * Check if server information is available and bail out early if it isn't. + * This should give us a default (playback) sink and (recording) source. + */ + LogRel(("PulseAudio: Retrieving server information ...\n")); + CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED; + pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, drvHstAudPaEnumServerCallback, &CbCtx); + if (paOpServerInfo) + rc = drvHstAudPaWaitFor(pThis, paOpServerInfo); + else + { + LogRel(("PulseAudio: Server information not available, skipping enumeration.\n")); + return VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + rc = CbCtx.rcEnum; + if (RT_FAILURE(rc)) + { + if (fLog) + LogRel(("PulseAudio: Error enumerating PulseAudio server properties: %Rrc\n", rc)); + return rc; + } + + /* + * Get info about the playback sink. + */ + if (fLog && CbCtx.pszDefaultSink) + LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink)); + else if (fLog) + LogRel2(("PulseAudio: No default output sink found\n")); + + if (CbCtx.pszDefaultSink || !fOnlyDefault) + { + CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED; + if (!fOnlyDefault) + rc = drvHstAudPaWaitFor(pThis, + pa_context_get_sink_info_list(pThis->pContext, drvHstAudPaEnumSinkCallback, &CbCtx)); + else + rc = drvHstAudPaWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink, + drvHstAudPaEnumSinkCallback, &CbCtx)); + if (RT_SUCCESS(rc)) + rc = CbCtx.rcEnum; + if (fLog && RT_FAILURE(rc)) + LogRel(("PulseAudio: Error enumerating properties for default output sink '%s': %Rrc\n", + CbCtx.pszDefaultSink, rc)); + } + + /* + * Get info about the recording source. + */ + if (fLog && CbCtx.pszDefaultSource) + LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource)); + else if (fLog) + LogRel2(("PulseAudio: No default input source found\n")); + if (CbCtx.pszDefaultSource || !fOnlyDefault) + { + CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED; + int rc2; + if (!fOnlyDefault) + rc2 = drvHstAudPaWaitFor(pThis, pa_context_get_source_info_list(pThis->pContext, + drvHstAudPaEnumSourceCallback, &CbCtx)); + else + rc2 = drvHstAudPaWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource, + drvHstAudPaEnumSourceCallback, &CbCtx)); + if (RT_SUCCESS(rc2)) + rc2 = CbCtx.rcEnum; + if (fLog && RT_FAILURE(rc2)) + LogRel(("PulseAudio: Error enumerating properties for default input source '%s': %Rrc\n", + CbCtx.pszDefaultSource, rc)); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + /* clean up */ + RTStrFree(CbCtx.pszDefaultSink); + RTStrFree(CbCtx.pszDefaultSource); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHstAudPaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * The configuration. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "PulseAudio"); + pBackendCfg->cbStream = sizeof(DRVHSTAUDPASTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + +#if 0 + /* + * In case we want to gather info about default devices, we can do this: + */ + PDMAUDIOHOSTENUM DeviceEnum; + PDMAudioHostEnumInit(&DeviceEnum); + pa_threaded_mainloop_lock(pThis->pMainLoop); + int rc = drvHstAudPaEnumerate(pThis, DRVHSTAUDPAENUMCB_F_DEFAULT_ONLY | DRVHSTAUDPAENUMCB_F_LOG, &DeviceEnum); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + AssertRCReturn(rc, rc); + /** @todo do stuff with DeviceEnum. */ + PDMAudioHostEnumDelete(&DeviceEnum); +#else + RT_NOREF(pThis); +#endif + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} + */ +static DECLCALLBACK(int) drvHstAudPaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); + PDMAudioHostEnumInit(pDeviceEnum); + + /* Refine it or something (currently only some LogRel2 stuff): */ + pa_threaded_mainloop_lock(pThis->pMainLoop); + int rc = drvHstAudPaEnumerate(pThis, DRVHSTAUDPAENUMCB_F_NONE, pDeviceEnum); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice} + */ +static DECLCALLBACK(int) drvHstAudPaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + + /* + * Validate and normalize input. + */ + AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pszId, VERR_INVALID_POINTER); + if (!pszId || !*pszId) + pszId = ""; + else + { + size_t cch = strlen(pszId); + AssertReturn(cch < sizeof(pThis->szInputDev), VERR_INVALID_NAME); + } + LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId)); + + /* + * Update input. + */ + if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + if (strcmp(pThis->szInputDev, pszId) == 0) + pa_threaded_mainloop_unlock(pThis->pMainLoop); + else + { + LogRel(("PulseAudio: Changing input device: '%s' -> '%s'\n", pThis->szInputDev, pszId)); + RTStrCopy(pThis->szInputDev, sizeof(pThis->szInputDev), pszId); + PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort; + pa_threaded_mainloop_unlock(pThis->pMainLoop); + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about input device change...\n")); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/); + } + } + } + + /* + * Update output. + */ + if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + if (strcmp(pThis->szOutputDev, pszId) == 0) + pa_threaded_mainloop_unlock(pThis->pMainLoop); + else + { + LogRel(("PulseAudio: Changing output device: '%s' -> '%s'\n", pThis->szOutputDev, pszId)); + RTStrCopy(pThis->szOutputDev, sizeof(pThis->szOutputDev), pszId); + PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort; + pa_threaded_mainloop_unlock(pThis->pMainLoop); + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about output device change...\n")); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/); + } + } + } + + return VINF_SUCCESS; +} + + + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudPaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * Stream status changed. + */ +static void drvHstAudPaStreamStateChangedCallback(pa_stream *pStream, void *pvUser) +{ + AssertPtrReturnVoid(pStream); + + PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser; + AssertPtrReturnVoid(pThis); + + switch (pa_stream_get_state(pStream)) + { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + drvHstAudPaSignalWaiter(pThis); + break; + + default: + break; + } +} + + +/** + * Underflow notification. + */ +static void drvHstAudPaStreamUnderflowStatsCallback(pa_stream *pStream, void *pvContext) +{ + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvContext; + AssertPtrReturnVoid(pStreamPA); + AssertPtrReturnVoid(pStreamPA->pDrv); + + /* This may happen when draining/corking, so don't count those. */ + if (!pStreamPA->pDrainOp) + STAM_REL_COUNTER_INC(&pStreamPA->pDrv->StatUnderruns); + + pStreamPA->cUnderflows++; + + LogRel2(("PulseAudio: Warning: Hit underflow #%RU32%s%s\n", pStreamPA->cUnderflows, + pStreamPA->pDrainOp && pa_operation_get_state(pStreamPA->pDrainOp) == PA_OPERATION_RUNNING ? " (draining)" : "", + pStreamPA->pCorkOp && pa_operation_get_state(pStreamPA->pCorkOp) == PA_OPERATION_RUNNING ? " (corking)" : "" )); + + if (LogRelIs2Enabled() || LogIs2Enabled()) + { + pa_usec_t cUsLatency = 0; + int fNegative = 0; + pa_stream_get_latency(pStream, &cUsLatency, &fNegative); + LogRel2(("PulseAudio: Latency now is %'RU64 us\n", cUsLatency)); + +# ifdef LOG_ENABLED + if (LogIs2Enabled()) + { + const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream); + AssertReturnVoid(pTInfo); + const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream); + AssertReturnVoid(pSpec); + Log2Func(("writepos=%'RU64 us, readpost=%'RU64 us, age=%'RU64 us, latency=%'RU64 us (%RU32Hz %RU8ch)\n", + pa_bytes_to_usec(pTInfo->write_index, pSpec), pa_bytes_to_usec(pTInfo->read_index, pSpec), + pa_rtclock_now() - pStreamPA->tsStartUs, cUsLatency, pSpec->rate, pSpec->channels)); + } +# endif + } +} + + +/** + * Overflow notification. + */ +static void drvHstAudPaStreamOverflowStatsCallback(pa_stream *pStream, void *pvContext) +{ + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvContext; + AssertPtrReturnVoid(pStreamPA); + AssertPtrReturnVoid(pStreamPA->pDrv); + + STAM_REL_COUNTER_INC(&pStreamPA->pDrv->StatOverruns); + LogRel2(("PulseAudio: Warning: Hit overflow.\n")); + RT_NOREF(pStream); +} + + +#ifdef DEBUG +/** + * Debug PA callback: Need data to output. + */ +static void drvHstAudPaStreamReqWriteDebugCallback(pa_stream *pStream, size_t cbLen, void *pvContext) +{ + RT_NOREF(cbLen, pvContext); + pa_usec_t cUsLatency = 0; + int fNegative = 0; + int rcPa = pa_stream_get_latency(pStream, &cUsLatency, &fNegative); + Log2Func(("Requesting %zu bytes; Latency: %'RU64 us (rcPa=%d n=%d)\n", cbLen, cUsLatency, rcPa, fNegative)); +} +#endif /* DEBUG */ + +/** + * Converts from PDM PCM properties to pulse audio format. + * + * Worker for the stream creation code. + * + * @returns PA format. + * @retval PA_SAMPLE_INVALID if format not supported. + * @param pProps The PDM audio source properties. + */ +static pa_sample_format_t drvHstAudPaPropsToPulse(PCPDMAUDIOPCMPROPS pProps) +{ + switch (PDMAudioPropsSampleSize(pProps)) + { + case 1: + if (!PDMAudioPropsIsSigned(pProps)) + return PA_SAMPLE_U8; + break; + + case 2: + if (PDMAudioPropsIsSigned(pProps)) + return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE; + break; + +#ifdef PA_SAMPLE_S32LE + case 4: + if (PDMAudioPropsIsSigned(pProps)) + return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S32LE : PA_SAMPLE_S32BE; + break; +#endif + } + + AssertMsgFailed(("%RU8%s not supported\n", PDMAudioPropsSampleSize(pProps), PDMAudioPropsIsSigned(pProps) ? "S" : "U")); + return PA_SAMPLE_INVALID; +} + + +/** + * Converts from pulse audio sample specification to PDM PCM audio properties. + * + * Worker for the stream creation code. + * + * @returns VBox status code. + * @param pProps The PDM audio source properties. + * @param enmPulseFmt The PA format. + * @param cChannels The number of channels. + * @param uHz The frequency. + */ +static int drvHstAudPaToAudioProps(PPDMAUDIOPCMPROPS pProps, pa_sample_format_t enmPulseFmt, uint8_t cChannels, uint32_t uHz) +{ + AssertReturn(cChannels > 0, VERR_INVALID_PARAMETER); + AssertReturn(cChannels < 16, VERR_INVALID_PARAMETER); + + switch (enmPulseFmt) + { + case PA_SAMPLE_U8: + PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz); + break; + + case PA_SAMPLE_S16LE: + PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/); + break; + + case PA_SAMPLE_S16BE: + PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/); + break; + +#ifdef PA_SAMPLE_S32LE + case PA_SAMPLE_S32LE: + PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/); + break; +#endif + +#ifdef PA_SAMPLE_S32BE + case PA_SAMPLE_S32BE: + PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/); + break; +#endif + + default: + AssertLogRelMsgFailed(("PulseAudio: Format (%d) not supported\n", enmPulseFmt)); + return VERR_NOT_SUPPORTED; + } + + return VINF_SUCCESS; +} + + +#if 0 /* experiment */ +/** + * Completion callback used with pa_stream_set_buffer_attr(). + */ +static void drvHstAudPaStreamSetBufferAttrCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser) +{ + PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser; + LogFlowFunc(("fSuccess=%d\n", fSuccess)); + pa_threaded_mainloop_signal(pThis->pMainLoop, 0 /*fWaitForAccept*/); + RT_NOREF(pStream); +} +#endif + + +/** + * Worker that does the actual creation of an PA stream. + * + * @returns VBox status code. + * @param pThis Our driver instance data. + * @param pStreamPA Our stream data. + * @param pszName How we name the stream. + * @param pCfgAcq The requested stream properties, the Props member is + * updated upon successful return. + * + * @note Caller owns the mainloop lock. + */ +static int drvHstAudPaStreamCreateLocked(PDRVHSTAUDPA pThis, PDRVHSTAUDPASTREAM pStreamPA, + const char *pszName, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + /* + * Create the stream. + */ + pa_stream *pStream = pa_stream_new(pThis->pContext, pszName, &pStreamPA->SampleSpec, &pStreamPA->ChannelMap); + if (!pStream) + { + LogRel(("PulseAudio: Failed to create stream '%s': %s (%d)\n", + pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext))); + return VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + /* + * Set the state callback, and in debug builds a few more... + */ + pa_stream_set_state_callback(pStream, drvHstAudPaStreamStateChangedCallback, pThis); + pa_stream_set_underflow_callback(pStream, drvHstAudPaStreamUnderflowStatsCallback, pStreamPA); + pa_stream_set_overflow_callback(pStream, drvHstAudPaStreamOverflowStatsCallback, pStreamPA); +#ifdef DEBUG + pa_stream_set_write_callback(pStream, drvHstAudPaStreamReqWriteDebugCallback, pStreamPA); +#endif + + /* + * Connect the stream. + */ + int rc; + unsigned const fFlags = PA_STREAM_START_CORKED /* Require explicit starting (uncorking). */ + /* For using pa_stream_get_latency() and pa_stream_get_time(). */ + | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE +#if PA_API_VERSION >= 12 + | PA_STREAM_ADJUST_LATENCY +#endif + ; + if (pCfgAcq->enmDir == PDMAUDIODIR_IN) + { + LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n", + pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.fragsize)); + rc = pa_stream_connect_record(pStream, pThis->szInputDev[0] ? pThis->szInputDev : NULL, + &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags); + } + else + { + LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n", + pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.minreq)); + rc = pa_stream_connect_playback(pStream, pThis->szOutputDev[0] ? pThis->szOutputDev : NULL, &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags, + NULL /*volume*/, NULL /*sync_stream*/); + } + if (rc >= 0) + { + /* + * Wait for the stream to become ready. + */ + uint64_t const nsStart = RTTimeNanoTS(); + pa_stream_state_t enmStreamState; + while ( (enmStreamState = pa_stream_get_state(pStream)) != PA_STREAM_READY + && PA_STREAM_IS_GOOD(enmStreamState) + && RTTimeNanoTS() - nsStart < RT_NS_10SEC /* not really timed */ ) + drvHstAudPaMainloopWait(pThis); + if (enmStreamState == PA_STREAM_READY) + { + LogFunc(("Connecting stream took %'RU64 ns\n", RTTimeNanoTS() - nsStart)); +#ifdef LOG_ENABLED + pStreamPA->tsStartUs = pa_rtclock_now(); +#endif + /* + * Update the buffer attributes. + */ + const pa_buffer_attr *pBufAttribs = pa_stream_get_buffer_attr(pStream); +#if 0 /* Experiment for getting tlength closer to what we requested (ADJUST_LATENCY effect). + Will slow down stream creation, so not pursued any further at present. */ + if ( pCfgAcq->enmDir == PDMAUDIODIR_OUT + && pBufAttribs + && pBufAttribs->tlength < pStreamPA->BufAttr.tlength) + { + pStreamPA->BufAttr.maxlength += (pStreamPA->BufAttr.tlength - pBufAttribs->tlength) * 2; + pStreamPA->BufAttr.tlength += (pStreamPA->BufAttr.tlength - pBufAttribs->tlength) * 2; + LogRel(("Before pa_stream_set_buffer_attr: tlength=%#x (trying =%#x)\n", pBufAttribs->tlength, pStreamPA->BufAttr.tlength)); + drvHstAudPaWaitFor(pThis, pa_stream_set_buffer_attr(pStream, &pStreamPA->BufAttr, + drvHstAudPaStreamSetBufferAttrCompletionCallback, pThis)); + pBufAttribs = pa_stream_get_buffer_attr(pStream); + LogRel(("After pa_stream_set_buffer_attr: tlength=%#x\n", pBufAttribs->tlength)); + } +#endif + AssertPtr(pBufAttribs); + if (pBufAttribs) + { + pStreamPA->BufAttr = *pBufAttribs; + LogFunc(("Obtained %s buffer attributes: maxlength=%RU32 tlength=%RU32 prebuf=%RU32 minreq=%RU32 fragsize=%RU32\n", + pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output", pBufAttribs->maxlength, pBufAttribs->tlength, + pBufAttribs->prebuf, pBufAttribs->minreq, pBufAttribs->fragsize)); + + /* + * Convert the sample spec back to PDM speak. + * Note! This isn't strictly speaking needed as SampleSpec has *not* been + * modified since the caller converted it from pCfgReq. + */ + rc = drvHstAudPaToAudioProps(&pCfgAcq->Props, pStreamPA->SampleSpec.format, + pStreamPA->SampleSpec.channels, pStreamPA->SampleSpec.rate); + if (RT_SUCCESS(rc)) + { + pStreamPA->pStream = pStream; + LogFlowFunc(("returns VINF_SUCCESS\n")); + return VINF_SUCCESS; + } + } + else + { + LogRelMax(99, ("PulseAudio: Failed to get buffer attribs for stream '%s': %s (%d)\n", + pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext))); + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + } + else + { + LogRelMax(99, ("PulseAudio: Failed to initialize stream '%s': state=%d, waited %'RU64 ns\n", + pszName, enmStreamState, RTTimeNanoTS() - nsStart)); + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + pa_stream_disconnect(pStream); + } + else + { + LogRelMax(99, ("PulseAudio: Could not connect %s stream '%s': %s (%d/%d)\n", + pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output", + pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext), rc)); + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + pa_stream_unref(pStream); + Assert(RT_FAILURE_NP(rc)); + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * Translates a PDM channel ID to a PA channel position. + * + * @returns PA channel position, INVALID if no mapping found. + */ +static pa_channel_position_t drvHstAudPaConvertChannelId(uint8_t idChannel) +{ + switch (idChannel) + { + case PDMAUDIOCHANNELID_FRONT_LEFT: return PA_CHANNEL_POSITION_FRONT_LEFT; + case PDMAUDIOCHANNELID_FRONT_RIGHT: return PA_CHANNEL_POSITION_FRONT_RIGHT; + case PDMAUDIOCHANNELID_FRONT_CENTER: return PA_CHANNEL_POSITION_FRONT_CENTER; + case PDMAUDIOCHANNELID_LFE: return PA_CHANNEL_POSITION_LFE; + case PDMAUDIOCHANNELID_REAR_LEFT: return PA_CHANNEL_POSITION_REAR_LEFT; + case PDMAUDIOCHANNELID_REAR_RIGHT: return PA_CHANNEL_POSITION_REAR_RIGHT; + case PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER: return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + case PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER: return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + case PDMAUDIOCHANNELID_REAR_CENTER: return PA_CHANNEL_POSITION_REAR_CENTER; + case PDMAUDIOCHANNELID_SIDE_LEFT: return PA_CHANNEL_POSITION_SIDE_LEFT; + case PDMAUDIOCHANNELID_SIDE_RIGHT: return PA_CHANNEL_POSITION_SIDE_RIGHT; + case PDMAUDIOCHANNELID_TOP_CENTER: return PA_CHANNEL_POSITION_TOP_CENTER; + case PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + case PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + case PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + case PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_LEFT; + case PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_CENTER; + case PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + default: return PA_CHANNEL_POSITION_INVALID; + } +} + + +/** + * Translates a PA channel position to a PDM channel ID. + * + * @returns PDM channel ID, UNKNOWN if no mapping found. + */ +static PDMAUDIOCHANNELID drvHstAudPaConvertChannelPos(pa_channel_position_t enmChannelPos) +{ + switch (enmChannelPos) + { + case PA_CHANNEL_POSITION_INVALID: return PDMAUDIOCHANNELID_INVALID; + case PA_CHANNEL_POSITION_MONO: return PDMAUDIOCHANNELID_MONO; + case PA_CHANNEL_POSITION_FRONT_LEFT: return PDMAUDIOCHANNELID_FRONT_LEFT; + case PA_CHANNEL_POSITION_FRONT_RIGHT: return PDMAUDIOCHANNELID_FRONT_RIGHT; + case PA_CHANNEL_POSITION_FRONT_CENTER: return PDMAUDIOCHANNELID_FRONT_CENTER; + case PA_CHANNEL_POSITION_LFE: return PDMAUDIOCHANNELID_LFE; + case PA_CHANNEL_POSITION_REAR_LEFT: return PDMAUDIOCHANNELID_REAR_LEFT; + case PA_CHANNEL_POSITION_REAR_RIGHT: return PDMAUDIOCHANNELID_REAR_RIGHT; + case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER; + case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER; + case PA_CHANNEL_POSITION_REAR_CENTER: return PDMAUDIOCHANNELID_REAR_CENTER; + case PA_CHANNEL_POSITION_SIDE_LEFT: return PDMAUDIOCHANNELID_SIDE_LEFT; + case PA_CHANNEL_POSITION_SIDE_RIGHT: return PDMAUDIOCHANNELID_SIDE_RIGHT; + case PA_CHANNEL_POSITION_TOP_CENTER: return PDMAUDIOCHANNELID_TOP_CENTER; + case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT; + case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT; + case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT; + case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT; + case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT; + case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT; + default: return PDMAUDIOCHANNELID_UNKNOWN; + } +} + + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq)); + int rc; + + /* + * Prepare name, sample spec and the stream instance data. + */ + char szName[256]; + RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]", PDMAudioPathGetName(pCfgReq->enmPath), pThis->szStreamName); + + pStreamPA->pDrv = pThis; + pStreamPA->pDrainOp = NULL; + pStreamPA->pbPeekBuf = NULL; + pStreamPA->SampleSpec.rate = PDMAudioPropsHz(&pCfgReq->Props); + pStreamPA->SampleSpec.channels = PDMAudioPropsChannels(&pCfgReq->Props); + pStreamPA->SampleSpec.format = drvHstAudPaPropsToPulse(&pCfgReq->Props); + + /* + * Initialize the channelmap. This may change the channel count. + */ + AssertCompile(RT_ELEMENTS(pStreamPA->ChannelMap.map) >= PDMAUDIO_MAX_CHANNELS); + uint8_t const cSrcChannels = pStreamPA->ChannelMap.channels = PDMAudioPropsChannels(&pCfgReq->Props); + uintptr_t iDst = 0; + if (cSrcChannels == 1 && pCfgReq->Props.aidChannels[0] == PDMAUDIOCHANNELID_MONO) + pStreamPA->ChannelMap.map[iDst++] = PA_CHANNEL_POSITION_MONO; + else + { + uintptr_t iSrc; + for (iSrc = iDst = 0; iSrc < cSrcChannels; iSrc++) + { + pStreamPA->ChannelMap.map[iDst] = drvHstAudPaConvertChannelId(pCfgReq->Props.aidChannels[iSrc]); + if (pStreamPA->ChannelMap.map[iDst] != PA_CHANNEL_POSITION_INVALID) + iDst++; + else + { + LogRel2(("PulseAudio: Dropping channel #%u (%d/%s)\n", iSrc, pCfgReq->Props.aidChannels[iSrc], + PDMAudioChannelIdGetName((PDMAUDIOCHANNELID)pCfgReq->Props.aidChannels[iSrc]))); + pStreamPA->ChannelMap.channels--; + pStreamPA->SampleSpec.channels--; + PDMAudioPropsSetChannels(&pCfgAcq->Props, pStreamPA->SampleSpec.channels); + } + } + Assert(iDst == pStreamPA->ChannelMap.channels); + } + while (iDst < RT_ELEMENTS(pStreamPA->ChannelMap.map)) + pStreamPA->ChannelMap.map[iDst++] = PA_CHANNEL_POSITION_INVALID; + + LogFunc(("Opening '%s', rate=%dHz, channels=%d (%d), format=%s\n", szName, pStreamPA->SampleSpec.rate, + pStreamPA->SampleSpec.channels, cSrcChannels, pa_sample_format_to_string(pStreamPA->SampleSpec.format))); + + if (pa_sample_spec_valid(&pStreamPA->SampleSpec)) + { + /* + * Convert the requested buffer parameters to PA bytes. + */ + uint32_t const cbBuffer = pa_usec_to_bytes(PDMAudioPropsFramesToMicro(&pCfgAcq->Props, + pCfgReq->Backend.cFramesBufferSize), + &pStreamPA->SampleSpec); + uint32_t const cbPreBuffer = pa_usec_to_bytes(PDMAudioPropsFramesToMicro(&pCfgAcq->Props, + pCfgReq->Backend.cFramesPreBuffering), + &pStreamPA->SampleSpec); + uint32_t const cbSchedHint = pa_usec_to_bytes(pCfgReq->Device.cMsSchedulingHint * RT_US_1MS, &pStreamPA->SampleSpec); + RT_NOREF(cbBuffer, cbSchedHint, cbPreBuffer); + + /* + * Set up buffer attributes according to the stream type. + */ + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + { + /* Set maxlength to the requested buffer size. */ + pStreamPA->BufAttr.maxlength = cbBuffer; + + /* Set the fragment size according to the scheduling hint (forget + cFramesPeriod, it's generally rubbish on input). */ + pStreamPA->BufAttr.fragsize = cbSchedHint; + + /* (tlength, minreq and prebuf are playback only) */ + LogRel2(("PulseAudio: Requesting: BufAttr: fragsize=%#RX32 maxLength=%#RX32\n", + pStreamPA->BufAttr.fragsize, pStreamPA->BufAttr.maxlength)); + } + else + { + /* Set tlength to the desired buffer size as PA doesn't have any way + of telling us if anything beyond tlength is writable or not (see + drvHstAudPaStreamGetWritableLocked for more). Because of the + ADJUST_LATENCY flag, this value will be adjusted down, so we'll + end up with less buffer than what we requested, however it should + probably reflect the actual latency a bit closer. Probably not + worth trying to adjust this via pa_stream_set_buffer_attr. */ + pStreamPA->BufAttr.tlength = cbBuffer; + + /* Set maxlength to the same as tlength as we won't ever write more + than tlength. */ + pStreamPA->BufAttr.maxlength = pStreamPA->BufAttr.tlength; + + /* According to vlc, pulseaudio goes berserk if the minreq is not + significantly smaller than half of tlength. They use a 1:3 ratio + between minreq and tlength. Traditionally, we've used to just + pass the period value here, however the quality of the incoming + cFramesPeriod value is so variable that just ignore it. This + minreq value is mainly about updating the pa_stream_writable_size + return value, so it makes sense that it need to be well below + half of the buffer length, otherwise we will think the buffer + is full for too long when it isn't. + + The DMA scheduling hint is often a much better indicator. Just + to avoid generating too much IPC, limit this to 10 ms. */ + uint32_t const cbMinUpdate = pa_usec_to_bytes(RT_US_10MS, &pStreamPA->SampleSpec); + pStreamPA->BufAttr.minreq = RT_MIN(RT_MAX(cbSchedHint, cbMinUpdate), + pStreamPA->BufAttr.tlength / 4); + + /* Just pass along the requested pre-buffering size. This seems + typically to be unaltered by pa_stream_connect_playback. Not + sure if tlength is perhaps adjusted relative to it... Ratio + seen here is prebuf=93.75% of tlength. This isn't entirely + optimal as we use 50% by default (see DrvAudio) so that there + is equal room for the guest to run too fast and too slow. Not + much we can do about it w/o slowing down stream creation. */ + pStreamPA->BufAttr.prebuf = cbPreBuffer; + + /* (fragsize is capture only) */ + LogRel2(("PulseAudio: Requesting: BufAttr: tlength=%#RX32 minReq=%#RX32 prebuf=%#RX32 maxLength=%#RX32\n", + pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.minreq, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.maxlength)); + } + + /* + * Do the actual PA stream creation. + */ + pa_threaded_mainloop_lock(pThis->pMainLoop); + rc = drvHstAudPaStreamCreateLocked(pThis, pStreamPA, szName, pCfgAcq); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + if (RT_SUCCESS(rc)) + { + /* + * Set the acquired stream config according to the actual buffer + * attributes we got and the stream type. + * + * Note! We use maxlength for input buffer and tlength for the + * output buffer size. See above for why. + */ + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + { + LogRel2(("PulseAudio: Got: BufAttr: fragsize=%#RX32 maxLength=%#RX32\n", + pStreamPA->BufAttr.fragsize, pStreamPA->BufAttr.maxlength)); + pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.fragsize); + pCfgAcq->Backend.cFramesBufferSize = pStreamPA->BufAttr.maxlength != UINT32_MAX /* paranoia */ + ? PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.maxlength) + : pCfgAcq->Backend.cFramesPeriod * 3 /* whatever */; + pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pCfgAcq->Backend.cFramesBufferSize + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + } + else + { + LogRel2(("PulseAudio: Got: BufAttr: tlength=%#RX32 minReq=%#RX32 prebuf=%#RX32 maxLength=%#RX32\n", + pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.minreq, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.maxlength)); + pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.minreq); + pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.tlength); + pCfgAcq->Backend.cFramesPreBuffering = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.prebuf); + + LogRel2(("PulseAudio: Initial output latency is %RU64 us (%RU32 bytes)\n", + PDMAudioPropsBytesToMicro(&pCfgAcq->Props, pStreamPA->BufAttr.tlength), pStreamPA->BufAttr.tlength)); + } + + /* + * Translate back the channel mapping. + */ + for (iDst = 0; iDst < pStreamPA->ChannelMap.channels; iDst++) + pCfgAcq->Props.aidChannels[iDst] = drvHstAudPaConvertChannelPos(pStreamPA->ChannelMap.map[iDst]); + while (iDst < RT_ELEMENTS(pCfgAcq->Props.aidChannels)) + pCfgAcq->Props.aidChannels[iDst++] = PDMAUDIOCHANNELID_INVALID; + + PDMAudioStrmCfgCopy(&pStreamPA->Cfg, pCfgAcq); + } + } + else + { + LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", szName)); + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Cancel and release any pending stream requests (drain and cork/uncork). + * + * @note Caller has locked the mainloop. + */ +static void drvHstAudPaStreamCancelAndReleaseOperations(PDRVHSTAUDPASTREAM pStreamPA) +{ + if (pStreamPA->pDrainOp) + { + LogFlowFunc(("drain operation (%p) status: %d\n", pStreamPA->pDrainOp, pa_operation_get_state(pStreamPA->pDrainOp))); + pa_operation_cancel(pStreamPA->pDrainOp); + pa_operation_unref(pStreamPA->pDrainOp); + pStreamPA->pDrainOp = NULL; + } + + if (pStreamPA->pCorkOp) + { + LogFlowFunc(("cork operation (%p) status: %d\n", pStreamPA->pCorkOp, pa_operation_get_state(pStreamPA->pCorkOp))); + pa_operation_cancel(pStreamPA->pCorkOp); + pa_operation_unref(pStreamPA->pCorkOp); + pStreamPA->pCorkOp = NULL; + } + + if (pStreamPA->pTriggerOp) + { + LogFlowFunc(("trigger operation (%p) status: %d\n", pStreamPA->pTriggerOp, pa_operation_get_state(pStreamPA->pTriggerOp))); + pa_operation_cancel(pStreamPA->pTriggerOp); + pa_operation_unref(pStreamPA->pTriggerOp); + pStreamPA->pTriggerOp = NULL; + } +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER); + RT_NOREF(fImmediate); + + if (pStreamPA->pStream) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + + drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA); + pa_stream_disconnect(pStreamPA->pStream); + + pa_stream_unref(pStreamPA->pStream); + pStreamPA->pStream = NULL; + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + } + + return VINF_SUCCESS; +} + + +/** + * Common worker for the cork/uncork completion callbacks. + * @note This is fully async, so nobody is waiting for this. + */ +static void drvHstAudPaStreamCorkUncorkCommon(PDRVHSTAUDPASTREAM pStreamPA, int fSuccess, const char *pszOperation) +{ + AssertPtrReturnVoid(pStreamPA); + LogFlowFunc(("%s '%s': fSuccess=%RTbool\n", pszOperation, pStreamPA->Cfg.szName, fSuccess)); + + if (!fSuccess) + drvHstAudPaError(pStreamPA->pDrv, "%s stream '%s' failed", pszOperation, pStreamPA->Cfg.szName); + + if (pStreamPA->pCorkOp) + { + pa_operation_unref(pStreamPA->pCorkOp); + pStreamPA->pCorkOp = NULL; + } +} + + +/** + * Completion callback used with pa_stream_cork(,false,). + */ +static void drvHstAudPaStreamUncorkCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser) +{ + RT_NOREF(pStream); + drvHstAudPaStreamCorkUncorkCommon((PDRVHSTAUDPASTREAM)pvUser, fSuccess, "Uncorking"); +} + + +/** + * Completion callback used with pa_stream_cork(,true,). + */ +static void drvHstAudPaStreamCorkCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser) +{ + RT_NOREF(pStream); + drvHstAudPaStreamCorkUncorkCommon((PDRVHSTAUDPASTREAM)pvUser, fSuccess, "Corking"); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + LogFlowFunc(("\n")); + + /* + * Uncork (start or resume playback/capture) the stream. + */ + pa_threaded_mainloop_lock(pThis->pMainLoop); + + drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA); + pStreamPA->pCorkOp = pa_stream_cork(pStreamPA->pStream, 0 /*uncork it*/, + drvHstAudPaStreamUncorkCompletionCallback, pStreamPA); + LogFlowFunc(("Uncorking '%s': %p (async)\n", pStreamPA->Cfg.szName, pStreamPA->pCorkOp)); + int const rc = pStreamPA->pCorkOp ? VINF_SUCCESS + : drvHstAudPaError(pThis, "pa_stream_cork('%s', 0 /*uncork it*/,,) failed", pStreamPA->Cfg.szName); + + pStreamPA->offInternal = 0; + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + LogFlowFunc(("\n")); + + pa_threaded_mainloop_lock(pThis->pMainLoop); + + /* + * For output streams, we will ignore the request if there is a pending drain + * as it will cork the stream in the end. + */ + if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT) + { + if (pStreamPA->pDrainOp) + { + pa_operation_state_t const enmOpState = pa_operation_get_state(pStreamPA->pDrainOp); + if (enmOpState == PA_OPERATION_RUNNING) + { +/** @todo consider corking it immediately instead, as that's what the caller + * wants now... */ + LogFlowFunc(("Drain (%p) already running on '%s', skipping.\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName)); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + return VINF_SUCCESS; + } + LogFlowFunc(("Drain (%p) not running: %d\n", pStreamPA->pDrainOp, enmOpState)); + } + } + /* + * For input stream we always cork it, but we clean up the peek buffer first. + */ + /** @todo r=bird: It is (probably) not technically be correct to drop the peek buffer + * here when we're only pausing the stream (VM paused) as it means we'll + * risk underruns when later resuming. */ + else if (pStreamPA->pbPeekBuf) /** @todo Do we need to drop the peek buffer?*/ + { + pStreamPA->pbPeekBuf = NULL; + pStreamPA->cbPeekBuf = 0; + pa_stream_drop(pStreamPA->pStream); + } + + /* + * Cork (pause playback/capture) the stream. + */ + drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA); + pStreamPA->pCorkOp = pa_stream_cork(pStreamPA->pStream, 1 /* cork it */, + drvHstAudPaStreamCorkCompletionCallback, pStreamPA); + LogFlowFunc(("Corking '%s': %p (async)\n", pStreamPA->Cfg.szName, pStreamPA->pCorkOp)); + int const rc = pStreamPA->pCorkOp ? VINF_SUCCESS + : drvHstAudPaError(pThis, "pa_stream_cork('%s', 1 /*cork*/,,) failed", pStreamPA->Cfg.szName); + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + /* Same as disable. */ + return drvHstAudPaHA_StreamDisable(pInterface, pStream); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + /* Same as enable. */ + return drvHstAudPaHA_StreamEnable(pInterface, pStream); +} + + +/** + * Pulse audio pa_stream_drain() completion callback. + * @note This is fully async, so nobody is waiting for this. + */ +static void drvHstAudPaStreamDrainCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser) +{ + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvUser; + AssertPtrReturnVoid(pStreamPA); + Assert(pStreamPA->pStream == pStream); + LogFlowFunc(("'%s': fSuccess=%RTbool\n", pStreamPA->Cfg.szName, fSuccess)); + + if (!fSuccess) + drvHstAudPaError(pStreamPA->pDrv, "Draining stream '%s' failed", pStreamPA->Cfg.szName); + + /* Now cork the stream (doing it unconditionally atm). */ + if (pStreamPA->pCorkOp) + { + LogFlowFunc(("Cancelling & releasing cork/uncork operation %p (state: %d)\n", + pStreamPA->pCorkOp, pa_operation_get_state(pStreamPA->pCorkOp))); + pa_operation_cancel(pStreamPA->pCorkOp); + pa_operation_unref(pStreamPA->pCorkOp); + } + + pStreamPA->pCorkOp = pa_stream_cork(pStream, 1 /* cork it*/, drvHstAudPaStreamCorkCompletionCallback, pStreamPA); + if (pStreamPA->pCorkOp) + LogFlowFunc(("Started cork operation %p of %s (following drain)\n", pStreamPA->pCorkOp, pStreamPA->Cfg.szName)); + else + drvHstAudPaError(pStreamPA->pDrv, "pa_stream_cork failed on '%s' (following drain)", pStreamPA->Cfg.szName); +} + + +/** + * Callback used with pa_stream_tigger(), starts draining. + */ +static void drvHstAudPaStreamTriggerCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser) +{ + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvUser; + AssertPtrReturnVoid(pStreamPA); + RT_NOREF(pStream); + LogFlowFunc(("'%s': fSuccess=%RTbool\n", pStreamPA->Cfg.szName, fSuccess)); + + if (!fSuccess) + drvHstAudPaError(pStreamPA->pDrv, "Forcing playback before drainig '%s' failed", pStreamPA->Cfg.szName); + + if (pStreamPA->pTriggerOp) + { + pa_operation_unref(pStreamPA->pTriggerOp); + pStreamPA->pTriggerOp = NULL; + } +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + AssertReturn(pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + LogFlowFunc(("\n")); + + pa_threaded_mainloop_lock(pThis->pMainLoop); + + /* + * If there is a drain running already, don't try issue another as pulse + * doesn't support more than one concurrent drain per stream. + */ + if (pStreamPA->pDrainOp) + { + if (pa_operation_get_state(pStreamPA->pDrainOp) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_unlock(pThis->pMainLoop); + LogFlowFunc(("returns VINF_SUCCESS (drain already running)\n")); + return VINF_SUCCESS; + } + LogFlowFunc(("Releasing drain operation %p (state: %d)\n", pStreamPA->pDrainOp, pa_operation_get_state(pStreamPA->pDrainOp))); + pa_operation_unref(pStreamPA->pDrainOp); + pStreamPA->pDrainOp = NULL; + } + + /* + * Make sure pre-buffered data is played before we drain it. + * + * ASSUMES that the async stream requests are executed in the order they're + * issued here, so that we avoid waiting for the trigger request to complete. + */ + int rc = VINF_SUCCESS; + if ( pStreamPA->offInternal + < PDMAudioPropsFramesToBytes(&pStreamPA->Cfg.Props, pStreamPA->Cfg.Backend.cFramesPreBuffering) * 2) + { + if (pStreamPA->pTriggerOp) + { + LogFlowFunc(("Cancelling & releasing trigger operation %p (state: %d)\n", + pStreamPA->pTriggerOp, pa_operation_get_state(pStreamPA->pTriggerOp))); + pa_operation_cancel(pStreamPA->pTriggerOp); + pa_operation_unref(pStreamPA->pTriggerOp); + } + pStreamPA->pTriggerOp = pa_stream_trigger(pStreamPA->pStream, drvHstAudPaStreamTriggerCompletionCallback, pStreamPA); + if (pStreamPA->pTriggerOp) + LogFlowFunc(("Started tigger operation %p on %s\n", pStreamPA->pTriggerOp, pStreamPA->Cfg.szName)); + else + rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_trigger failed on '%s'", pStreamPA->Cfg.szName); + } + + /* + * Initiate the draining (async), will cork the stream when it completes. + */ + pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, drvHstAudPaStreamDrainCompletionCallback, pStreamPA); + if (pStreamPA->pDrainOp) + LogFlowFunc(("Started drain operation %p of %s\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName)); + else + rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_drain failed on '%s'", pStreamPA->Cfg.szName); + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudPaHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + AssertPtrReturn(pStreamPA, PDMHOSTAUDIOSTREAMSTATE_INVALID); + + /* Check PulseAudio's general status. */ + PDMHOSTAUDIOSTREAMSTATE enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; + if (pThis->pContext) + { + pa_context_state_t const enmPaCtxState = pa_context_get_state(pThis->pContext); + if (PA_CONTEXT_IS_GOOD(enmPaCtxState)) + { + pa_stream_state_t const enmPaStreamState = pa_stream_get_state(pStreamPA->pStream); + if (PA_STREAM_IS_GOOD(enmPaStreamState)) + { + if (enmPaStreamState != PA_STREAM_CREATING) + { + if ( pStreamPA->Cfg.enmDir != PDMAUDIODIR_OUT + || pStreamPA->pDrainOp == NULL + || pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_RUNNING) + enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY; + else + enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING; + } + else + enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING; + } + else + LogFunc(("non-good PA stream state: %d\n", enmPaStreamState)); + } + else + LogFunc(("non-good PA context state: %d\n", enmPaCtxState)); + } + else + LogFunc(("No context!\n")); + LogFlowFunc(("returns %s for stream '%s'\n", PDMHostAudioStreamStateGetName(enmBackendStreamState), pStreamPA->Cfg.szName)); + return enmBackendStreamState; +} + + +/** + * Gets the number of bytes that can safely be written to a stream. + * + * @returns Number of writable bytes, ~(size_t)0 on error. + * @param pStreamPA The stream. + */ +DECLINLINE(uint32_t) drvHstAudPaStreamGetWritableLocked(PDRVHSTAUDPASTREAM pStreamPA) +{ + /* pa_stream_writable_size() returns the amount requested currently by the + server, we could write more than this if we liked. The documentation says + up to maxlength, whoever I'm not sure how that limitation is enforced or + what would happen if we exceed it. There seems to be no (simple) way to + figure out how much buffer we have left between what pa_stream_writable_size + returns and what maxlength indicates. + + An alternative would be to guess the difference using the read and write + positions in the timing info, however the read position is only updated + when starting and stopping. In the auto update mode it's updated at a + sharply decreasing rate starting at 10ms and ending at 1500ms. So, not + all that helpful. (As long as pa_stream_writable_size returns a non-zero + value, though, we could just add the maxlength-tlength difference. But + the problem is after that.) + + So, for now we just use tlength = maxlength for output streams and + problem solved. */ + size_t const cbWritablePa = pa_stream_writable_size(pStreamPA->pStream); +#if 1 + return cbWritablePa; +#else + if (cbWritablePa > 0 && cbWritablePa != (size_t)-1) + return cbWritablePa + (pStreamPA->BufAttr.maxlength - pStreamPA->BufAttr.tlength); + //const pa_timing_info * const pTimingInfo = pa_stream_get_timing_info(pStreamPA->pStream); + return 0; +#endif +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHstAudPaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + uint32_t cbWritable = 0; + if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + + pa_stream_state_t const enmState = pa_stream_get_state(pStreamPA->pStream); + if (PA_STREAM_IS_GOOD(enmState)) + { + size_t cbWritablePa = drvHstAudPaStreamGetWritableLocked(pStreamPA); + if (cbWritablePa != (size_t)-1) + cbWritable = cbWritablePa <= UINT32_MAX ? (uint32_t)cbWritablePa : UINT32_MAX; + else + drvHstAudPaError(pThis, "pa_stream_writable_size failed on '%s'", pStreamPA->Cfg.szName); + } + else + LogFunc(("non-good stream state: %d\n", enmState)); + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + } + Log3Func(("returns %#x (%u) [max=%#RX32 min=%#RX32]\n", + cbWritable, cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq)); + return cbWritable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + else + { + /* Fend off draining calls. */ + *pcbWritten = 0; + return VINF_SUCCESS; + } + + pa_threaded_mainloop_lock(pThis->pMainLoop); + +#ifdef LOG_ENABLED + const pa_usec_t tsNowUs = pa_rtclock_now(); + Log3Func(("play delta: %'RI64 us; cbBuf=%#x @%#RX64\n", + pStreamPA->tsLastReadWrittenUs ? tsNowUs - pStreamPA->tsLastReadWrittenUs : -1, cbBuf, pStreamPA->offInternal)); + pStreamPA->tsLastReadWrittenUs = tsNowUs; +#endif + + /* + * Using a loop here so we can stuff the buffer as full as it gets. + */ + int rc = VINF_SUCCESS; + uint32_t cbTotalWritten = 0; + uint32_t iLoop; + for (iLoop = 0; ; iLoop++) + { + size_t const cbWriteable = drvHstAudPaStreamGetWritableLocked(pStreamPA); + if ( cbWriteable != (size_t)-1 + && cbWriteable >= PDMAudioPropsFrameSize(&pStreamPA->Cfg.Props)) + { + uint32_t cbToWrite = (uint32_t)RT_MIN(cbWriteable, cbBuf); + cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamPA->Cfg.Props, cbToWrite); + if (pa_stream_write(pStreamPA->pStream, pvBuf, cbToWrite, NULL /*pfnFree*/, 0 /*offset*/, PA_SEEK_RELATIVE) >= 0) + { + cbTotalWritten += cbToWrite; + cbBuf -= cbToWrite; + pStreamPA->offInternal += cbToWrite; + if (!cbBuf) + break; + pvBuf = (uint8_t const *)pvBuf + cbToWrite; + Log3Func(("%#x left to write\n", cbBuf)); + } + else + { + rc = drvHstAudPaError(pStreamPA->pDrv, "Failed to write to output stream"); + break; + } + } + else + { + if (cbWriteable == (size_t)-1) + rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_writable_size failed on '%s'", pStreamPA->Cfg.szName); + break; + } + } + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + *pcbWritten = cbTotalWritten; + if (RT_SUCCESS(rc) || cbTotalWritten == 0) + { /* likely */ } + else + { + LogFunc(("Supressing %Rrc because we wrote %#x bytes\n", rc, cbTotalWritten)); + rc = VINF_SUCCESS; + } + Log3Func(("returns %Rrc *pcbWritten=%#x iLoop=%u @%#RX64\n", rc, cbTotalWritten, iLoop, pStreamPA->offInternal)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHstAudPaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + uint32_t cbReadable = 0; + if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_IN) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + + pa_stream_state_t const enmState = pa_stream_get_state(pStreamPA->pStream); + if (PA_STREAM_IS_GOOD(enmState)) + { + size_t cbReadablePa = pa_stream_readable_size(pStreamPA->pStream); + if (cbReadablePa != (size_t)-1) + { + /* As with WASAPI on windows, the peek buffer must be subtracked.*/ + if (cbReadablePa >= pStreamPA->cbPeekBuf) + cbReadable = (uint32_t)(cbReadablePa - pStreamPA->cbPeekBuf); + else + { + AssertMsgFailed(("%#zx vs %#zx\n", cbReadablePa, pStreamPA->cbPeekBuf)); + cbReadable = 0; + } + } + else + drvHstAudPaError(pThis, "pa_stream_readable_size failed on '%s'", pStreamPA->Cfg.szName); + } + else + LogFunc(("non-good stream state: %d\n", enmState)); + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + } + Log3Func(("returns %#x (%u)\n", cbReadable, cbReadable)); + return cbReadable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHstAudPaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio); + PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream; + AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + +#ifdef LOG_ENABLED + const pa_usec_t tsNowUs = pa_rtclock_now(); + Log3Func(("capture delta: %'RI64 us; cbBuf=%#x @%#RX64\n", + pStreamPA->tsLastReadWrittenUs ? tsNowUs - pStreamPA->tsLastReadWrittenUs : -1, cbBuf, pStreamPA->offInternal)); + pStreamPA->tsLastReadWrittenUs = tsNowUs; +#endif + + /* + * If we have left over peek buffer space from the last call, + * copy out the data from there. + */ + uint32_t cbTotalRead = 0; + if ( pStreamPA->pbPeekBuf + && pStreamPA->offPeekBuf < pStreamPA->cbPeekBuf) + { + uint32_t cbToCopy = pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf; + if (cbToCopy >= cbBuf) + { + memcpy(pvBuf, &pStreamPA->pbPeekBuf[pStreamPA->offPeekBuf], cbBuf); + pStreamPA->offPeekBuf += cbBuf; + pStreamPA->offInternal += cbBuf; + *pcbRead = cbBuf; + + if (cbToCopy == cbBuf) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + pStreamPA->pbPeekBuf = NULL; + pStreamPA->cbPeekBuf = 0; + pa_stream_drop(pStreamPA->pStream); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + } + Log3Func(("returns *pcbRead=%#x from prev peek buf (%#x/%#x) @%#RX64\n", + cbBuf, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->offInternal)); + return VINF_SUCCESS; + } + + memcpy(pvBuf, &pStreamPA->pbPeekBuf[pStreamPA->offPeekBuf], cbToCopy); + cbBuf -= cbToCopy; + pvBuf = (uint8_t *)pvBuf + cbToCopy; + cbTotalRead += cbToCopy; + pStreamPA->offPeekBuf = pStreamPA->cbPeekBuf; + } + + /* + * Copy out what we can. + */ + int rc = VINF_SUCCESS; + pa_threaded_mainloop_lock(pThis->pMainLoop); + while (cbBuf > 0) + { + /* + * Drop the old peek buffer first, if we have one. + */ + if (pStreamPA->pbPeekBuf) + { + Assert(pStreamPA->offPeekBuf >= pStreamPA->cbPeekBuf); + pStreamPA->pbPeekBuf = NULL; + pStreamPA->cbPeekBuf = 0; + pa_stream_drop(pStreamPA->pStream); + } + + /* + * Check if there is anything to read, the get the peek buffer for it. + */ + size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream); + if (cbAvail > 0 && cbAvail != (size_t)-1) + { + pStreamPA->pbPeekBuf = NULL; + pStreamPA->cbPeekBuf = 0; + int rcPa = pa_stream_peek(pStreamPA->pStream, (const void **)&pStreamPA->pbPeekBuf, &pStreamPA->cbPeekBuf); + if (rcPa == 0) + { + if (pStreamPA->cbPeekBuf) + { + if (pStreamPA->pbPeekBuf) + { + /* + * We got data back. Copy it into the return buffer, return if it's full. + */ + if (cbBuf < pStreamPA->cbPeekBuf) + { + memcpy(pvBuf, pStreamPA->pbPeekBuf, cbBuf); + cbTotalRead += cbBuf; + pStreamPA->offPeekBuf = cbBuf; + pStreamPA->offInternal += cbBuf; + cbBuf = 0; + break; + } + memcpy(pvBuf, pStreamPA->pbPeekBuf, pStreamPA->cbPeekBuf); + cbBuf -= pStreamPA->cbPeekBuf; + pvBuf = (uint8_t *)pvBuf + pStreamPA->cbPeekBuf; + cbTotalRead += pStreamPA->cbPeekBuf; + pStreamPA->offInternal += cbBuf; + + pStreamPA->pbPeekBuf = NULL; + } + else + { + /* + * We got a hole (drop needed). We will skip it as we leave it to + * the device's DMA engine to fill in buffer gaps with silence. + */ + LogFunc(("pa_stream_peek returned a %#zx (%zu) byte hole - skipping.\n", + pStreamPA->cbPeekBuf, pStreamPA->cbPeekBuf)); + } + pStreamPA->cbPeekBuf = 0; + pa_stream_drop(pStreamPA->pStream); + } + else + { + Assert(!pStreamPA->pbPeekBuf); + LogFunc(("pa_stream_peek returned empty buffer\n")); + break; + } + } + else + { + rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_peek failed on '%s' (%d)", pStreamPA->Cfg.szName, rcPa); + pStreamPA->pbPeekBuf = NULL; + pStreamPA->cbPeekBuf = 0; + break; + } + } + else + { + if (cbAvail == (size_t)-1) + rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_readable_size failed on '%s'", pStreamPA->Cfg.szName); + break; + } + } + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + *pcbRead = cbTotalRead; + if (RT_SUCCESS(rc) || cbTotalRead == 0) + { /* likely */ } + else + { + LogFunc(("Supressing %Rrc because we're returning %#x bytes\n", rc, cbTotalRead)); + rc = VINF_SUCCESS; + } + Log3Func(("returns %Rrc *pcbRead=%#x (%#x left, peek %#x/%#x) @%#RX64\n", + rc, cbTotalRead, cbBuf, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->offInternal)); + return rc; +} + + +/********************************************************************************************************************************* +* PDMIBASE * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHstAudPaQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + AssertPtrReturn(pInterface, NULL); + AssertPtrReturn(pszIID, NULL); + + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG * +*********************************************************************************************************************************/ + +/** + * Destructs a PulseAudio Audio driver instance. + * + * @copydoc FNPDMDRVDESTRUCT + */ +static DECLCALLBACK(void) drvHstAudPaDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA); + LogFlowFuncEnter(); + + if (pThis->pMainLoop) + pa_threaded_mainloop_stop(pThis->pMainLoop); + + if (pThis->pContext) + { + pa_context_disconnect(pThis->pContext); + pa_context_unref(pThis->pContext); + pThis->pContext = NULL; + } + + if (pThis->pMainLoop) + { + pa_threaded_mainloop_free(pThis->pMainLoop); + pThis->pMainLoop = NULL; + } + + LogFlowFuncLeave(); +} + + +/** + * Pulse audio callback for context status changes, init variant. + * + * Signalls our event semaphore so we can do a timed wait from + * drvHstAudPaConstruct(). + */ +static void drvHstAudPaCtxCallbackStateChangedInit(pa_context *pCtx, void *pvUser) +{ + AssertPtrReturnVoid(pCtx); + PDRVHSTAUDPASTATECHGCTX pStateChgCtx = (PDRVHSTAUDPASTATECHGCTX)pvUser; + pa_context_state_t enmCtxState = pa_context_get_state(pCtx); + switch (enmCtxState) + { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + AssertPtrReturnVoid(pStateChgCtx); + pStateChgCtx->enmCtxState = enmCtxState; + RTSemEventSignal(pStateChgCtx->hEvtInit); + break; + + default: + break; + } +} + + +/** + * Constructs a PulseAudio Audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHstAudPaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA); + LogRel(("Audio: Initializing PulseAudio driver\n")); + + /* + * Initialize instance data. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHstAudPaQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHstAudPaHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = drvHstAudPaHA_GetDevices; + pThis->IHostAudio.pfnSetDevice = drvHstAudPaHA_SetDevice; + pThis->IHostAudio.pfnGetStatus = drvHstAudPaHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHstAudPaHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHstAudPaHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHstAudPaHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHstAudPaHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHstAudPaHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHstAudPaHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHstAudPaHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetState = drvHstAudPaHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetWritable = drvHstAudPaHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvHstAudPaHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvHstAudPaHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvHstAudPaHA_StreamCapture; + + /* + * Read configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "VmName|InputDeviceID|OutputDeviceID", ""); + int rc = CFGMR3QueryString(pCfg, "VmName", pThis->szStreamName, sizeof(pThis->szStreamName)); + AssertMsgRCReturn(rc, ("Confguration error: No/bad \"VmName\" value, rc=%Rrc\n", rc), rc); + rc = CFGMR3QueryStringDef(pCfg, "InputDeviceID", pThis->szInputDev, sizeof(pThis->szInputDev), ""); + AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"InputDeviceID\" as string: rc=%Rrc\n", rc), rc); + rc = CFGMR3QueryStringDef(pCfg, "OutputDeviceID", pThis->szOutputDev, sizeof(pThis->szOutputDev), ""); + AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"OutputDeviceID\" as string: rc=%Rrc\n", rc), rc); + + /* + * Query the notification interface from the driver/device above us. + */ + pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT); + AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE); + + /* + * Load the pulse audio library. + */ + rc = audioLoadPulseLib(); + if (RT_SUCCESS(rc)) + LogRel(("PulseAudio: Using version %s\n", pa_get_library_version())); + else + { + LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc)); + return rc; + } + + /* + * Set up the basic pulse audio bits (remember the destructore is always called). + */ + //pThis->fAbortLoop = false; + pThis->pMainLoop = pa_threaded_mainloop_new(); + if (!pThis->pMainLoop) + { + LogRel(("PulseAudio: Failed to allocate main loop: %s\n", pa_strerror(pa_context_errno(pThis->pContext)))); + return VERR_NO_MEMORY; + } + + pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox"); + if (!pThis->pContext) + { + LogRel(("PulseAudio: Failed to allocate context: %s\n", pa_strerror(pa_context_errno(pThis->pContext)))); + return VERR_NO_MEMORY; + } + + if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0) + { + LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n", pa_strerror(pa_context_errno(pThis->pContext)))); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + + /* + * Connect to the pulse audio server. + * + * We install an init state callback so we can do a timed wait in case + * connecting to the pulseaudio server should take too long. + */ + pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT; + pThis->InitStateChgCtx.enmCtxState = PA_CONTEXT_UNCONNECTED; + rc = RTSemEventCreate(&pThis->InitStateChgCtx.hEvtInit); + AssertLogRelRCReturn(rc, rc); + + pa_threaded_mainloop_lock(pThis->pMainLoop); + pa_context_set_state_callback(pThis->pContext, drvHstAudPaCtxCallbackStateChangedInit, &pThis->InitStateChgCtx); + if (!pa_context_connect(pThis->pContext, NULL /* pszServer */, PA_CONTEXT_NOFLAGS, NULL)) + { + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + rc = RTSemEventWait(pThis->InitStateChgCtx.hEvtInit, RT_MS_10SEC); /* 10 seconds should be plenty. */ + if (RT_SUCCESS(rc)) + { + if (pThis->InitStateChgCtx.enmCtxState == PA_CONTEXT_READY) + { + /* Install the main state changed callback to know if something happens to our acquired context. */ + pa_threaded_mainloop_lock(pThis->pMainLoop); + pa_context_set_state_callback(pThis->pContext, drvHstAudPaCtxCallbackStateChanged, pThis /* pvUserData */); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + } + else + { + LogRel(("PulseAudio: Failed to initialize context (state %d, rc=%Rrc)\n", pThis->InitStateChgCtx.enmCtxState, rc)); + rc = VERR_AUDIO_BACKEND_INIT_FAILED; + } + } + else + { + LogRel(("PulseAudio: Waiting for context to become ready failed: %Rrc\n", rc)); + rc = VERR_AUDIO_BACKEND_INIT_FAILED; + } + } + else + { + pa_threaded_mainloop_unlock(pThis->pMainLoop); + LogRel(("PulseAudio: Failed to connect to server: %s\n", pa_strerror(pa_context_errno(pThis->pContext)))); + rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* bird: This used to be VINF_SUCCESS. */ + } + + RTSemEventDestroy(pThis->InitStateChgCtx.hEvtInit); + pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT; + + /* + * Register statistics. + */ + if (RT_SUCCESS(rc)) + { + PDMDrvHlpSTAMRegister(pDrvIns, &pThis->StatOverruns, STAMTYPE_COUNTER, "Overruns", STAMUNIT_OCCURENCES, + "Pulse-server side buffer overruns (all streams)"); + PDMDrvHlpSTAMRegister(pDrvIns, &pThis->StatUnderruns, STAMTYPE_COUNTER, "Underruns", STAMUNIT_OCCURENCES, + "Pulse-server side buffer underruns (all streams)"); + } + + return rc; +} + + +/** + * Pulse audio driver registration record. + */ +const PDMDRVREG g_DrvHostPulseAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "PulseAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Pulse Audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHSTAUDPA), + /* pfnConstruct */ + drvHstAudPaConstruct, + /* pfnDestruct */ + drvHstAudPaDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,363 @@ +/* $Id: DrvHostAudioPulseAudioStubs.cpp $ */ +/** @file + * Stubs for libpulse. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include +#include +#include + +#include + +#include "DrvHostAudioPulseAudioStubs.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_PULSE_LIB "libpulse.so.0" + +#define PROXY_STUB(function, rettype, signature, shortsig) \ + static rettype (*g_pfn_ ## function) signature; \ + \ + extern "C" rettype VBox_##function signature; \ + rettype VBox_##function signature \ + { \ + return g_pfn_ ## function shortsig; \ + } + +#define PROXY_STUB_VOID(function, signature, shortsig) \ + static void (*g_pfn_ ## function) signature; \ + \ + extern "C" void VBox_##function signature; \ + void VBox_##function signature \ + { \ + g_pfn_ ## function shortsig; \ + } + +PROXY_STUB (pa_bytes_per_second, size_t, + (const pa_sample_spec *spec), + (spec)) +PROXY_STUB (pa_bytes_to_usec, pa_usec_t, + (uint64_t l, const pa_sample_spec *spec), + (l, spec)) +PROXY_STUB (pa_channel_map_init_auto, pa_channel_map*, + (pa_channel_map *m, unsigned channels, pa_channel_map_def_t def), + (m, channels, def)) + +PROXY_STUB (pa_context_connect, int, + (pa_context *c, const char *server, pa_context_flags_t flags, + const pa_spawn_api *api), + (c, server, flags, api)) +PROXY_STUB_VOID(pa_context_disconnect, + (pa_context *c), + (c)) +PROXY_STUB (pa_context_get_server_info, pa_operation*, + (pa_context *c, pa_server_info_cb_t cb, void *userdata), + (c, cb, userdata)) +PROXY_STUB (pa_context_get_sink_info_by_name, pa_operation*, + (pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata), + (c, name, cb, userdata)) +PROXY_STUB (pa_context_get_sink_info_list, pa_operation *, + (pa_context *c, pa_sink_info_cb_t cb, void *userdata), + (c, cb, userdata)) +PROXY_STUB (pa_context_get_source_info_by_name, pa_operation*, + (pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata), + (c, name, cb, userdata)) +PROXY_STUB (pa_context_get_source_info_list, pa_operation *, + (pa_context *c, pa_source_info_cb_t cb, void *userdata), + (c, cb, userdata)) +PROXY_STUB (pa_context_get_state, pa_context_state_t, + (pa_context *c), + (c)) +PROXY_STUB_VOID(pa_context_unref, + (pa_context *c), + (c)) +PROXY_STUB (pa_context_errno, int, + (pa_context *c), + (c)) +PROXY_STUB (pa_context_new, pa_context*, + (pa_mainloop_api *mainloop, const char *name), + (mainloop, name)) +PROXY_STUB_VOID(pa_context_set_state_callback, + (pa_context *c, pa_context_notify_cb_t cb, void *userdata), + (c, cb, userdata)) + +PROXY_STUB (pa_frame_size, size_t, + (const pa_sample_spec *spec), + (spec)) +PROXY_STUB (pa_get_library_version, const char *, (void), ()) +PROXY_STUB_VOID(pa_operation_unref, + (pa_operation *o), + (o)) +PROXY_STUB (pa_operation_get_state, pa_operation_state_t, + (pa_operation *o), + (o)) +PROXY_STUB_VOID(pa_operation_cancel, + (pa_operation *o), + (o)) + +PROXY_STUB (pa_rtclock_now, pa_usec_t, + (void), + ()) +PROXY_STUB (pa_sample_format_to_string, const char*, + (pa_sample_format_t f), + (f)) +PROXY_STUB (pa_sample_spec_valid, int, + (const pa_sample_spec *spec), + (spec)) +PROXY_STUB (pa_strerror, const char*, + (int error), + (error)) + +#if PA_PROTOCOL_VERSION >= 16 +PROXY_STUB (pa_stream_connect_playback, int, + (pa_stream *s, const char *dev, const pa_buffer_attr *attr, + pa_stream_flags_t flags, const pa_cvolume *volume, pa_stream *sync_stream), + (s, dev, attr, flags, volume, sync_stream)) +#else +PROXY_STUB (pa_stream_connect_playback, int, + (pa_stream *s, const char *dev, const pa_buffer_attr *attr, + pa_stream_flags_t flags, pa_cvolume *volume, pa_stream *sync_stream), + (s, dev, attr, flags, volume, sync_stream)) +#endif +PROXY_STUB (pa_stream_connect_record, int, + (pa_stream *s, const char *dev, const pa_buffer_attr *attr, + pa_stream_flags_t flags), + (s, dev, attr, flags)) +PROXY_STUB (pa_stream_disconnect, int, + (pa_stream *s), + (s)) +PROXY_STUB (pa_stream_get_sample_spec, const pa_sample_spec*, + (pa_stream *s), + (s)) +PROXY_STUB_VOID(pa_stream_set_latency_update_callback, + (pa_stream *p, pa_stream_notify_cb_t cb, void *userdata), + (p, cb, userdata)) +PROXY_STUB (pa_stream_write, int, + (pa_stream *p, const void *data, size_t bytes, pa_free_cb_t free_cb, + int64_t offset, pa_seek_mode_t seek), + (p, data, bytes, free_cb, offset, seek)) +PROXY_STUB_VOID(pa_stream_unref, + (pa_stream *s), + (s)) +PROXY_STUB (pa_stream_get_state, pa_stream_state_t, + (pa_stream *p), + (p)) +PROXY_STUB (pa_stream_get_latency, int, + (pa_stream *s, pa_usec_t *r_usec, int *negative), + (s, r_usec, negative)) +PROXY_STUB (pa_stream_get_timing_info, pa_timing_info*, + (pa_stream *s), + (s)) +PROXY_STUB (pa_stream_readable_size, size_t, + (pa_stream *p), + (p)) +PROXY_STUB (pa_stream_set_buffer_attr, pa_operation *, + (pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata), + (s, attr, cb, userdata)) +PROXY_STUB_VOID(pa_stream_set_state_callback, + (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB_VOID(pa_stream_set_underflow_callback, + (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB_VOID(pa_stream_set_overflow_callback, + (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB_VOID(pa_stream_set_write_callback, + (pa_stream *s, pa_stream_request_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB (pa_stream_flush, pa_operation*, + (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB (pa_stream_drain, pa_operation*, + (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB (pa_stream_trigger, pa_operation*, + (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB (pa_stream_new, pa_stream*, + (pa_context *c, const char *name, const pa_sample_spec *ss, + const pa_channel_map *map), + (c, name, ss, map)) +PROXY_STUB (pa_stream_get_buffer_attr, const pa_buffer_attr*, + (pa_stream *s), + (s)) +PROXY_STUB (pa_stream_peek, int, + (pa_stream *p, const void **data, size_t *bytes), + (p, data, bytes)) +PROXY_STUB (pa_stream_cork, pa_operation*, + (pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata), + (s, b, cb, userdata)) +PROXY_STUB (pa_stream_drop, int, + (pa_stream *p), + (p)) +PROXY_STUB (pa_stream_writable_size, size_t, + (pa_stream *p), + (p)) + +PROXY_STUB_VOID(pa_threaded_mainloop_stop, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB (pa_threaded_mainloop_get_api, pa_mainloop_api*, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB_VOID(pa_threaded_mainloop_free, + (pa_threaded_mainloop* m), + (m)) +PROXY_STUB_VOID(pa_threaded_mainloop_signal, + (pa_threaded_mainloop *m, int wait_for_accept), + (m, wait_for_accept)) +PROXY_STUB_VOID(pa_threaded_mainloop_unlock, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB (pa_threaded_mainloop_new, pa_threaded_mainloop *, + (void), + ()) +PROXY_STUB_VOID(pa_threaded_mainloop_wait, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB (pa_threaded_mainloop_start, int, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB_VOID(pa_threaded_mainloop_lock, + (pa_threaded_mainloop *m), + (m)) + +PROXY_STUB (pa_usec_to_bytes, size_t, + (pa_usec_t t, const pa_sample_spec *spec), + (t, spec)) + +#define FUNC_ENTRY(function) { #function , (void (**)(void)) & g_pfn_ ## function } +static struct +{ + const char *pszName; + void (**pfn)(void); +} const g_aImportedFunctions[] = +{ + FUNC_ENTRY(pa_bytes_per_second), + FUNC_ENTRY(pa_bytes_to_usec), + FUNC_ENTRY(pa_channel_map_init_auto), + + FUNC_ENTRY(pa_context_connect), + FUNC_ENTRY(pa_context_disconnect), + FUNC_ENTRY(pa_context_get_server_info), + FUNC_ENTRY(pa_context_get_sink_info_by_name), + FUNC_ENTRY(pa_context_get_sink_info_list), + FUNC_ENTRY(pa_context_get_source_info_by_name), + FUNC_ENTRY(pa_context_get_source_info_list), + FUNC_ENTRY(pa_context_get_state), + FUNC_ENTRY(pa_context_unref), + FUNC_ENTRY(pa_context_errno), + FUNC_ENTRY(pa_context_new), + FUNC_ENTRY(pa_context_set_state_callback), + + FUNC_ENTRY(pa_frame_size), + FUNC_ENTRY(pa_get_library_version), + FUNC_ENTRY(pa_operation_unref), + FUNC_ENTRY(pa_operation_get_state), + FUNC_ENTRY(pa_operation_cancel), + FUNC_ENTRY(pa_rtclock_now), + FUNC_ENTRY(pa_sample_format_to_string), + FUNC_ENTRY(pa_sample_spec_valid), + FUNC_ENTRY(pa_strerror), + + FUNC_ENTRY(pa_stream_connect_playback), + FUNC_ENTRY(pa_stream_connect_record), + FUNC_ENTRY(pa_stream_disconnect), + FUNC_ENTRY(pa_stream_get_sample_spec), + FUNC_ENTRY(pa_stream_set_latency_update_callback), + FUNC_ENTRY(pa_stream_write), + FUNC_ENTRY(pa_stream_unref), + FUNC_ENTRY(pa_stream_get_state), + FUNC_ENTRY(pa_stream_get_latency), + FUNC_ENTRY(pa_stream_get_timing_info), + FUNC_ENTRY(pa_stream_readable_size), + FUNC_ENTRY(pa_stream_set_buffer_attr), + FUNC_ENTRY(pa_stream_set_state_callback), + FUNC_ENTRY(pa_stream_set_underflow_callback), + FUNC_ENTRY(pa_stream_set_overflow_callback), + FUNC_ENTRY(pa_stream_set_write_callback), + FUNC_ENTRY(pa_stream_flush), + FUNC_ENTRY(pa_stream_drain), + FUNC_ENTRY(pa_stream_trigger), + FUNC_ENTRY(pa_stream_new), + FUNC_ENTRY(pa_stream_get_buffer_attr), + FUNC_ENTRY(pa_stream_peek), + FUNC_ENTRY(pa_stream_cork), + FUNC_ENTRY(pa_stream_drop), + FUNC_ENTRY(pa_stream_writable_size), + + FUNC_ENTRY(pa_threaded_mainloop_stop), + FUNC_ENTRY(pa_threaded_mainloop_get_api), + FUNC_ENTRY(pa_threaded_mainloop_free), + FUNC_ENTRY(pa_threaded_mainloop_signal), + FUNC_ENTRY(pa_threaded_mainloop_unlock), + FUNC_ENTRY(pa_threaded_mainloop_new), + FUNC_ENTRY(pa_threaded_mainloop_wait), + FUNC_ENTRY(pa_threaded_mainloop_start), + FUNC_ENTRY(pa_threaded_mainloop_lock), + + FUNC_ENTRY(pa_usec_to_bytes) +}; +#undef FUNC_ENTRY + +/** Init once. */ +static RTONCE g_PulseAudioLibInitOnce = RTONCE_INITIALIZER; + +/** @callback_method_impl{FNRTONCE} */ +static DECLCALLBACK(int32_t) drvHostAudioPulseLibInitOnce(void *pvUser) +{ + RT_NOREF(pvUser); + LogFlowFunc(("\n")); + + RTLDRMOD hMod = NIL_RTLDRMOD; + int rc = RTLdrLoadSystemEx(VBOX_PULSE_LIB, RTLDRLOAD_FLAGS_NO_UNLOAD, &hMod); + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; i < RT_ELEMENTS(g_aImportedFunctions); i++) + { + rc = RTLdrGetSymbol(hMod, g_aImportedFunctions[i].pszName, (void **)g_aImportedFunctions[i].pfn); + if (RT_FAILURE(rc)) + { + LogRelFunc(("Failed to resolve function #%u: '%s' (%Rrc)\n", i, g_aImportedFunctions[i].pszName, rc)); + break; + } + } + } + else + LogRelFunc(("Failed to load library %s: %Rrc\n", VBOX_PULSE_LIB, rc)); + return rc; +} + +/** + * Try to dynamically load the PulseAudio libraries. + * + * @returns VBox status code. + */ +int audioLoadPulseLib(void) +{ + LogFlowFunc(("\n")); + return RTOnce(&g_PulseAudioLibInitOnce, drvHostAudioPulseLibInitOnce, NULL); +} + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,31 @@ +/* $Id: DrvHostAudioPulseAudioStubs.h $ */ +/** @file + * Stubs for libpulse. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubs_h +#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +RT_C_DECLS_BEGIN +extern int audioLoadPulseLib(void); +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubs_h */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubsMangling.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubsMangling.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubsMangling.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubsMangling.h 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,95 @@ +/* $Id: DrvHostAudioPulseAudioStubsMangling.h $ */ +/** @file + * Mangle libpulse symbols. + * + * This is necessary on hosts which don't support the -fvisibility gcc switch. + */ + +/* + * Copyright (C) 2013-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubsMangling_h +#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubsMangling_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define PULSE_MANGLER(symbol) VBox_##symbol + +#define pa_bytes_per_second PULSE_MANGLER(pa_bytes_per_second) +#define pa_bytes_to_usec PULSE_MANGLER(pa_bytes_to_usec) +#define pa_channel_map_init_auto PULSE_MANGLER(pa_channel_map_init_auto) + +#define pa_context_connect PULSE_MANGLER(pa_context_connect) +#define pa_context_disconnect PULSE_MANGLER(pa_context_disconnect) +#define pa_context_get_server_info PULSE_MANGLER(pa_context_get_server_info) +#define pa_context_get_sink_info_by_name PULSE_MANGLER(pa_context_get_sink_info_by_name) +#define pa_context_get_sink_info_list PULSE_MANGLER(pa_context_get_sink_info_list) +#define pa_context_get_source_info_by_name PULSE_MANGLER(pa_context_get_source_info_by_name) +#define pa_context_get_source_info_list PULSE_MANGLER(pa_context_get_source_info_list) +#define pa_context_get_state PULSE_MANGLER(pa_context_get_state) +#define pa_context_unref PULSE_MANGLER(pa_context_unref) +#define pa_context_errno PULSE_MANGLER(pa_context_errno) +#define pa_context_new PULSE_MANGLER(pa_context_new) +#define pa_context_set_state_callback PULSE_MANGLER(pa_context_set_state_callback) + +#define pa_frame_size PULSE_MANGLER(pa_frame_size) +#define pa_get_library_version PULSE_MANGLER(pa_get_library_version) +#define pa_operation_unref PULSE_MANGLER(pa_operation_unref) +#define pa_operation_get_state PULSE_MANGLER(pa_operation_get_state) +#define pa_operation_cancel PULSE_MANGLER(pa_operation_cancel) +#define pa_rtclock_now PULSE_MANGLER(pa_rtclock_now) +#define pa_sample_format_to_string PULSE_MANGLER(pa_sample_format_to_string) +#define pa_sample_spec_valid PULSE_MANGLER(pa_sample_spec_valid) + +#define pa_stream_connect_playback PULSE_MANGLER(pa_stream_connect_playback) +#define pa_stream_connect_record PULSE_MANGLER(pa_stream_connect_record) +#define pa_stream_cork PULSE_MANGLER(pa_stream_cork) +#define pa_stream_disconnect PULSE_MANGLER(pa_stream_disconnect) +#define pa_stream_drop PULSE_MANGLER(pa_stream_drop) +#define pa_stream_get_sample_spec PULSE_MANGLER(pa_stream_get_sample_spec) +#define pa_stream_set_latency_update_callback PULSE_MANGLER(pa_stream_set_latency_update_callback) +#define pa_stream_write PULSE_MANGLER(pa_stream_write) +#define pa_stream_unref PULSE_MANGLER(pa_stream_unref) +#define pa_stream_get_state PULSE_MANGLER(pa_stream_get_state) +#define pa_stream_get_latency PULSE_MANGLER(pa_stream_get_latency) +#define pa_stream_get_timing_info PULSE_MANGLER(pa_stream_get_timing_info) +#define pa_stream_set_buffer_attr PULSE_MANGLER(pa_stream_set_buffer_attr) +#define pa_stream_set_state_callback PULSE_MANGLER(pa_stream_set_state_callback) +#define pa_stream_set_underflow_callback PULSE_MANGLER(pa_stream_set_underflow_callback) +#define pa_stream_set_overflow_callback PULSE_MANGLER(pa_stream_set_overflow_callback) +#define pa_stream_set_write_callback PULSE_MANGLER(pa_stream_set_write_callback) +#define pa_stream_flush PULSE_MANGLER(pa_stream_flush) +#define pa_stream_drain PULSE_MANGLER(pa_stream_drain) +#define pa_stream_trigger PULSE_MANGLER(pa_stream_trigger) +#define pa_stream_new PULSE_MANGLER(pa_stream_new) +#define pa_stream_get_buffer_attr PULSE_MANGLER(pa_stream_get_buffer_attr) +#define pa_stream_peek PULSE_MANGLER(pa_stream_peek) +#define pa_stream_readable_size PULSE_MANGLER(pa_stream_readable_size) +#define pa_stream_writable_size PULSE_MANGLER(pa_stream_writable_size) + +#define pa_strerror PULSE_MANGLER(pa_strerror) + +#define pa_threaded_mainloop_stop PULSE_MANGLER(pa_threaded_mainloop_stop) +#define pa_threaded_mainloop_get_api PULSE_MANGLER(pa_threaded_mainloop_get_api) +#define pa_threaded_mainloop_free PULSE_MANGLER(pa_threaded_mainloop_free) +#define pa_threaded_mainloop_signal PULSE_MANGLER(pa_threaded_mainloop_signal) +#define pa_threaded_mainloop_unlock PULSE_MANGLER(pa_threaded_mainloop_unlock) +#define pa_threaded_mainloop_new PULSE_MANGLER(pa_threaded_mainloop_new) +#define pa_threaded_mainloop_wait PULSE_MANGLER(pa_threaded_mainloop_wait) +#define pa_threaded_mainloop_start PULSE_MANGLER(pa_threaded_mainloop_start) +#define pa_threaded_mainloop_lock PULSE_MANGLER(pa_threaded_mainloop_lock) + +#define pa_usec_to_bytes PULSE_MANGLER(pa_usec_to_bytes) + +#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubsMangling_h */ + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,429 @@ +/* $Id: DrvHostAudioValidationKit.cpp $ */ +/** @file + * Host audio driver - ValidationKit - For dumping and injecting audio data from/to the device emulation. + */ + +/* + * Copyright (C) 2016-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include +#include +#include +#include +#include /* For PDMIBASE_2_PDMDRV. */ + +#include +#include +#include + +#include "AudioHlp.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Structure for keeping a validation kit input/output stream. + */ +typedef struct VAKITAUDIOSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** Audio file to dump output to or read input from. */ + PAUDIOHLPFILE pFile; + /** Text file to store timing of audio buffers submittions. */ + PRTSTREAM pFileTiming; + /** Timestamp of the first play or record request. */ + uint64_t tsStarted; + /** Total number of frames played or recorded so far. */ + uint32_t cFramesSinceStarted; + union + { + struct + { + /** Timestamp of last captured samples. */ + uint64_t tsLastCaptured; + } In; + struct + { + /** Timestamp of last played samples. */ + uint64_t tsLastPlayed; + uint8_t *pbPlayBuffer; + uint32_t cbPlayBuffer; + } Out; + }; +} VAKITAUDIOSTREAM; +/** Pointer to a validation kit stream. */ +typedef VAKITAUDIOSTREAM *PVAKITAUDIOSTREAM; + +/** + * Validation kit audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTVAKITAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; +} DRVHOSTVAKITAUDIO; +/** Pointer to a validation kit host audio driver instance. */ +typedef DRVHOSTVAKITAUDIO *PDRVHOSTVAKITAUDIO; + + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Validation Kit"); + pBackendCfg->cbStream = sizeof(VAKITAUDIOSTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsOut = 1; /* Output */ + pBackendCfg->cMaxStreamsIn = 0; /* No input supported yet. */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostValKitAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHOSTVAKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio); + PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamDbg, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + RT_NOREF(pThis); + + int rc = VINF_SUCCESS; + PDMAudioStrmCfgCopy(&pStreamDbg->Cfg, pCfgAcq); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fImmediate) +{ + RT_NOREF(pInterface, fImmediate); + PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamDbg, VERR_INVALID_POINTER); + + if ( pStreamDbg->Cfg.enmDir == PDMAUDIODIR_OUT + && pStreamDbg->Out.pbPlayBuffer) + { + RTMemFree(pStreamDbg->Out.pbPlayBuffer); + pStreamDbg->Out.pbPlayBuffer = NULL; + } + + if (pStreamDbg->pFile) + { + if (pStreamDbg->pFile->cbWaveData) + LogRel(("ValKitAudio: Created output file '%s' (%'RU64 bytes)\n", + pStreamDbg->pFile->szName, pStreamDbg->pFile->cbWaveData)); + + AudioHlpFileDestroy(pStreamDbg->pFile); + pStreamDbg->pFile = NULL; + } + + if (pStreamDbg->pFileTiming) + { + RTStrmClose(pStreamDbg->pFileTiming); + pStreamDbg->pFileTiming = NULL; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostValKitAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostValKitAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostValKitAudioHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID); + return PDMHOSTAUDIOSTREAMSTATE_OKAY; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + PDRVHOSTVAKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio); + PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; + RT_NOREF(pThis); + + uint64_t cNsSinceStart; + if (pStreamDbg->tsStarted != 0) + cNsSinceStart = RTTimeNanoTS() - pStreamDbg->tsStarted; + else + { + pStreamDbg->tsStarted = RTTimeNanoTS(); + cNsSinceStart = 0; + } + + // Microseconds are used everythere below + uint32_t const cFrames = PDMAudioPropsBytesToFrames(&pStreamDbg->Cfg.Props, cbBuf); + RTStrmPrintf(pStreamDbg->pFileTiming, "%d %d %d %d\n", + // Host time elapsed since Guest submitted the first buffer for playback: + (uint32_t)(cNsSinceStart / 1000), + // how long all the samples submitted previously were played: + (uint32_t)(pStreamDbg->cFramesSinceStarted * 1.0E6 / pStreamDbg->Cfg.Props.uHz), + // how long a new uSamplesReady samples should/will be played: + (uint32_t)(cFrames * 1.0E6 / pStreamDbg->Cfg.Props.uHz), + cFrames); + + pStreamDbg->cFramesSinceStarted += cFrames; + + /* Remember when samples were consumed. */ + // pStreamDbg->Out.tsLastPlayed = PDMDrvHlpTMGetVirtualTime(pThis->pDrvIns); + + int rc2 = AudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cbBuf); + if (RT_FAILURE(rc2)) + LogRel(("ValKitAudio: Writing output failed with %Rrc\n", rc2)); + + *pcbWritten = cbBuf; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface, pStream, pvBuf, cbBuf); + + /* Never capture anything. */ + *pcbRead = 0; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostValKitAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/** + * Constructs a VaKit audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHostValKitAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO); + LogRel(("Audio: Initializing VALKIT driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostValKitAudioQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHostValKitAudioHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = NULL; + pThis->IHostAudio.pfnSetDevice = NULL; + pThis->IHostAudio.pfnGetStatus = drvHostValKitAudioHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHostValKitAudioHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHostValKitAudioHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHostValKitAudioHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHostValKitAudioHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHostValKitAudioHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHostValKitAudioHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHostValKitAudioHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetReadable = drvHostValKitAudioHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamGetWritable = drvHostValKitAudioHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetState = drvHostValKitAudioHA_StreamGetState; + pThis->IHostAudio.pfnStreamPlay = drvHostValKitAudioHA_StreamPlay; + pThis->IHostAudio.pfnStreamCapture = drvHostValKitAudioHA_StreamCapture; + + return VINF_SUCCESS; +} + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostValidationKitAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "ValidationKitAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "ValidationKitAudio audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTVAKITAUDIO), + /* pfnConstruct */ + drvHostValKitAudioConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp 2022-09-01 13:23:47.000000000 +0000 @@ -0,0 +1,3332 @@ +/* $Id: DrvHostAudioWasApi.cpp $ */ +/** @file + * Host audio driver - Windows Audio Session API. + */ + +/* + * Copyright (C) 2021 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +/*#define INITGUID - defined in VBoxhostAudioDSound.cpp already */ +#include +#include +#include +#include +#include +#include +#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY +# define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY UINT32_C(0x08000000) +#endif +#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM +# define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM UINT32_C(0x80000000) +#endif + +#include +#include + +#include +#include +#include +#include + +#include /* std::bad_alloc */ + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Max GetCurrentPadding value we accept (to make sure it's safe to convert to bytes). */ +#define VBOX_WASAPI_MAX_PADDING UINT32_C(0x007fffff) + +/** Maximum number of cached device configs in each direction. + * The number 4 was picked at random. */ +#define VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES 4 + +#if 0 +/** @name WM_DRVHOSTAUDIOWAS_XXX - Worker thread messages. + * @{ */ +#define WM_DRVHOSTAUDIOWAS_PURGE_CACHE (WM_APP + 3) +/** @} */ +#endif + + +/** @name DRVHOSTAUDIOWAS_DO_XXX - Worker thread operations. + * @{ */ +#define DRVHOSTAUDIOWAS_DO_PURGE_CACHE ((uintptr_t)0x49f37300 + 1) +#define DRVHOSTAUDIOWAS_DO_PRUNE_CACHE ((uintptr_t)0x49f37300 + 2) +#define DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH ((uintptr_t)0x49f37300 + 3) +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +class DrvHostAudioWasMmNotifyClient; + +/** Pointer to the cache entry for a host audio device (+dir). */ +typedef struct DRVHOSTAUDIOWASCACHEDEV *PDRVHOSTAUDIOWASCACHEDEV; + +/** + * Cached pre-initialized audio client for a device. + * + * The activation and initialization of an IAudioClient has been observed to be + * very very slow (> 100 ms) and not suitable to be done on an EMT. So, we'll + * pre-initialize the device clients at construction time and when the default + * device changes to try avoid this problem. + * + * A client is returned to the cache after we're done with it, provided it still + * works fine. + */ +typedef struct DRVHOSTAUDIOWASCACHEDEVCFG +{ + /** Entry in DRVHOSTAUDIOWASCACHEDEV::ConfigList. */ + RTLISTNODE ListEntry; + /** The device. */ + PDRVHOSTAUDIOWASCACHEDEV pDevEntry; + /** The cached audio client. */ + IAudioClient *pIAudioClient; + /** Output streams: The render client interface. */ + IAudioRenderClient *pIAudioRenderClient; + /** Input streams: The capture client interface. */ + IAudioCaptureClient *pIAudioCaptureClient; + /** The configuration. */ + PDMAUDIOPCMPROPS Props; + /** The buffer size in frames. */ + uint32_t cFramesBufferSize; + /** The device/whatever period in frames. */ + uint32_t cFramesPeriod; + /** The setup status code. + * This is set to VERR_AUDIO_STREAM_INIT_IN_PROGRESS while the asynchronous + * initialization is still running. */ + int volatile rcSetup; + /** Creation timestamp (just for reference). */ + uint64_t nsCreated; + /** Init complete timestamp (just for reference). */ + uint64_t nsInited; + /** When it was last used. */ + uint64_t nsLastUsed; + /** The stringified properties. */ + char szProps[32]; +} DRVHOSTAUDIOWASCACHEDEVCFG; +/** Pointer to a pre-initialized audio client. */ +typedef DRVHOSTAUDIOWASCACHEDEVCFG *PDRVHOSTAUDIOWASCACHEDEVCFG; + +/** + * Per audio device (+ direction) cache entry. + */ +typedef struct DRVHOSTAUDIOWASCACHEDEV +{ + /** Entry in DRVHOSTAUDIOWAS::CacheHead. */ + RTLISTNODE ListEntry; + /** The MM device associated with the stream. */ + IMMDevice *pIDevice; + /** The direction of the device. */ + PDMAUDIODIR enmDir; +#if 0 /* According to https://social.msdn.microsoft.com/Forums/en-US/1d974d90-6636-4121-bba3-a8861d9ab92a, + these were always support just missing from the SDK. */ + /** Support for AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM: -1=unknown, 0=no, 1=yes. */ + int8_t fSupportsAutoConvertPcm; + /** Support for AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY: -1=unknown, 0=no, 1=yes. */ + int8_t fSupportsSrcDefaultQuality; +#endif + /** List of cached configurations (DRVHOSTAUDIOWASCACHEDEVCFG). */ + RTLISTANCHOR ConfigList; + /** The device ID length in RTUTF16 units. */ + size_t cwcDevId; + /** The device ID. */ + RTUTF16 wszDevId[RT_FLEXIBLE_ARRAY]; +} DRVHOSTAUDIOWASCACHEDEV; + + +/** + * Data for a WASABI stream. + */ +typedef struct DRVHOSTAUDIOWASSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + + /** Entry in DRVHOSTAUDIOWAS::StreamHead. */ + RTLISTNODE ListEntry; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** Cache entry to be relased when destroying the stream. */ + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg; + + /** Set if the stream is enabled. */ + bool fEnabled; + /** Set if the stream is started (playing/capturing). */ + bool fStarted; + /** Set if the stream is draining (output only). */ + bool fDraining; + /** Set if we should restart the stream on resume (saved pause state). */ + bool fRestartOnResume; + /** Set if we're switching to a new output/input device. */ + bool fSwitchingDevice; + + /** The RTTimeMilliTS() deadline for the draining of this stream (output). */ + uint64_t msDrainDeadline; + /** Internal stream offset (bytes). */ + uint64_t offInternal; + /** The RTTimeMilliTS() at the end of the last transfer. */ + uint64_t msLastTransfer; + + /** Input: Current capture buffer (advanced as we read). */ + uint8_t *pbCapture; + /** Input: The number of bytes left in the current capture buffer. */ + uint32_t cbCapture; + /** Input: The full size of what pbCapture is part of (for ReleaseBuffer). */ + uint32_t cFramesCaptureToRelease; + + /** Critical section protecting: . */ + RTCRITSECT CritSect; + /** Buffer that drvHostWasStreamStatusString uses. */ + char szStatus[128]; +} DRVHOSTAUDIOWASSTREAM; +/** Pointer to a WASABI stream. */ +typedef DRVHOSTAUDIOWASSTREAM *PDRVHOSTAUDIOWASSTREAM; + + +/** + * WASAPI-specific device entry. + */ +typedef struct DRVHOSTAUDIOWASDEV +{ + /** The core structure. */ + PDMAUDIOHOSTDEV Core; + /** The device ID (flexible length). */ + RTUTF16 wszDevId[RT_FLEXIBLE_ARRAY]; +} DRVHOSTAUDIOWASDEV; +/** Pointer to a DirectSound device entry. */ +typedef DRVHOSTAUDIOWASDEV *PDRVHOSTAUDIOWASDEV; + + +/** + * Data for a WASAPI host audio instance. + */ +typedef struct DRVHOSTAUDIOWAS +{ + /** The audio host audio interface we export. */ + PDMIHOSTAUDIO IHostAudio; + /** Pointer to the PDM driver instance. */ + PPDMDRVINS pDrvIns; + /** Audio device enumerator instance that we use for getting the default + * devices (or specific ones if overriden by config). Also used for + * implementing enumeration. */ + IMMDeviceEnumerator *pIEnumerator; + /** The upwards interface. */ + PPDMIHOSTAUDIOPORT pIHostAudioPort; + /** The output device ID, NULL for default. + * Protected by DrvHostAudioWasMmNotifyClient::m_CritSect. */ + PRTUTF16 pwszOutputDevId; + /** The input device ID, NULL for default. + * Protected by DrvHostAudioWasMmNotifyClient::m_CritSect. */ + PRTUTF16 pwszInputDevId; + + /** Pointer to the MM notification client instance. */ + DrvHostAudioWasMmNotifyClient *pNotifyClient; + /** The input device to use. This can be NULL if there wasn't a suitable one + * around when we last looked or if it got removed/disabled/whatever. + * All access must be done inside the pNotifyClient critsect. */ + IMMDevice *pIDeviceInput; + /** The output device to use. This can be NULL if there wasn't a suitable one + * around when we last looked or if it got removed/disabled/whatever. + * All access must be done inside the pNotifyClient critsect. */ + IMMDevice *pIDeviceOutput; + + /** List of streams (DRVHOSTAUDIOWASSTREAM). + * Requires CritSect ownership. */ + RTLISTANCHOR StreamHead; + /** Serializing access to StreamHead. */ + RTCRITSECTRW CritSectStreamList; + + /** List of cached devices (DRVHOSTAUDIOWASCACHEDEV). + * Protected by CritSectCache */ + RTLISTANCHOR CacheHead; + /** Serializing access to CacheHead. */ + RTCRITSECT CritSectCache; + /** Semaphore for signalling that cache purge is done and that the destructor + * can do cleanups. */ + RTSEMEVENTMULTI hEvtCachePurge; + /** Total number of device config entire for capturing. + * This includes in-use ones. */ + uint32_t volatile cCacheEntriesIn; + /** Total number of device config entire for playback. + * This includes in-use ones. */ + uint32_t volatile cCacheEntriesOut; + +#if 0 + /** The worker thread. */ + RTTHREAD hWorkerThread; + /** The TID of the worker thread (for posting messages to it). */ + DWORD idWorkerThread; + /** The fixed wParam value for the worker thread. */ + WPARAM uWorkerThreadFixedParam; +#endif +} DRVHOSTAUDIOWAS; +/** Pointer to the data for a WASAPI host audio driver instance. */ +typedef DRVHOSTAUDIOWAS *PDRVHOSTAUDIOWAS; + + + + +/** + * Gets the stream status. + * + * @returns Pointer to stream status string. + * @param pStreamWas The stream to get the status for. + */ +static const char *drvHostWasStreamStatusString(PDRVHOSTAUDIOWASSTREAM pStreamWas) +{ + static RTSTRTUPLE const s_aEnable[2] = + { + { RT_STR_TUPLE("DISABLED") }, + { RT_STR_TUPLE("ENABLED ") }, + }; + PCRTSTRTUPLE pTuple = &s_aEnable[pStreamWas->fEnabled]; + memcpy(pStreamWas->szStatus, pTuple->psz, pTuple->cch); + size_t off = pTuple->cch; + + static RTSTRTUPLE const s_aStarted[2] = + { + { RT_STR_TUPLE(" STOPPED") }, + { RT_STR_TUPLE(" STARTED") }, + }; + pTuple = &s_aStarted[pStreamWas->fStarted]; + memcpy(&pStreamWas->szStatus[off], pTuple->psz, pTuple->cch); + off += pTuple->cch; + + static RTSTRTUPLE const s_aDraining[2] = + { + { RT_STR_TUPLE("") }, + { RT_STR_TUPLE(" DRAINING") }, + }; + pTuple = &s_aDraining[pStreamWas->fDraining]; + memcpy(&pStreamWas->szStatus[off], pTuple->psz, pTuple->cch); + off += pTuple->cch; + + Assert(off < sizeof(pStreamWas->szStatus)); + pStreamWas->szStatus[off] = '\0'; + return pStreamWas->szStatus; +} + + +/********************************************************************************************************************************* +* IMMNotificationClient implementation +*********************************************************************************************************************************/ +/** + * Multimedia notification client. + * + * We want to know when the default device changes so we can switch running + * streams to use the new one and so we can pre-activate it in preparation + * for new streams. + */ +class DrvHostAudioWasMmNotifyClient : public IMMNotificationClient +{ +private: + /** Reference counter. */ + uint32_t volatile m_cRefs; + /** The WASAPI host audio driver instance data. + * @note This can be NULL. Only access after entering critical section. */ + PDRVHOSTAUDIOWAS m_pDrvWas; + /** Critical section serializing access to m_pDrvWas. */ + RTCRITSECT m_CritSect; + +public: + /** + * @throws int on critical section init failure. + */ + DrvHostAudioWasMmNotifyClient(PDRVHOSTAUDIOWAS a_pDrvWas) + : m_cRefs(1) + , m_pDrvWas(a_pDrvWas) + { + int rc = RTCritSectInit(&m_CritSect); + AssertRCStmt(rc, throw(rc)); + } + + virtual ~DrvHostAudioWasMmNotifyClient() RT_NOEXCEPT + { + RTCritSectDelete(&m_CritSect); + } + + /** + * Called by drvHostAudioWasDestruct to set m_pDrvWas to NULL. + */ + void notifyDriverDestroyed() RT_NOEXCEPT + { + RTCritSectEnter(&m_CritSect); + m_pDrvWas = NULL; + RTCritSectLeave(&m_CritSect); + } + + /** + * Enters the notification critsect for getting at the IMMDevice members in + * PDMHOSTAUDIOWAS. + */ + void lockEnter() RT_NOEXCEPT + { + RTCritSectEnter(&m_CritSect); + } + + /** + * Leaves the notification critsect. + */ + void lockLeave() RT_NOEXCEPT + { + RTCritSectLeave(&m_CritSect); + } + + /** @name IUnknown interface + * @{ */ + IFACEMETHODIMP_(ULONG) AddRef() + { + uint32_t cRefs = ASMAtomicIncU32(&m_cRefs); + AssertMsg(cRefs < 64, ("%#x\n", cRefs)); + Log6Func(("returns %u\n", cRefs)); + return cRefs; + } + + IFACEMETHODIMP_(ULONG) Release() + { + uint32_t cRefs = ASMAtomicDecU32(&m_cRefs); + AssertMsg(cRefs < 64, ("%#x\n", cRefs)); + if (cRefs == 0) + delete this; + Log6Func(("returns %u\n", cRefs)); + return cRefs; + } + + IFACEMETHODIMP QueryInterface(const IID &rIID, void **ppvInterface) + { + if (IsEqualIID(rIID, IID_IUnknown)) + *ppvInterface = static_cast(this); + else if (IsEqualIID(rIID, __uuidof(IMMNotificationClient))) + *ppvInterface = static_cast(this); + else + { + LogFunc(("Unknown rIID={%RTuuid}\n", &rIID)); + *ppvInterface = NULL; + return E_NOINTERFACE; + } + Log6Func(("returns S_OK + %p\n", *ppvInterface)); + return S_OK; + } + /** @} */ + + /** @name IMMNotificationClient interface + * @{ */ + IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR pwszDeviceId, DWORD dwNewState) + { + RT_NOREF(pwszDeviceId, dwNewState); + Log7Func(("pwszDeviceId=%ls dwNewState=%u (%#x)\n", pwszDeviceId, dwNewState, dwNewState)); + + /* + * Just trigger device re-enumeration. + */ + notifyDeviceChanges(); + + /** @todo do we need to check for our devices here too? Not when using a + * default device. But when using a specific device, we could perhaps + * re-init the stream when dwNewState indicates precense. We might + * also take action when a devices ceases to be operating, but again + * only for non-default devices, probably... */ + + return S_OK; + } + + IFACEMETHODIMP OnDeviceAdded(LPCWSTR pwszDeviceId) + { + RT_NOREF(pwszDeviceId); + Log7Func(("pwszDeviceId=%ls\n", pwszDeviceId)); + + /* + * Is this a device we're interested in? Grab the enumerator if it is. + */ + bool fOutput = false; + IMMDeviceEnumerator *pIEnumerator = NULL; + RTCritSectEnter(&m_CritSect); + if ( m_pDrvWas != NULL + && ( (fOutput = RTUtf16ICmp(m_pDrvWas->pwszOutputDevId, pwszDeviceId) == 0) + || RTUtf16ICmp(m_pDrvWas->pwszInputDevId, pwszDeviceId) == 0)) + { + pIEnumerator = m_pDrvWas->pIEnumerator; + if (pIEnumerator /* paranoia */) + pIEnumerator->AddRef(); + } + RTCritSectLeave(&m_CritSect); + if (pIEnumerator) + { + /* + * Get the device and update it. + */ + IMMDevice *pIDevice = NULL; + HRESULT hrc = pIEnumerator->GetDevice(pwszDeviceId, &pIDevice); + if (SUCCEEDED(hrc)) + setDevice(fOutput, pIDevice, pwszDeviceId, __PRETTY_FUNCTION__); + else + LogRelMax(64, ("WasAPI: Failed to get %s device '%ls' (OnDeviceAdded): %Rhrc\n", + fOutput ? "output" : "input", pwszDeviceId, hrc)); + pIEnumerator->Release(); + + /* + * Trigger device re-enumeration. + */ + notifyDeviceChanges(); + } + return S_OK; + } + + IFACEMETHODIMP OnDeviceRemoved(LPCWSTR pwszDeviceId) + { + RT_NOREF(pwszDeviceId); + Log7Func(("pwszDeviceId=%ls\n", pwszDeviceId)); + + /* + * Is this a device we're interested in? Then set it to NULL. + */ + bool fOutput = false; + RTCritSectEnter(&m_CritSect); + if ( m_pDrvWas != NULL + && ( (fOutput = RTUtf16ICmp(m_pDrvWas->pwszOutputDevId, pwszDeviceId) == 0) + || RTUtf16ICmp(m_pDrvWas->pwszInputDevId, pwszDeviceId) == 0)) + { + RTCritSectLeave(&m_CritSect); + setDevice(fOutput, NULL, pwszDeviceId, __PRETTY_FUNCTION__); + } + else + RTCritSectLeave(&m_CritSect); + + /* + * Trigger device re-enumeration. + */ + notifyDeviceChanges(); + return S_OK; + } + + IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow enmFlow, ERole enmRole, LPCWSTR pwszDefaultDeviceId) + { + /* + * Are we interested in this device? If so grab the enumerator. + */ + IMMDeviceEnumerator *pIEnumerator = NULL; + RTCritSectEnter(&m_CritSect); + if ( m_pDrvWas != NULL + && ( (enmFlow == eRender && enmRole == eMultimedia && !m_pDrvWas->pwszOutputDevId) + || (enmFlow == eCapture && enmRole == eMultimedia && !m_pDrvWas->pwszInputDevId))) + { + pIEnumerator = m_pDrvWas->pIEnumerator; + if (pIEnumerator /* paranoia */) + pIEnumerator->AddRef(); + } + RTCritSectLeave(&m_CritSect); + if (pIEnumerator) + { + /* + * Get the device and update it. + */ + IMMDevice *pIDevice = NULL; + HRESULT hrc = pIEnumerator->GetDefaultAudioEndpoint(enmFlow, enmRole, &pIDevice); + if (SUCCEEDED(hrc)) + setDevice(enmFlow == eRender, pIDevice, pwszDefaultDeviceId, __PRETTY_FUNCTION__); + else + LogRelMax(64, ("WasAPI: Failed to get default %s device (OnDefaultDeviceChange): %Rhrc\n", + enmFlow == eRender ? "output" : "input", hrc)); + pIEnumerator->Release(); + + /* + * Trigger device re-enumeration. + */ + notifyDeviceChanges(); + } + + Log7Func(("enmFlow=%d enmRole=%d pwszDefaultDeviceId=%ls\n", enmFlow, enmRole, pwszDefaultDeviceId)); + return S_OK; + } + + IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR pwszDeviceId, const PROPERTYKEY Key) + { + RT_NOREF(pwszDeviceId, Key); + Log7Func(("pwszDeviceId=%ls Key={%RTuuid, %u (%#x)}\n", pwszDeviceId, &Key.fmtid, Key.pid, Key.pid)); + return S_OK; + } + /** @} */ + +private: + /** + * Sets DRVHOSTAUDIOWAS::pIDeviceOutput or DRVHOSTAUDIOWAS::pIDeviceInput to @a pIDevice. + */ + void setDevice(bool fOutput, IMMDevice *pIDevice, LPCWSTR pwszDeviceId, const char *pszCaller) + { + RT_NOREF(pszCaller, pwszDeviceId); + + RTCritSectEnter(&m_CritSect); + + /* + * Update our internal device reference. + */ + if (m_pDrvWas) + { + if (fOutput) + { + Log7((LOG_FN_FMT ": Changing output device from %p to %p (%ls)\n", + pszCaller, m_pDrvWas->pIDeviceOutput, pIDevice, pwszDeviceId)); + if (m_pDrvWas->pIDeviceOutput) + m_pDrvWas->pIDeviceOutput->Release(); + m_pDrvWas->pIDeviceOutput = pIDevice; + } + else + { + Log7((LOG_FN_FMT ": Changing input device from %p to %p (%ls)\n", + pszCaller, m_pDrvWas->pIDeviceInput, pIDevice, pwszDeviceId)); + if (m_pDrvWas->pIDeviceInput) + m_pDrvWas->pIDeviceInput->Release(); + m_pDrvWas->pIDeviceInput = pIDevice; + } + } + else if (pIDevice) + pIDevice->Release(); + + /* + * Tell DrvAudio that the device has changed for one of the directions. + * + * We have to exit the critsect when doing so, or we'll create a locking + * order violation. So, try make sure the VM won't be destroyed while + * till DrvAudio have entered its critical section... + */ + if (m_pDrvWas) + { + PPDMIHOSTAUDIOPORT const pIHostAudioPort = m_pDrvWas->pIHostAudioPort; + if (pIHostAudioPort) + { + VMSTATE const enmVmState = PDMDrvHlpVMState(m_pDrvWas->pDrvIns); + if (enmVmState < VMSTATE_POWERING_OFF) + { + RTCritSectLeave(&m_CritSect); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fOutput ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN, NULL); + return; + } + LogFlowFunc(("Ignoring change: enmVmState=%d\n", enmVmState)); + } + } + + RTCritSectLeave(&m_CritSect); + } + + /** + * Tell DrvAudio to re-enumerate devices when it get a chance. + * + * We exit the critsect here too before calling DrvAudio just to be on the safe + * side (see setDevice()), even though the current DrvAudio code doesn't take + * any critsects. + */ + void notifyDeviceChanges(void) + { + RTCritSectEnter(&m_CritSect); + if (m_pDrvWas) + { + PPDMIHOSTAUDIOPORT const pIHostAudioPort = m_pDrvWas->pIHostAudioPort; + if (pIHostAudioPort) + { + VMSTATE const enmVmState = PDMDrvHlpVMState(m_pDrvWas->pDrvIns); + if (enmVmState < VMSTATE_POWERING_OFF) + { + RTCritSectLeave(&m_CritSect); + pIHostAudioPort->pfnNotifyDevicesChanged(pIHostAudioPort); + return; + } + LogFlowFunc(("Ignoring change: enmVmState=%d\n", enmVmState)); + } + } + RTCritSectLeave(&m_CritSect); + } +}; + + +/********************************************************************************************************************************* +* Pre-configured audio client cache. * +*********************************************************************************************************************************/ +#define WAS_CACHE_MAX_ENTRIES_SAME_DEVICE 2 + +/** + * Converts from PDM stream config to windows WAVEFORMATEXTENSIBLE struct. + * + * @param pProps The PDM audio PCM properties to convert from. + * @param pFmt The windows structure to initialize. + */ +static void drvHostAudioWasWaveFmtExtFromProps(PCPDMAUDIOPCMPROPS pProps, PWAVEFORMATEXTENSIBLE pFmt) +{ + RT_ZERO(*pFmt); + pFmt->Format.wFormatTag = WAVE_FORMAT_PCM; + pFmt->Format.nChannels = PDMAudioPropsChannels(pProps); + pFmt->Format.wBitsPerSample = PDMAudioPropsSampleBits(pProps); + pFmt->Format.nSamplesPerSec = PDMAudioPropsHz(pProps); + pFmt->Format.nBlockAlign = PDMAudioPropsFrameSize(pProps); + pFmt->Format.nAvgBytesPerSec = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps)); + pFmt->Format.cbSize = 0; /* No extra data specified. */ + + /* + * We need to use the extensible structure if there are more than two channels + * or if the channels have non-standard assignments. + */ + if ( pFmt->Format.nChannels > 2 + || ( pFmt->Format.nChannels == 1 + ? pProps->aidChannels[0] != PDMAUDIOCHANNELID_MONO + : pProps->aidChannels[0] != PDMAUDIOCHANNELID_FRONT_LEFT + || pProps->aidChannels[1] != PDMAUDIOCHANNELID_FRONT_RIGHT)) + { + pFmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pFmt->Format.cbSize = sizeof(*pFmt) - sizeof(pFmt->Format); + pFmt->Samples.wValidBitsPerSample = PDMAudioPropsSampleBits(pProps); + pFmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + pFmt->dwChannelMask = 0; + unsigned const cSrcChannels = pFmt->Format.nChannels; + for (unsigned i = 0; i < cSrcChannels; i++) + if ( pProps->aidChannels[i] >= PDMAUDIOCHANNELID_FIRST_STANDARD + && pProps->aidChannels[i] < PDMAUDIOCHANNELID_END_STANDARD) + pFmt->dwChannelMask |= RT_BIT_32(pProps->aidChannels[i] - PDMAUDIOCHANNELID_FIRST_STANDARD); + else + pFmt->Format.nChannels -= 1; + } +} + + +#if 0 /* unused */ +/** + * Converts from windows WAVEFORMATEX and stream props to PDM audio properties. + * + * @returns VINF_SUCCESS on success, VERR_AUDIO_STREAM_COULD_NOT_CREATE if not + * supported. + * @param pProps The output properties structure. + * @param pFmt The windows wave format structure. + * @param pszStream The stream name for error logging. + * @param pwszDevId The device ID for error logging. + */ +static int drvHostAudioWasCacheWaveFmtExToProps(PPDMAUDIOPCMPROPS pProps, WAVEFORMATEX const *pFmt, + const char *pszStream, PCRTUTF16 pwszDevId) +{ + if (pFmt->wFormatTag == WAVE_FORMAT_PCM) + { + if ( pFmt->wBitsPerSample == 8 + || pFmt->wBitsPerSample == 16 + || pFmt->wBitsPerSample == 32) + { + if (pFmt->nChannels > 0 && pFmt->nChannels < 16) + { + if (pFmt->nSamplesPerSec >= 4096 && pFmt->nSamplesPerSec <= 768000) + { + PDMAudioPropsInit(pProps, pFmt->wBitsPerSample / 8, true /*fSigned*/, pFmt->nChannels, pFmt->nSamplesPerSec); + if (PDMAudioPropsFrameSize(pProps) == pFmt->nBlockAlign) + return VINF_SUCCESS; + } + } + } + } + LogRelMax(64, ("WasAPI: Error! Unsupported stream format for '%s' suggested by '%ls':\n" + "WasAPI: wFormatTag = %RU16 (expected %d)\n" + "WasAPI: nChannels = %RU16 (expected 1..15)\n" + "WasAPI: nSamplesPerSec = %RU32 (expected 4096..768000)\n" + "WasAPI: nAvgBytesPerSec = %RU32\n" + "WasAPI: nBlockAlign = %RU16\n" + "WasAPI: wBitsPerSample = %RU16 (expected 8, 16, or 32)\n" + "WasAPI: cbSize = %RU16\n", + pszStream, pwszDevId, pFmt->wFormatTag, WAVE_FORMAT_PCM, pFmt->nChannels, pFmt->nSamplesPerSec, pFmt->nAvgBytesPerSec, + pFmt->nBlockAlign, pFmt->wBitsPerSample, pFmt->cbSize)); + return VERR_AUDIO_STREAM_COULD_NOT_CREATE; +} +#endif + + +/** + * Destroys a devie config cache entry. + * + * @param pThis The WASAPI host audio driver instance data. + * @param pDevCfg Device config entry. Must not be in the list. + */ +static void drvHostAudioWasCacheDestroyDevConfig(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg) +{ + if (pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN) + ASMAtomicDecU32(&pThis->cCacheEntriesIn); + else + ASMAtomicDecU32(&pThis->cCacheEntriesOut); + + uint32_t cTypeClientRefs = 0; + if (pDevCfg->pIAudioCaptureClient) + { + cTypeClientRefs = pDevCfg->pIAudioCaptureClient->Release(); + pDevCfg->pIAudioCaptureClient = NULL; + } + + if (pDevCfg->pIAudioRenderClient) + { + cTypeClientRefs = pDevCfg->pIAudioRenderClient->Release(); + pDevCfg->pIAudioRenderClient = NULL; + } + + uint32_t cClientRefs = 0; + if (pDevCfg->pIAudioClient /* paranoia */) + { + cClientRefs = pDevCfg->pIAudioClient->Release(); + pDevCfg->pIAudioClient = NULL; + } + + Log8Func(("Destroying cache config entry: '%ls: %s' - cClientRefs=%u cTypeClientRefs=%u\n", + pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, cClientRefs, cTypeClientRefs)); + RT_NOREF(cClientRefs, cTypeClientRefs); + + pDevCfg->pDevEntry = NULL; + RTMemFree(pDevCfg); +} + + +/** + * Destroys a device cache entry. + * + * @param pThis The WASAPI host audio driver instance data. + * @param pDevEntry The device entry. Must not be in the cache! + */ +static void drvHostAudioWasCacheDestroyDevEntry(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEV pDevEntry) +{ + Log8Func(("Destroying cache entry: %p - '%ls'\n", pDevEntry, pDevEntry->wszDevId)); + + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg, pDevCfgNext; + RTListForEachSafe(&pDevEntry->ConfigList, pDevCfg, pDevCfgNext, DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry) + { + drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg); + } + + uint32_t cDevRefs = 0; + if (pDevEntry->pIDevice /* paranoia */) + { + cDevRefs = pDevEntry->pIDevice->Release(); + pDevEntry->pIDevice = NULL; + } + + pDevEntry->cwcDevId = 0; + pDevEntry->wszDevId[0] = '\0'; + RTMemFree(pDevEntry); + Log8Func(("Destroyed cache entry: %p cDevRefs=%u\n", pDevEntry, cDevRefs)); +} + + +/** + * Prunes the cache. + */ +static void drvHostAudioWasCachePrune(PDRVHOSTAUDIOWAS pThis) +{ + /* + * Prune each direction separately. + */ + struct + { + PDMAUDIODIR enmDir; + uint32_t volatile *pcEntries; + } aWork[] = { { PDMAUDIODIR_IN, &pThis->cCacheEntriesIn }, { PDMAUDIODIR_OUT, &pThis->cCacheEntriesOut }, }; + for (uint32_t iWork = 0; iWork < RT_ELEMENTS(aWork); iWork++) + { + /* + * Remove the least recently used entry till we're below the threshold + * or there are no more inactive entries. + */ + LogFlowFunc(("iWork=%u cEntries=%u\n", iWork, *aWork[iWork].pcEntries)); + while (*aWork[iWork].pcEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES) + { + RTCritSectEnter(&pThis->CritSectCache); + PDRVHOSTAUDIOWASCACHEDEVCFG pLeastRecentlyUsed = NULL; + PDRVHOSTAUDIOWASCACHEDEV pDevEntry; + RTListForEach(&pThis->CacheHead, pDevEntry, DRVHOSTAUDIOWASCACHEDEV, ListEntry) + { + if (pDevEntry->enmDir == aWork[iWork].enmDir) + { + PDRVHOSTAUDIOWASCACHEDEVCFG pHeadCfg = RTListGetFirst(&pDevEntry->ConfigList, + DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry); + if ( pHeadCfg + && (!pLeastRecentlyUsed || pHeadCfg->nsLastUsed < pLeastRecentlyUsed->nsLastUsed)) + pLeastRecentlyUsed = pHeadCfg; + } + } + if (pLeastRecentlyUsed) + RTListNodeRemove(&pLeastRecentlyUsed->ListEntry); + RTCritSectLeave(&pThis->CritSectCache); + + if (!pLeastRecentlyUsed) + break; + drvHostAudioWasCacheDestroyDevConfig(pThis, pLeastRecentlyUsed); + } + } +} + + +/** + * Purges all the entries in the cache. + */ +static void drvHostAudioWasCachePurge(PDRVHOSTAUDIOWAS pThis, bool fOnWorker) +{ + for (;;) + { + RTCritSectEnter(&pThis->CritSectCache); + PDRVHOSTAUDIOWASCACHEDEV pDevEntry = RTListRemoveFirst(&pThis->CacheHead, DRVHOSTAUDIOWASCACHEDEV, ListEntry); + RTCritSectLeave(&pThis->CritSectCache); + if (!pDevEntry) + break; + drvHostAudioWasCacheDestroyDevEntry(pThis, pDevEntry); + } + + if (fOnWorker) + { + int rc = RTSemEventMultiSignal(pThis->hEvtCachePurge); + AssertRC(rc); + } +} + + +/** + * Looks up a specific configuration. + * + * @returns Pointer to the device config (removed from cache) on success. NULL + * if no matching config found. + * @param pDevEntry Where to perform the lookup. + * @param pProps The config properties to match. + */ +static PDRVHOSTAUDIOWASCACHEDEVCFG +drvHostAudioWasCacheLookupLocked(PDRVHOSTAUDIOWASCACHEDEV pDevEntry, PCPDMAUDIOPCMPROPS pProps) +{ + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg; + RTListForEach(&pDevEntry->ConfigList, pDevCfg, DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry) + { + if (PDMAudioPropsAreEqual(&pDevCfg->Props, pProps)) + { + RTListNodeRemove(&pDevCfg->ListEntry); + pDevCfg->nsLastUsed = RTTimeNanoTS(); + return pDevCfg; + } + } + return NULL; +} + + +/** + * Initializes a device config entry. + * + * This is usually done on the worker thread. + * + * @returns VBox status code. + * @param pDevCfg The device configuration entry to initialize. + */ +static int drvHostAudioWasCacheInitConfig(PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg) +{ + /* + * Assert some sanity given that we migth be called on the worker thread + * and pDevCfg being a message parameter. + */ + AssertPtrReturn(pDevCfg, VERR_INTERNAL_ERROR_2); + AssertReturn(pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS, VERR_INTERNAL_ERROR_2); + AssertReturn(pDevCfg->pIAudioClient == NULL, VERR_INTERNAL_ERROR_2); + AssertReturn(pDevCfg->pIAudioCaptureClient == NULL, VERR_INTERNAL_ERROR_2); + AssertReturn(pDevCfg->pIAudioRenderClient == NULL, VERR_INTERNAL_ERROR_2); + AssertReturn(PDMAudioPropsAreValid(&pDevCfg->Props), VERR_INTERNAL_ERROR_2); + + PDRVHOSTAUDIOWASCACHEDEV pDevEntry = pDevCfg->pDevEntry; + AssertPtrReturn(pDevEntry, VERR_INTERNAL_ERROR_2); + AssertPtrReturn(pDevEntry->pIDevice, VERR_INTERNAL_ERROR_2); + AssertReturn(pDevEntry->enmDir == PDMAUDIODIR_IN || pDevEntry->enmDir == PDMAUDIODIR_OUT, VERR_INTERNAL_ERROR_2); + + /* + * First we need an IAudioClient interface for calling IsFormatSupported + * on so we can get guidance as to what to do next. + * + * Initially, I thought the AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM was not + * supported all the way back to Vista and that we'd had to try different + * things here to get the most optimal format. However, according to + * https://social.msdn.microsoft.com/Forums/en-US/1d974d90-6636-4121-bba3-a8861d9ab92a + * it is supported, just maybe missing from the SDK or something... + * + * I'll leave the IsFormatSupported call here as it gives us a clue as to + * what exactly the WAS needs to convert our audio stream into/from. + */ + Log8Func(("Activating an IAudioClient for '%ls' ...\n", pDevEntry->wszDevId)); + IAudioClient *pIAudioClient = NULL; + HRESULT hrc = pDevEntry->pIDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, + NULL /*pActivationParams*/, (void **)&pIAudioClient); + Log8Func(("Activate('%ls', IAudioClient) -> %Rhrc\n", pDevEntry->wszDevId, hrc)); + if (FAILED(hrc)) + { + LogRelMax(64, ("WasAPI: Activate(%ls, IAudioClient) failed: %Rhrc\n", pDevEntry->wszDevId, hrc)); + pDevCfg->nsInited = RTTimeNanoTS(); + pDevCfg->nsLastUsed = pDevCfg->nsInited; + return pDevCfg->rcSetup = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + WAVEFORMATEXTENSIBLE WaveFmtExt; + drvHostAudioWasWaveFmtExtFromProps(&pDevCfg->Props, &WaveFmtExt); + + PWAVEFORMATEX pClosestMatch = NULL; + hrc = pIAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &WaveFmtExt.Format, &pClosestMatch); + + /* + * If the format is supported, go ahead and initialize the client instance. + * + * The docs talks about AUDCLNT_E_UNSUPPORTED_FORMAT being success too, but + * that doesn't seem to be the case (at least not for mixing up the + * WAVEFORMATEX::wFormatTag values). Seems that is the standard return code + * if there is anything it doesn't grok. + */ + if (SUCCEEDED(hrc)) + { + if (hrc == S_OK) + Log8Func(("IsFormatSupported(,%s,) -> S_OK + %p: requested format is supported\n", pDevCfg->szProps, pClosestMatch)); + else + Log8Func(("IsFormatSupported(,%s,) -> %Rhrc + %p: %uch S%u %uHz\n", pDevCfg->szProps, hrc, pClosestMatch, + pClosestMatch ? pClosestMatch->nChannels : 0, pClosestMatch ? pClosestMatch->wBitsPerSample : 0, + pClosestMatch ? pClosestMatch->nSamplesPerSec : 0)); + + REFERENCE_TIME const cBufferSizeInNtTicks = PDMAudioPropsFramesToNtTicks(&pDevCfg->Props, pDevCfg->cFramesBufferSize); + uint32_t fInitFlags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM + | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY; + hrc = pIAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, fInitFlags, cBufferSizeInNtTicks, + 0 /*cPeriodicityInNtTicks*/, &WaveFmtExt.Format, NULL /*pAudioSessionGuid*/); + Log8Func(("Initialize(,%x, %RI64, %s,) -> %Rhrc\n", fInitFlags, cBufferSizeInNtTicks, pDevCfg->szProps, hrc)); + if (SUCCEEDED(hrc)) + { + /* + * The direction specific client interface. + */ + if (pDevEntry->enmDir == PDMAUDIODIR_IN) + hrc = pIAudioClient->GetService(__uuidof(IAudioCaptureClient), (void **)&pDevCfg->pIAudioCaptureClient); + else + hrc = pIAudioClient->GetService(__uuidof(IAudioRenderClient), (void **)&pDevCfg->pIAudioRenderClient); + Log8Func(("GetService -> %Rhrc + %p\n", hrc, pDevEntry->enmDir == PDMAUDIODIR_IN + ? (void *)pDevCfg->pIAudioCaptureClient : (void *)pDevCfg->pIAudioRenderClient)); + if (SUCCEEDED(hrc)) + { + /* + * Obtain the actual stream format and buffer config. + */ + UINT32 cFramesBufferSize = 0; + REFERENCE_TIME cDefaultPeriodInNtTicks = 0; + REFERENCE_TIME cMinimumPeriodInNtTicks = 0; + REFERENCE_TIME cLatencyinNtTicks = 0; + hrc = pIAudioClient->GetBufferSize(&cFramesBufferSize); + if (SUCCEEDED(hrc)) + { + hrc = pIAudioClient->GetDevicePeriod(&cDefaultPeriodInNtTicks, &cMinimumPeriodInNtTicks); + if (SUCCEEDED(hrc)) + { + hrc = pIAudioClient->GetStreamLatency(&cLatencyinNtTicks); + if (SUCCEEDED(hrc)) + { + LogRel2(("WasAPI: Aquired buffer parameters for %s:\n" + "WasAPI: cFramesBufferSize = %RU32\n" + "WasAPI: cDefaultPeriodInNtTicks = %RI64\n" + "WasAPI: cMinimumPeriodInNtTicks = %RI64\n" + "WasAPI: cLatencyinNtTicks = %RI64\n", + pDevCfg->szProps, cFramesBufferSize, cDefaultPeriodInNtTicks, + cMinimumPeriodInNtTicks, cLatencyinNtTicks)); + + pDevCfg->pIAudioClient = pIAudioClient; + pDevCfg->cFramesBufferSize = cFramesBufferSize; + pDevCfg->cFramesPeriod = PDMAudioPropsNanoToFrames(&pDevCfg->Props, + cDefaultPeriodInNtTicks * 100); + pDevCfg->nsInited = RTTimeNanoTS(); + pDevCfg->nsLastUsed = pDevCfg->nsInited; + pDevCfg->rcSetup = VINF_SUCCESS; + + if (pClosestMatch) + CoTaskMemFree(pClosestMatch); + Log8Func(("returns VINF_SUCCESS (%p (%s) inited in %'RU64 ns)\n", + pDevCfg, pDevCfg->szProps, pDevCfg->nsInited - pDevCfg->nsCreated)); + return VINF_SUCCESS; + } + LogRelMax(64, ("WasAPI: GetStreamLatency failed: %Rhrc\n", hrc)); + } + else + LogRelMax(64, ("WasAPI: GetDevicePeriod failed: %Rhrc\n", hrc)); + } + else + LogRelMax(64, ("WasAPI: GetBufferSize failed: %Rhrc\n", hrc)); + + if (pDevCfg->pIAudioCaptureClient) + { + pDevCfg->pIAudioCaptureClient->Release(); + pDevCfg->pIAudioCaptureClient = NULL; + } + + if (pDevCfg->pIAudioRenderClient) + { + pDevCfg->pIAudioRenderClient->Release(); + pDevCfg->pIAudioRenderClient = NULL; + } + } + else + LogRelMax(64, ("WasAPI: IAudioClient::GetService(%s) failed: %Rhrc\n", pDevCfg->szProps, hrc)); + } + else + LogRelMax(64, ("WasAPI: IAudioClient::Initialize(%s) failed: %Rhrc\n", pDevCfg->szProps, hrc)); + } + else + LogRelMax(64,("WasAPI: IAudioClient::IsFormatSupported(,%s,) failed: %Rhrc\n", pDevCfg->szProps, hrc)); + + pIAudioClient->Release(); + if (pClosestMatch) + CoTaskMemFree(pClosestMatch); + pDevCfg->nsInited = RTTimeNanoTS(); + pDevCfg->nsLastUsed = 0; + Log8Func(("returns VERR_AUDIO_STREAM_COULD_NOT_CREATE (inited in %'RU64 ns)\n", pDevCfg->nsInited - pDevCfg->nsCreated)); + return pDevCfg->rcSetup = VERR_AUDIO_STREAM_COULD_NOT_CREATE; +} + + +/** + * Worker for drvHostAudioWasCacheLookupOrCreate. + * + * If lookup fails, a new entry will be created. + * + * @note Called holding the lock, returning without holding it! + */ +static int drvHostAudioWasCacheLookupOrCreateConfig(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEV pDevEntry, + PCPDMAUDIOSTREAMCFG pCfgReq, bool fOnWorker, + PDRVHOSTAUDIOWASCACHEDEVCFG *ppDevCfg) +{ + /* + * Check if we've got a matching config. + */ + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = drvHostAudioWasCacheLookupLocked(pDevEntry, &pCfgReq->Props); + if (pDevCfg) + { + *ppDevCfg = pDevCfg; + RTCritSectLeave(&pThis->CritSectCache); + Log8Func(("Config cache hit '%s' on '%ls': %p\n", pDevCfg->szProps, pDevEntry->wszDevId, pDevCfg)); + return VINF_SUCCESS; + } + + RTCritSectLeave(&pThis->CritSectCache); + + /* + * Allocate an device config entry and hand the creation task over to the + * worker thread, unless we're already on it. + */ + pDevCfg = (PDRVHOSTAUDIOWASCACHEDEVCFG)RTMemAllocZ(sizeof(*pDevCfg)); + AssertReturn(pDevCfg, VERR_NO_MEMORY); + RTListInit(&pDevCfg->ListEntry); + pDevCfg->pDevEntry = pDevEntry; + pDevCfg->rcSetup = VERR_AUDIO_STREAM_INIT_IN_PROGRESS; + pDevCfg->Props = pCfgReq->Props; + pDevCfg->cFramesBufferSize = pCfgReq->Backend.cFramesBufferSize; + PDMAudioPropsToString(&pDevCfg->Props, pDevCfg->szProps, sizeof(pDevCfg->szProps)); + pDevCfg->nsCreated = RTTimeNanoTS(); + pDevCfg->nsLastUsed = pDevCfg->nsCreated; + + uint32_t cCacheEntries; + if (pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN) + cCacheEntries = ASMAtomicIncU32(&pThis->cCacheEntriesIn); + else + cCacheEntries = ASMAtomicIncU32(&pThis->cCacheEntriesOut); + if (cCacheEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES) + { + LogFlowFunc(("Trigger cache pruning.\n")); + int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/, + DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/); + AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis)); + } + + if (!fOnWorker) + { + *ppDevCfg = pDevCfg; + LogFlowFunc(("Doing the rest of the work on %p via pfnStreamInitAsync...\n", pDevCfg)); + return VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED; + } + + /* + * Initialize the entry on the calling thread. + */ + int rc = drvHostAudioWasCacheInitConfig(pDevCfg); + AssertRC(pDevCfg->rcSetup == rc); + if (RT_SUCCESS(rc)) + rc = pDevCfg->rcSetup; /* paranoia */ + if (RT_SUCCESS(rc)) + { + *ppDevCfg = pDevCfg; + LogFlowFunc(("Returning %p\n", pDevCfg)); + return VINF_SUCCESS; + } + RTMemFree(pDevCfg); + *ppDevCfg = NULL; + return rc; +} + + +/** + * Looks up the given device + config combo in the cache, creating a new entry + * if missing. + * + * @returns VBox status code. + * @retval VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED if @a fOnWorker is @c false and + * we created a new entry that needs initalization by calling + * drvHostAudioWasCacheInitConfig() on it. + * @param pThis The WASAPI host audio driver instance data. + * @param pIDevice The device to look up. + * @param pCfgReq The configuration to look up. + * @param fOnWorker Set if we're on a worker thread, otherwise false. When + * set to @c true, VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED will + * not be returned and a new entry will be fully + * initialized before returning. + * @param ppDevCfg Where to return the requested device config. + */ +static int drvHostAudioWasCacheLookupOrCreate(PDRVHOSTAUDIOWAS pThis, IMMDevice *pIDevice, PCPDMAUDIOSTREAMCFG pCfgReq, + bool fOnWorker, PDRVHOSTAUDIOWASCACHEDEVCFG *ppDevCfg) +{ + *ppDevCfg = NULL; + + /* + * Get the device ID so we can perform the lookup. + */ + int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + LPWSTR pwszDevId = NULL; + HRESULT hrc = pIDevice->GetId(&pwszDevId); + if (SUCCEEDED(hrc)) + { + size_t cwcDevId = RTUtf16Len(pwszDevId); + + /* + * The cache has two levels, so first the device entry. + */ + PDRVHOSTAUDIOWASCACHEDEV pDevEntry; + RTCritSectEnter(&pThis->CritSectCache); + RTListForEach(&pThis->CacheHead, pDevEntry, DRVHOSTAUDIOWASCACHEDEV, ListEntry) + { + if ( pDevEntry->cwcDevId == cwcDevId + && pDevEntry->enmDir == pCfgReq->enmDir + && RTUtf16Cmp(pDevEntry->wszDevId, pwszDevId) == 0) + { + CoTaskMemFree(pwszDevId); + Log8Func(("Cache hit for device '%ls': %p\n", pDevEntry->wszDevId, pDevEntry)); + return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry, pCfgReq, fOnWorker, ppDevCfg); + } + } + RTCritSectLeave(&pThis->CritSectCache); + + /* + * Device not in the cache, add it. + */ + pDevEntry = (PDRVHOSTAUDIOWASCACHEDEV)RTMemAllocZVar(RT_UOFFSETOF_DYN(DRVHOSTAUDIOWASCACHEDEV, wszDevId[cwcDevId + 1])); + if (pDevEntry) + { + pIDevice->AddRef(); + pDevEntry->pIDevice = pIDevice; + pDevEntry->enmDir = pCfgReq->enmDir; + pDevEntry->cwcDevId = cwcDevId; +#if 0 + pDevEntry->fSupportsAutoConvertPcm = -1; + pDevEntry->fSupportsSrcDefaultQuality = -1; +#endif + RTListInit(&pDevEntry->ConfigList); + memcpy(pDevEntry->wszDevId, pwszDevId, cwcDevId * sizeof(RTUTF16)); + pDevEntry->wszDevId[cwcDevId] = '\0'; + + CoTaskMemFree(pwszDevId); + pwszDevId = NULL; + + /* + * Before adding the device, check that someone didn't race us adding it. + */ + RTCritSectEnter(&pThis->CritSectCache); + PDRVHOSTAUDIOWASCACHEDEV pDevEntry2; + RTListForEach(&pThis->CacheHead, pDevEntry2, DRVHOSTAUDIOWASCACHEDEV, ListEntry) + { + if ( pDevEntry2->cwcDevId == cwcDevId + && pDevEntry2->enmDir == pCfgReq->enmDir + && RTUtf16Cmp(pDevEntry2->wszDevId, pDevEntry->wszDevId) == 0) + { + pIDevice->Release(); + RTMemFree(pDevEntry); + pDevEntry = NULL; + + Log8Func(("Lost race adding device '%ls': %p\n", pDevEntry2->wszDevId, pDevEntry2)); + return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry2, pCfgReq, fOnWorker, ppDevCfg); + } + } + RTListPrepend(&pThis->CacheHead, &pDevEntry->ListEntry); + + Log8Func(("Added device '%ls' to cache: %p\n", pDevEntry->wszDevId, pDevEntry)); + return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry, pCfgReq, fOnWorker, ppDevCfg); + } + CoTaskMemFree(pwszDevId); + } + else + LogRelMax(64, ("WasAPI: GetId failed (lookup): %Rhrc\n", hrc)); + return rc; +} + + +/** + * Return the given config to the cache. + * + * @param pThis The WASAPI host audio driver instance data. + * @param pDevCfg The device config to put back. + */ +static void drvHostAudioWasCachePutBack(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg) +{ + /* + * Reset the audio client to see that it works and to make sure it's in a sensible state. + */ + HRESULT hrc = pDevCfg->pIAudioClient ? pDevCfg->pIAudioClient->Reset() + : pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS ? S_OK : E_FAIL; + if (SUCCEEDED(hrc)) + { + Log8Func(("Putting %p/'%s' back\n", pDevCfg, pDevCfg->szProps)); + RTCritSectEnter(&pThis->CritSectCache); + RTListAppend(&pDevCfg->pDevEntry->ConfigList, &pDevCfg->ListEntry); + uint32_t const cEntries = pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN ? pThis->cCacheEntriesIn : pThis->cCacheEntriesOut; + RTCritSectLeave(&pThis->CritSectCache); + + /* Trigger pruning if we're over the threshold. */ + if (cEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES) + { + LogFlowFunc(("Trigger cache pruning.\n")); + int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/, + DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/); + AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis)); + } + } + else + { + Log8Func(("IAudioClient::Reset failed (%Rhrc) on %p/'%s', destroying it.\n", hrc, pDevCfg, pDevCfg->szProps)); + drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg); + } +} + + +static void drvHostWasCacheConfigHinting(PDRVHOSTAUDIOWAS pThis, PPDMAUDIOSTREAMCFG pCfgReq, bool fOnWorker) +{ + /* + * Get the device. + */ + pThis->pNotifyClient->lockEnter(); + IMMDevice *pIDevice = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput; + if (pIDevice) + pIDevice->AddRef(); + pThis->pNotifyClient->lockLeave(); + if (pIDevice) + { + /* + * Look up the config and put it back. + */ + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL; + int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, pCfgReq, fOnWorker, &pDevCfg); + LogFlowFunc(("pDevCfg=%p rc=%Rrc\n", pDevCfg, rc)); + if (pDevCfg && RT_SUCCESS(rc)) + drvHostAudioWasCachePutBack(pThis, pDevCfg); + pIDevice->Release(); + } +} + + +/** + * Prefills the cache. + * + * @param pThis The WASAPI host audio driver instance data. + */ +static void drvHostAudioWasCacheFill(PDRVHOSTAUDIOWAS pThis) +{ +#if 0 /* we don't have the buffer config nor do we really know which frequences to expect */ + Log8Func(("enter\n")); + struct + { + PCRTUTF16 pwszDevId; + PDMAUDIODIR enmDir; + } aToCache[] = + { + { pThis->pwszInputDevId, PDMAUDIODIR_IN }, + { pThis->pwszOutputDevId, PDMAUDIODIR_OUT } + }; + for (unsigned i = 0; i < RT_ELEMENTS(aToCache); i++) + { + PCRTUTF16 pwszDevId = aToCache[i].pwszDevId; + IMMDevice *pIDevice = NULL; + HRESULT hrc; + if (pwszDevId) + hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice); + else + { + hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(aToCache[i].enmDir == PDMAUDIODIR_IN ? eCapture : eRender, + eMultimedia, &pIDevice); + pwszDevId = aToCache[i].enmDir == PDMAUDIODIR_IN ? L"{Default-In}" : L"{Default-Out}"; + } + if (SUCCEEDED(hrc)) + { + PDMAUDIOSTREAMCFG Cfg = { aToCache[i].enmDir, { PDMAUDIOPLAYBACKDST_INVALID }, + PDMAUDIOPCMPROPS_INITIALIZER(2, true, 2, 44100, false) }; + Cfg.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&Cfg.Props, 300); + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, &Cfg); + if (pDevCfg) + drvHostAudioWasCachePutBack(pThis, pDevCfg); + + pIDevice->Release(); + } + else + LogRelMax(64, ("WasAPI: Failed to open audio device '%ls' (pre-caching): %Rhrc\n", pwszDevId, hrc)); + } + Log8Func(("leave\n")); +#else + RT_NOREF(pThis); +#endif +} + + +/********************************************************************************************************************************* +* Worker thread * +*********************************************************************************************************************************/ +#if 0 + +/** + * @callback_method_impl{FNRTTHREAD, + * Asynchronous thread for setting up audio client configs.} + */ +static DECLCALLBACK(int) drvHostWasWorkerThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PDRVHOSTAUDIOWAS pThis = (PDRVHOSTAUDIOWAS)pvUser; + + /* + * We need to set the thread ID so others can post us thread messages. + * And before we signal that we're ready, make sure we've got a message queue. + */ + pThis->idWorkerThread = GetCurrentThreadId(); + LogFunc(("idWorkerThread=%#x (%u)\n", pThis->idWorkerThread, pThis->idWorkerThread)); + + MSG Msg; + PeekMessageW(&Msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); + + int rc = RTThreadUserSignal(hThreadSelf); + AssertRC(rc); + + /* + * Message loop. + */ + BOOL fRet; + while ((fRet = GetMessageW(&Msg, NULL, 0, 0)) != FALSE) + { + if (fRet != -1) + { + TranslateMessage(&Msg); + Log9Func(("Msg: time=%u: msg=%#x l=%p w=%p for hwnd=%p\n", Msg.time, Msg.message, Msg.lParam, Msg.wParam, Msg.hwnd)); + switch (Msg.message) + { + case WM_DRVHOSTAUDIOWAS_PURGE_CACHE: + { + AssertMsgBreak(Msg.wParam == pThis->uWorkerThreadFixedParam, ("%p\n", Msg.wParam)); + AssertBreak(Msg.hwnd == NULL); + AssertBreak(Msg.lParam == 0); + + drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/); + break; + } + + default: + break; + } + DispatchMessageW(&Msg); + } + else + AssertMsgFailed(("GetLastError()=%u\n", GetLastError())); + } + + LogFlowFunc(("Pre-quit cache purge...\n")); + drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/); + + LogFunc(("Quits\n")); + return VINF_SUCCESS; +} +#endif + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "WasAPI"); + pBackendCfg->cbStream = sizeof(DRVHOSTAUDIOWASSTREAM); + pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_HINT; + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + + return VINF_SUCCESS; +} + + +/** + * Queries information for @a pDevice and adds an entry to the enumeration. + * + * @returns VBox status code. + * @param pDevEnm The enumeration to add the device to. + * @param pIDevice The device. + * @param enmType The type of device. + * @param fDefault Whether it's the default device. + */ +static int drvHostWasEnumAddDev(PPDMAUDIOHOSTENUM pDevEnm, IMMDevice *pIDevice, EDataFlow enmType, bool fDefault) +{ + int rc = VINF_SUCCESS; /* ignore most errors */ + RT_NOREF(fDefault); /** @todo default device marking/skipping. */ + + /* + * Gather the necessary properties. + */ + IPropertyStore *pProperties = NULL; + HRESULT hrc = pIDevice->OpenPropertyStore(STGM_READ, &pProperties); + if (SUCCEEDED(hrc)) + { + /* Get the friendly name (string). */ + PROPVARIANT VarName; + PropVariantInit(&VarName); + hrc = pProperties->GetValue(PKEY_Device_FriendlyName, &VarName); + if (SUCCEEDED(hrc)) + { + /* Get the device ID (string). */ + LPWSTR pwszDevId = NULL; + hrc = pIDevice->GetId(&pwszDevId); + if (SUCCEEDED(hrc)) + { + size_t const cwcDevId = RTUtf16Len(pwszDevId); + + /* Get the device format (blob). */ + PROPVARIANT VarFormat; + PropVariantInit(&VarFormat); + hrc = pProperties->GetValue(PKEY_AudioEngine_DeviceFormat, &VarFormat); + if (SUCCEEDED(hrc)) + { + WAVEFORMATEX const * const pFormat = (WAVEFORMATEX const *)VarFormat.blob.pBlobData; + AssertPtr(pFormat); /* Observed sometimes being NULL on windows 7 sp1. */ + + /* + * Create a enumeration entry for it. + */ + size_t const cbId = RTUtf16CalcUtf8Len(pwszDevId) + 1; + size_t const cbName = RTUtf16CalcUtf8Len(VarName.pwszVal) + 1; + size_t const cbDev = RT_ALIGN_Z( RT_OFFSETOF(DRVHOSTAUDIOWASDEV, wszDevId) + + (cwcDevId + 1) * sizeof(RTUTF16), + 64); + PDRVHOSTAUDIOWASDEV pDev = (PDRVHOSTAUDIOWASDEV)PDMAudioHostDevAlloc(cbDev, cbName, cbId); + if (pDev) + { + pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN; + pDev->Core.enmUsage = enmType == eRender ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN; + if (fDefault) + pDev->Core.fFlags = enmType == eRender ? PDMAUDIOHOSTDEV_F_DEFAULT_OUT : PDMAUDIOHOSTDEV_F_DEFAULT_IN; + if (enmType == eRender) + pDev->Core.cMaxOutputChannels = RT_VALID_PTR(pFormat) ? pFormat->nChannels : 2; + else + pDev->Core.cMaxInputChannels = RT_VALID_PTR(pFormat) ? pFormat->nChannels : 1; + + memcpy(pDev->wszDevId, pwszDevId, cwcDevId * sizeof(RTUTF16)); + pDev->wszDevId[cwcDevId] = '\0'; + + Assert(pDev->Core.pszName); + rc = RTUtf16ToUtf8Ex(VarName.pwszVal, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL); + if (RT_SUCCESS(rc)) + { + Assert(pDev->Core.pszId); + rc = RTUtf16ToUtf8Ex(pDev->wszDevId, RTSTR_MAX, &pDev->Core.pszId, cbId, NULL); + if (RT_SUCCESS(rc)) + PDMAudioHostEnumAppend(pDevEnm, &pDev->Core); + else + PDMAudioHostDevFree(&pDev->Core); + } + else + PDMAudioHostDevFree(&pDev->Core); + } + else + rc = VERR_NO_MEMORY; + PropVariantClear(&VarFormat); + } + else + LogFunc(("Failed to get PKEY_AudioEngine_DeviceFormat: %Rhrc\n", hrc)); + CoTaskMemFree(pwszDevId); + } + else + LogFunc(("Failed to get the device ID: %Rhrc\n", hrc)); + PropVariantClear(&VarName); + } + else + LogFunc(("Failed to get PKEY_Device_FriendlyName: %Rhrc\n", hrc)); + pProperties->Release(); + } + else + LogFunc(("OpenPropertyStore failed: %Rhrc\n", hrc)); + + if (hrc == E_OUTOFMEMORY && RT_SUCCESS_NP(rc)) + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Does a (Re-)enumeration of the host's playback + capturing devices. + * + * @return VBox status code. + * @param pThis The WASAPI host audio driver instance data. + * @param pDevEnm Where to store the enumerated devices. + */ +static int drvHostWasEnumerateDevices(PDRVHOSTAUDIOWAS pThis, PPDMAUDIOHOSTENUM pDevEnm) +{ + LogRel2(("WasAPI: Enumerating devices ...\n")); + + int rc = VINF_SUCCESS; + for (unsigned idxPass = 0; idxPass < 2 && RT_SUCCESS(rc); idxPass++) + { + EDataFlow const enmType = idxPass == 0 ? /*EDataFlow::*/eRender : /*EDataFlow::*/eCapture; + + /* Get the default device first. */ + IMMDevice *pIDefaultDevice = NULL; + HRESULT hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(enmType, eMultimedia, &pIDefaultDevice); + if (SUCCEEDED(hrc)) + rc = drvHostWasEnumAddDev(pDevEnm, pIDefaultDevice, enmType, true); + else + pIDefaultDevice = NULL; + + /* Enumerate the devices. */ + IMMDeviceCollection *pCollection = NULL; + hrc = pThis->pIEnumerator->EnumAudioEndpoints(enmType, DEVICE_STATE_ACTIVE /*| DEVICE_STATE_UNPLUGGED?*/, &pCollection); + if (SUCCEEDED(hrc) && pCollection != NULL) + { + UINT cDevices = 0; + hrc = pCollection->GetCount(&cDevices); + if (SUCCEEDED(hrc)) + { + for (UINT idxDevice = 0; idxDevice < cDevices && RT_SUCCESS(rc); idxDevice++) + { + IMMDevice *pIDevice = NULL; + hrc = pCollection->Item(idxDevice, &pIDevice); + if (SUCCEEDED(hrc) && pIDevice) + { + if (pIDevice != pIDefaultDevice) + rc = drvHostWasEnumAddDev(pDevEnm, pIDevice, enmType, false); + pIDevice->Release(); + } + } + } + pCollection->Release(); + } + else + LogRelMax(10, ("EnumAudioEndpoints(%s) failed: %Rhrc\n", idxPass == 0 ? "output" : "input", hrc)); + + if (pIDefaultDevice) + pIDefaultDevice->Release(); + } + + LogRel2(("WasAPI: Enumerating devices done - %u device (%Rrc)\n", pDevEnm->cDevices, rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); + + PDMAudioHostEnumInit(pDeviceEnum); + int rc = drvHostWasEnumerateDevices(pThis, pDeviceEnum); + if (RT_FAILURE(rc)) + PDMAudioHostEnumDelete(pDeviceEnum); + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +/** + * Worker for drvHostAudioWasHA_SetDevice. + */ +static int drvHostAudioWasSetDeviceWorker(PDRVHOSTAUDIOWAS pThis, const char *pszId, PRTUTF16 *ppwszDevId, IMMDevice **ppIDevice, + EDataFlow enmFlow, PDMAUDIODIR enmDir, const char *pszWhat) +{ + pThis->pNotifyClient->lockEnter(); + + /* + * Did anything actually change? + */ + if ( (pszId == NULL) != (*ppwszDevId == NULL) + || ( pszId + && RTUtf16ICmpUtf8(*ppwszDevId, pszId) != 0)) + { + /* + * Duplicate the ID. + */ + PRTUTF16 pwszDevId = NULL; + if (pszId) + { + int rc = RTStrToUtf16(pszId, &pwszDevId); + AssertRCReturnStmt(rc, pThis->pNotifyClient->lockLeave(), rc); + } + + /* + * Try get the device. + */ + IMMDevice *pIDevice = NULL; + HRESULT hrc; + if (pwszDevId) + hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice); + else + hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(enmFlow, eMultimedia, &pIDevice); + LogFlowFunc(("Got device %p (%Rhrc)\n", pIDevice, hrc)); + if (FAILED(hrc)) + { + LogRel(("WasAPI: Failed to get IMMDevice for %s audio device '%s' (SetDevice): %Rhrc\n", + pszWhat, pszId ? pszId : "{default}", hrc)); + pIDevice = NULL; + } + + /* + * Make the switch. + */ + LogRel(("PulseAudio: Changing %s device: '%ls' -> '%s'\n", + pszWhat, *ppwszDevId ? *ppwszDevId : L"{Default}", pszId ? pszId : "{Default}")); + + if (*ppIDevice) + (*ppIDevice)->Release(); + *ppIDevice = pIDevice; + + RTUtf16Free(*ppwszDevId); + *ppwszDevId = pwszDevId; + + /* + * Only notify the driver above us. + */ + PPDMIHOSTAUDIOPORT const pIHostAudioPort = pThis->pIHostAudioPort; + pThis->pNotifyClient->lockLeave(); + + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about %s device change...\n", pszWhat)); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, enmDir, NULL); + } + } + else + { + pThis->pNotifyClient->lockLeave(); + LogFunc(("No %s device change\n", pszWhat)); + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + + /* + * Validate and normalize input. + */ + AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pszId, VERR_INVALID_POINTER); + if (!pszId || !*pszId) + pszId = NULL; + else + AssertReturn(strlen(pszId) < 1024, VERR_INVALID_NAME); + LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId)); + + /* + * Do the updating. + */ + if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX) + { + int rc = drvHostAudioWasSetDeviceWorker(pThis, pszId, &pThis->pwszInputDevId, &pThis->pIDeviceInput, + eCapture, PDMAUDIODIR_IN, "input"); + AssertRCReturn(rc, rc); + } + + if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX) + { + int rc = drvHostAudioWasSetDeviceWorker(pThis, pszId, &pThis->pwszOutputDevId, &pThis->pIDeviceOutput, + eRender, PDMAUDIODIR_OUT, "output"); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioWasHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * Performs the actual switching of device config. + * + * Worker for drvHostAudioWasDoStreamDevSwitch() and + * drvHostAudioWasHA_StreamNotifyDeviceChanged(). + */ +static void drvHostAudioWasCompleteStreamDevSwitch(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas, + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg) +{ + RTCritSectEnter(&pStreamWas->CritSect); + + /* Do the switch. */ + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfgOld = pStreamWas->pDevCfg; + pStreamWas->pDevCfg = pDevCfg; + + /* The new stream is neither started nor draining. */ + pStreamWas->fStarted = false; + pStreamWas->fDraining = false; + + /* Device switching is done now. */ + pStreamWas->fSwitchingDevice = false; + + /* Stop the old stream or Reset() will fail when putting it back into the cache. */ + if (pStreamWas->fEnabled && pDevCfgOld->pIAudioClient) + pDevCfgOld->pIAudioClient->Stop(); + + RTCritSectLeave(&pStreamWas->CritSect); + + /* Notify DrvAudio. */ + pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, false /*fReInit*/); + + /* Put the old config back into the cache. */ + drvHostAudioWasCachePutBack(pThis, pDevCfgOld); + + LogFlowFunc(("returns with '%s' state: %s\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) )); +} + + +/** + * Called on a worker thread to initialize a new device config and switch the + * given stream to using it. + * + * @sa drvHostAudioWasHA_StreamNotifyDeviceChanged + */ +static void drvHostAudioWasDoStreamDevSwitch(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas, + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg) +{ + /* + * Do the initializing. + */ + int rc = drvHostAudioWasCacheInitConfig(pDevCfg); + if (RT_SUCCESS(rc)) + drvHostAudioWasCompleteStreamDevSwitch(pThis, pStreamWas, pDevCfg); + else + { + LogRelMax(64, ("WasAPI: Failed to set up new device config '%ls:%s' for stream '%s': %Rrc\n", + pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc)); + drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg); + pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/); + } +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnDoOnWorkerThread} + */ +static DECLCALLBACK(void) drvHostAudioWasHA_DoOnWorkerThread(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + uintptr_t uUser, void *pvUser) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + LogFlowFunc(("uUser=%#zx pStream=%p pvUser=%p\n", uUser, pStream, pvUser)); + + switch (uUser) + { + case DRVHOSTAUDIOWAS_DO_PURGE_CACHE: + Assert(pStream == NULL); + Assert(pvUser == NULL); + drvHostAudioWasCachePurge(pThis, true /*fOnWorker*/); + break; + + case DRVHOSTAUDIOWAS_DO_PRUNE_CACHE: + Assert(pStream == NULL); + Assert(pvUser == NULL); + drvHostAudioWasCachePrune(pThis); + break; + + case DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH: + AssertPtr(pStream); + AssertPtr(pvUser); + drvHostAudioWasDoStreamDevSwitch(pThis, (PDRVHOSTAUDIOWASSTREAM)pStream, (PDRVHOSTAUDIOWASCACHEDEVCFG)pvUser); + break; + + default: + AssertMsgFailedBreak(("%#zx\n", uUser)); + } +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamConfigHint} + * + * @note This is called on a DrvAudio worker thread. + */ +static DECLCALLBACK(void) drvHostAudioWasHA_StreamConfigHint(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAMCFG pCfg) +{ +#if 0 /* disable to test async stream creation. */ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + LogFlowFunc(("pCfg=%p\n", pCfg)); + + drvHostWasCacheConfigHinting(pThis, pCfg); +#else + RT_NOREF(pInterface, pCfg); +#endif +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq)); + + const char * const pszStreamType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "capture" : "playback"; RT_NOREF(pszStreamType); + LogFlowFunc(("enmPath=%s '%s'\n", PDMAudioPathGetName(pCfgReq->enmPath), pCfgReq->szName)); +#if defined(RTLOG_REL_ENABLED) || defined(LOG_ENABLED) + char szTmp[64]; +#endif + LogRel2(("WasAPI: Opening %s stream '%s' (%s)\n", pCfgReq->szName, pszStreamType, + PDMAudioPropsToString(&pCfgReq->Props, szTmp, sizeof(szTmp)))); + + RTListInit(&pStreamWas->ListEntry); + + /* + * Do configuration conversion. + */ + WAVEFORMATEXTENSIBLE WaveFmtExt; + drvHostAudioWasWaveFmtExtFromProps(&pCfgReq->Props, &WaveFmtExt); + LogRel2(("WasAPI: Requested %s format for '%s':\n" + "WasAPI: wFormatTag = %#RX16\n" + "WasAPI: nChannels = %RU16\n" + "WasAPI: nSamplesPerSec = %RU32\n" + "WasAPI: nAvgBytesPerSec = %RU32\n" + "WasAPI: nBlockAlign = %RU16\n" + "WasAPI: wBitsPerSample = %RU16\n" + "WasAPI: cbSize = %RU16\n" + "WasAPI: cBufferSizeInNtTicks = %RU64\n", + pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels, + WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign, + WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize, + PDMAudioPropsFramesToNtTicks(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize) )); + if (WaveFmtExt.Format.cbSize != 0) + LogRel2(("WasAPI: dwChannelMask = %#RX32\n" + "WasAPI: wValidBitsPerSample = %RU16\n", + WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample)); + + /* Set up the acquired format here as channel count + layout may have + changed and need to be communicated to caller and used in cache lookup. */ + *pCfgAcq = *pCfgReq; + if (WaveFmtExt.Format.cbSize != 0) + { + PDMAudioPropsSetChannels(&pCfgAcq->Props, WaveFmtExt.Format.nChannels); + uint8_t idCh = 0; + for (unsigned iBit = 0; iBit < 32 && idCh < WaveFmtExt.Format.nChannels; iBit++) + if (WaveFmtExt.dwChannelMask & RT_BIT_32(iBit)) + { + pCfgAcq->Props.aidChannels[idCh] = (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD + iBit; + idCh++; + } + Assert(idCh == WaveFmtExt.Format.nChannels); + } + + /* + * Get the device we're supposed to use. + * (We cache this as it takes ~2ms to get the default device on a random W10 19042 system.) + */ + pThis->pNotifyClient->lockEnter(); + IMMDevice *pIDevice = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput; + if (pIDevice) + pIDevice->AddRef(); + pThis->pNotifyClient->lockLeave(); + + PRTUTF16 pwszDevId = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pwszInputDevId : pThis->pwszOutputDevId; + PRTUTF16 const pwszDevIdDesc = pwszDevId ? pwszDevId : pCfgReq->enmDir == PDMAUDIODIR_IN ? L"{Default-In}" : L"{Default-Out}"; + if (!pIDevice) + { + /* This might not strictly be necessary anymore, however it shouldn't + hurt and may be useful when using specific devices. */ + HRESULT hrc; + if (pwszDevId) + hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice); + else + hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(pCfgReq->enmDir == PDMAUDIODIR_IN ? eCapture : eRender, + eMultimedia, &pIDevice); + LogFlowFunc(("Got device %p (%Rhrc)\n", pIDevice, hrc)); + if (FAILED(hrc)) + { + LogRelMax(64, ("WasAPI: Failed to open audio %s device '%ls': %Rhrc\n", pszStreamType, pwszDevIdDesc, hrc)); + return VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + } + + /* + * Ask the cache to retrieve or instantiate the requested configuration. + */ + /** @todo make it return a status code too and retry if the default device + * was invalidated/changed while we where working on it here. */ + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL; + int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, pCfgAcq, false /*fOnWorker*/, &pDevCfg); + + pIDevice->Release(); + pIDevice = NULL; + + if (pDevCfg && RT_SUCCESS(rc)) + { + pStreamWas->pDevCfg = pDevCfg; + + pCfgAcq->Props = pDevCfg->Props; + pCfgAcq->Backend.cFramesBufferSize = pDevCfg->cFramesBufferSize; + pCfgAcq->Backend.cFramesPeriod = pDevCfg->cFramesPeriod; + pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pDevCfg->cFramesBufferSize + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + + PDMAudioStrmCfgCopy(&pStreamWas->Cfg, pCfgAcq); + + /* Finally, the critical section. */ + int rc2 = RTCritSectInit(&pStreamWas->CritSect); + if (RT_SUCCESS(rc2)) + { + RTCritSectRwEnterExcl(&pThis->CritSectStreamList); + RTListAppend(&pThis->StreamHead, &pStreamWas->ListEntry); + RTCritSectRwLeaveExcl(&pThis->CritSectStreamList); + + if (pStreamWas->pDevCfg->pIAudioClient != NULL) + { + LogFlowFunc(("returns VINF_SUCCESS\n", rc)); + return VINF_SUCCESS; + } + LogFlowFunc(("returns VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED\n", rc)); + return VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED; + } + + LogRelMax(64, ("WasAPI: Failed to create critical section for stream.\n")); + drvHostAudioWasCachePutBack(pThis, pDevCfg); + pStreamWas->pDevCfg = NULL; + } + else + LogRelMax(64, ("WasAPI: Failed to setup %s on audio device '%ls' (%Rrc).\n", pszStreamType, pwszDevIdDesc, rc)); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamInitAsync} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamInitAsync(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fDestroyed) +{ + RT_NOREF(pInterface); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER); + LogFlowFunc(("Stream '%s'%s\n", pStreamWas->Cfg.szName, fDestroyed ? " - destroyed!" : "")); + + /* + * Assert sane preconditions for this call. + */ + AssertPtrReturn(pStreamWas->Core.pStream, VERR_INTERNAL_ERROR); + AssertPtrReturn(pStreamWas->pDevCfg, VERR_INTERNAL_ERROR_2); + AssertPtrReturn(pStreamWas->pDevCfg->pDevEntry, VERR_INTERNAL_ERROR_3); + AssertPtrReturn(pStreamWas->pDevCfg->pDevEntry->pIDevice, VERR_INTERNAL_ERROR_4); + AssertReturn(pStreamWas->pDevCfg->pDevEntry->enmDir == pStreamWas->Core.pStream->Cfg.enmDir, VERR_INTERNAL_ERROR_4); + AssertReturn(pStreamWas->pDevCfg->pIAudioClient == NULL, VERR_INTERNAL_ERROR_5); + AssertReturn(pStreamWas->pDevCfg->pIAudioRenderClient == NULL, VERR_INTERNAL_ERROR_5); + AssertReturn(pStreamWas->pDevCfg->pIAudioCaptureClient == NULL, VERR_INTERNAL_ERROR_5); + + /* + * Do the job. + */ + int rc; + if (!fDestroyed) + rc = drvHostAudioWasCacheInitConfig(pStreamWas->pDevCfg); + else + { + AssertReturn(pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS, VERR_INTERNAL_ERROR_2); + pStreamWas->pDevCfg->rcSetup = VERR_WRONG_ORDER; + rc = VINF_SUCCESS; + } + + LogFlowFunc(("returns %Rrc (%s)\n", rc, pStreamWas->Cfg.szName)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fImmediate) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER); + LogFlowFunc(("Stream '%s'\n", pStreamWas->Cfg.szName)); + RT_NOREF(fImmediate); + HRESULT hrc; + + if (RTCritSectIsInitialized(&pStreamWas->CritSect)) + { + RTCritSectRwEnterExcl(&pThis->CritSectStreamList); + RTListNodeRemove(&pStreamWas->ListEntry); + RTCritSectRwLeaveExcl(&pThis->CritSectStreamList); + + RTCritSectDelete(&pStreamWas->CritSect); + } + + if (pStreamWas->fStarted && pStreamWas->pDevCfg && pStreamWas->pDevCfg->pIAudioClient) + { + hrc = pStreamWas->pDevCfg->pIAudioClient->Stop(); + LogFunc(("Stop('%s') -> %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + pStreamWas->fStarted = false; + } + + if (pStreamWas->cFramesCaptureToRelease) + { + hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(0); + Log4Func(("Releasing capture buffer (%#x frames): %Rhrc\n", pStreamWas->cFramesCaptureToRelease, hrc)); + pStreamWas->cFramesCaptureToRelease = 0; + pStreamWas->pbCapture = NULL; + pStreamWas->cbCapture = 0; + } + + if (pStreamWas->pDevCfg) + { + drvHostAudioWasCachePutBack(pThis, pStreamWas->pDevCfg); + pStreamWas->pDevCfg = NULL; + } + + LogFlowFunc(("returns\n")); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamNotifyDeviceChanged} + */ +static DECLCALLBACK(void) drvHostAudioWasHA_StreamNotifyDeviceChanged(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, void *pvUser) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + LogFlowFunc(("pStreamWas=%p (%s)\n", pStreamWas, pStreamWas->Cfg.szName)); + RT_NOREF(pvUser); + + /* + * See if we've got a cached config for the new device around. + * We ignore this entirely, for now at least, if the device was + * disconnected and there is no replacement. + */ + pThis->pNotifyClient->lockEnter(); + IMMDevice *pIDevice = pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput; + if (pIDevice) + pIDevice->AddRef(); + pThis->pNotifyClient->lockLeave(); + if (pIDevice) + { + PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL; + int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, &pStreamWas->Cfg, false /*fOnWorker*/, &pDevCfg); + + pIDevice->Release(); + pIDevice = NULL; + + /* + * If we have a working audio client, just do the switch. + */ + if (RT_SUCCESS(rc) && pDevCfg->pIAudioClient) + { + LogFlowFunc(("New device config is ready already!\n")); + Assert(rc == VINF_SUCCESS); + drvHostAudioWasCompleteStreamDevSwitch(pThis, pStreamWas, pDevCfg); + } + /* + * Otherwise create one asynchronously on a worker thread. + */ + else if (RT_SUCCESS(rc)) + { + LogFlowFunc(("New device config needs async init ...\n")); + Assert(rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED); + + RTCritSectEnter(&pStreamWas->CritSect); + pStreamWas->fSwitchingDevice = true; + RTCritSectLeave(&pStreamWas->CritSect); + + pThis->pIHostAudioPort->pfnStreamNotifyPreparingDeviceSwitch(pThis->pIHostAudioPort, &pStreamWas->Core); + + rc = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, &pStreamWas->Core, + DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH, pDevCfg); + AssertRCStmt(rc, drvHostAudioWasDoStreamDevSwitch(pThis, pStreamWas, pDevCfg)); + } + else + { + LogRelMax(64, ("WasAPI: Failed to create new device config '%ls:%s' for stream '%s': %Rrc\n", + pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc)); + + pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/); + } + } + else + LogFlowFunc(("no new device, leaving it as-is\n")); +} + + +/** + * Wrapper for starting a stream. + * + * @returns VBox status code. + * @param pThis The WASAPI host audio driver instance data. + * @param pStreamWas The stream. + * @param pszOperation The operation we're doing. + */ +static int drvHostAudioWasStreamStartWorker(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas, const char *pszOperation) +{ + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Start(); + LogFlow(("%s: Start(%s) returns %Rhrc\n", pszOperation, pStreamWas->Cfg.szName, hrc)); + AssertStmt(hrc != AUDCLNT_E_NOT_STOPPED, hrc = S_OK); + if (SUCCEEDED(hrc)) + { + pStreamWas->fStarted = true; + return VINF_SUCCESS; + } + + /** @todo try re-setup the stuff on AUDCLNT_E_DEVICEINVALIDATED. + * Need some way of telling the caller (e.g. playback, capture) so they can + * retry what they're doing */ + RT_NOREF(pThis); + + pStreamWas->fStarted = false; + LogRelMax(64, ("WasAPI: Starting '%s' failed (%s): %Rhrc\n", pStreamWas->Cfg.szName, pszOperation, hrc)); + return VERR_AUDIO_STREAM_NOT_READY; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas))); + HRESULT hrc; + RTCritSectEnter(&pStreamWas->CritSect); + + Assert(!pStreamWas->fEnabled); + Assert(!pStreamWas->fStarted); + + /* + * We always reset the buffer before enabling the stream (normally never necessary). + */ + if (pStreamWas->cFramesCaptureToRelease) + { + hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(pStreamWas->cFramesCaptureToRelease); + Log4Func(("Releasing capture buffer (%#x frames): %Rhrc\n", pStreamWas->cFramesCaptureToRelease, hrc)); + pStreamWas->cFramesCaptureToRelease = 0; + pStreamWas->pbCapture = NULL; + pStreamWas->cbCapture = 0; + } + + hrc = pStreamWas->pDevCfg->pIAudioClient->Reset(); + if (FAILED(hrc)) + LogRelMax(64, ("WasAPI: Stream reset failed when enabling '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + pStreamWas->offInternal = 0; + pStreamWas->fDraining = false; + pStreamWas->fEnabled = true; + pStreamWas->fRestartOnResume = false; + + /* + * Input streams will start capturing, while output streams will only start + * playing once we get some audio data to play. + */ + int rc = VINF_SUCCESS; + if (pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN) + rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "enable"); + else + Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT); + + RTCritSectLeave(&pStreamWas->CritSect); + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1, + pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) )); + RTCritSectEnter(&pStreamWas->CritSect); + + /* + * Always try stop it (draining or no). + */ + pStreamWas->fEnabled = false; + pStreamWas->fRestartOnResume = false; + Assert(!pStreamWas->fDraining || pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT); + + int rc = VINF_SUCCESS; + if (pStreamWas->fStarted) + { + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop(); + LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + if (FAILED(hrc)) + { + LogRelMax(64, ("WasAPI: Stopping '%s' failed (disable): %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + rc = VERR_GENERAL_FAILURE; + } + pStreamWas->fStarted = false; + pStreamWas->fDraining = false; + } + + RTCritSectLeave(&pStreamWas->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + * + * @note Basically the same as drvHostAudioWasHA_StreamDisable, just w/o the + * buffer resetting and fEnabled change. + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1, + pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) )); + RTCritSectEnter(&pStreamWas->CritSect); + + /* + * Unless we're draining the stream, stop it if it's started. + */ + int rc = VINF_SUCCESS; + if (pStreamWas->fStarted && !pStreamWas->fDraining) + { + pStreamWas->fRestartOnResume = true; + + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop(); + LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + if (FAILED(hrc)) + { + LogRelMax(64, ("WasAPI: Stopping '%s' failed (pause): %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + rc = VERR_GENERAL_FAILURE; + } + pStreamWas->fStarted = false; + } + else + { + pStreamWas->fRestartOnResume = false; + if (pStreamWas->fDraining) + { + LogFunc(("Stream '%s' is draining\n", pStreamWas->Cfg.szName)); + Assert(pStreamWas->fStarted); + } + } + + RTCritSectLeave(&pStreamWas->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas))); + RTCritSectEnter(&pStreamWas->CritSect); + + /* + * Resume according to state saved by drvHostAudioWasHA_StreamPause. + */ + int rc; + if (pStreamWas->fRestartOnResume) + rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "resume"); + else + rc = VINF_SUCCESS; + pStreamWas->fRestartOnResume = false; + + RTCritSectLeave(&pStreamWas->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1, + pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) )); + + /* + * If the stram was started, calculate when the buffered data has finished + * playing and switch to drain mode. DrvAudio will keep on calling + * pfnStreamPlay with an empty buffer while we're draining, so we'll use + * that for checking the deadline and finally stopping the stream. + */ + RTCritSectEnter(&pStreamWas->CritSect); + int rc = VINF_SUCCESS; + if (pStreamWas->fStarted) + { + if (!pStreamWas->fDraining) + { + uint64_t const msNow = RTTimeMilliTS(); + uint64_t msDrainDeadline = 0; + UINT32 cFramesPending = 0; + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending); + if (SUCCEEDED(hrc)) + msDrainDeadline = msNow + + PDMAudioPropsFramesToMilli(&pStreamWas->Cfg.Props, + RT_MIN(cFramesPending, + pStreamWas->Cfg.Backend.cFramesBufferSize * 2)) + + 1 /*fudge*/; + else + { + msDrainDeadline = msNow; + LogRelMax(64, ("WasAPI: GetCurrentPadding fail on '%s' when starting draining: %Rhrc\n", + pStreamWas->Cfg.szName, hrc)); + } + pStreamWas->msDrainDeadline = msDrainDeadline; + pStreamWas->fDraining = true; + } + else + LogFlowFunc(("Already draining '%s' ...\n", pStreamWas->Cfg.szName)); + } + else + { + LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamWas->Cfg.szName)); + AssertStmt(!pStreamWas->fDraining, pStreamWas->fDraining = false); + } + RTCritSectLeave(&pStreamWas->CritSect); + + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAudioWasHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, PDMHOSTAUDIOSTREAMSTATE_INVALID); + + PDMHOSTAUDIOSTREAMSTATE enmState; + AssertPtr(pStreamWas->pDevCfg); + if (pStreamWas->pDevCfg /*paranoia*/) + { + if (RT_SUCCESS(pStreamWas->pDevCfg->rcSetup)) + { + if (!pStreamWas->fDraining) + enmState = PDMHOSTAUDIOSTREAMSTATE_OKAY; + else + { + Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT); + enmState = PDMHOSTAUDIOSTREAMSTATE_DRAINING; + } + } + else if ( pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS + || pStreamWas->fSwitchingDevice ) + enmState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING; + else + enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; + } + else if (pStreamWas->fSwitchingDevice) + enmState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING; + else + enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; + + LogFlowFunc(("returns %d for '%s' {%s}\n", enmState, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas))); + return enmState; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} + */ +static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, 0); + LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas))); + AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, 0); + + uint32_t cbPending = 0; + RTCritSectEnter(&pStreamWas->CritSect); + + if ( pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT + && pStreamWas->pDevCfg->pIAudioClient /* paranoia */) + { + if (pStreamWas->fStarted) + { + UINT32 cFramesPending = 0; + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending); + if (SUCCEEDED(hrc)) + { + AssertMsg(cFramesPending <= pStreamWas->Cfg.Backend.cFramesBufferSize, + ("cFramesPending=%#x cFramesBufferSize=%#x\n", + cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize)); + cbPending = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, RT_MIN(cFramesPending, VBOX_WASAPI_MAX_PADDING)); + } + else + LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + } + } + + RTCritSectLeave(&pStreamWas->CritSect); + + LogFlowFunc(("returns %#x (%u) {%s}\n", cbPending, cbPending, drvHostWasStreamStatusString(pStreamWas))); + return cbPending; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, 0); + LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas))); + Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT); + + uint32_t cbWritable = 0; + RTCritSectEnter(&pStreamWas->CritSect); + + if ( pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT + && pStreamWas->pDevCfg->pIAudioClient /* paranoia */) + { + UINT32 cFramesPending = 0; + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending); + if (SUCCEEDED(hrc)) + { + if (cFramesPending < pStreamWas->Cfg.Backend.cFramesBufferSize) + cbWritable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, + pStreamWas->Cfg.Backend.cFramesBufferSize - cFramesPending); + else if (cFramesPending > pStreamWas->Cfg.Backend.cFramesBufferSize) + { + LogRelMax(64, ("WasAPI: Warning! GetCurrentPadding('%s') return too high: cFramesPending=%#x > cFramesBufferSize=%#x\n", + pStreamWas->Cfg.szName, cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize)); + AssertMsgFailed(("cFramesPending=%#x > cFramesBufferSize=%#x\n", + cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize)); + } + } + else + LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + } + + RTCritSectLeave(&pStreamWas->CritSect); + + LogFlowFunc(("returns %#x (%u) {%s}\n", cbWritable, cbWritable, drvHostWasStreamStatusString(pStreamWas))); + return cbWritable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf)); + + RTCritSectEnter(&pStreamWas->CritSect); + if (pStreamWas->fEnabled) + { /* likely */ } + else + { + RTCritSectLeave(&pStreamWas->CritSect); + *pcbWritten = 0; + LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHostWasStreamStatusString(pStreamWas))); + return VINF_SUCCESS; + } + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) )); + + /* + * Transfer loop. + */ + int rc = VINF_SUCCESS; + uint32_t cReInits = 0; + uint32_t cbWritten = 0; + while (cbBuf > 0) + { + AssertBreakStmt(pStreamWas->pDevCfg && pStreamWas->pDevCfg->pIAudioRenderClient && pStreamWas->pDevCfg->pIAudioClient, + rc = VERR_AUDIO_STREAM_NOT_READY); + + /* + * Figure out how much we can possibly write. + */ + UINT32 cFramesPending = 0; + uint32_t cbWritable = 0; + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending); + if (SUCCEEDED(hrc)) + cbWritable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, + pStreamWas->Cfg.Backend.cFramesBufferSize + - RT_MIN(cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize)); + else + { + LogRelMax(64, ("WasAPI: GetCurrentPadding(%s) failed during playback: %Rhrc (@%#RX64)\n", + pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal)); + /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */ + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + if (cbWritable <= PDMAudioPropsFrameSize(&pStreamWas->Cfg.Props)) + break; + + uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamWas->Cfg.Props, RT_MIN(cbWritable, cbBuf)); + uint32_t const cFramesToWrite = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, cbToWrite); + Assert(PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesToWrite) == cbToWrite); + Log3Func(("@%#RX64: cFramesPending=%#x -> cbWritable=%#x cbToWrite=%#x cFramesToWrite=%#x {%s}\n", + pStreamWas->offInternal, cFramesPending, cbWritable, cbToWrite, cFramesToWrite, + drvHostWasStreamStatusString(pStreamWas) )); + + /* + * Get the buffer, copy the data into it, and relase it back to the WAS machinery. + */ + BYTE *pbData = NULL; + hrc = pStreamWas->pDevCfg->pIAudioRenderClient->GetBuffer(cFramesToWrite, &pbData); + if (SUCCEEDED(hrc)) + { + memcpy(pbData, pvBuf, cbToWrite); + hrc = pStreamWas->pDevCfg->pIAudioRenderClient->ReleaseBuffer(cFramesToWrite, 0 /*fFlags*/); + if (SUCCEEDED(hrc)) + { + /* + * Before we advance the buffer position (so we can resubmit it + * after re-init), make sure we've successfully started stream. + */ + if (pStreamWas->fStarted) + { } + else + { + rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "play"); + if (rc == VINF_SUCCESS) + { /* likely */ } + else if (RT_SUCCESS(rc) && ++cReInits < 5) + continue; /* re-submit buffer after re-init */ + else + break; + } + + /* advance. */ + pvBuf = (uint8_t *)pvBuf + cbToWrite; + cbBuf -= cbToWrite; + cbWritten += cbToWrite; + pStreamWas->offInternal += cbToWrite; + } + else + { + LogRelMax(64, ("WasAPI: ReleaseBuffer(%#x) failed on '%s' during playback: %Rhrc (@%#RX64)\n", + cFramesToWrite, pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal)); + /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */ + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + } + else + { + LogRelMax(64, ("WasAPI: GetBuffer(%#x) failed on '%s' during playback: %Rhrc (@%#RX64)\n", + cFramesToWrite, pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal)); + /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */ + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + } + + /* + * Do draining deadline processing. + */ + uint64_t const msNow = RTTimeMilliTS(); + if ( !pStreamWas->fDraining + || msNow < pStreamWas->msDrainDeadline) + { /* likely */ } + else + { + LogRel2(("WasAPI: Stopping draining of '%s' {%s} ...\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas))); + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop(); + if (FAILED(hrc)) + LogRelMax(64, ("WasAPI: Failed to stop draining stream '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + pStreamWas->fDraining = false; + pStreamWas->fStarted = false; + pStreamWas->fEnabled = false; + } + + /* + * Done. + */ + uint64_t const msPrev = pStreamWas->msLastTransfer; RT_NOREF(msPrev); + if (cbWritten) + pStreamWas->msLastTransfer = msNow; + + RTCritSectLeave(&pStreamWas->CritSect); + + *pcbWritten = cbWritten; + if (RT_SUCCESS(rc) || !cbWritten) + { } + else + { + LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten)); + rc = VINF_SUCCESS; + } + LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamWas->offInternal, rc, cbWritten, + msPrev ? msNow - msPrev : 0, msPrev, pStreamWas->msLastTransfer, drvHostWasStreamStatusString(pStreamWas) )); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, 0); + Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN); + + uint32_t cbReadable = 0; + RTCritSectEnter(&pStreamWas->CritSect); + + if (pStreamWas->pDevCfg->pIAudioCaptureClient /* paranoia */) + { + UINT32 cFramesPending = 0; + HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending); + if (SUCCEEDED(hrc)) + { + /* An unreleased buffer is included in the pending frame count, so subtract + whatever we've got hanging around since the previous pfnStreamCapture call. */ + AssertMsgStmt(cFramesPending >= pStreamWas->cFramesCaptureToRelease, + ("%#x vs %#x\n", cFramesPending, pStreamWas->cFramesCaptureToRelease), + cFramesPending = pStreamWas->cFramesCaptureToRelease); + cFramesPending -= pStreamWas->cFramesCaptureToRelease; + + /* Add what we've got left in said buffer. */ + uint32_t cFramesCurPacket = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, pStreamWas->cbCapture); + cFramesPending += cFramesCurPacket; + + /* Paranoia: Make sure we don't exceed the buffer size. */ + AssertMsgStmt(cFramesPending <= pStreamWas->Cfg.Backend.cFramesBufferSize, + ("cFramesPending=%#x cFramesCaptureToRelease=%#x cFramesCurPacket=%#x cFramesBufferSize=%#x\n", + cFramesPending, pStreamWas->cFramesCaptureToRelease, cFramesCurPacket, + pStreamWas->Cfg.Backend.cFramesBufferSize), + cFramesPending = pStreamWas->Cfg.Backend.cFramesBufferSize); + + cbReadable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesPending); + } + else + LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc)); + } + + RTCritSectLeave(&pStreamWas->CritSect); + + LogFlowFunc(("returns %#x (%u) {%s}\n", cbReadable, cbReadable, drvHostWasStreamStatusString(pStreamWas))); + return cbReadable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostAudioWasHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface); //PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio); + PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream; + AssertPtrReturn(pStreamWas, 0); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf)); + + RTCritSectEnter(&pStreamWas->CritSect); + if (pStreamWas->fEnabled) + { /* likely */ } + else + { + RTCritSectLeave(&pStreamWas->CritSect); + *pcbRead = 0; + LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHostWasStreamStatusString(pStreamWas))); + return VINF_SUCCESS; + } + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) )); + + + /* + * Transfer loop. + */ + int rc = VINF_SUCCESS; + uint32_t cbRead = 0; + uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamWas->Cfg.Props); + while (cbBuf >= cbFrame) + { + AssertBreakStmt(pStreamWas->pDevCfg->pIAudioCaptureClient && pStreamWas->pDevCfg->pIAudioClient, rc = VERR_AUDIO_STREAM_NOT_READY); + + /* + * Anything pending from last call? + * (This is rather similar to the Pulse interface.) + */ + if (pStreamWas->cFramesCaptureToRelease) + { + uint32_t const cbToCopy = RT_MIN(pStreamWas->cbCapture, cbBuf); + memcpy(pvBuf, pStreamWas->pbCapture, cbToCopy); + pvBuf = (uint8_t *)pvBuf + cbToCopy; + cbBuf -= cbToCopy; + cbRead += cbToCopy; + pStreamWas->offInternal += cbToCopy; + pStreamWas->pbCapture += cbToCopy; + pStreamWas->cbCapture -= cbToCopy; + if (!pStreamWas->cbCapture) + { + HRESULT hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(pStreamWas->cFramesCaptureToRelease); + Log4Func(("@%#RX64: Releasing capture buffer (%#x frames): %Rhrc\n", + pStreamWas->offInternal, pStreamWas->cFramesCaptureToRelease, hrc)); + if (SUCCEEDED(hrc)) + { + pStreamWas->cFramesCaptureToRelease = 0; + pStreamWas->pbCapture = NULL; + } + else + { + LogRelMax(64, ("WasAPI: ReleaseBuffer(%s) failed during capture: %Rhrc (@%#RX64)\n", + pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal)); + /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */ + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + } + if (cbBuf < cbFrame) + break; + } + + /* + * Figure out if there is any data available to be read now. (Docs hint that we can not + * skip this and go straight for GetBuffer or we risk getting unwritten buffer space back). + */ + UINT32 cFramesCaptured = 0; + HRESULT hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->GetNextPacketSize(&cFramesCaptured); + if (SUCCEEDED(hrc)) + { + if (!cFramesCaptured) + break; + } + else + { + LogRelMax(64, ("WasAPI: GetNextPacketSize(%s) failed during capture: %Rhrc (@%#RX64)\n", + pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal)); + /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */ + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + /* + * Get the buffer. + */ + cFramesCaptured = 0; + UINT64 uQpsNtTicks = 0; + UINT64 offDevice = 0; + DWORD fBufFlags = 0; + BYTE *pbData = NULL; + hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->GetBuffer(&pbData, &cFramesCaptured, &fBufFlags, &offDevice, &uQpsNtTicks); + Log4Func(("@%#RX64: GetBuffer -> %Rhrc pbData=%p cFramesCaptured=%#x fBufFlags=%#x offDevice=%#RX64 uQpcNtTicks=%#RX64\n", + pStreamWas->offInternal, hrc, pbData, cFramesCaptured, fBufFlags, offDevice, uQpsNtTicks)); + if (SUCCEEDED(hrc)) + { + Assert(cFramesCaptured < VBOX_WASAPI_MAX_PADDING); + pStreamWas->pbCapture = pbData; + pStreamWas->cFramesCaptureToRelease = cFramesCaptured; + pStreamWas->cbCapture = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesCaptured); + /* Just loop and re-use the copying code above. Can optimize later. */ + } + else + { + LogRelMax(64, ("WasAPI: GetBuffer() failed on '%s' during capture: %Rhrc (@%#RX64)\n", + pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal)); + /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */ + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + } + + /* + * Done. + */ + uint64_t const msPrev = pStreamWas->msLastTransfer; RT_NOREF(msPrev); + uint64_t const msNow = RTTimeMilliTS(); + if (cbRead) + pStreamWas->msLastTransfer = msNow; + + RTCritSectLeave(&pStreamWas->CritSect); + + *pcbRead = cbRead; + if (RT_SUCCESS(rc) || !cbRead) + { } + else + { + LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead)); + rc = VINF_SUCCESS; + } + LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%#RX32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamWas->offInternal, rc, cbRead, + msPrev ? msNow - msPrev : 0, msPrev, pStreamWas->msLastTransfer, drvHostWasStreamStatusString(pStreamWas) )); + return rc; +} + + +/********************************************************************************************************************************* +* PDMDRVINS::IBase Interface * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostAudioWasQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG Interface * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct} + */ +static DECLCALLBACK(void) drvHostAudioWasPowerOff(PPDMDRVINS pDrvIns) +{ + PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS); + + /* + * Start purging the cache asynchronously before we get to destruct. + * This might speed up VM shutdown a tiny fraction and also stress + * the shutting down of the thread pool a little. + */ +#if 0 + if (pThis->hWorkerThread != NIL_RTTHREAD) + { + BOOL fRc = PostThreadMessageW(pThis->idWorkerThread, WM_DRVHOSTAUDIOWAS_PURGE_CACHE, pThis->uWorkerThreadFixedParam, 0); + LogFlowFunc(("Posted WM_DRVHOSTAUDIOWAS_PURGE_CACHE: %d\n", fRc)); + Assert(fRc); RT_NOREF(fRc); + } +#else + if (!RTListIsEmpty(&pThis->CacheHead) && pThis->pIHostAudioPort) + { + int rc = RTSemEventMultiCreate(&pThis->hEvtCachePurge); + if (RT_SUCCESS(rc)) + { + rc = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL/*pStream*/, + DRVHOSTAUDIOWAS_DO_PURGE_CACHE, NULL /*pvUser*/); + if (RT_FAILURE(rc)) + { + LogFunc(("pfnDoOnWorkerThread/DRVHOSTAUDIOWAS_DO_PURGE_CACHE failed: %Rrc\n", rc)); + RTSemEventMultiDestroy(pThis->hEvtCachePurge); + pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI; + } + } + } +#endif + + /* + * Deregister the notification client to reduce the risk of notifications + * comming in while we're being detatched or the VM is being destroyed. + */ + if (pThis->pNotifyClient) + { + pThis->pNotifyClient->notifyDriverDestroyed(); + pThis->pIEnumerator->UnregisterEndpointNotificationCallback(pThis->pNotifyClient); + pThis->pNotifyClient->Release(); + pThis->pNotifyClient = NULL; + } +} + + +/** + * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct} + */ +static DECLCALLBACK(void) drvHostAudioWasDestruct(PPDMDRVINS pDrvIns) +{ + PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS); + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + LogFlowFuncEnter(); + + /* + * Release the notification client first. + */ + if (pThis->pNotifyClient) + { + pThis->pNotifyClient->notifyDriverDestroyed(); + pThis->pIEnumerator->UnregisterEndpointNotificationCallback(pThis->pNotifyClient); + pThis->pNotifyClient->Release(); + pThis->pNotifyClient = NULL; + } + +#if 0 + if (pThis->hWorkerThread != NIL_RTTHREAD) + { + BOOL fRc = PostThreadMessageW(pThis->idWorkerThread, WM_QUIT, 0, 0); + Assert(fRc); RT_NOREF(fRc); + + int rc = RTThreadWait(pThis->hWorkerThread, RT_MS_15SEC, NULL); + AssertRC(rc); + } +#endif + + if (RTCritSectIsInitialized(&pThis->CritSectCache)) + { + drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/); + if (pThis->hEvtCachePurge != NIL_RTSEMEVENTMULTI) + RTSemEventMultiWait(pThis->hEvtCachePurge, RT_MS_30SEC); + RTCritSectDelete(&pThis->CritSectCache); + } + + if (pThis->hEvtCachePurge != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(pThis->hEvtCachePurge); + pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI; + } + + if (pThis->pIEnumerator) + { + uint32_t cRefs = pThis->pIEnumerator->Release(); RT_NOREF(cRefs); + LogFlowFunc(("cRefs=%d\n", cRefs)); + } + + if (pThis->pIDeviceOutput) + { + pThis->pIDeviceOutput->Release(); + pThis->pIDeviceOutput = NULL; + } + + if (pThis->pIDeviceInput) + { + pThis->pIDeviceInput->Release(); + pThis->pIDeviceInput = NULL; + } + + + if (RTCritSectRwIsInitialized(&pThis->CritSectStreamList)) + RTCritSectRwDelete(&pThis->CritSectStreamList); + + LogFlowFuncLeave(); +} + + +/** + * @callback_method_impl{FNPDMDRVCONSTRUCT, pfnConstruct} + */ +static DECLCALLBACK(int) drvHostAudioWasConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS); + RT_NOREF(fFlags, pCfg); + + /* + * Init basic data members and interfaces. + */ + pThis->pDrvIns = pDrvIns; + pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI; +#if 0 + pThis->hWorkerThread = NIL_RTTHREAD; + pThis->idWorkerThread = 0; +#endif + RTListInit(&pThis->StreamHead); + RTListInit(&pThis->CacheHead); + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostAudioWasQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHostAudioWasHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = drvHostAudioWasHA_GetDevices; + pThis->IHostAudio.pfnSetDevice = drvHostAudioWasHA_SetDevice; + pThis->IHostAudio.pfnGetStatus = drvHostAudioWasHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = drvHostAudioWasHA_DoOnWorkerThread; + pThis->IHostAudio.pfnStreamConfigHint = drvHostAudioWasHA_StreamConfigHint; + pThis->IHostAudio.pfnStreamCreate = drvHostAudioWasHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = drvHostAudioWasHA_StreamInitAsync; + pThis->IHostAudio.pfnStreamDestroy = drvHostAudioWasHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = drvHostAudioWasHA_StreamNotifyDeviceChanged; + pThis->IHostAudio.pfnStreamEnable = drvHostAudioWasHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHostAudioWasHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHostAudioWasHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHostAudioWasHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHostAudioWasHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetState = drvHostAudioWasHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetPending = drvHostAudioWasHA_StreamGetPending; + pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioWasHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvHostAudioWasHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioWasHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvHostAudioWasHA_StreamCapture; + + /* + * Validate and read the configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "VmName|VmUuid|InputDeviceID|OutputDeviceID", ""); + + char szTmp[1024]; + int rc = CFGMR3QueryStringDef(pCfg, "InputDeviceID", szTmp, sizeof(szTmp), ""); + AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"InputDeviceID\" as string: rc=%Rrc\n", rc), rc); + if (szTmp[0]) + { + rc = RTStrToUtf16(szTmp, &pThis->pwszInputDevId); + AssertRCReturn(rc, rc); + } + + rc = CFGMR3QueryStringDef(pCfg, "OutputDeviceID", szTmp, sizeof(szTmp), ""); + AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"OutputDeviceID\" as string: rc=%Rrc\n", rc), rc); + if (szTmp[0]) + { + rc = RTStrToUtf16(szTmp, &pThis->pwszOutputDevId); + AssertRCReturn(rc, rc); + } + + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * Initialize the critical sections early. + */ + rc = RTCritSectRwInit(&pThis->CritSectStreamList); + AssertRCReturn(rc, rc); + + rc = RTCritSectInit(&pThis->CritSectCache); + AssertRCReturn(rc, rc); + + /* + * Create an enumerator instance that we can get the default devices from + * as well as do enumeration thru. + */ + HRESULT hrc = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), + (void **)&pThis->pIEnumerator); + if (FAILED(hrc)) + { + pThis->pIEnumerator = NULL; + LogRel(("WasAPI: Failed to create an MMDeviceEnumerator object: %Rhrc\n", hrc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + AssertPtr(pThis->pIEnumerator); + + /* + * Resolve the interface to the driver above us. + */ + pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT); + AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE); + + /* + * Instantiate and register the notification client with the enumerator. + * + * Failure here isn't considered fatal at this time as we'll just miss + * default device changes. + */ + try + { + pThis->pNotifyClient = new DrvHostAudioWasMmNotifyClient(pThis); + } + catch (std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + catch (int rcXcpt) + { + return rcXcpt; + } + hrc = pThis->pIEnumerator->RegisterEndpointNotificationCallback(pThis->pNotifyClient); + AssertMsg(SUCCEEDED(hrc), ("%Rhrc\n", hrc)); + if (FAILED(hrc)) + { + LogRel(("WasAPI: RegisterEndpointNotificationCallback failed: %Rhrc (ignored)\n" + "WasAPI: Warning! Will not be able to detect default device changes!\n")); + pThis->pNotifyClient->notifyDriverDestroyed(); + pThis->pNotifyClient->Release(); + pThis->pNotifyClient = NULL; + } + + /* + * Retrieve the input and output device. + */ + IMMDevice *pIDeviceInput = NULL; + if (pThis->pwszInputDevId) + hrc = pThis->pIEnumerator->GetDevice(pThis->pwszInputDevId, &pIDeviceInput); + else + hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &pIDeviceInput); + if (SUCCEEDED(hrc)) + LogFlowFunc(("pIDeviceInput=%p\n", pIDeviceInput)); + else + { + LogRel(("WasAPI: Failed to get audio input device '%ls': %Rhrc\n", + pThis->pwszInputDevId ? pThis->pwszInputDevId : L"{Default}", hrc)); + pIDeviceInput = NULL; + } + + IMMDevice *pIDeviceOutput = NULL; + if (pThis->pwszOutputDevId) + hrc = pThis->pIEnumerator->GetDevice(pThis->pwszOutputDevId, &pIDeviceOutput); + else + hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pIDeviceOutput); + if (SUCCEEDED(hrc)) + LogFlowFunc(("pIDeviceOutput=%p\n", pIDeviceOutput)); + else + { + LogRel(("WasAPI: Failed to get audio output device '%ls': %Rhrc\n", + pThis->pwszOutputDevId ? pThis->pwszOutputDevId : L"{Default}", hrc)); + pIDeviceOutput = NULL; + } + + /* Carefully place them in the instance data: */ + pThis->pNotifyClient->lockEnter(); + + if (pThis->pIDeviceInput) + pThis->pIDeviceInput->Release(); + pThis->pIDeviceInput = pIDeviceInput; + + if (pThis->pIDeviceOutput) + pThis->pIDeviceOutput->Release(); + pThis->pIDeviceOutput = pIDeviceOutput; + + pThis->pNotifyClient->lockLeave(); + +#if 0 + /* + * Create the worker thread. This thread has a message loop and will be + * signalled by DrvHostAudioWasMmNotifyClient while the VM is paused/whatever, + * so better make it a regular thread rather than PDM thread. + */ + pThis->uWorkerThreadFixedParam = (WPARAM)RTRandU64(); + rc = RTThreadCreateF(&pThis->hWorkerThread, drvHostWasWorkerThread, pThis, 0 /*cbStack*/, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE | RTTHREADFLAGS_COM_MTA, "WasWork%u", pDrvIns->iInstance); + AssertRCReturn(rc, rc); + + rc = RTThreadUserWait(pThis->hWorkerThread, RT_MS_10SEC); + AssertRC(rc); +#endif + + /* + * Prime the cache. + */ + drvHostAudioWasCacheFill(pThis); + + return VINF_SUCCESS; +} + + +/** + * PDM driver registration for WasAPI. + */ +const PDMDRVREG g_DrvHostAudioWas = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "HostAudioWas", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Windows Audio Session API (WASAPI) host audio driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTAUDIOWAS), + /* pfnConstruct */ + drvHostAudioWasConstruct, + /* pfnDestruct */ + drvHostAudioWasDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + drvHostAudioWasPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostCoreAudio-auth.mm virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostCoreAudio-auth.mm --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostCoreAudio-auth.mm 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostCoreAudio-auth.mm 1970-01-01 00:00:00.000000000 +0000 @@ -1,112 +0,0 @@ -/* $Id: DrvHostCoreAudio-auth.mm $ */ -/** @file - * VBox audio devices - Mac OS X CoreAudio audio driver, authorization helpers for Mojave+. - */ - -/* - * Copyright (C) 2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include - -#include -#include - -#import -#import -#import - -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 -/** - * Starting macOS 10.14 we need to request permissions in order to use any audio input device - * but as we build against an older SDK where this is not available we have to duplicate - * AVAuthorizationStatus and do everything dynmically during runtime, sigh... - */ - -/** - * The authorization status enum. - */ -typedef enum AVAuthorizationStatus: NSInteger -{ - AVAuthorizationStatusNotDetermined = 0, - AVAuthorizationStatusRestricted = 1, - AVAuthorizationStatusDenied = 2, - AVAuthorizationStatusAuthorized = 3, -} AVAuthorizationStatus; - -#endif - - -/** - * Requests camera permissions for Mojave and onwards. - * - * @returns VBox status code. - */ -static int coreAudioInputPermissionRequest(void) -{ - __block RTSEMEVENT hEvt = NIL_RTSEMEVENT; - __block int rc = RTSemEventCreate(&hEvt); - if (RT_SUCCESS(rc)) - { - /* Perform auth request. */ - [AVCaptureDevice performSelector: @selector(requestAccessForMediaType: completionHandler:) withObject: (id)AVMediaTypeAudio withObject: (id)^(BOOL granted) { - if (!granted) { - LogRel(("CoreAudio: Access denied!\n")); - rc = VERR_ACCESS_DENIED; - } - RTSemEventSignal(hEvt); - }]; - - rc = RTSemEventWait(hEvt, 10 * RT_MS_1SEC); - RTSemEventDestroy(hEvt); - } - - return rc; -} - -/** - * Checks permission for capturing devices on Mojave and onwards. - * - * @returns VBox status code. - */ -DECLHIDDEN(int) coreAudioInputPermissionCheck(void) -{ - int rc = VINF_SUCCESS; - - if (NSFoundationVersionNumber >= 10.14) - { - /* - * Because we build with an older SDK where the authorization APIs are not available - * (introduced with Mojave 10.14) we have to resort to resolving the APIs dynamically. - */ - LogRel(("CoreAudio: macOS 10.14+ detected, checking audio input permissions\n")); - - if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)]) - { - AVAuthorizationStatus enmAuthSts = (AVAuthorizationStatus)(NSInteger)[AVCaptureDevice performSelector: @selector(authorizationStatusForMediaType:) withObject: (id)AVMediaTypeAudio]; - if (enmAuthSts == AVAuthorizationStatusNotDetermined) - rc = coreAudioInputPermissionRequest(); - else if ( enmAuthSts == AVAuthorizationStatusRestricted - || enmAuthSts == AVAuthorizationStatusDenied) - { - LogRel(("CoreAudio: Access denied!\n")); - rc = VERR_ACCESS_DENIED; - } - } - } - - return rc; -} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,2738 +0,0 @@ -/* $Id: DrvHostCoreAudio.cpp $ */ -/** @file - * VBox audio devices - Mac OS X CoreAudio audio driver. - */ - -/* - * Copyright (C) 2010-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include - -#include "DrvAudio.h" -#include "VBoxDD.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - - - -/* Enables utilizing the Core Audio converter unit for converting - * input / output from/to our requested formats. That might be more - * performant than using our own routines later down the road. */ -/** @todo Needs more investigation and testing first before enabling. */ -//# define VBOX_WITH_AUDIO_CA_CONVERTER - -/** @todo - * - Maybe make sure the threads are immediately stopped if playing/recording stops. - */ - -/* - * Most of this is based on: - * http://developer.apple.com/mac/library/technotes/tn2004/tn2097.html - * http://developer.apple.com/mac/library/technotes/tn2002/tn2091.html - * http://developer.apple.com/mac/library/qa/qa2007/qa1533.html - * http://developer.apple.com/mac/library/qa/qa2001/qa1317.html - * http://developer.apple.com/mac/library/documentation/AudioUnit/Reference/AUComponentServicesReference/Reference/reference.html - */ - -/* Prototypes needed for COREAUDIODEVICE. */ -struct DRVHOSTCOREAUDIO; -typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO; - -/** - * Structure for holding Core Audio-specific device data. - * This data then lives in the pvData part of the PDMAUDIODEVICE struct. - */ -typedef struct COREAUDIODEVICEDATA -{ - /** Pointer to driver instance this device is bound to. */ - PDRVHOSTCOREAUDIO pDrv; - /** The audio device ID of the currently used device (UInt32 typedef). */ - AudioDeviceID deviceID; - /** The device' UUID. */ - CFStringRef UUID; - /** List of attached (native) Core Audio streams attached to this device. */ - RTLISTANCHOR lstStreams; -} COREAUDIODEVICEDATA, *PCOREAUDIODEVICEDATA; - -/** - * Host Coreaudio driver instance data. - * @implements PDMIAUDIOCONNECTOR - */ -typedef struct DRVHOSTCOREAUDIO -{ - /** Pointer to the driver instance structure. */ - PPDMDRVINS pDrvIns; - /** Pointer to host audio interface. */ - PDMIHOSTAUDIO IHostAudio; - /** Critical section to serialize access. */ - RTCRITSECT CritSect; - /** Current (last reported) device enumeration. */ - PDMAUDIODEVICEENUM Devices; - /** Pointer to the currently used input device in the device enumeration. - * Can be NULL if none assigned. */ - PPDMAUDIODEVICE pDefaultDevIn; - /** Pointer to the currently used output device in the device enumeration. - * Can be NULL if none assigned. */ - PPDMAUDIODEVICE pDefaultDevOut; -#ifdef VBOX_WITH_AUDIO_CALLBACKS - /** Callback function to the upper driver. - * Can be NULL if not being used / registered. */ - PFNPDMHOSTAUDIOCALLBACK pfnCallback; -#endif -} DRVHOSTCOREAUDIO, *PDRVHOSTCOREAUDIO; - -/** Converts a pointer to DRVHOSTCOREAUDIO::IHostAudio to a PDRVHOSTCOREAUDIO. */ -#define PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface) RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio) - -/** - * Structure for holding a Core Audio unit - * and its data. - */ -typedef struct COREAUDIOUNIT -{ - /** Pointer to the device this audio unit is bound to. - * Can be NULL if not bound to a device (anymore). */ - PPDMAUDIODEVICE pDevice; - /** The actual audio unit object. */ - AudioUnit audioUnit; - /** Stream description for using with VBox: - * - When using this audio unit for input (capturing), this format states - * the unit's output format. - * - When using this audio unit for output (playback), this format states - * the unit's input format. */ - AudioStreamBasicDescription streamFmt; -} COREAUDIOUNIT, *PCOREAUDIOUNIT; - - -DECLHIDDEN(int) coreAudioInputPermissionCheck(void); - -/******************************************************************************* - * - * Helper function section - * - ******************************************************************************/ - -/* Move these down below the internal function prototypes... */ - -static void coreAudioPrintASBD(const char *pszDesc, const AudioStreamBasicDescription *pASBD) -{ - char pszSampleRate[32]; - LogRel2(("CoreAudio: %s description:\n", pszDesc)); - LogRel2(("CoreAudio:\tFormat ID: %RU32 (%c%c%c%c)\n", pASBD->mFormatID, - RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID), - RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID))); - LogRel2(("CoreAudio:\tFlags: %RU32", pASBD->mFormatFlags)); - if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat) - LogRel2((" Float")); - if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian) - LogRel2((" BigEndian")); - if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger) - LogRel2((" SignedInteger")); - if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked) - LogRel2((" Packed")); - if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh) - LogRel2((" AlignedHigh")); - if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved) - LogRel2((" NonInterleaved")); - if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable) - LogRel2((" NonMixable")); - if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear) - LogRel2((" AllClear")); - LogRel2(("\n")); - snprintf(pszSampleRate, 32, "%.2f", (float)pASBD->mSampleRate); /** @todo r=andy Use RTStrPrint*. */ - LogRel2(("CoreAudio:\tSampleRate : %s\n", pszSampleRate)); - LogRel2(("CoreAudio:\tChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame)); - LogRel2(("CoreAudio:\tFramesPerPacket : %RU32\n", pASBD->mFramesPerPacket)); - LogRel2(("CoreAudio:\tBitsPerChannel : %RU32\n", pASBD->mBitsPerChannel)); - LogRel2(("CoreAudio:\tBytesPerFrame : %RU32\n", pASBD->mBytesPerFrame)); - LogRel2(("CoreAudio:\tBytesPerPacket : %RU32\n", pASBD->mBytesPerPacket)); -} - -static void coreAudioPCMPropsToASBD(PDMAUDIOPCMPROPS *pPCMProps, AudioStreamBasicDescription *pASBD) -{ - AssertPtrReturnVoid(pPCMProps); - AssertPtrReturnVoid(pASBD); - - RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription)); - - pASBD->mFormatID = kAudioFormatLinearPCM; - pASBD->mFormatFlags = kAudioFormatFlagIsPacked; - pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */ - pASBD->mSampleRate = (Float64)pPCMProps->uHz; - pASBD->mChannelsPerFrame = pPCMProps->cChannels; - pASBD->mBitsPerChannel = pPCMProps->cbSample * 8; - if (pPCMProps->fSigned) - pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger; - pASBD->mBytesPerFrame = pASBD->mChannelsPerFrame * (pASBD->mBitsPerChannel / 8); - pASBD->mBytesPerPacket = pASBD->mFramesPerPacket * pASBD->mBytesPerFrame; -} - -#ifndef VBOX_WITH_AUDIO_CALLBACKS -static int coreAudioASBDToStreamCfg(AudioStreamBasicDescription *pASBD, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pASBD, VERR_INVALID_PARAMETER); - AssertPtrReturn(pCfg, VERR_INVALID_PARAMETER); - - pCfg->Props.cChannels = pASBD->mChannelsPerFrame; - pCfg->Props.uHz = (uint32_t)pASBD->mSampleRate; - AssertMsg(!(pASBD->mBitsPerChannel & 7), ("%u\n", pASBD->mBitsPerChannel)); - pCfg->Props.cbSample = pASBD->mBitsPerChannel / 8; - pCfg->Props.fSigned = RT_BOOL(pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger); - pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels); - /** @todo r=bird: pCfg->Props.fSwapEndian is not initialized here! */ - - return VINF_SUCCESS; -} -#endif /* !VBOX_WITH_AUDIO_CALLBACKS */ - -#if 0 /* unused */ -static int coreAudioCFStringToCString(const CFStringRef pCFString, char **ppszString) -{ - CFIndex cLen = CFStringGetLength(pCFString) + 1; - char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char)); - if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8)) - { - RTMemFree(pszResult); - return VERR_NOT_FOUND; - } - - *ppszString = pszResult; - return VINF_SUCCESS; -} - -static AudioDeviceID coreAudioDeviceUIDtoID(const char* pszUID) -{ - /* Create a CFString out of our CString. */ - CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman); - - /* Fill the translation structure. */ - AudioDeviceID deviceID; - - AudioValueTranslation translation; - translation.mInputData = &strUID; - translation.mInputDataSize = sizeof(CFStringRef); - translation.mOutputData = &deviceID; - translation.mOutputDataSize = sizeof(AudioDeviceID); - - /* Fetch the translation from the UID to the device ID. */ - AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDeviceForUID, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - UInt32 uSize = sizeof(AudioValueTranslation); - OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdr, 0, NULL, &uSize, &translation); - - /* Release the temporary CFString */ - CFRelease(strUID); - - if (RT_LIKELY(err == noErr)) - return deviceID; - - /* Return the unknown device on error. */ - return kAudioDeviceUnknown; -} -#endif /* unused */ - - -/********************************************************************************************************************************* -* Defined Constants And Macros * -*********************************************************************************************************************************/ - -/** @name Initialization status indicator used for the recreation of the AudioUnits. - * - * Global structures section - * - ******************************************************************************/ - -/** - * Enumeration for a Core Audio stream status. - */ -typedef enum COREAUDIOSTATUS -{ - /** The device is uninitialized. */ - COREAUDIOSTATUS_UNINIT = 0, - /** The device is currently initializing. */ - COREAUDIOSTATUS_IN_INIT, - /** The device is initialized. */ - COREAUDIOSTATUS_INIT, - /** The device is currently uninitializing. */ - COREAUDIOSTATUS_IN_UNINIT, -#ifndef VBOX_WITH_AUDIO_CALLBACKS - /** The device has to be reinitialized. - * Note: Only needed if VBOX_WITH_AUDIO_CALLBACKS is not defined, as otherwise - * the Audio Connector will take care of this as soon as this backend - * tells it to do so via the provided audio callback. */ - COREAUDIOSTATUS_REINIT, -#endif - /** The usual 32-bit hack. */ - COREAUDIOSTATUS_32BIT_HACK = 0x7fffffff -} COREAUDIOSTATUS, *PCOREAUDIOSTATUS; - -#ifdef VBOX_WITH_AUDIO_CA_CONVERTER - /* Error code which indicates "End of data" */ - static const OSStatus caConverterEOFDErr = 0x656F6664; /* 'eofd' */ -#endif - -/* Prototypes needed for COREAUDIOSTREAMCBCTX. */ -struct COREAUDIOSTREAM; -typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM; - -/** - * Structure for keeping a conversion callback context. - * This is needed when using an audio converter during input/output processing. - */ -typedef struct COREAUDIOCONVCBCTX -{ - /** Pointer to the stream this context is bound to. */ - PCOREAUDIOSTREAM pStream; - /** Source stream description. */ - AudioStreamBasicDescription asbdSrc; - /** Destination stream description. */ - AudioStreamBasicDescription asbdDst; - /** Pointer to native buffer list used for rendering the source audio data into. */ - AudioBufferList *pBufLstSrc; - /** Total packet conversion count. */ - UInt32 uPacketCnt; - /** Current packet conversion index. */ - UInt32 uPacketIdx; - /** Error count, for limiting the logging. */ - UInt32 cErrors; -} COREAUDIOCONVCBCTX, *PCOREAUDIOCONVCBCTX; - -/** - * Structure for keeping the input stream specifics. - */ -typedef struct COREAUDIOSTREAMIN -{ -#ifdef VBOX_WITH_AUDIO_CA_CONVERTER - /** The audio converter if necessary. NULL if no converter is being used. */ - AudioConverterRef ConverterRef; - /** Callback context for the audio converter. */ - COREAUDIOCONVCBCTX convCbCtx; -#endif - /** The ratio between the device & the stream sample rate. */ - Float64 sampleRatio; -} COREAUDIOSTREAMIN, *PCOREAUDIOSTREAMIN; - -/** - * Structure for keeping the output stream specifics. - */ -typedef struct COREAUDIOSTREAMOUT -{ - /** Nothing here yet. */ -} COREAUDIOSTREAMOUT, *PCOREAUDIOSTREAMOUT; - -/** - * Structure for maintaining a Core Audio stream. - */ -typedef struct COREAUDIOSTREAM -{ - /** The stream's acquired configuration. */ - PPDMAUDIOSTREAMCFG pCfg; - /** Stream-specific data, depending on the stream type. */ - union - { - COREAUDIOSTREAMIN In; - COREAUDIOSTREAMOUT Out; - }; - /** List node for the device's stream list. */ - RTLISTNODE Node; - /** Pointer to driver instance this stream is bound to. */ - PDRVHOSTCOREAUDIO pDrv; - /** The stream's thread handle for maintaining the audio queue. */ - RTTHREAD hThread; - /** Flag indicating to start a stream's data processing. */ - bool fRun; - /** Whether the stream is in a running (active) state or not. - * For playback streams this means that audio data can be (or is being) played, - * for capturing streams this means that audio data is being captured (if available). */ - bool fIsRunning; - /** Thread shutdown indicator. */ - bool fShutdown; - /** Critical section for serializing access between thread + callbacks. */ - RTCRITSECT CritSect; - /** The actual audio queue being used. */ - AudioQueueRef audioQueue; - /** The audio buffers which are used with the above audio queue. */ - AudioQueueBufferRef audioBuffer[2]; - /** The acquired (final) audio format for this stream. */ - AudioStreamBasicDescription asbdStream; - /** The audio unit for this stream. */ - COREAUDIOUNIT Unit; - /** Initialization status tracker, actually COREAUDIOSTATUS. - * Used when some of the device parameters or the device itself is changed - * during the runtime. */ - volatile uint32_t enmStatus; - /** An internal ring buffer for transferring data from/to the rendering callbacks. */ - PRTCIRCBUF pCircBuf; -} COREAUDIOSTREAM, *PCOREAUDIOSTREAM; - -static int coreAudioStreamInit(PCOREAUDIOSTREAM pCAStream, PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev); -#ifndef VBOX_WITH_AUDIO_CALLBACKS -static int coreAudioStreamReinit(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev); -#endif -static int coreAudioStreamUninit(PCOREAUDIOSTREAM pCAStream); - -static int coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd); - -static int coreAudioDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev); -static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev); -static void coreAudioDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID, bool fIsInput, PDRVHOSTCOREAUDIO pDrv); - -static OSStatus coreAudioDevPropChgCb(AudioObjectID propertyID, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void *pvUser); - -static int coreAudioStreamInitQueue(PCOREAUDIOSTREAM pCAStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq); -static void coreAudioInputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer, const AudioTimeStamp *pAudioTS, UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc); -static void coreAudioOutputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer); - -#ifdef VBOX_WITH_AUDIO_CA_CONVERTER -/** - * Initializes a conversion callback context. - * - * @return IPRT status code. - * @param pConvCbCtx Conversion callback context to initialize. - * @param pStream Pointer to stream to use. - * @param pASBDSrc Input (source) stream description to use. - * @param pASBDDst Output (destination) stream description to use. - */ -static int coreAudioInitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx, PCOREAUDIOSTREAM pStream, - AudioStreamBasicDescription *pASBDSrc, AudioStreamBasicDescription *pASBDDst) -{ - AssertPtrReturn(pConvCbCtx, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pASBDSrc, VERR_INVALID_POINTER); - AssertPtrReturn(pASBDDst, VERR_INVALID_POINTER); - -#ifdef DEBUG - coreAudioPrintASBD("CbCtx: Src", pASBDSrc); - coreAudioPrintASBD("CbCtx: Dst", pASBDDst); -#endif - - pConvCbCtx->pStream = pStream; - - memcpy(&pConvCbCtx->asbdSrc, pASBDSrc, sizeof(AudioStreamBasicDescription)); - memcpy(&pConvCbCtx->asbdDst, pASBDDst, sizeof(AudioStreamBasicDescription)); - - pConvCbCtx->pBufLstSrc = NULL; - pConvCbCtx->cErrors = 0; - - return VINF_SUCCESS; -} - - -/** - * Uninitializes a conversion callback context. - * - * @return IPRT status code. - * @param pConvCbCtx Conversion callback context to uninitialize. - */ -static void coreAudioUninitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx) -{ - AssertPtrReturnVoid(pConvCbCtx); - - pConvCbCtx->pStream = NULL; - - RT_ZERO(pConvCbCtx->asbdSrc); - RT_ZERO(pConvCbCtx->asbdDst); - - pConvCbCtx->pBufLstSrc = NULL; - pConvCbCtx->cErrors = 0; -} -#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */ - - -/** - * Does a (re-)enumeration of the host's playback + recording devices. - * - * @return IPRT status code. - * @param pThis Host audio driver instance. - * @param enmUsage Which devices to enumerate. - * @param pDevEnm Where to store the enumerated devices. - */ -static int coreAudioDevicesEnumerate(PDRVHOSTCOREAUDIO pThis, PDMAUDIODIR enmUsage, PPDMAUDIODEVICEENUM pDevEnm) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER); - - int rc = VINF_SUCCESS; - - do - { - AudioDeviceID defaultDeviceID = kAudioDeviceUnknown; - - /* Fetch the default audio device currently in use. */ - AudioObjectPropertyAddress propAdrDefaultDev = { enmUsage == PDMAUDIODIR_IN - ? kAudioHardwarePropertyDefaultInputDevice - : kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - UInt32 uSize = sizeof(defaultDeviceID); - OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdrDefaultDev, 0, NULL, &uSize, &defaultDeviceID); - if (err != noErr) - { - LogRel(("CoreAudio: Unable to determine default %s device (%RI32)\n", - enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback", err)); - return VERR_NOT_FOUND; - } - - if (defaultDeviceID == kAudioDeviceUnknown) - { - LogFunc(("No default %s device found\n", enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback")); - /* Keep going. */ - } - - AudioObjectPropertyAddress propAdrDevList = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAdrDevList, 0, NULL, &uSize); - if (err != kAudioHardwareNoError) - break; - - AudioDeviceID *pDevIDs = (AudioDeviceID *)alloca(uSize); - if (pDevIDs == NULL) - break; - - err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdrDevList, 0, NULL, &uSize, pDevIDs); - if (err != kAudioHardwareNoError) - break; - - rc = DrvAudioHlpDeviceEnumInit(pDevEnm); - if (RT_FAILURE(rc)) - break; - - UInt16 cDevices = uSize / sizeof(AudioDeviceID); - - PPDMAUDIODEVICE pDev = NULL; - for (UInt16 i = 0; i < cDevices; i++) - { - if (pDev) /* Some (skipped) device to clean up first? */ - DrvAudioHlpDeviceFree(pDev); - - pDev = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA)); - if (!pDev) - { - rc = VERR_NO_MEMORY; - break; - } - - /* Set usage. */ - pDev->enmUsage = enmUsage; - - /* Init backend-specific device data. */ - PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pDev->pvData; - AssertPtr(pDevData); - coreAudioDeviceDataInit(pDevData, pDevIDs[i], enmUsage == PDMAUDIODIR_IN, pThis); - - /* Check if the device is valid. */ - AudioDeviceID curDevID = pDevData->deviceID; - - /* Is the device the default device? */ - if (curDevID == defaultDeviceID) - pDev->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT; - - AudioObjectPropertyAddress propAddrCfg = { kAudioDevicePropertyStreamConfiguration, - enmUsage == PDMAUDIODIR_IN - ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster }; - - err = AudioObjectGetPropertyDataSize(curDevID, &propAddrCfg, 0, NULL, &uSize); - if (err != noErr) - continue; - - AudioBufferList *pBufList = (AudioBufferList *)RTMemAlloc(uSize); - if (!pBufList) - continue; - - err = AudioObjectGetPropertyData(curDevID, &propAddrCfg, 0, NULL, &uSize, pBufList); - if (err == noErr) - { - for (UInt32 a = 0; a < pBufList->mNumberBuffers; a++) - { - if (enmUsage == PDMAUDIODIR_IN) - pDev->cMaxInputChannels += pBufList->mBuffers[a].mNumberChannels; - else if (enmUsage == PDMAUDIODIR_OUT) - pDev->cMaxOutputChannels += pBufList->mBuffers[a].mNumberChannels; - } - } - - if (pBufList) - { - RTMemFree(pBufList); - pBufList = NULL; - } - - /* Check if the device is valid, e.g. has any input/output channels according to its usage. */ - if ( enmUsage == PDMAUDIODIR_IN - && !pDev->cMaxInputChannels) - continue; - if ( enmUsage == PDMAUDIODIR_OUT - && !pDev->cMaxOutputChannels) - continue; - - /* Resolve the device's name. */ - AudioObjectPropertyAddress propAddrName = { kAudioObjectPropertyName, - enmUsage == PDMAUDIODIR_IN - ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster }; - uSize = sizeof(CFStringRef); - CFStringRef pcfstrName = NULL; - - err = AudioObjectGetPropertyData(curDevID, &propAddrName, 0, NULL, &uSize, &pcfstrName); - if (err != kAudioHardwareNoError) - continue; - - CFIndex cbName = CFStringGetMaximumSizeForEncoding(CFStringGetLength(pcfstrName), kCFStringEncodingUTF8) + 1; - if (cbName) - { - char *pszName = (char *)RTStrAlloc(cbName); - if ( pszName - && CFStringGetCString(pcfstrName, pszName, cbName, kCFStringEncodingUTF8)) - RTStrCopy(pDev->szName, sizeof(pDev->szName), pszName); - - LogFunc(("Device '%s': %RU32\n", pszName, curDevID)); - - if (pszName) - { - RTStrFree(pszName); - pszName = NULL; - } - } - - CFRelease(pcfstrName); - - /* Check if the device is alive for the intended usage. */ - AudioObjectPropertyAddress propAddrAlive = { kAudioDevicePropertyDeviceIsAlive, - enmUsage == PDMAUDIODIR_IN - ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster }; - - UInt32 uAlive = 0; - uSize = sizeof(uAlive); - - err = AudioObjectGetPropertyData(curDevID, &propAddrAlive, 0, NULL, &uSize, &uAlive); - if ( (err == noErr) - && !uAlive) - { - pDev->fFlags |= PDMAUDIODEV_FLAGS_DEAD; - } - - /* Check if the device is being hogged by someone else. */ - AudioObjectPropertyAddress propAddrHogged = { kAudioDevicePropertyHogMode, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - - pid_t pid = 0; - uSize = sizeof(pid); - - err = AudioObjectGetPropertyData(curDevID, &propAddrHogged, 0, NULL, &uSize, &pid); - if ( (err == noErr) - && (pid != -1)) - { - pDev->fFlags |= PDMAUDIODEV_FLAGS_LOCKED; - } - - /* Add the device to the enumeration. */ - rc = DrvAudioHlpDeviceEnumAdd(pDevEnm, pDev); - if (RT_FAILURE(rc)) - break; - - /* NULL device pointer because it's now part of the device enumeration. */ - pDev = NULL; - } - - if (RT_FAILURE(rc)) - { - DrvAudioHlpDeviceFree(pDev); - pDev = NULL; - } - - } while (0); - - if (RT_SUCCESS(rc)) - { -#ifdef DEBUG - LogFunc(("Devices for pDevEnm=%p, enmUsage=%RU32:\n", pDevEnm, enmUsage)); - DrvAudioHlpDeviceEnumPrint("Core Audio", pDevEnm); -#endif - } - else - DrvAudioHlpDeviceEnumFree(pDevEnm); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * Checks if an audio device with a specific device ID is in the given device - * enumeration or not. - * - * @retval true if the node is the last element in the list. - * @retval false otherwise. - * - * @param pEnmSrc Device enumeration to search device ID in. - * @param deviceID Device ID to search. - */ -bool coreAudioDevicesHasDevice(PPDMAUDIODEVICEENUM pEnmSrc, AudioDeviceID deviceID) -{ - PPDMAUDIODEVICE pDevSrc; - RTListForEach(&pEnmSrc->lstDevices, pDevSrc, PDMAUDIODEVICE, Node) - { - PCOREAUDIODEVICEDATA pDevSrcData = (PCOREAUDIODEVICEDATA)pDevSrc->pvData; - AssertPtr(pDevSrcData); - - if (pDevSrcData->deviceID == deviceID) - return true; - } - - return false; -} - - -/** - * Enumerates all host devices and builds a final device enumeration list, consisting - * of (duplex) input and output devices. - * - * @return IPRT status code. - * @param pThis Host audio driver instance. - * @param pEnmDst Where to store the device enumeration list. - */ -int coreAudioDevicesEnumerateAll(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICEENUM pEnmDst) -{ - PDMAUDIODEVICEENUM devEnmIn; - int rc = coreAudioDevicesEnumerate(pThis, PDMAUDIODIR_IN, &devEnmIn); - if (RT_SUCCESS(rc)) - { - PDMAUDIODEVICEENUM devEnmOut; - rc = coreAudioDevicesEnumerate(pThis, PDMAUDIODIR_OUT, &devEnmOut); - if (RT_SUCCESS(rc)) - { - /* - * Build up the final device enumeration, based on the input and output device lists - * just enumerated. - * - * Also make sure to handle duplex devices, that is, devices which act as input and output - * at the same time. - */ - - rc = DrvAudioHlpDeviceEnumInit(pEnmDst); - if (RT_SUCCESS(rc)) - { - PPDMAUDIODEVICE pDevSrcIn; - RTListForEach(&devEnmIn.lstDevices, pDevSrcIn, PDMAUDIODEVICE, Node) - { - PCOREAUDIODEVICEDATA pDevSrcInData = (PCOREAUDIODEVICEDATA)pDevSrcIn->pvData; - AssertPtr(pDevSrcInData); - - PPDMAUDIODEVICE pDevDst = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA)); - if (!pDevDst) - { - rc = VERR_NO_MEMORY; - break; - } - - PCOREAUDIODEVICEDATA pDevDstData = (PCOREAUDIODEVICEDATA)pDevDst->pvData; - AssertPtr(pDevDstData); - coreAudioDeviceDataInit(pDevDstData, pDevSrcInData->deviceID, true /* fIsInput */, pThis); - - RTStrCopy(pDevDst->szName, sizeof(pDevDst->szName), pDevSrcIn->szName); - - pDevDst->enmUsage = PDMAUDIODIR_IN; /* Input device by default (simplex). */ - pDevDst->cMaxInputChannels = pDevSrcIn->cMaxInputChannels; - - /* Handle flags. */ - if (pDevSrcIn->fFlags & PDMAUDIODEV_FLAGS_DEFAULT) - pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT; - /** @todo Handle hot plugging? */ - - /* - * Now search through the list of all found output devices and check if we found - * an output device with the same device ID as the currently handled input device. - * - * If found, this means we have to treat that device as a duplex device then. - */ - PPDMAUDIODEVICE pDevSrcOut; - RTListForEach(&devEnmOut.lstDevices, pDevSrcOut, PDMAUDIODEVICE, Node) - { - PCOREAUDIODEVICEDATA pDevSrcOutData = (PCOREAUDIODEVICEDATA)pDevSrcOut->pvData; - AssertPtr(pDevSrcOutData); - - if (pDevSrcInData->deviceID == pDevSrcOutData->deviceID) - { - pDevDst->enmUsage = PDMAUDIODIR_ANY; - pDevDst->cMaxOutputChannels = pDevSrcOut->cMaxOutputChannels; - break; - } - } - - if (RT_SUCCESS(rc)) - { - rc = DrvAudioHlpDeviceEnumAdd(pEnmDst, pDevDst); - } - else - { - DrvAudioHlpDeviceFree(pDevDst); - pDevDst = NULL; - } - } - - if (RT_SUCCESS(rc)) - { - /* - * As a last step, add all remaining output devices which have not been handled in the loop above, - * that is, all output devices which operate in simplex mode. - */ - PPDMAUDIODEVICE pDevSrcOut; - RTListForEach(&devEnmOut.lstDevices, pDevSrcOut, PDMAUDIODEVICE, Node) - { - PCOREAUDIODEVICEDATA pDevSrcOutData = (PCOREAUDIODEVICEDATA)pDevSrcOut->pvData; - AssertPtr(pDevSrcOutData); - - if (coreAudioDevicesHasDevice(pEnmDst, pDevSrcOutData->deviceID)) - continue; /* Already in our list, skip. */ - - PPDMAUDIODEVICE pDevDst = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA)); - if (!pDevDst) - { - rc = VERR_NO_MEMORY; - break; - } - - PCOREAUDIODEVICEDATA pDevDstData = (PCOREAUDIODEVICEDATA)pDevDst->pvData; - AssertPtr(pDevDstData); - coreAudioDeviceDataInit(pDevDstData, pDevSrcOutData->deviceID, false /* fIsInput */, pThis); - - RTStrCopy(pDevDst->szName, sizeof(pDevDst->szName), pDevSrcOut->szName); - - pDevDst->enmUsage = PDMAUDIODIR_OUT; - pDevDst->cMaxOutputChannels = pDevSrcOut->cMaxOutputChannels; - - pDevDstData->deviceID = pDevSrcOutData->deviceID; - - /* Handle flags. */ - if (pDevSrcOut->fFlags & PDMAUDIODEV_FLAGS_DEFAULT) - pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT; - /** @todo Handle hot plugging? */ - - rc = DrvAudioHlpDeviceEnumAdd(pEnmDst, pDevDst); - if (RT_FAILURE(rc)) - { - DrvAudioHlpDeviceFree(pDevDst); - break; - } - } - } - - if (RT_FAILURE(rc)) - DrvAudioHlpDeviceEnumFree(pEnmDst); - } - - DrvAudioHlpDeviceEnumFree(&devEnmOut); - } - - DrvAudioHlpDeviceEnumFree(&devEnmIn); - } - -#ifdef DEBUG - if (RT_SUCCESS(rc)) - DrvAudioHlpDeviceEnumPrint("Core Audio (Final)", pEnmDst); -#endif - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * Initializes a Core Audio-specific device data structure. - * - * @returns IPRT status code. - * @param pDevData Device data structure to initialize. - * @param deviceID Core Audio device ID to assign this structure to. - * @param fIsInput Whether this is an input device or not. - * @param pDrv Driver instance to use. - */ -static void coreAudioDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID, bool fIsInput, PDRVHOSTCOREAUDIO pDrv) -{ - AssertPtrReturnVoid(pDevData); - AssertPtrReturnVoid(pDrv); - - pDevData->deviceID = deviceID; - pDevData->pDrv = pDrv; - - /* Get the device UUID. */ - AudioObjectPropertyAddress propAdrDevUUID = { kAudioDevicePropertyDeviceUID, - fIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster }; - UInt32 uSize = sizeof(pDevData->UUID); - OSStatus err = AudioObjectGetPropertyData(pDevData->deviceID, &propAdrDevUUID, 0, NULL, &uSize, &pDevData->UUID); - if (err != noErr) - LogRel(("CoreAudio: Failed to retrieve device UUID for device %RU32 (%RI32)\n", deviceID, err)); - - RTListInit(&pDevData->lstStreams); -} - - -/** - * Propagates an audio device status to all its connected Core Audio streams. - * - * @return IPRT status code. - * @param pDev Audio device to propagate status for. - * @param enmSts Status to propagate. - */ -static int coreAudioDevicePropagateStatus(PPDMAUDIODEVICE pDev, COREAUDIOSTATUS enmSts) -{ - AssertPtrReturn(pDev, VERR_INVALID_POINTER); - - PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pDev->pvData; - AssertPtrReturn(pDevData, VERR_INVALID_POINTER); - - /* Sanity. */ - AssertPtr(pDevData->pDrv); - - LogFlowFunc(("pDev=%p, pDevData=%p, enmSts=%RU32\n", pDev, pDevData, enmSts)); - - PCOREAUDIOSTREAM pCAStream; - RTListForEach(&pDevData->lstStreams, pCAStream, COREAUDIOSTREAM, Node) - { - LogFlowFunc(("pCAStream=%p\n", pCAStream)); - - /* We move the reinitialization to the next output event. - * This make sure this thread isn't blocked and the - * reinitialization is done when necessary only. */ - ASMAtomicXchgU32(&pCAStream->enmStatus, enmSts); - } - - return VINF_SUCCESS; -} - - -static DECLCALLBACK(OSStatus) coreAudioDeviceStateChangedCb(AudioObjectID propertyID, - UInt32 nAddresses, - const AudioObjectPropertyAddress properties[], - void *pvUser) -{ - RT_NOREF(propertyID, nAddresses, properties); - - LogFlowFunc(("propertyID=%u, nAddresses=%u, pvUser=%p\n", propertyID, nAddresses, pvUser)); - - PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)pvUser; - AssertPtr(pDev); - - PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; - AssertPtrReturn(pData, VERR_INVALID_POINTER); - - PDRVHOSTCOREAUDIO pThis = pData->pDrv; - AssertPtr(pThis); - - int rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); - - UInt32 uAlive = 1; - UInt32 uSize = sizeof(UInt32); - - AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - AudioDeviceID deviceID = pData->deviceID; - - OSStatus err = AudioObjectGetPropertyData(deviceID, &propAdr, 0, NULL, &uSize, &uAlive); - - bool fIsDead = false; - - if (err == kAudioHardwareBadDeviceError) - fIsDead = true; /* Unplugged. */ - else if ((err == kAudioHardwareNoError) && (!RT_BOOL(uAlive))) - fIsDead = true; /* Something else happened. */ - - if (fIsDead) - { - LogRel2(("CoreAudio: Device '%s' stopped functioning\n", pDev->szName)); - - /* Mark device as dead. */ - rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_UNINIT); - AssertRC(rc2); - } - - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); - - return noErr; -} - -/* Callback for getting notified when the default recording/playback device has been changed. */ -static DECLCALLBACK(OSStatus) coreAudioDefaultDeviceChangedCb(AudioObjectID propertyID, - UInt32 nAddresses, - const AudioObjectPropertyAddress properties[], - void *pvUser) -{ - RT_NOREF(propertyID, nAddresses); - - LogFlowFunc(("propertyID=%u, nAddresses=%u, pvUser=%p\n", propertyID, nAddresses, pvUser)); - - PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser; - AssertPtr(pThis); - - int rc2 = RTCritSectEnter(&pThis->CritSect); - AssertRC(rc2); - - for (UInt32 idxAddress = 0; idxAddress < nAddresses; idxAddress++) - { - PPDMAUDIODEVICE pDev = NULL; - - /* - * Check if the default input / output device has been changed. - */ - const AudioObjectPropertyAddress *pProperty = &properties[idxAddress]; - switch (pProperty->mSelector) - { - case kAudioHardwarePropertyDefaultInputDevice: - LogFlowFunc(("kAudioHardwarePropertyDefaultInputDevice\n")); - pDev = pThis->pDefaultDevIn; - break; - - case kAudioHardwarePropertyDefaultOutputDevice: - LogFlowFunc(("kAudioHardwarePropertyDefaultOutputDevice\n")); - pDev = pThis->pDefaultDevOut; - break; - - default: - /* Skip others. */ - break; - } - - LogFlowFunc(("pDev=%p\n", pDev)); - -#ifndef VBOX_WITH_AUDIO_CALLBACKS - if (pDev) - { - PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; - AssertPtr(pData); - - /* This listener is called on every change of the hardware - * device. So check if the default device has really changed. */ - UInt32 uSize = sizeof(AudioDeviceID); - UInt32 uResp = 0; - - OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, pProperty, 0, NULL, &uSize, &uResp); - if (err == noErr) - { - if (pData->deviceID != uResp) /* Has the device ID changed? */ - { - rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT); - AssertRC(rc2); - } - } - } -#endif /* VBOX_WITH_AUDIO_CALLBACKS */ - } - -#ifdef VBOX_WITH_AUDIO_CALLBACKS - PFNPDMHOSTAUDIOCALLBACK pfnCallback = pThis->pfnCallback; -#endif - - /* Make sure to leave the critical section before calling the callback. */ - rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); - -#ifdef VBOX_WITH_AUDIO_CALLBACKS - if (pfnCallback) - /* Ignore rc */ pfnCallback(pThis->pDrvIns, PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED, NULL, 0); -#endif - - return noErr; -} - -#ifndef VBOX_WITH_AUDIO_CALLBACKS -/** - * Re-initializes a Core Audio stream with a specific audio device and stream configuration. - * - * @return IPRT status code. - * @param pThis Driver instance. - * @param pCAStream Audio stream to re-initialize. - * @param pDev Audio device to use for re-initialization. - * @param pCfg Stream configuration to use for re-initialization. - */ -static int coreAudioStreamReinitEx(PDRVHOSTCOREAUDIO pThis, - PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev, PPDMAUDIOSTREAMCFG pCfg) -{ - LogFunc(("pCAStream=%p\n", pCAStream)); - - int rc = coreAudioStreamUninit(pCAStream); - if (RT_SUCCESS(rc)) - { - rc = coreAudioStreamInit(pCAStream, pThis, pDev); - if (RT_SUCCESS(rc)) - { - rc = coreAudioStreamInitQueue(pCAStream, pCfg /* pCfgReq */, NULL /* pCfgAcq */); - if (RT_SUCCESS(rc)) - rc = coreAudioStreamControl(pCAStream->pDrv, pCAStream, PDMAUDIOSTREAMCMD_ENABLE); - - if (RT_FAILURE(rc)) - { - int rc2 = coreAudioStreamUninit(pCAStream); - AssertRC(rc2); - } - } - } - - if (RT_FAILURE(rc)) - LogRel(("CoreAudio: Unable to re-init stream: %Rrc\n", rc)); - - return rc; -} - -/** - * Re-initializes a Core Audio stream with a specific audio device. - * - * @return IPRT status code. - * @param pThis Driver instance. - * @param pCAStream Audio stream to re-initialize. - * @param pDev Audio device to use for re-initialization. - */ -static int coreAudioStreamReinit(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev) -{ - int rc = coreAudioStreamUninit(pCAStream); - if (RT_SUCCESS(rc)) - { - /* Use the acquired stream configuration from the former initialization to - * re-initialize the stream. */ - PDMAUDIOSTREAMCFG CfgAcq; - rc = coreAudioASBDToStreamCfg(&pCAStream->Unit.streamFmt, &CfgAcq); - if (RT_SUCCESS(rc)) - rc = coreAudioStreamReinitEx(pThis, pCAStream, pDev, &CfgAcq); - } - - return rc; -} -#endif /* !VBOX_WITH_AUDIO_CALLBACKS */ - -#ifdef VBOX_WITH_AUDIO_CA_CONVERTER -/* Callback to convert audio input data from one format to another. */ -static DECLCALLBACK(OSStatus) coreAudioConverterCb(AudioConverterRef inAudioConverter, - UInt32 *ioNumberDataPackets, - AudioBufferList *ioData, - AudioStreamPacketDescription **ppASPD, - void *pvUser) -{ - RT_NOREF(inAudioConverter); - - AssertPtrReturn(ioNumberDataPackets, caConverterEOFDErr); - AssertPtrReturn(ioData, caConverterEOFDErr); - - PCOREAUDIOCONVCBCTX pConvCbCtx = (PCOREAUDIOCONVCBCTX)pvUser; - AssertPtr(pConvCbCtx); - - /* Initialize values. */ - ioData->mBuffers[0].mNumberChannels = 0; - ioData->mBuffers[0].mDataByteSize = 0; - ioData->mBuffers[0].mData = NULL; - - if (ppASPD) - { - Log3Func(("Handling packet description not implemented\n")); - } - else - { - /** @todo Check converter ID? */ - - /** @todo Handled non-interleaved data by going through the full buffer list, - * not only through the first buffer like we do now. */ - Log3Func(("ioNumberDataPackets=%RU32\n", *ioNumberDataPackets)); - - UInt32 cNumberDataPackets = *ioNumberDataPackets; - Assert(pConvCbCtx->uPacketIdx + cNumberDataPackets <= pConvCbCtx->uPacketCnt); - - if (cNumberDataPackets) - { - AssertPtr(pConvCbCtx->pBufLstSrc); - Assert(pConvCbCtx->pBufLstSrc->mNumberBuffers == 1); /* Only one buffer for the source supported atm. */ - - AudioStreamBasicDescription *pSrcASBD = &pConvCbCtx->asbdSrc; - AudioBuffer *pSrcBuf = &pConvCbCtx->pBufLstSrc->mBuffers[0]; - - size_t cbOff = pConvCbCtx->uPacketIdx * pSrcASBD->mBytesPerPacket; - - cNumberDataPackets = RT_MIN((pSrcBuf->mDataByteSize - cbOff) / pSrcASBD->mBytesPerPacket, - cNumberDataPackets); - - void *pvAvail = (uint8_t *)pSrcBuf->mData + cbOff; - size_t cbAvail = RT_MIN(pSrcBuf->mDataByteSize - cbOff, cNumberDataPackets * pSrcASBD->mBytesPerPacket); - - Log3Func(("cNumberDataPackets=%RU32, cbOff=%zu, cbAvail=%zu\n", cNumberDataPackets, cbOff, cbAvail)); - - /* Set input data for the converter to use. - * Note: For VBR (Variable Bit Rates) or interleaved data handling we need multiple buffers here. */ - ioData->mNumberBuffers = 1; - - ioData->mBuffers[0].mNumberChannels = pSrcBuf->mNumberChannels; - ioData->mBuffers[0].mDataByteSize = cbAvail; - ioData->mBuffers[0].mData = pvAvail; - -#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA - RTFILE fh; - int rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - if (RT_SUCCESS(rc)) - { - RTFileWrite(fh, pvAvail, cbAvail, NULL); - RTFileClose(fh); - } - else - AssertFailed(); -#endif - pConvCbCtx->uPacketIdx += cNumberDataPackets; - Assert(pConvCbCtx->uPacketIdx <= pConvCbCtx->uPacketCnt); - - *ioNumberDataPackets = cNumberDataPackets; - } - } - - Log3Func(("%RU32 / %RU32 -> ioNumberDataPackets=%RU32\n", - pConvCbCtx->uPacketIdx, pConvCbCtx->uPacketCnt, *ioNumberDataPackets)); - - return noErr; -} -#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */ - - -/** - * Initializes a Core Audio stream. - * - * @return IPRT status code. - * @param pThis Driver instance. - * @param pCAStream Stream to initialize. - * @param pDev Audio device to use for this stream. - */ -static int coreAudioStreamInit(PCOREAUDIOSTREAM pCAStream, PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev) -{ - AssertPtrReturn(pCAStream, VERR_INVALID_POINTER); - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pDev, VERR_INVALID_POINTER); - - Assert(pCAStream->Unit.pDevice == NULL); /* Make sure no device is assigned yet. */ - AssertPtr(pDev->pvData); - Assert(pDev->cbData == sizeof(COREAUDIODEVICEDATA)); - -#ifdef DEBUG - PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; - LogFunc(("pCAStream=%p, pDev=%p ('%s', ID=%RU32)\n", pCAStream, pDev, pDev->szName, pData->deviceID)); -#endif - - pCAStream->Unit.pDevice = pDev; - pCAStream->pDrv = pThis; - - return VINF_SUCCESS; -} - -# define CA_BREAK_STMT(stmt) \ - stmt; \ - break; - -/** - * Thread for a Core Audio stream's audio queue handling. - * - * This thread is required per audio queue to pump data to/from the Core Audio - * stream and handling its callbacks. - * - * @returns IPRT status code. - * @param hThreadSelf Thread handle. - * @param pvUser User argument. - */ -static DECLCALLBACK(int) coreAudioQueueThread(RTTHREAD hThreadSelf, void *pvUser) -{ - RT_NOREF(hThreadSelf); - - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser; - AssertPtr(pCAStream); - AssertPtr(pCAStream->pCfg); - - const bool fIn = pCAStream->pCfg->enmDir == PDMAUDIODIR_IN; - - LogFunc(("Thread started for pCAStream=%p, fIn=%RTbool\n", pCAStream, fIn)); - - /* - * Create audio queue. - */ - OSStatus err; - if (fIn) - err = AudioQueueNewInput(&pCAStream->asbdStream, coreAudioInputQueueCb, pCAStream /* pvData */, - CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pCAStream->audioQueue); - else - err = AudioQueueNewOutput(&pCAStream->asbdStream, coreAudioOutputQueueCb, pCAStream /* pvData */, - CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pCAStream->audioQueue); - - if (err != noErr) - return VERR_GENERAL_FAILURE; /** @todo Fudge! */ - - /* - * Assign device to queue. - */ - PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pCAStream->Unit.pDevice->pvData; - AssertPtr(pData); - - UInt32 uSize = sizeof(pData->UUID); - err = AudioQueueSetProperty(pCAStream->audioQueue, kAudioQueueProperty_CurrentDevice, &pData->UUID, uSize); - if (err != noErr) - return VERR_GENERAL_FAILURE; /** @todo Fudge! */ - - const size_t cbBufSize = DrvAudioHlpFramesToBytes(pCAStream->pCfg->Backend.cFramesPeriod, &pCAStream->pCfg->Props); - - /* - * Allocate audio buffers. - */ - for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++) - { - err = AudioQueueAllocateBuffer(pCAStream->audioQueue, cbBufSize, &pCAStream->audioBuffer[i]); - if (err != noErr) - break; - } - - if (err != noErr) - return VERR_GENERAL_FAILURE; /** @todo Fudge! */ - - /* Signal the main thread before entering the main loop. */ - RTThreadUserSignal(RTThreadSelf()); - - /* - * Enter the main loop. - */ - while (!ASMAtomicReadBool(&pCAStream->fShutdown)) - { - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); - } - - /* - * Cleanup. - */ - if (fIn) - { - AudioQueueStop(pCAStream->audioQueue, 1); - } - else - { - AudioQueueStop(pCAStream->audioQueue, 0); - } - - for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++) - { - if (pCAStream->audioBuffer[i]) - AudioQueueFreeBuffer(pCAStream->audioQueue, pCAStream->audioBuffer[i]); - } - - AudioQueueDispose(pCAStream->audioQueue, 1); - - LogFunc(("Thread ended for pCAStream=%p, fIn=%RTbool\n", pCAStream, fIn)); - return VINF_SUCCESS; -} - -/** - * Processes input data of an audio queue buffer and stores it into a Core Audio stream. - * - * @returns IPRT status code. - * @param pCAStream Core Audio stream to store input data into. - * @param audioBuffer Audio buffer to process input data from. - */ -int coreAudioInputQueueProcBuffer(PCOREAUDIOSTREAM pCAStream, AudioQueueBufferRef audioBuffer) -{ - PRTCIRCBUF pCircBuf = pCAStream->pCircBuf; - AssertPtr(pCircBuf); - - UInt8 *pvSrc = (UInt8 *)audioBuffer->mAudioData; - UInt8 *pvDst = NULL; - - size_t cbWritten = 0; - - size_t cbToWrite = audioBuffer->mAudioDataByteSize; - size_t cbLeft = RT_MIN(cbToWrite, RTCircBufFree(pCircBuf)); - - while (cbLeft) - { - /* Try to acquire the necessary block from the ring buffer. */ - RTCircBufAcquireWriteBlock(pCircBuf, cbLeft, (void **)&pvDst, &cbToWrite); - - if (!cbToWrite) - break; - - /* Copy the data from our ring buffer to the core audio buffer. */ - memcpy((UInt8 *)pvDst, pvSrc + cbWritten, cbToWrite); - - /* Release the read buffer, so it could be used for new data. */ - RTCircBufReleaseWriteBlock(pCircBuf, cbToWrite); - - cbWritten += cbToWrite; - - Assert(cbLeft >= cbToWrite); - cbLeft -= cbToWrite; - } - - Log3Func(("pCAStream=%p, cbBuffer=%RU32/%zu, cbWritten=%zu\n", - pCAStream, audioBuffer->mAudioDataByteSize, audioBuffer->mAudioDataBytesCapacity, cbWritten)); - - return VINF_SUCCESS; -} - -/** - * Input audio queue callback. Called whenever input data from the audio queue becomes available. - * - * @param pvUser User argument. - * @param audioQueue Audio queue to process input data from. - * @param audioBuffer Audio buffer to process input data from. Must be part of audio queue. - * @param pAudioTS Audio timestamp. - * @param cPacketDesc Number of packet descriptors. - * @param paPacketDesc Array of packet descriptors. - */ -static DECLCALLBACK(void) coreAudioInputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer, - const AudioTimeStamp *pAudioTS, - UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc) -{ - RT_NOREF(audioQueue, pAudioTS, cPacketDesc, paPacketDesc); - - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser; - AssertPtr(pCAStream); - - int rc = RTCritSectEnter(&pCAStream->CritSect); - AssertRC(rc); - - rc = coreAudioInputQueueProcBuffer(pCAStream, audioBuffer); - if (RT_SUCCESS(rc)) - AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL); - - rc = RTCritSectLeave(&pCAStream->CritSect); - AssertRC(rc); -} - -/** - * Processes output data of a Core Audio stream into an audio queue buffer. - * - * @returns IPRT status code. - * @param pCAStream Core Audio stream to process output data for. - * @param audioBuffer Audio buffer to store data into. - */ -int coreAudioOutputQueueProcBuffer(PCOREAUDIOSTREAM pCAStream, AudioQueueBufferRef audioBuffer) -{ - AssertPtr(pCAStream); - - PRTCIRCBUF pCircBuf = pCAStream->pCircBuf; - AssertPtr(pCircBuf); - - size_t cbRead = 0; - - UInt8 *pvSrc = NULL; - UInt8 *pvDst = (UInt8 *)audioBuffer->mAudioData; - - size_t cbToRead = RT_MIN(RTCircBufUsed(pCircBuf), audioBuffer->mAudioDataBytesCapacity); - size_t cbLeft = cbToRead; - - while (cbLeft) - { - /* Try to acquire the necessary block from the ring buffer. */ - RTCircBufAcquireReadBlock(pCircBuf, cbLeft, (void **)&pvSrc, &cbToRead); - - if (cbToRead) - { - /* Copy the data from our ring buffer to the core audio buffer. */ - memcpy((UInt8 *)pvDst + cbRead, pvSrc, cbToRead); - } - - /* Release the read buffer, so it could be used for new data. */ - RTCircBufReleaseReadBlock(pCircBuf, cbToRead); - - if (!cbToRead) - break; - - /* Move offset. */ - cbRead += cbToRead; - Assert(cbRead <= audioBuffer->mAudioDataBytesCapacity); - - Assert(cbToRead <= cbLeft); - cbLeft -= cbToRead; - } - - audioBuffer->mAudioDataByteSize = cbRead; - - if (audioBuffer->mAudioDataByteSize < audioBuffer->mAudioDataBytesCapacity) - { - RT_BZERO((UInt8 *)audioBuffer->mAudioData + audioBuffer->mAudioDataByteSize, - audioBuffer->mAudioDataBytesCapacity - audioBuffer->mAudioDataByteSize); - - audioBuffer->mAudioDataByteSize = audioBuffer->mAudioDataBytesCapacity; - } - - Log3Func(("pCAStream=%p, cbCapacity=%RU32, cbRead=%zu\n", - pCAStream, audioBuffer->mAudioDataBytesCapacity, cbRead)); - - return VINF_SUCCESS; -} - -/** - * Output audio queue callback. Called whenever an audio queue is ready to process more output data. - * - * @param pvUser User argument. - * @param audioQueue Audio queue to process output data for. - * @param audioBuffer Audio buffer to store output data in. Must be part of audio queue. - */ -static DECLCALLBACK(void) coreAudioOutputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer) -{ - RT_NOREF(audioQueue); - - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser; - AssertPtr(pCAStream); - - int rc = RTCritSectEnter(&pCAStream->CritSect); - AssertRC(rc); - - rc = coreAudioOutputQueueProcBuffer(pCAStream, audioBuffer); - if (RT_SUCCESS(rc)) - AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL); - - rc = RTCritSectLeave(&pCAStream->CritSect); - AssertRC(rc); -} - -/** - * Invalidates a Core Audio stream's audio queue. - * - * @returns IPRT status code. - * @param pCAStream Core Audio stream to invalidate its queue for. - */ -static int coreAudioStreamInvalidateQueue(PCOREAUDIOSTREAM pCAStream) -{ - int rc = VINF_SUCCESS; - - Log3Func(("pCAStream=%p\n", pCAStream)); - - for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++) - { - AudioQueueBufferRef pBuf = pCAStream->audioBuffer[i]; - - if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN) - { - int rc2 = coreAudioInputQueueProcBuffer(pCAStream, pBuf); - if (RT_SUCCESS(rc2)) - { - AudioQueueEnqueueBuffer(pCAStream->audioQueue, pBuf, 0, NULL); - } - } - else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT) - { - int rc2 = coreAudioOutputQueueProcBuffer(pCAStream, pBuf); - if ( RT_SUCCESS(rc2) - && pBuf->mAudioDataByteSize) - { - AudioQueueEnqueueBuffer(pCAStream->audioQueue, pBuf, 0, NULL); - } - - if (RT_SUCCESS(rc)) - rc = rc2; - } - else - AssertFailed(); - } - - return rc; -} - -/** - * Initializes a Core Audio stream's audio queue. - * - * @returns IPRT status code. - * @param pCAStream Core Audio stream to initialize audio queue for. - * @param pCfgReq Requested stream configuration. - * @param pCfgAcq Acquired stream configuration on success. - */ -static int coreAudioStreamInitQueue(PCOREAUDIOSTREAM pCAStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - RT_NOREF(pCfgAcq); - - LogFunc(("pCAStream=%p, pCfgReq=%p, pCfgAcq=%p\n", pCAStream, pCfgReq, pCfgAcq)); - - /* No device assigned? Bail out early. */ - if (pCAStream->Unit.pDevice == NULL) - return VERR_NOT_AVAILABLE; - - const bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN; - - int rc = VINF_SUCCESS; - - if (fIn) - { - rc = coreAudioInputPermissionCheck(); - if (RT_FAILURE(rc)) - return rc; - } - - /* Create the recording device's out format based on our required audio settings. */ - Assert(pCAStream->pCfg == NULL); - pCAStream->pCfg = DrvAudioHlpStreamCfgDup(pCfgReq); - if (!pCAStream->pCfg) - rc = VERR_NO_MEMORY; - - coreAudioPCMPropsToASBD(&pCfgReq->Props, &pCAStream->asbdStream); - /** @todo Do some validation? */ - - coreAudioPrintASBD( fIn - ? "Capturing queue format" - : "Playback queue format", &pCAStream->asbdStream); - - if (RT_FAILURE(rc)) - { - LogRel(("CoreAudio: Failed to convert requested %s format to native format (%Rrc)\n", fIn ? "input" : "output", rc)); - return rc; - } - - rc = RTCircBufCreate(&pCAStream->pCircBuf, PDMAUDIOSTREAMCFG_F2B(pCfgReq, pCfgReq->Backend.cFramesBufferSize)); - if (RT_FAILURE(rc)) - return rc; - - /* - * Start the thread. - */ - rc = RTThreadCreate(&pCAStream->hThread, coreAudioQueueThread, - pCAStream /* pvUser */, 0 /* Default stack size */, - RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CAQUEUE"); - if (RT_SUCCESS(rc)) - rc = RTThreadUserWait(pCAStream->hThread, 10 * 1000 /* 10s timeout */); - - LogFunc(("Returning %Rrc\n", rc)); - return rc; -} - -/** - * Unitializes a Core Audio stream's audio queue. - * - * @returns IPRT status code. - * @param pCAStream Core Audio stream to unitialize audio queue for. - */ -static int coreAudioStreamUninitQueue(PCOREAUDIOSTREAM pCAStream) -{ - LogFunc(("pCAStream=%p\n", pCAStream)); - - if (pCAStream->hThread != NIL_RTTHREAD) - { - LogFunc(("Waiting for thread ...\n")); - - ASMAtomicXchgBool(&pCAStream->fShutdown, true); - - int rcThread; - int rc = RTThreadWait(pCAStream->hThread, 30 * 1000, &rcThread); - if (RT_FAILURE(rc)) - return rc; - - RT_NOREF(rcThread); - LogFunc(("Thread stopped with %Rrc\n", rcThread)); - - pCAStream->hThread = NIL_RTTHREAD; - } - - if (pCAStream->pCfg) - { - DrvAudioHlpStreamCfgFree(pCAStream->pCfg); - pCAStream->pCfg = NULL; - } - - if (pCAStream->pCircBuf) - { - RTCircBufDestroy(pCAStream->pCircBuf); - pCAStream->pCircBuf = NULL; - } - - LogFunc(("Returning\n")); - return VINF_SUCCESS; -} - -/** - * Unitializes a Core Audio stream. - * - * @returns IPRT status code. - * @param pCAStream Core Audio stream to uninitialize. - */ -static int coreAudioStreamUninit(PCOREAUDIOSTREAM pCAStream) -{ - LogFunc(("pCAStream=%p\n", pCAStream)); - - int rc = coreAudioStreamUninitQueue(pCAStream); - if (RT_SUCCESS(rc)) - { - pCAStream->Unit.pDevice = NULL; - pCAStream->pDrv = NULL; - } - - return rc; -} - -/** - * Registers callbacks for a specific Core Audio device. - * - * @return IPRT status code. - * @param pThis Host audio driver instance. - * @param pDev Audio device to use for the registered callbacks. - */ -static int coreAudioDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev) -{ - RT_NOREF(pThis); - - AudioDeviceID deviceID = kAudioDeviceUnknown; - - PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; - if (pData) - deviceID = pData->deviceID; - - if (deviceID != kAudioDeviceUnknown) - { - LogFunc(("deviceID=%RU32\n", deviceID)); - - /* - * Register device callbacks. - */ - AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - OSStatus err = AudioObjectAddPropertyListener(deviceID, &propAdr, - coreAudioDeviceStateChangedCb, pDev /* pvUser */); - if ( err != noErr - && err != kAudioHardwareIllegalOperationError) - { - LogRel(("CoreAudio: Failed to add the recording device state changed listener (%RI32)\n", err)); - } - - propAdr.mSelector = kAudioDeviceProcessorOverload; - propAdr.mScope = kAudioUnitScope_Global; - err = AudioObjectAddPropertyListener(deviceID, &propAdr, - coreAudioDevPropChgCb, pDev /* pvUser */); - if (err != noErr) - LogRel(("CoreAudio: Failed to register processor overload listener (%RI32)\n", err)); - - propAdr.mSelector = kAudioDevicePropertyNominalSampleRate; - propAdr.mScope = kAudioUnitScope_Global; - err = AudioObjectAddPropertyListener(deviceID, &propAdr, - coreAudioDevPropChgCb, pDev /* pvUser */); - if (err != noErr) - LogRel(("CoreAudio: Failed to register sample rate changed listener (%RI32)\n", err)); - } - - return VINF_SUCCESS; -} - -/** - * Unregisters all formerly registered callbacks of a Core Audio device again. - * - * @return IPRT status code. - * @param pThis Host audio driver instance. - * @param pDev Audio device to use for the registered callbacks. - */ -static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev) -{ - RT_NOREF(pThis); - - AudioDeviceID deviceID = kAudioDeviceUnknown; - - if (pDev) - { - PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; - if (pData) - deviceID = pData->deviceID; - } - - if (deviceID != kAudioDeviceUnknown) - { - LogFunc(("deviceID=%RU32\n", deviceID)); - - /* - * Unregister per-device callbacks. - */ - AudioObjectPropertyAddress propAdr = { kAudioDeviceProcessorOverload, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - OSStatus err = AudioObjectRemovePropertyListener(deviceID, &propAdr, - coreAudioDevPropChgCb, pDev /* pvUser */); - if ( err != noErr - && err != kAudioHardwareBadObjectError) - { - LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%RI32)\n", err)); - } - - propAdr.mSelector = kAudioDevicePropertyNominalSampleRate; - err = AudioObjectRemovePropertyListener(deviceID, &propAdr, - coreAudioDevPropChgCb, pDev /* pvUser */); - if ( err != noErr - && err != kAudioHardwareBadObjectError) - { - LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err)); - } - - propAdr.mSelector = kAudioDevicePropertyDeviceIsAlive; - err = AudioObjectRemovePropertyListener(deviceID, &propAdr, - coreAudioDeviceStateChangedCb, pDev /* pvUser */); - if ( err != noErr - && err != kAudioHardwareBadObjectError) - { - LogRel(("CoreAudio: Failed to remove the device alive listener (%RI32)\n", err)); - } - } - - return VINF_SUCCESS; -} - -/* Callback for getting notified when some of the properties of an audio device have changed. */ -static DECLCALLBACK(OSStatus) coreAudioDevPropChgCb(AudioObjectID propertyID, - UInt32 cAddresses, - const AudioObjectPropertyAddress properties[], - void *pvUser) -{ - RT_NOREF(cAddresses, properties, pvUser); - - PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)pvUser; - AssertPtr(pDev); - - LogFlowFunc(("propertyID=%u, nAddresses=%u, pDev=%p\n", propertyID, cAddresses, pDev)); - - switch (propertyID) - { -#ifdef DEBUG - case kAudioDeviceProcessorOverload: - { - LogFunc(("Processor overload detected!\n")); - break; - } -#endif /* DEBUG */ - case kAudioDevicePropertyNominalSampleRate: - { -#ifndef VBOX_WITH_AUDIO_CALLBACKS - int rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT); - AssertRC(rc2); -#else - RT_NOREF(pDev); -#endif - break; - } - - default: - /* Just skip. */ - break; - } - - return noErr; -} - -/** - * Enumerates all available host audio devices internally. - * - * @returns IPRT status code. - * @param pThis Host audio driver instance. - */ -static int coreAudioEnumerateDevices(PDRVHOSTCOREAUDIO pThis) -{ - LogFlowFuncEnter(); - - /* - * Unregister old default devices, if any. - */ - if (pThis->pDefaultDevIn) - { - coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevIn); - pThis->pDefaultDevIn = NULL; - } - - if (pThis->pDefaultDevOut) - { - coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevOut); - pThis->pDefaultDevOut = NULL; - } - - /* Remove old / stale device entries. */ - DrvAudioHlpDeviceEnumFree(&pThis->Devices); - - /* Enumerate all devices internally. */ - int rc = coreAudioDevicesEnumerateAll(pThis, &pThis->Devices); - if (RT_SUCCESS(rc)) - { - /* - * Default input device. - */ - pThis->pDefaultDevIn = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_IN); - if (pThis->pDefaultDevIn) - { - LogRel2(("CoreAudio: Default capturing device is '%s'\n", pThis->pDefaultDevIn->szName)); - -#ifdef DEBUG - PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pThis->pDefaultDevIn->pvData; - AssertPtr(pDevData); - LogFunc(("pDefaultDevIn=%p, ID=%RU32\n", pThis->pDefaultDevIn, pDevData->deviceID)); -#endif - rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevIn); - } - else - LogRel2(("CoreAudio: No default capturing device found\n")); - - /* - * Default output device. - */ - pThis->pDefaultDevOut = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_OUT); - if (pThis->pDefaultDevOut) - { - LogRel2(("CoreAudio: Default playback device is '%s'\n", pThis->pDefaultDevOut->szName)); - -#ifdef DEBUG - PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pThis->pDefaultDevOut->pvData; - AssertPtr(pDevData); - LogFunc(("pDefaultDevOut=%p, ID=%RU32\n", pThis->pDefaultDevOut, pDevData->deviceID)); -#endif - rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevOut); - } - else - LogRel2(("CoreAudio: No default playback device found\n")); - } - - LogFunc(("Returning %Rrc\n", rc)); - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - /* puRead is optional. */ - - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - -#ifndef VBOX_WITH_AUDIO_CALLBACKS - /* Check if the audio device should be reinitialized. If so do it. */ - if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT) - { - /* For now re just re-initialize with the current input device. */ - if (pThis->pDefaultDevIn) - { - int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevIn); - if (RT_FAILURE(rc2)) - return VERR_NOT_AVAILABLE; - } - else - return VERR_NOT_AVAILABLE; - } -#else - RT_NOREF(pThis); -#endif - - if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT) - { - if (puRead) - *puRead = 0; - return VINF_SUCCESS; - } - - int rc = VINF_SUCCESS; - - uint32_t cbReadTotal = 0; - - rc = RTCritSectEnter(&pCAStream->CritSect); - AssertRC(rc); - - do - { - size_t cbToWrite = RT_MIN(uBufSize, RTCircBufUsed(pCAStream->pCircBuf)); - - uint8_t *pvChunk; - size_t cbChunk; - - Log3Func(("cbToWrite=%zu/%zu\n", cbToWrite, RTCircBufSize(pCAStream->pCircBuf))); - - while (cbToWrite) - { - /* Try to acquire the necessary block from the ring buffer. */ - RTCircBufAcquireReadBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk); - if (cbChunk) - memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk); - - /* Release the read buffer, so it could be used for new data. */ - RTCircBufReleaseReadBlock(pCAStream->pCircBuf, cbChunk); - - if (RT_FAILURE(rc)) - break; - - Assert(cbToWrite >= cbChunk); - cbToWrite -= cbChunk; - - cbReadTotal += cbChunk; - } - } - while (0); - - int rc2 = RTCritSectLeave(&pCAStream->CritSect); - AssertRC(rc2); - - if (RT_SUCCESS(rc)) - { - if (puRead) - *puRead = cbReadTotal; - } - - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten) -{ - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; - -#ifndef VBOX_WITH_AUDIO_CALLBACKS - /* Check if the audio device should be reinitialized. If so do it. */ - if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT) - { - if (pThis->pDefaultDevOut) - { - /* For now re just re-initialize with the current output device. */ - int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevOut); - if (RT_FAILURE(rc2)) - return VERR_NOT_AVAILABLE; - } - else - return VERR_NOT_AVAILABLE; - } -#else - RT_NOREF(pThis); -#endif - - if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT) - { - if (puWritten) - *puWritten = 0; - return VINF_SUCCESS; - } - - uint32_t cbWrittenTotal = 0; - - int rc = VINF_SUCCESS; - - rc = RTCritSectEnter(&pCAStream->CritSect); - AssertRC(rc); - - size_t cbToWrite = RT_MIN(uBufSize, RTCircBufFree(pCAStream->pCircBuf)); - Log3Func(("cbToWrite=%zu\n", cbToWrite)); - - uint8_t *pvChunk; - size_t cbChunk; - - while (cbToWrite) - { - /* Try to acquire the necessary space from the ring buffer. */ - RTCircBufAcquireWriteBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk); - if (!cbChunk) - { - RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk); - break; - } - - Assert(cbChunk <= cbToWrite); - Assert(cbWrittenTotal + cbChunk <= uBufSize); - - memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk); - -#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA - RTFILE fh; - rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - if (RT_SUCCESS(rc)) - { - RTFileWrite(fh, pvChunk, cbChunk, NULL); - RTFileClose(fh); - } - else - AssertFailed(); -#endif - - /* Release the ring buffer, so the read thread could start reading this data. */ - RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk); - - if (RT_FAILURE(rc)) - break; - - Assert(cbToWrite >= cbChunk); - cbToWrite -= cbChunk; - - cbWrittenTotal += cbChunk; - } - - if ( RT_SUCCESS(rc) - && pCAStream->fRun - && !pCAStream->fIsRunning) - { - rc = coreAudioStreamInvalidateQueue(pCAStream); - if (RT_SUCCESS(rc)) - { - AudioQueueStart(pCAStream->audioQueue, NULL); - pCAStream->fRun = false; - pCAStream->fIsRunning = true; - } - } - - int rc2 = RTCritSectLeave(&pCAStream->CritSect); - AssertRC(rc2); - - if (RT_SUCCESS(rc)) - { - if (puWritten) - *puWritten = cbWrittenTotal; - } - - return rc; -} - -static DECLCALLBACK(int) coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, - PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - RT_NOREF(pThis); - - uint32_t enmStatus = ASMAtomicReadU32(&pCAStream->enmStatus); - - LogFlowFunc(("enmStreamCmd=%RU32, enmStatus=%RU32\n", enmStreamCmd, enmStatus)); - - if (!( enmStatus == COREAUDIOSTATUS_INIT -#ifndef VBOX_WITH_AUDIO_CALLBACKS - || enmStatus == COREAUDIOSTATUS_REINIT -#endif - )) - { - return VINF_SUCCESS; - } - - if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc = VINF_SUCCESS; - - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - case PDMAUDIOSTREAMCMD_RESUME: - { - LogFunc(("Queue enable\n")); - if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN) - { - rc = coreAudioStreamInvalidateQueue(pCAStream); - if (RT_SUCCESS(rc)) - { - /* Start the audio queue immediately. */ - AudioQueueStart(pCAStream->audioQueue, NULL); - } - } - else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT) - { - /* Touch the run flag to start the audio queue as soon as - * we have anough data to actually play something. */ - ASMAtomicXchgBool(&pCAStream->fRun, true); - } - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - { - LogFunc(("Queue disable\n")); - AudioQueueStop(pCAStream->audioQueue, 1 /* Immediately */); - ASMAtomicXchgBool(&pCAStream->fRun, false); - ASMAtomicXchgBool(&pCAStream->fIsRunning, false); - break; - } - case PDMAUDIOSTREAMCMD_PAUSE: - { - LogFunc(("Queue pause\n")); - AudioQueuePause(pCAStream->audioQueue); - ASMAtomicXchgBool(&pCAStream->fIsRunning, false); - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); - - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - - RT_BZERO(pBackendCfg, sizeof(PDMAUDIOBACKENDCFG)); - - RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio"); - - pBackendCfg->cbStreamIn = sizeof(COREAUDIOSTREAM); - pBackendCfg->cbStreamOut = sizeof(COREAUDIOSTREAM); - - /* For Core Audio we provide one stream per device for now. */ - pBackendCfg->cMaxStreamsIn = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_IN); - pBackendCfg->cMaxStreamsOut = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_OUT); - - LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS)); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIODEVICEENUM pDeviceEnum) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); - - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_SUCCESS(rc)) - { - rc = coreAudioEnumerateDevices(pThis); - if (RT_SUCCESS(rc)) - { - if (pDeviceEnum) - { - rc = DrvAudioHlpDeviceEnumInit(pDeviceEnum); - if (RT_SUCCESS(rc)) - rc = DrvAudioHlpDeviceEnumCopy(pDeviceEnum, &pThis->Devices); - - if (RT_FAILURE(rc)) - DrvAudioHlpDeviceEnumFree(pDeviceEnum); - } - } - - int rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); - } - - LogFlowFunc(("Returning %Rrc\n", rc)); - return rc; -} - - -#ifdef VBOX_WITH_AUDIO_CALLBACKS -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_SetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - /* pfnCallback will be handled below. */ - - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_SUCCESS(rc)) - { - LogFunc(("pfnCallback=%p\n", pfnCallback)); - - if (pfnCallback) /* Register. */ - { - Assert(pThis->pfnCallback == NULL); - pThis->pfnCallback = pfnCallback; - } - else /* Unregister. */ - { - if (pThis->pfnCallback) - pThis->pfnCallback = NULL; - } - - int rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); - } - - return rc; -} -#endif - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostCoreAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) -{ - RT_NOREF(pInterface, enmDir); - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); - - return PDMAUDIOBACKENDSTS_RUNNING; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; - - int rc = RTCritSectInit(&pCAStream->CritSect); - if (RT_FAILURE(rc)) - return rc; - - pCAStream->hThread = NIL_RTTHREAD; - pCAStream->fRun = false; - pCAStream->fIsRunning = false; - pCAStream->fShutdown = false; - - /* Input or output device? */ - bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN; - - /* For now, just use the default device available. */ - PPDMAUDIODEVICE pDev = fIn ? pThis->pDefaultDevIn : pThis->pDefaultDevOut; - - LogFunc(("pStream=%p, pCfgReq=%p, pCfgAcq=%p, fIn=%RTbool, pDev=%p\n", pStream, pCfgReq, pCfgAcq, fIn, pDev)); - - if (pDev) /* (Default) device available? */ - { - /* Sanity. */ - AssertPtr(pDev->pvData); - Assert(pDev->cbData); - - /* Init the Core Audio stream. */ - rc = coreAudioStreamInit(pCAStream, pThis, pDev); - if (RT_SUCCESS(rc)) - { - ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_INIT); - - rc = coreAudioStreamInitQueue(pCAStream, pCfgReq, pCfgAcq); - if (RT_SUCCESS(rc)) - { - ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_INIT); - } - else - { - ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT); - - int rc2 = coreAudioStreamUninit(pCAStream); - AssertRC(rc2); - - ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT); - } - } - } - else - rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; - - LogFunc(("Returning %Rrc\n", rc)); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; - - uint32_t status = ASMAtomicReadU32(&pCAStream->enmStatus); - if (!( status == COREAUDIOSTATUS_INIT -#ifndef VBOX_WITH_AUDIO_CALLBACKS - || status == COREAUDIOSTATUS_REINIT -#endif - )) - { - return VINF_SUCCESS; - } - - if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc = coreAudioStreamControl(pThis, pCAStream, PDMAUDIOSTREAMCMD_DISABLE); - if (RT_SUCCESS(rc)) - { - ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT); - - rc = coreAudioStreamUninit(pCAStream); - - if (RT_SUCCESS(rc)) - ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT); - } - - if (RT_SUCCESS(rc)) - { - if (RTCritSectIsInitialized(&pCAStream->CritSect)) - RTCritSectDelete(&pCAStream->CritSect); - } - - LogFunc(("rc=%Rrc\n", rc)); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, - PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; - - return coreAudioStreamControl(pThis, pCAStream, enmStreamCmd); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} - */ -static DECLCALLBACK(uint32_t) drvHostCoreAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; - - if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT) - return 0; - - AssertPtr(pCAStream->pCfg); - AssertPtr(pCAStream->pCircBuf); - - switch (pCAStream->pCfg->enmDir) - { - case PDMAUDIODIR_IN: - return (uint32_t)RTCircBufUsed(pCAStream->pCircBuf); - - case PDMAUDIODIR_OUT: - default: - AssertFailed(); - break; - } - - return 0; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(uint32_t) drvHostCoreAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; - - uint32_t cbWritable = 0; - - if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT) - { - AssertPtr(pCAStream->pCfg); - AssertPtr(pCAStream->pCircBuf); - - switch (pCAStream->pCfg->enmDir) - { - case PDMAUDIODIR_OUT: - cbWritable = (uint32_t)RTCircBufFree(pCAStream->pCircBuf); - break; - - default: - break; - } - } - - LogFlowFunc(("cbWritable=%RU32\n", cbWritable)); - return cbWritable; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} - */ -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostCoreAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; - - PDMAUDIOSTREAMSTS fStrmStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE; - - if (pCAStream->pCfg) /* Configured? */ - { - if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT) - fStrmStatus |= PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED; - } - - return fStrmStatus; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - RT_NOREF(pInterface, pStream); - - /* Nothing to do here for Core Audio. */ - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} - */ -static DECLCALLBACK(int) drvHostCoreAudioHA_Init(PPDMIHOSTAUDIO pInterface) -{ - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - - int rc = DrvAudioHlpDeviceEnumInit(&pThis->Devices); - if (RT_SUCCESS(rc)) - { - /* Do the first (initial) internal device enumeration. */ - rc = coreAudioEnumerateDevices(pThis); - } - - if (RT_SUCCESS(rc)) - { - /* Register system callbacks. */ - AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - OSStatus err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr, - coreAudioDefaultDeviceChangedCb, pThis /* pvUser */); - if ( err != noErr - && err != kAudioHardwareIllegalOperationError) - { - LogRel(("CoreAudio: Failed to add the input default device changed listener (%RI32)\n", err)); - } - - propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr, - coreAudioDefaultDeviceChangedCb, pThis /* pvUser */); - if ( err != noErr - && err != kAudioHardwareIllegalOperationError) - { - LogRel(("CoreAudio: Failed to add the output default device changed listener (%RI32)\n", err)); - } - } - - LogFlowFunc(("Returning %Rrc\n", rc)); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} - */ -static DECLCALLBACK(void) drvHostCoreAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface) -{ - PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); - - /* - * Unregister system callbacks. - */ - AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - OSStatus err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr, - coreAudioDefaultDeviceChangedCb, pThis /* pvUser */); - if ( err != noErr - && err != kAudioHardwareBadObjectError) - { - LogRel(("CoreAudio: Failed to remove the default input device changed listener (%RI32)\n", err)); - } - - propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr, - coreAudioDefaultDeviceChangedCb, pThis /* pvUser */); - if ( err != noErr - && err != kAudioHardwareBadObjectError) - { - LogRel(("CoreAudio: Failed to remove the default output device changed listener (%RI32)\n", err)); - } - - LogFlowFuncEnter(); -} - - -/** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) drvHostCoreAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) -{ - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); - - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); - - return NULL; -} - - -/** - * @callback_method_impl{FNPDMDRVCONSTRUCT, - * Construct a Core Audio driver instance.} - */ -static DECLCALLBACK(int) drvHostCoreAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) -{ - RT_NOREF(pCfg, fFlags); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); - LogRel(("Audio: Initializing Core Audio driver\n")); - - /* - * Init the static parts. - */ - pThis->pDrvIns = pDrvIns; - /* IBase */ - pDrvIns->IBase.pfnQueryInterface = drvHostCoreAudioQueryInterface; - /* IHostAudio */ - pThis->IHostAudio.pfnInit = drvHostCoreAudioHA_Init; - pThis->IHostAudio.pfnShutdown = drvHostCoreAudioHA_Shutdown; - pThis->IHostAudio.pfnGetConfig = drvHostCoreAudioHA_GetConfig; - pThis->IHostAudio.pfnGetStatus = drvHostCoreAudioHA_GetStatus; - pThis->IHostAudio.pfnStreamCreate = drvHostCoreAudioHA_StreamCreate; - pThis->IHostAudio.pfnStreamDestroy = drvHostCoreAudioHA_StreamDestroy; - pThis->IHostAudio.pfnStreamControl = drvHostCoreAudioHA_StreamControl; - pThis->IHostAudio.pfnStreamGetReadable = drvHostCoreAudioHA_StreamGetReadable; - pThis->IHostAudio.pfnStreamGetWritable = drvHostCoreAudioHA_StreamGetWritable; - pThis->IHostAudio.pfnStreamGetStatus = drvHostCoreAudioHA_StreamGetStatus; - pThis->IHostAudio.pfnStreamIterate = drvHostCoreAudioHA_StreamIterate; - pThis->IHostAudio.pfnStreamPlay = drvHostCoreAudioHA_StreamPlay; - pThis->IHostAudio.pfnStreamCapture = drvHostCoreAudioHA_StreamCapture; -#ifdef VBOX_WITH_AUDIO_CALLBACKS - pThis->IHostAudio.pfnSetCallback = drvHostCoreAudioHA_SetCallback; - pThis->pfnCallback = NULL; -#else - pThis->IHostAudio.pfnSetCallback = NULL; -#endif - pThis->IHostAudio.pfnGetDevices = drvHostCoreAudioHA_GetDevices; - pThis->IHostAudio.pfnStreamGetPending = NULL; - pThis->IHostAudio.pfnStreamPlayBegin = NULL; - pThis->IHostAudio.pfnStreamPlayEnd = NULL; - pThis->IHostAudio.pfnStreamCaptureBegin = NULL; - pThis->IHostAudio.pfnStreamCaptureEnd = NULL; - - int rc = RTCritSectInit(&pThis->CritSect); - AssertRCReturn(rc, rc); - -#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA - RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm"); - RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm"); -#endif - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * @callback_method_impl{FNPDMDRVDESTRUCT} - */ -static DECLCALLBACK(void) drvHostCoreAudioDestruct(PPDMDRVINS pDrvIns) -{ - PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); - PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); - - int rc2 = RTCritSectDelete(&pThis->CritSect); - AssertRC(rc2); - - LogFlowFuncLeaveRC(rc2); -} - - -/** - * Char driver registration record. - */ -const PDMDRVREG g_DrvHostCoreAudio = -{ - /* u32Version */ - PDM_DRVREG_VERSION, - /* szName */ - "CoreAudio", - /* szRCMod */ - "", - /* szR0Mod */ - "", - /* pszDescription */ - "Core Audio host driver", - /* fFlags */ - PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, - /* fClass. */ - PDM_DRVREG_CLASS_AUDIO, - /* cMaxInstances */ - ~0U, - /* cbInstance */ - sizeof(DRVHOSTCOREAUDIO), - /* pfnConstruct */ - drvHostCoreAudioConstruct, - /* pfnDestruct */ - drvHostCoreAudioDestruct, - /* pfnRelocate */ - NULL, - /* pfnIOCtl */ - NULL, - /* pfnPowerOn */ - NULL, - /* pfnReset */ - NULL, - /* pfnSuspend */ - NULL, - /* pfnResume */ - NULL, - /* pfnAttach */ - NULL, - /* pfnDetach */ - NULL, - /* pfnPowerOff */ - NULL, - /* pfnSoftReset */ - NULL, - /* u32EndVersion */ - PDM_DRVREG_VERSION -}; - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,445 +0,0 @@ -/* $Id: DrvHostDebugAudio.cpp $ */ -/** @file - * Debug audio driver. - * - * Host backend for dumping and injecting audio data from/to the device emulation. - */ - -/* - * Copyright (C) 2016-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#include -#include /* For PDMIBASE_2_PDMDRV. */ - -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include - -#include "DrvAudio.h" -#include "VBoxDD.h" - - -/** - * Structure for keeping a debug input/output stream. - */ -typedef struct DEBUGAUDIOSTREAM -{ - /** The stream's acquired configuration. */ - PPDMAUDIOSTREAMCFG pCfg; - /** Audio file to dump output to or read input from. */ - PPDMAUDIOFILE pFile; - union - { - struct - { - /** Timestamp of last captured samples. */ - uint64_t tsLastCaptured; - } In; - }; -} DEBUGAUDIOSTREAM, *PDEBUGAUDIOSTREAM; - -/** - * Debug audio driver instance data. - * @implements PDMIAUDIOCONNECTOR - */ -typedef struct DRVHOSTDEBUGAUDIO -{ - /** Pointer to the driver instance structure. */ - PPDMDRVINS pDrvIns; - /** Pointer to host audio interface. */ - PDMIHOSTAUDIO IHostAudio; -} DRVHOSTDEBUGAUDIO, *PDRVHOSTDEBUGAUDIO; - -/*******************************************PDM_AUDIO_DRIVER******************************/ - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} - */ -static DECLCALLBACK(int) drvHostDebugAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); - - RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "DebugAudio"); - - pBackendCfg->cbStreamOut = sizeof(DEBUGAUDIOSTREAM); - pBackendCfg->cbStreamIn = sizeof(DEBUGAUDIOSTREAM); - - pBackendCfg->cMaxStreamsOut = 1; /* Output; writing to a file. */ - pBackendCfg->cMaxStreamsIn = 0; /** @todo Right now we don't support any input (capturing, injecting from a file). */ - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} - */ -static DECLCALLBACK(int) drvHostDebugAudioHA_Init(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); - - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} - */ -static DECLCALLBACK(void) drvHostDebugAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDebugAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) -{ - RT_NOREF(enmDir); - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); - - return PDMAUDIOBACKENDSTS_RUNNING; -} - - -static int debugCreateStreamIn(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - RT_NOREF(pDrv, pStreamDbg, pCfgReq, pCfgAcq); - - return VINF_SUCCESS; -} - - -static int debugCreateStreamOut(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - RT_NOREF(pDrv, pCfgAcq); - - char szTemp[RTPATH_MAX]; - int rc = RTPathTemp(szTemp, sizeof(szTemp)); - if (RT_SUCCESS(rc)) - { - char szFile[RTPATH_MAX]; - rc = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, "DebugAudioOut", - pDrv->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - if (RT_SUCCESS(rc)) - { - rc = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAGS_NONE, &pStreamDbg->pFile); - if (RT_SUCCESS(rc)) - { - rc = DrvAudioHlpFileOpen(pStreamDbg->pFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE, - &pCfgReq->Props); - } - - if (RT_FAILURE(rc)) - LogRel(("DebugAudio: Creating output file '%s' failed with %Rrc\n", szFile, rc)); - } - else - LogRel(("DebugAudio: Unable to build file name for temp dir '%s': %Rrc\n", szTemp, rc)); - } - else - LogRel(("DebugAudio: Unable to retrieve temp dir: %Rrc\n", rc)); - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} - */ -static DECLCALLBACK(int) drvHostDebugAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - PDRVHOSTDEBUGAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTDEBUGAUDIO, IHostAudio); - PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream; - - int rc; - if (pCfgReq->enmDir == PDMAUDIODIR_IN) - rc = debugCreateStreamIn( pDrv, pStreamDbg, pCfgReq, pCfgAcq); - else - rc = debugCreateStreamOut(pDrv, pStreamDbg, pCfgReq, pCfgAcq); - - if (RT_SUCCESS(rc)) - { - pStreamDbg->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); - if (!pStreamDbg->pCfg) - rc = VERR_NO_MEMORY; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} - */ -static DECLCALLBACK(int) drvHostDebugAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten) -{ - RT_NOREF(pInterface); - PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream; - - int rc = DrvAudioHlpFileWrite(pStreamDbg->pFile, pvBuf, uBufSize, 0 /* fFlags */); - if (RT_FAILURE(rc)) - { - LogRel(("DebugAudio: Writing output failed with %Rrc\n", rc)); - return rc; - } - - if (puWritten) - *puWritten = uBufSize; - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} - */ -static DECLCALLBACK(int) drvHostDebugAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead) -{ - RT_NOREF(pInterface, pStream, pvBuf, uBufSize); - - /* Never capture anything. */ - if (puRead) - *puRead = 0; - - return VINF_SUCCESS; -} - - -static int debugDestroyStreamIn(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg) -{ - RT_NOREF(pDrv, pStreamDbg); - return VINF_SUCCESS; -} - - -static int debugDestroyStreamOut(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg) -{ - RT_NOREF(pDrv); - - DrvAudioHlpFileDestroy(pStreamDbg->pFile); - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} - */ -static DECLCALLBACK(int) drvHostDebugAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - - PDRVHOSTDEBUGAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTDEBUGAUDIO, IHostAudio); - PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream; - - if (!pStreamDbg->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamDbg->pCfg->enmDir == PDMAUDIODIR_IN) - rc = debugDestroyStreamIn (pDrv, pStreamDbg); - else - rc = debugDestroyStreamOut(pDrv, pStreamDbg); - - if (RT_SUCCESS(rc)) - { - DrvAudioHlpStreamCfgFree(pStreamDbg->pCfg); - pStreamDbg->pCfg = NULL; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} - */ -static DECLCALLBACK(int) drvHostDebugAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, - PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - RT_NOREF(enmStreamCmd); - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} - */ -static DECLCALLBACK(uint32_t) drvHostDebugAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return 0; /* Never capture anything. */ -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(uint32_t) drvHostDebugAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return UINT32_MAX; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDebugAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} - */ -static DECLCALLBACK(int) drvHostDebugAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) drvHostDebugAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) -{ - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVHOSTDEBUGAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDEBUGAUDIO); - - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); - return NULL; -} - - -/** - * Constructs a Null audio driver instance. - * - * @copydoc FNPDMDRVCONSTRUCT - */ -static DECLCALLBACK(int) drvHostDebugAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) -{ - RT_NOREF(pCfg, fFlags); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - PDRVHOSTDEBUGAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDEBUGAUDIO); - LogRel(("Audio: Initializing DEBUG driver\n")); - - /* - * Init the static parts. - */ - pThis->pDrvIns = pDrvIns; - /* IBase */ - pDrvIns->IBase.pfnQueryInterface = drvHostDebugAudioQueryInterface; - /* IHostAudio */ - pThis->IHostAudio.pfnInit = drvHostDebugAudioHA_Init; - pThis->IHostAudio.pfnShutdown = drvHostDebugAudioHA_Shutdown; - pThis->IHostAudio.pfnGetConfig = drvHostDebugAudioHA_GetConfig; - pThis->IHostAudio.pfnGetStatus = drvHostDebugAudioHA_GetStatus; - pThis->IHostAudio.pfnStreamCreate = drvHostDebugAudioHA_StreamCreate; - pThis->IHostAudio.pfnStreamDestroy = drvHostDebugAudioHA_StreamDestroy; - pThis->IHostAudio.pfnStreamControl = drvHostDebugAudioHA_StreamControl; - pThis->IHostAudio.pfnStreamGetReadable = drvHostDebugAudioHA_StreamGetReadable; - pThis->IHostAudio.pfnStreamGetWritable = drvHostDebugAudioHA_StreamGetWritable; - pThis->IHostAudio.pfnStreamGetStatus = drvHostDebugAudioHA_StreamGetStatus; - pThis->IHostAudio.pfnStreamIterate = drvHostDebugAudioHA_StreamIterate; - pThis->IHostAudio.pfnStreamPlay = drvHostDebugAudioHA_StreamPlay; - pThis->IHostAudio.pfnStreamCapture = drvHostDebugAudioHA_StreamCapture; - pThis->IHostAudio.pfnSetCallback = NULL; - pThis->IHostAudio.pfnGetDevices = NULL; - pThis->IHostAudio.pfnStreamGetPending = NULL; - pThis->IHostAudio.pfnStreamPlayBegin = NULL; - pThis->IHostAudio.pfnStreamPlayEnd = NULL; - pThis->IHostAudio.pfnStreamCaptureBegin = NULL; - pThis->IHostAudio.pfnStreamCaptureEnd = NULL; - -#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA - RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "AudioDebugOutput.pcm"); -#endif - - return VINF_SUCCESS; -} - -/** - * Char driver registration record. - */ -const PDMDRVREG g_DrvHostDebugAudio = -{ - /* u32Version */ - PDM_DRVREG_VERSION, - /* szName */ - "DebugAudio", - /* szRCMod */ - "", - /* szR0Mod */ - "", - /* pszDescription */ - "Debug audio host driver", - /* fFlags */ - PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, - /* fClass. */ - PDM_DRVREG_CLASS_AUDIO, - /* cMaxInstances */ - ~0U, - /* cbInstance */ - sizeof(DRVHOSTDEBUGAUDIO), - /* pfnConstruct */ - drvHostDebugAudioConstruct, - /* pfnDestruct */ - NULL, - /* pfnRelocate */ - NULL, - /* pfnIOCtl */ - NULL, - /* pfnPowerOn */ - NULL, - /* pfnReset */ - NULL, - /* pfnSuspend */ - NULL, - /* pfnResume */ - NULL, - /* pfnAttach */ - NULL, - /* pfnDetach */ - NULL, - /* pfnPowerOff */ - NULL, - /* pfnSoftReset */ - NULL, - /* u32EndVersion */ - PDM_DRVREG_VERSION -}; - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostDSound.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostDSound.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostDSound.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostDSound.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,2761 +0,0 @@ -/* $Id: DrvHostDSound.cpp $ */ -/** @file - * Windows host backend driver using DirectSound. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include -#include - -#include -#include -#include -#include - -#include "DrvAudio.h" -#include "VBoxDD.h" -#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT -# include /* For bad_alloc. */ -# include "VBoxMMNotificationClient.h" -#endif - - -/********************************************************************************************************************************* -* Defined Constants And Macros * -*********************************************************************************************************************************/ -/* - * IDirectSound* interface uses HRESULT status codes and the driver callbacks use - * the IPRT status codes. To minimize HRESULT->IPRT conversion most internal functions - * in the driver return HRESULT and conversion is done in the driver callbacks. - * - * Naming convention: - * 'dsound*' functions return IPRT status code; - * 'directSound*' - return HRESULT. - */ - -/* - * Optional release logging, which a user can turn on with the - * 'VBoxManage debugvm' command. - * Debug logging still uses the common Log* macros from IPRT. - * Messages which always should go to the release log use LogRel. - */ -/* General code behavior. */ -#define DSLOG(a) do { LogRel2(a); } while(0) -/* Something which produce a lot of logging during playback/recording. */ -#define DSLOGF(a) do { LogRel3(a); } while(0) -/* Important messages like errors. Limited in the default release log to avoid log flood. */ -#define DSLOGREL(a) \ - do { \ - static int8_t s_cLogged = 0; \ - if (s_cLogged < 8) { \ - ++s_cLogged; \ - LogRel(a); \ - } else DSLOG(a); \ - } while (0) - -/** Maximum number of attempts to restore the sound buffer before giving up. */ -#define DRV_DSOUND_RESTORE_ATTEMPTS_MAX 3 -/** Default input latency (in ms). */ -#define DRV_DSOUND_DEFAULT_LATENCY_MS_IN 50 -/** Default output latency (in ms). */ -#define DRV_DSOUND_DEFAULT_LATENCY_MS_OUT 50 - -/** Makes DRVHOSTDSOUND out of PDMIHOSTAUDIO. */ -#define PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface) \ - ( (PDRVHOSTDSOUND)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTDSOUND, IHostAudio)) ) - - -/********************************************************************************************************************************* -* Structures and Typedefs * -*********************************************************************************************************************************/ -/* Dynamically load dsound.dll. */ -typedef HRESULT WINAPI FNDIRECTSOUNDENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext); -typedef FNDIRECTSOUNDENUMERATEW *PFNDIRECTSOUNDENUMERATEW; -typedef HRESULT WINAPI FNDIRECTSOUNDCAPTUREENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext); -typedef FNDIRECTSOUNDCAPTUREENUMERATEW *PFNDIRECTSOUNDCAPTUREENUMERATEW; -typedef HRESULT WINAPI FNDIRECTSOUNDCAPTURECREATE8(LPCGUID lpcGUID, LPDIRECTSOUNDCAPTURE8 *lplpDSC, LPUNKNOWN pUnkOuter); -typedef FNDIRECTSOUNDCAPTURECREATE8 *PFNDIRECTSOUNDCAPTURECREATE8; - -#define VBOX_DSOUND_MAX_EVENTS 3 - -typedef enum DSOUNDEVENT -{ - DSOUNDEVENT_NOTIFY = 0, - DSOUNDEVENT_INPUT, - DSOUNDEVENT_OUTPUT, -} DSOUNDEVENT; - -typedef struct DSOUNDHOSTCFG -{ - RTUUID uuidPlay; - LPCGUID pGuidPlay; - RTUUID uuidCapture; - LPCGUID pGuidCapture; -} DSOUNDHOSTCFG, *PDSOUNDHOSTCFG; - -typedef struct DSOUNDSTREAM -{ - /** The stream's acquired configuration. */ - PDMAUDIOSTREAMCFG Cfg; - /** Buffer alignment. */ - uint8_t uAlign; - /** Whether this stream is in an enable state on the DirectSound side. */ - bool fEnabled; - bool afPadding[2]; - /** Size (in bytes) of the DirectSound buffer. - * @note This in *not* the size of the circular buffer above! */ - DWORD cbBufSize; - /** The stream's critical section for synchronizing access. */ - RTCRITSECT CritSect; - /** The internal playback / capturing buffer. */ - PRTCIRCBUF pCircBuf; - union - { - struct - { - /** The actual DirectSound Buffer (DSB) used for the capturing. - * This is a secondary buffer and is used as a streaming buffer. */ - LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB; - /** Current read offset (in bytes) within the DSB. */ - DWORD offReadPos; - /** Number of buffer overruns happened. Used for logging. */ - uint8_t cOverruns; - } In; - struct - { - /** The actual DirectSound Buffer (DSB) used for playback. - * This is a secondary buffer and is used as a streaming buffer. */ - LPDIRECTSOUNDBUFFER8 pDSB; - /** Current write offset (in bytes) within the DSB. */ - DWORD offWritePos; - /** Offset of last play cursor within the DSB when checked for pending. */ - DWORD offPlayCursorLastPending; - /** Offset of last play cursor within the DSB when last played. */ - DWORD offPlayCursorLastPlayed; - /** Total amount (in bytes) written to our internal ring buffer. */ - uint64_t cbWritten; - /** Total amount (in bytes) played (to the DirectSound buffer). */ - uint64_t cbTransferred; - /** Flag indicating whether playback was just (re)started. */ - bool fFirstTransfer; - /** Flag indicating whether this stream is in draining mode, e.g. no new - * data is being written to it but DirectSound still needs to be able to - * play its remaining (buffered) data. */ - bool fDrain; - /** How much (in bytes) the last transfer from the internal buffer - * to the DirectSound buffer was. */ - uint32_t cbLastTransferred; - /** Timestamp (in ms) of the last transfer from the internal buffer - * to the DirectSound buffer. */ - uint64_t tsLastTransferredMs; - /** Number of buffer underruns happened. Used for logging. */ - uint8_t cUnderruns; - } Out; - }; -#ifdef LOG_ENABLED - struct - { - uint64_t tsLastTransferredMs; - } Dbg; -#endif -} DSOUNDSTREAM, *PDSOUNDSTREAM; - -/** - * Structure for keeping a DirectSound-specific device entry. - * This is then bound to the PDMAUDIODEVICE's pvData area. - */ -typedef struct DSOUNDDEV -{ - GUID Guid; -} DSOUNDDEV, *PDSOUNDDEV; - -/** - * Structure for holding a device enumeration context. - */ -typedef struct DSOUNDENUMCBCTX -{ - /** Enumeration flags. */ - uint32_t fFlags; - /** Pointer to device list to populate. */ - PPDMAUDIODEVICEENUM pDevEnm; -} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX; - -typedef struct DRVHOSTDSOUND -{ - /** Pointer to the driver instance structure. */ - PPDMDRVINS pDrvIns; - /** Our audio host audio interface. */ - PDMIHOSTAUDIO IHostAudio; - /** Critical section to serialize access. */ - RTCRITSECT CritSect; - /** DirectSound configuration options. */ - DSOUNDHOSTCFG Cfg; - /** List of devices of last enumeration. */ - PDMAUDIODEVICEENUM DeviceEnum; - /** Whether this backend supports any audio input. */ - bool fEnabledIn; - /** Whether this backend supports any audio output. */ - bool fEnabledOut; - /** The Direct Sound playback interface. */ - LPDIRECTSOUND8 pDS; - /** The Direct Sound capturing interface. */ - LPDIRECTSOUNDCAPTURE8 pDSC; -#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT - VBoxMMNotificationClient *m_pNotificationClient; -#endif -#ifdef VBOX_WITH_AUDIO_CALLBACKS - /** Callback function to the upper driver. - * Can be NULL if not being used / registered. */ - PFNPDMHOSTAUDIOCALLBACK pfnCallback; -#endif - /** Pointer to the input stream. */ - PDSOUNDSTREAM pDSStrmIn; - /** Pointer to the output stream. */ - PDSOUNDSTREAM pDSStrmOut; -} DRVHOSTDSOUND, *PDRVHOSTDSOUND; - - -/********************************************************************************************************************************* -* Internal Functions * -*********************************************************************************************************************************/ -static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB); -static HRESULT directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS); -static HRESULT directSoundPlayStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush); -static HRESULT directSoundCaptureStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush); - -static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PDMAUDIODEVICEENUM pDevEnm, uint32_t fEnum); - -static int dsoundStreamEnable(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fEnable); -static void dsoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS); -static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis); - - -static DWORD dsoundRingDistance(DWORD offEnd, DWORD offBegin, DWORD cSize) -{ - AssertReturn(offEnd <= cSize, 0); - AssertReturn(offBegin <= cSize, 0); - - return offEnd >= offBegin ? offEnd - offBegin : cSize - offBegin + offEnd; -} - -static int dsoundWaveFmtFromCfg(PPDMAUDIOSTREAMCFG pCfg, PWAVEFORMATEX pFmt) -{ - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - AssertPtrReturn(pFmt, VERR_INVALID_POINTER); - - RT_BZERO(pFmt, sizeof(WAVEFORMATEX)); - - pFmt->wFormatTag = WAVE_FORMAT_PCM; - pFmt->nChannels = pCfg->Props.cChannels; - pFmt->wBitsPerSample = pCfg->Props.cbSample * 8; - pFmt->nSamplesPerSec = pCfg->Props.uHz; - pFmt->nBlockAlign = pFmt->nChannels * pFmt->wBitsPerSample / 8; - pFmt->nAvgBytesPerSec = pFmt->nSamplesPerSec * pFmt->nBlockAlign; - pFmt->cbSize = 0; /* No extra data specified. */ - - return VINF_SUCCESS; -} - -/** - * Retrieves the number of free bytes available for writing to a DirectSound output stream. - * - * @return IPRT status code. VERR_NOT_AVAILABLE if unable to determine or the buffer was not recoverable. - * @param pThis Host audio driver instance. - * @param pStreamDS DirectSound output stream to retrieve number for. - * @param pdwFree Where to return the free amount on success. - */ -static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER); - AssertPtrReturn(pdwFree, VERR_INVALID_POINTER); - - Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */ - - LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB; - if (!pDSB) - { - AssertPtr(pDSB); - return VERR_INVALID_POINTER; - } - - HRESULT hr = S_OK; - - /* Get the current play position which is used for calculating the free space in the buffer. */ - for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) - { - DWORD cbPlayCursor, cbWriteCursor; - hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &cbPlayCursor, &cbWriteCursor); - if (SUCCEEDED(hr)) - { - int32_t cbDiff = cbWriteCursor - cbPlayCursor; - if (cbDiff < 0) - cbDiff += pStreamDS->cbBufSize; - - int32_t cbFree = cbPlayCursor - pStreamDS->Out.offWritePos; - if (cbFree < 0) - cbFree += pStreamDS->cbBufSize; - - if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff) - { - pStreamDS->Out.offWritePos = cbWriteCursor; - cbFree = pStreamDS->cbBufSize - cbDiff; - } - - /* When starting to use a DirectSound buffer, cbPlayCursor and cbWriteCursor - * both point at position 0, so we won't be able to detect how many bytes - * are writable that way. - * - * So use our per-stream written indicator to see if we just started a stream. */ - if (pStreamDS->Out.cbWritten == 0) - cbFree = pStreamDS->cbBufSize; - - DSLOGREL(("DSound: cbPlayCursor=%RU32, cbWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n", - cbPlayCursor, cbWriteCursor, pStreamDS->Out.offWritePos, cbFree)); - - *pdwFree = cbFree; - - return VINF_SUCCESS; - } - - if (hr != DSERR_BUFFERLOST) /** @todo MSDN doesn't state this error for GetCurrentPosition(). */ - break; - - LogFunc(("Getting playing position failed due to lost buffer, restoring ...\n")); - - directSoundPlayRestore(pThis, pDSB); - } - - if (hr != DSERR_BUFFERLOST) /* Avoid log flooding if the error is still there. */ - DSLOGREL(("DSound: Getting current playback position failed with %Rhrc\n", hr)); - - LogFunc(("Failed with %Rhrc\n", hr)); - - return VERR_NOT_AVAILABLE; -} - -static char *dsoundGUIDToUtf8StrA(LPCGUID pGUID) -{ - if (pGUID) - { - LPOLESTR lpOLEStr; - HRESULT hr = StringFromCLSID(*pGUID, &lpOLEStr); - if (SUCCEEDED(hr)) - { - char *pszGUID; - int rc = RTUtf16ToUtf8(lpOLEStr, &pszGUID); - CoTaskMemFree(lpOLEStr); - - return RT_SUCCESS(rc) ? pszGUID : NULL; - } - } - - return RTStrDup("{Default device}"); -} - -static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB) -{ - RT_NOREF(pThis); - HRESULT hr = IDirectSoundBuffer8_Restore(pDSB); - if (FAILED(hr)) - DSLOG(("DSound: Restoring playback buffer\n")); - else - DSLOGREL(("DSound: Restoring playback buffer failed with %Rhrc\n", hr)); - - return hr; -} - -static HRESULT directSoundPlayUnlock(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, - PVOID pv1, PVOID pv2, - DWORD cb1, DWORD cb2) -{ - RT_NOREF(pThis); - HRESULT hr = IDirectSoundBuffer8_Unlock(pDSB, pv1, cb1, pv2, cb2); - if (FAILED(hr)) - DSLOGREL(("DSound: Unlocking playback buffer failed with %Rhrc\n", hr)); - return hr; -} - -static HRESULT directSoundCaptureUnlock(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB, - PVOID pv1, PVOID pv2, - DWORD cb1, DWORD cb2) -{ - HRESULT hr = IDirectSoundCaptureBuffer8_Unlock(pDSCB, pv1, cb1, pv2, cb2); - if (FAILED(hr)) - DSLOGREL(("DSound: Unlocking capture buffer failed with %Rhrc\n", hr)); - return hr; -} - -static HRESULT directSoundPlayLock(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, - DWORD dwOffset, DWORD dwBytes, - PVOID *ppv1, PVOID *ppv2, - DWORD *pcb1, DWORD *pcb2, - DWORD dwFlags) -{ - AssertReturn(dwBytes, VERR_INVALID_PARAMETER); - - HRESULT hr = E_FAIL; - AssertCompile(DRV_DSOUND_RESTORE_ATTEMPTS_MAX > 0); - for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) - { - PVOID pv1, pv2; - DWORD cb1, cb2; - hr = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, dwOffset, dwBytes, &pv1, &cb1, &pv2, &cb2, dwFlags); - if (SUCCEEDED(hr)) - { - if ( (!pv1 || !(cb1 & pStreamDS->uAlign)) - && (!pv2 || !(cb2 & pStreamDS->uAlign))) - { - if (ppv1) - *ppv1 = pv1; - if (ppv2) - *ppv2 = pv2; - if (pcb1) - *pcb1 = cb1; - if (pcb2) - *pcb2 = cb2; - return S_OK; - } - DSLOGREL(("DSound: Locking playback buffer returned misaligned buffer: cb1=%#RX32, cb2=%#RX32 (alignment: %#RX32)\n", - *pcb1, *pcb2, pStreamDS->uAlign)); - directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); - return E_FAIL; - } - - if (hr != DSERR_BUFFERLOST) - break; - - LogFlowFunc(("Locking failed due to lost buffer, restoring ...\n")); - directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); - } - - DSLOGREL(("DSound: Locking playback buffer failed with %Rhrc (dwOff=%ld, dwBytes=%ld)\n", hr, dwOffset, dwBytes)); - return hr; -} - -static HRESULT directSoundCaptureLock(PDSOUNDSTREAM pStreamDS, - DWORD dwOffset, DWORD dwBytes, - PVOID *ppv1, PVOID *ppv2, - DWORD *pcb1, DWORD *pcb2, - DWORD dwFlags) -{ - PVOID pv1 = NULL; - PVOID pv2 = NULL; - DWORD cb1 = 0; - DWORD cb2 = 0; - - HRESULT hr = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, dwOffset, dwBytes, - &pv1, &cb1, &pv2, &cb2, dwFlags); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Locking capture buffer failed with %Rhrc\n", hr)); - return hr; - } - - if ( (pv1 && (cb1 & pStreamDS->uAlign)) - || (pv2 && (cb2 & pStreamDS->uAlign))) - { - DSLOGREL(("DSound: Locking capture buffer returned misaligned buffer: cb1=%RI32, cb2=%RI32 (alignment: %RU32)\n", - cb1, cb2, pStreamDS->uAlign)); - directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); - return E_FAIL; - } - - *ppv1 = pv1; - *ppv2 = pv2; - *pcb1 = cb1; - *pcb2 = cb2; - - return S_OK; -} - -/* - * DirectSound playback - */ - -/** - * Destroys a DirectSound playback interface. - * - * @param pDS Playback interface to destroy. - */ -static void directSoundPlayInterfaceDestroy(LPDIRECTSOUND8 pDS) -{ - if (pDS) - { - LogFlowFuncEnter(); - - IDirectSound8_Release(pDS); - pDS = NULL; - } -} - -/** - * Creates a DirectSound playback interface. - * - * @return HRESULT - * @param pGUID GUID of device to create the playback interface for. - * @param ppDS Where to store the created interface. Optional. - */ -static HRESULT directSoundPlayInterfaceCreate(LPCGUID pGUID, LPDIRECTSOUND8 *ppDS) -{ - /* pGUID can be NULL, if this is the default device. */ - /* ppDS is optional. */ - - LogFlowFuncEnter(); - - LPDIRECTSOUND8 pDS; - HRESULT hr = CoCreateInstance(CLSID_DirectSound8, NULL, CLSCTX_ALL, - IID_IDirectSound8, (void **)&pDS); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Creating playback instance failed with %Rhrc\n", hr)); - } - else - { - hr = IDirectSound8_Initialize(pDS, pGUID); - if (SUCCEEDED(hr)) - { - HWND hWnd = GetDesktopWindow(); - hr = IDirectSound8_SetCooperativeLevel(pDS, hWnd, DSSCL_PRIORITY); - if (FAILED(hr)) - DSLOGREL(("DSound: Setting cooperative level for window %p failed with %Rhrc\n", hWnd, hr)); - } - - if (FAILED(hr)) - { - if (hr == DSERR_NODRIVER) /* Usually means that no playback devices are attached. */ - DSLOGREL(("DSound: DirectSound playback is currently unavailable\n")); - else - DSLOGREL(("DSound: DirectSound playback initialization failed with %Rhrc\n", hr)); - - directSoundPlayInterfaceDestroy(pDS); - } - else if (ppDS) - { - *ppDS = pDS; - } - } - - LogFlowFunc(("Returning %Rhrc\n", hr)); - return hr; -} - -static HRESULT directSoundPlayClose(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - AssertPtrReturn(pThis, E_POINTER); - AssertPtrReturn(pStreamDS, E_POINTER); - - LogFlowFuncEnter(); - - HRESULT hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */); - if (FAILED(hr)) - return hr; - - DSLOG(("DSound: Closing playback stream\n")); - - if (pStreamDS->pCircBuf) - Assert(RTCircBufUsed(pStreamDS->pCircBuf) == 0); - - if (SUCCEEDED(hr)) - { - RTCritSectEnter(&pThis->CritSect); - - if (pStreamDS->pCircBuf) - { - RTCircBufDestroy(pStreamDS->pCircBuf); - pStreamDS->pCircBuf = NULL; - } - - if (pStreamDS->Out.pDSB) - { - IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB); - pStreamDS->Out.pDSB = NULL; - } - - pThis->pDSStrmOut = NULL; - - RTCritSectLeave(&pThis->CritSect); - } - - if (FAILED(hr)) - DSLOGREL(("DSound: Stopping playback stream %p failed with %Rhrc\n", pStreamDS, hr)); - - return hr; -} - -static HRESULT directSoundPlayOpen(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pThis, E_POINTER); - AssertPtrReturn(pStreamDS, E_POINTER); - AssertPtrReturn(pCfgReq, E_POINTER); - AssertPtrReturn(pCfgAcq, E_POINTER); - - LogFlowFuncEnter(); - - Assert(pStreamDS->Out.pDSB == NULL); - - DSLOG(("DSound: Opening playback stream (uHz=%RU32, cChannels=%RU8, cBits=%u, fSigned=%RTbool)\n", - pCfgReq->Props.uHz, pCfgReq->Props.cChannels, pCfgReq->Props.cbSample * 8, pCfgReq->Props.fSigned)); - - WAVEFORMATEX wfx; - int rc = dsoundWaveFmtFromCfg(pCfgReq, &wfx); - if (RT_FAILURE(rc)) - return E_INVALIDARG; - - DSLOG(("DSound: Requested playback format:\n" - " wFormatTag = %RI16\n" - " nChannels = %RI16\n" - " nSamplesPerSec = %RU32\n" - " nAvgBytesPerSec = %RU32\n" - " nBlockAlign = %RI16\n" - " wBitsPerSample = %RI16\n" - " cbSize = %RI16\n", - wfx.wFormatTag, - wfx.nChannels, - wfx.nSamplesPerSec, - wfx.nAvgBytesPerSec, - wfx.nBlockAlign, - wfx.wBitsPerSample, - wfx.cbSize)); - - dsoundUpdateStatusInternal(pThis); - - HRESULT hr = directSoundPlayInterfaceCreate(pThis->Cfg.pGuidPlay, &pThis->pDS); - if (FAILED(hr)) - return hr; - - do /* To use breaks. */ - { - LPDIRECTSOUNDBUFFER pDSB = NULL; - - DSBUFFERDESC bd; - RT_ZERO(bd); - bd.dwSize = sizeof(bd); - bd.lpwfxFormat = &wfx; - - /* - * As we reuse our (secondary) buffer for playing out data as it comes in, - * we're using this buffer as a so-called streaming buffer. - * - * See https://msdn.microsoft.com/en-us/library/windows/desktop/ee419014(v=vs.85).aspx - * - * However, as we do not want to use memory on the sound device directly - * (as most modern audio hardware on the host doesn't have this anyway), - * we're *not* going to use DSBCAPS_STATIC for that. - * - * Instead we're specifying DSBCAPS_LOCSOFTWARE, as this fits the bill - * of copying own buffer data to our secondary's Direct Sound buffer. - */ - bd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE; - bd.dwBufferBytes = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cFramesBufferSize, &pCfgReq->Props); - - DSLOG(("DSound: Requested playback buffer is %RU64ms (%ld bytes)\n", - DrvAudioHlpBytesToMilli(bd.dwBufferBytes, &pCfgReq->Props), bd.dwBufferBytes)); - - hr = IDirectSound8_CreateSoundBuffer(pThis->pDS, &bd, &pDSB, NULL); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Creating playback sound buffer failed with %Rhrc\n", hr)); - break; - } - - /* "Upgrade" to IDirectSoundBuffer8 interface. */ - hr = IDirectSoundBuffer_QueryInterface(pDSB, IID_IDirectSoundBuffer8, (PVOID *)&pStreamDS->Out.pDSB); - IDirectSoundBuffer_Release(pDSB); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Querying playback sound buffer interface failed with %Rhrc\n", hr)); - break; - } - - /* - * Query the actual parameters set for this stream. - * Those might be different than the initially requested parameters. - */ - RT_ZERO(wfx); - hr = IDirectSoundBuffer8_GetFormat(pStreamDS->Out.pDSB, &wfx, sizeof(wfx), NULL); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Getting playback format failed with %Rhrc\n", hr)); - break; - } - - DSBCAPS bc; - RT_ZERO(bc); - bc.dwSize = sizeof(bc); - - hr = IDirectSoundBuffer8_GetCaps(pStreamDS->Out.pDSB, &bc); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Getting playback capabilities failed with %Rhrc\n", hr)); - break; - } - - DSLOG(("DSound: Acquired playback buffer is %RU64ms (%ld bytes)\n", - DrvAudioHlpBytesToMilli(bc.dwBufferBytes, &pCfgReq->Props), bc.dwBufferBytes)); - - DSLOG(("DSound: Acquired playback format:\n" - " dwBufferBytes = %RI32\n" - " dwFlags = 0x%x\n" - " wFormatTag = %RI16\n" - " nChannels = %RI16\n" - " nSamplesPerSec = %RU32\n" - " nAvgBytesPerSec = %RU32\n" - " nBlockAlign = %RI16\n" - " wBitsPerSample = %RI16\n" - " cbSize = %RI16\n", - bc.dwBufferBytes, - bc.dwFlags, - wfx.wFormatTag, - wfx.nChannels, - wfx.nSamplesPerSec, - wfx.nAvgBytesPerSec, - wfx.nBlockAlign, - wfx.wBitsPerSample, - wfx.cbSize)); - - if (bc.dwBufferBytes & pStreamDS->uAlign) - DSLOGREL(("DSound: Playback capabilities returned misaligned buffer: size %RU32, alignment %RU32\n", - bc.dwBufferBytes, pStreamDS->uAlign + 1)); - - /* - * Initial state. - * dsoundPlayStart initializes part of it to make sure that Stop/Start continues with a correct - * playback buffer position. - */ - pStreamDS->cbBufSize = bc.dwBufferBytes; - - rc = RTCircBufCreate(&pStreamDS->pCircBuf, pStreamDS->cbBufSize * 2 /* Use "double buffering" */); - AssertRC(rc); - - pThis->pDSStrmOut = pStreamDS; - - const uint32_t cfBufSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamDS->cbBufSize); - - pCfgAcq->Backend.cFramesBufferSize = cfBufSize; - pCfgAcq->Backend.cFramesPeriod = cfBufSize / 4; - pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2; - - } while (0); - - if (FAILED(hr)) - directSoundPlayClose(pThis, pStreamDS); - - return hr; -} - -static void dsoundPlayClearBuffer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - AssertPtrReturnVoid(pStreamDS); - - PPDMAUDIOPCMPROPS pProps = &pStreamDS->Cfg.Props; - - HRESULT hr = IDirectSoundBuffer_SetCurrentPosition(pStreamDS->Out.pDSB, 0 /* Position */); - if (FAILED(hr)) - DSLOGREL(("DSound: Setting current position to 0 when clearing buffer failed with %Rhrc\n", hr)); - - PVOID pv1; - hr = directSoundPlayLock(pThis, pStreamDS, - 0 /* dwOffset */, pStreamDS->cbBufSize, - &pv1, NULL, 0, 0, DSBLOCK_ENTIREBUFFER); - if (SUCCEEDED(hr)) - { - DrvAudioHlpClearBuf(pProps, pv1, pStreamDS->cbBufSize, PDMAUDIOPCMPROPS_B2F(pProps, pStreamDS->cbBufSize)); - - directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, NULL, 0, 0); - - /* Make sure to get the last playback position and current write position from DirectSound again. - * Those positions in theory could have changed, re-fetch them to be sure. */ - hr = IDirectSoundBuffer_GetCurrentPosition(pStreamDS->Out.pDSB, - &pStreamDS->Out.offPlayCursorLastPlayed, &pStreamDS->Out.offWritePos); - if (FAILED(hr)) - DSLOGREL(("DSound: Re-fetching current position when clearing buffer failed with %Rhrc\n", hr)); - } -} - -/** - * Transfers audio data from the internal buffer to the DirectSound playback instance. - * Due to internal accounting and querying DirectSound, this function knows how much it can transfer at once. - * - * @return IPRT status code. - * @param pThis Host audio driver instance. - * @param pStreamDS Stream to transfer playback data for. - */ -static int dsoundPlayTransfer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - if (!pStreamDS->fEnabled) - { - Log2Func(("Stream disabled, skipping\n")); - return VINF_SUCCESS; - } - - uint32_t cbTransferred = 0; - - PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf; - AssertPtr(pCircBuf); - - LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB; - AssertPtr(pDSB); - - int rc = VINF_SUCCESS; - - DWORD offPlayCursor, offWriteCursor; - HRESULT hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor); - if (FAILED(hr)) - { - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - return rc; - } - - DWORD cbFree, cbRemaining; - if (pStreamDS->Out.fFirstTransfer) - { - cbRemaining = 0; - cbFree = pStreamDS->cbBufSize; - } - else - { - cbFree = dsoundRingDistance(offPlayCursor, pStreamDS->Out.offWritePos, pStreamDS->cbBufSize); - cbRemaining = dsoundRingDistance(pStreamDS->Out.offWritePos, offPlayCursor, pStreamDS->cbBufSize); - } - - uint32_t cbAvail = (uint32_t)RTCircBufUsed(pCircBuf); - uint32_t cbToTransfer = RT_MIN(cbFree, cbAvail); - -#ifdef LOG_ENABLED - if (!pStreamDS->Dbg.tsLastTransferredMs) - pStreamDS->Dbg.tsLastTransferredMs = RTTimeMilliTS(); - Log3Func(("offPlay=%RU32, offWrite=%RU32, tsLastTransferredMs=%RU64ms, cbAvail=%RU32, cbFree=%RU32 -> cbToTransfer=%RU32 " - "(fFirst=%RTbool, fDrain=%RTbool)\n", - offPlayCursor, offWriteCursor, RTTimeMilliTS() - pStreamDS->Dbg.tsLastTransferredMs, cbAvail, cbFree, cbToTransfer, - pStreamDS->Out.fFirstTransfer, pStreamDS->Out.fDrain)); - pStreamDS->Dbg.tsLastTransferredMs = RTTimeMilliTS(); -#endif - - while (cbToTransfer) - { - DWORD cb1 = 0; - DWORD cb2 = 0; - - void *pvBuf; - size_t cbBuf; - RTCircBufAcquireReadBlock(pCircBuf, cbToTransfer, &pvBuf, &cbBuf); - - if (cbBuf) - { - PVOID pv1, pv2; - hr = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, (DWORD)cbBuf, - &pv1, &pv2, &cb1, &cb2, 0 /* dwFlags */); - if (FAILED(hr)) - { - rc = VERR_ACCESS_DENIED; - break; - } - - AssertPtr(pv1); - Assert(cb1); - - memcpy(pv1, pvBuf, cb1); - - if (pv2 && cb2) /* Buffer wrap-around? Write second part. */ - memcpy(pv2, (uint8_t *)pvBuf + cb1, cb2); - - directSoundPlayUnlock(pThis, pDSB, pv1, pv2, cb1, cb2); - - pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize; - - Assert(cbToTransfer >= cbBuf); - cbToTransfer -= (uint32_t)cbBuf; - - cbTransferred += cb1 + cb2; - } - - RTCircBufReleaseReadBlock(pCircBuf, cb1 + cb2); - } - - pStreamDS->Out.cbTransferred += cbTransferred; - - if ( pStreamDS->Out.fFirstTransfer - && pStreamDS->Out.cbTransferred >= DrvAudioHlpFramesToBytes(pStreamDS->Cfg.Backend.cFramesPreBuffering, &pStreamDS->Cfg.Props)) - { - hr = directSoundPlayStart(pThis, pStreamDS); - if (SUCCEEDED(hr)) - { - pStreamDS->Out.fFirstTransfer = false; - } - else - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - - cbAvail = (uint32_t)RTCircBufUsed(pCircBuf); - if ( !cbAvail - && cbTransferred) - { - pStreamDS->Out.cbLastTransferred = cbTransferred; - pStreamDS->Out.tsLastTransferredMs = RTTimeMilliTS(); - - LogFlowFunc(("cbLastTransferred=%RU32, tsLastTransferredMs=%RU64\n", - pStreamDS->Out.cbLastTransferred, pStreamDS->Out.tsLastTransferredMs)); - } - - LogFlowFunc(("cbTransferred=%RU32, cbAvail=%RU32, rc=%Rrc\n", cbTransferred, cbAvail, rc)); - return rc; -} - -static HRESULT directSoundPlayGetStatus(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, DWORD *pdwStatus) -{ - AssertPtrReturn(pThis, E_POINTER); - AssertPtrReturn(pDSB, E_POINTER); - - AssertPtrNull(pdwStatus); - - DWORD dwStatus = 0; - HRESULT hr = E_FAIL; - for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) - { - hr = IDirectSoundBuffer8_GetStatus(pDSB, &dwStatus); - if ( hr == DSERR_BUFFERLOST - || ( SUCCEEDED(hr) - && (dwStatus & DSBSTATUS_BUFFERLOST))) - { - LogFlowFunc(("Getting status failed due to lost buffer, restoring ...\n")); - directSoundPlayRestore(pThis, pDSB); - } - else - break; - } - - if (SUCCEEDED(hr)) - { - if (pdwStatus) - *pdwStatus = dwStatus; - } - else - DSLOGREL(("DSound: Retrieving playback status failed with %Rhrc\n", hr)); - - return hr; -} - -static HRESULT directSoundPlayStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush) -{ - AssertPtrReturn(pThis, E_POINTER); - AssertPtrReturn(pStreamDS, E_POINTER); - - HRESULT hr = S_OK; - - if (pStreamDS->Out.pDSB) - { - DSLOG(("DSound: Stopping playback\n")); - hr = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); - if (FAILED(hr)) - { - hr = directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); - if (FAILED(hr)) - hr = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); - } - } - - if (SUCCEEDED(hr)) - { - if (fFlush) - dsoundStreamReset(pThis, pStreamDS); - } - - if (FAILED(hr)) - DSLOGREL(("DSound: %s playback failed with %Rhrc\n", fFlush ? "Stopping" : "Pausing", hr)); - - return hr; -} - -/** - * Enables or disables a stream. - * - * @return IPRT status code. - * @param pThis Host audio driver instance. - * @param pStreamDS Stream to enable / disable. - * @param fEnable Whether to enable or disable the stream. - */ -static int dsoundStreamEnable(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fEnable) -{ - RT_NOREF(pThis); - - LogFunc(("%s %s\n", - fEnable ? "Enabling" : "Disabling", - pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback")); - - if (fEnable) - dsoundStreamReset(pThis, pStreamDS); - - pStreamDS->fEnabled = fEnable; - - return VINF_SUCCESS; -} - - -/** - * Resets the state of a DirectSound stream. - * - * @param pThis Host audio driver instance. - * @param pStreamDS Stream to reset state for. - */ -static void dsoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - RT_NOREF(pThis); - - LogFunc(("Resetting %s\n", - pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback")); - - if (pStreamDS->pCircBuf) - RTCircBufReset(pStreamDS->pCircBuf); - - if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) - { - pStreamDS->In.offReadPos = 0; - pStreamDS->In.cOverruns = 0; - - /* Also reset the DirectSound Capture Buffer (DSCB) by clearing all data to make sure - * not stale audio data is left. */ - if (pStreamDS->In.pDSCB) - { - PVOID pv1; PVOID pv2; DWORD cb1; DWORD cb2; - HRESULT hr = directSoundCaptureLock(pStreamDS, 0 /* Offset */, pStreamDS->cbBufSize, &pv1, &pv2, &cb1, &cb2, - 0 /* Flags */); - if (SUCCEEDED(hr)) - { - DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1)); - if (pv2 && cb2) - DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2)); - directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); - } - } - } - else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) - { - pStreamDS->Out.fFirstTransfer = true; - pStreamDS->Out.fDrain = false; - pStreamDS->Out.cUnderruns = 0; - - pStreamDS->Out.cbLastTransferred = 0; - pStreamDS->Out.tsLastTransferredMs = 0; - - pStreamDS->Out.cbTransferred = 0; - pStreamDS->Out.cbWritten = 0; - - pStreamDS->Out.offWritePos = 0; - pStreamDS->Out.offPlayCursorLastPending = 0; - pStreamDS->Out.offPlayCursorLastPlayed = 0; - - /* Also reset the DirectSound Buffer (DSB) by setting the position to 0 and clear all data to make sure - * not stale audio data is left. */ - if (pStreamDS->Out.pDSB) - { - HRESULT hr = IDirectSoundBuffer8_SetCurrentPosition(pStreamDS->Out.pDSB, 0); - if (SUCCEEDED(hr)) - { - PVOID pv1; PVOID pv2; DWORD cb1; DWORD cb2; - hr = directSoundPlayLock(pThis, pStreamDS, 0 /* Offset */, pStreamDS->cbBufSize, &pv1, &pv2, &cb1, &cb2, - 0 /* Flags */); - if (SUCCEEDED(hr)) - { - DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1)); - if (pv2 && cb2) - DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2)); - directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); - } - } - } - } - -#ifdef LOG_ENABLED - pStreamDS->Dbg.tsLastTransferredMs = 0; -#endif -} - - -/** - * Starts playing a DirectSound stream. - * - * @return HRESULT - * @param pThis Host audio driver instance. - * @param pStreamDS Stream to start playing. - */ -static HRESULT directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - HRESULT hr = S_OK; - - DWORD fFlags = DSCBSTART_LOOPING; - - for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) - { - DSLOG(("DSound: Starting playback\n")); - hr = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, fFlags); - if ( SUCCEEDED(hr) - || hr != DSERR_BUFFERLOST) - break; - else - { - LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n")); - directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); - } - } - - return hr; -} - - -/* - * DirectSoundCapture - */ - -static LPCGUID dsoundCaptureSelectDevice(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pThis, NULL); - AssertPtrReturn(pCfg, NULL); - - int rc = VINF_SUCCESS; - - LPCGUID pGUID = pThis->Cfg.pGuidCapture; - if (!pGUID) - { - PPDMAUDIODEVICE pDev = NULL; - - switch (pCfg->u.enmSrc) - { - case PDMAUDIORECSRC_LINE: - /* - * At the moment we're only supporting line-in in the HDA emulation, - * and line-in + mic-in in the AC'97 emulation both are expected - * to use the host's mic-in as well. - * - * So the fall through here is intentional for now. - */ - case PDMAUDIORECSRC_MIC: - { - pDev = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->DeviceEnum, PDMAUDIODIR_IN); - break; - } - - default: - AssertFailedStmt(rc = VERR_NOT_SUPPORTED); - break; - } - - if ( RT_SUCCESS(rc) - && pDev) - { - DSLOG(("DSound: Guest source '%s' is using host recording device '%s'\n", - DrvAudioHlpRecSrcToStr(pCfg->u.enmSrc), pDev->szName)); - - PDSOUNDDEV pDSoundDev = (PDSOUNDDEV)pDev->pvData; - AssertPtr(pDSoundDev); - - pGUID = &pDSoundDev->Guid; - } - } - - if (RT_FAILURE(rc)) - { - LogRel(("DSound: Selecting recording device failed with %Rrc\n", rc)); - return NULL; - } - - char *pszGUID = dsoundGUIDToUtf8StrA(pGUID); - - /* This always has to be in the release log. */ - LogRel(("DSound: Guest source '%s' is using host recording device with GUID '%s'\n", - DrvAudioHlpRecSrcToStr(pCfg->u.enmSrc), pszGUID ? pszGUID: "{?}")); - - if (pszGUID) - { - RTStrFree(pszGUID); - pszGUID = NULL; - } - - return pGUID; -} - -/** - * Transfers audio data from the DirectSound capture instance to the internal buffer. - * Due to internal accounting and querying DirectSound, this function knows how much it can transfer at once. - * - * @return IPRT status code. - * @param pThis Host audio driver instance. - * @param pStreamDS Stream to capture audio data for. - */ -static int dsoundCaptureTransfer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - RT_NOREF(pThis); - - LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB = pStreamDS->In.pDSCB; - AssertPtr(pDSCB); - - DWORD offCaptureCursor; - HRESULT hr = IDirectSoundCaptureBuffer_GetCurrentPosition(pDSCB, NULL, &offCaptureCursor); - if (FAILED(hr)) - { - AssertFailed(); - return VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - - DWORD cbUsed = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize); - - PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf; - AssertPtr(pCircBuf); - - uint32_t cbFree = (uint32_t)RTCircBufFree(pCircBuf); - if ( !cbFree - && pStreamDS->In.cOverruns < 32) /** @todo Make this configurable. */ - { - DSLOG(("DSound: Warning: Internal buffer full (size is %zu bytes), skipping to record data (overflow #%RU32)\n", - RTCircBufSize(pCircBuf), pStreamDS->In.cOverruns)); - DSLOG(("DSound: Warning: DSound capture buffer currently uses %RU32/%RU32 bytes\n", cbUsed, pStreamDS->cbBufSize)); - pStreamDS->In.cOverruns++; - } - - DWORD cbToCapture = RT_MIN(cbUsed, cbFree); - - Log3Func(("cbUsed=%ld, cbToCapture=%ld\n", cbUsed, cbToCapture)); - - while (cbToCapture) - { - void *pvBuf; - size_t cbBuf; - RTCircBufAcquireWriteBlock(pCircBuf, cbToCapture, &pvBuf, &cbBuf); - - if (cbBuf) - { - PVOID pv1, pv2; - DWORD cb1, cb2; - hr = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, (DWORD)cbBuf, - &pv1, &pv2, &cb1, &cb2, 0 /* dwFlags */); - if (FAILED(hr)) - break; - - AssertPtr(pv1); - Assert(cb1); - - memcpy(pvBuf, pv1, cb1); - - if (pv2 && cb2) /* Buffer wrap-around? Write second part. */ - memcpy((uint8_t *)pvBuf + cb1, pv2, cb2); - - directSoundCaptureUnlock(pDSCB, pv1, pv2, cb1, cb2); - - pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize; - - Assert(cbToCapture >= cbBuf); - cbToCapture -= (uint32_t)cbBuf; - } - -#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA - if (cbBuf) - { - RTFILE fh; - int rc2 = RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "dsoundCapture.pcm", - RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - if (RT_SUCCESS(rc2)) - { - RTFileWrite(fh, pvBuf, cbBuf, NULL); - RTFileClose(fh); - } - } -#endif - RTCircBufReleaseWriteBlock(pCircBuf, cbBuf); - } - - return VINF_SUCCESS; -} - -/** - * Destroys a DirectSound capture interface. - * - * @param pDSC Capture interface to destroy. - */ -static void directSoundCaptureInterfaceDestroy(LPDIRECTSOUNDCAPTURE8 pDSC) -{ - if (pDSC) - { - LogFlowFuncEnter(); - - IDirectSoundCapture_Release(pDSC); - pDSC = NULL; - } -} - -/** - * Creates a DirectSound capture interface. - * - * @return HRESULT - * @param pGUID GUID of device to create the capture interface for. - * @param ppDSC Where to store the created interface. Optional. - */ -static HRESULT directSoundCaptureInterfaceCreate(LPCGUID pGUID, LPDIRECTSOUNDCAPTURE8 *ppDSC) -{ - /* pGUID can be NULL, if this is the default device. */ - /* ppDSC is optional. */ - - LogFlowFuncEnter(); - - LPDIRECTSOUNDCAPTURE8 pDSC; - HRESULT hr = CoCreateInstance(CLSID_DirectSoundCapture8, NULL, CLSCTX_ALL, - IID_IDirectSoundCapture8, (void **)&pDSC); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Creating capture instance failed with %Rhrc\n", hr)); - } - else - { - hr = IDirectSoundCapture_Initialize(pDSC, pGUID); - if (FAILED(hr)) - { - if (hr == DSERR_NODRIVER) /* Usually means that no capture devices are attached. */ - DSLOGREL(("DSound: Capture device currently is unavailable\n")); - else - DSLOGREL(("DSound: Initializing capturing device failed with %Rhrc\n", hr)); - - directSoundCaptureInterfaceDestroy(pDSC); - } - else if (ppDSC) - { - *ppDSC = pDSC; - } - } - - LogFlowFunc(("Returning %Rhrc\n", hr)); - return hr; -} - -static HRESULT directSoundCaptureClose(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - AssertPtrReturn(pThis, E_POINTER); - AssertPtrReturn(pStreamDS, E_POINTER); - - LogFlowFuncEnter(); - - HRESULT hr = directSoundCaptureStop(pThis, pStreamDS, true /* fFlush */); - if (FAILED(hr)) - return hr; - - if ( pStreamDS - && pStreamDS->In.pDSCB) - { - DSLOG(("DSound: Closing capturing stream\n")); - - IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB); - pStreamDS->In.pDSCB = NULL; - } - - LogFlowFunc(("Returning %Rhrc\n", hr)); - return hr; -} - -static HRESULT directSoundCaptureOpen(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pThis, E_POINTER); - AssertPtrReturn(pStreamDS, E_POINTER); - AssertPtrReturn(pCfgReq, E_POINTER); - AssertPtrReturn(pCfgAcq, E_POINTER); - - LogFlowFuncEnter(); - - Assert(pStreamDS->In.pDSCB == NULL); - - DSLOG(("DSound: Opening capturing stream (uHz=%RU32, cChannels=%RU8, cBits=%u, fSigned=%RTbool)\n", - pCfgReq->Props.uHz, pCfgReq->Props.cChannels, pCfgReq->Props.cbSample * 8, pCfgReq->Props.fSigned)); - - WAVEFORMATEX wfx; - int rc = dsoundWaveFmtFromCfg(pCfgReq, &wfx); - if (RT_FAILURE(rc)) - return E_INVALIDARG; - - dsoundUpdateStatusInternal(pThis); - - HRESULT hr = directSoundCaptureInterfaceCreate(pThis->Cfg.pGuidCapture, &pThis->pDSC); - if (FAILED(hr)) - return hr; - - do /* To use breaks. */ - { - DSCBUFFERDESC bd; - RT_ZERO(bd); - - bd.dwSize = sizeof(bd); - bd.lpwfxFormat = &wfx; - bd.dwBufferBytes = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cFramesBufferSize, &pCfgReq->Props); - - DSLOG(("DSound: Requested capture buffer is %RU64ms (%ld bytes)\n", - DrvAudioHlpBytesToMilli(bd.dwBufferBytes, &pCfgReq->Props), bd.dwBufferBytes)); - - LPDIRECTSOUNDCAPTUREBUFFER pDSCB; - hr = IDirectSoundCapture_CreateCaptureBuffer(pThis->pDSC, &bd, &pDSCB, NULL); - if (FAILED(hr)) - { - if (hr == E_ACCESSDENIED) - { - DSLOGREL(("DSound: Capturing input from host not possible, access denied\n")); - } - else - DSLOGREL(("DSound: Creating capture buffer failed with %Rhrc\n", hr)); - break; - } - - hr = IDirectSoundCaptureBuffer_QueryInterface(pDSCB, IID_IDirectSoundCaptureBuffer8, (void **)&pStreamDS->In.pDSCB); - IDirectSoundCaptureBuffer_Release(pDSCB); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Querying interface for capture buffer failed with %Rhrc\n", hr)); - break; - } - - /* - * Query the actual parameters. - */ - DWORD offByteReadPos = 0; - hr = IDirectSoundCaptureBuffer8_GetCurrentPosition(pStreamDS->In.pDSCB, NULL, &offByteReadPos); - if (FAILED(hr)) - { - offByteReadPos = 0; - DSLOGREL(("DSound: Getting capture position failed with %Rhrc\n", hr)); - } - - RT_ZERO(wfx); - hr = IDirectSoundCaptureBuffer8_GetFormat(pStreamDS->In.pDSCB, &wfx, sizeof(wfx), NULL); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Getting capture format failed with %Rhrc\n", hr)); - break; - } - - DSCBCAPS bc; - RT_ZERO(bc); - bc.dwSize = sizeof(bc); - hr = IDirectSoundCaptureBuffer8_GetCaps(pStreamDS->In.pDSCB, &bc); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Getting capture capabilities failed with %Rhrc\n", hr)); - break; - } - - DSLOG(("DSound: Acquired capture buffer is %RU64ms (%ld bytes)\n", - DrvAudioHlpBytesToMilli(bc.dwBufferBytes, &pCfgReq->Props), bc.dwBufferBytes)); - - DSLOG(("DSound: Capture format:\n" - " dwBufferBytes = %RI32\n" - " dwFlags = 0x%x\n" - " wFormatTag = %RI16\n" - " nChannels = %RI16\n" - " nSamplesPerSec = %RU32\n" - " nAvgBytesPerSec = %RU32\n" - " nBlockAlign = %RI16\n" - " wBitsPerSample = %RI16\n" - " cbSize = %RI16\n", - bc.dwBufferBytes, - bc.dwFlags, - wfx.wFormatTag, - wfx.nChannels, - wfx.nSamplesPerSec, - wfx.nAvgBytesPerSec, - wfx.nBlockAlign, - wfx.wBitsPerSample, - wfx.cbSize)); - - if (bc.dwBufferBytes & pStreamDS->uAlign) - DSLOGREL(("DSound: Capture GetCaps returned misaligned buffer: size %RU32, alignment %RU32\n", - bc.dwBufferBytes, pStreamDS->uAlign + 1)); - - /* Initial state: reading at the initial capture position, no error. */ - pStreamDS->In.offReadPos = 0; - pStreamDS->cbBufSize = bc.dwBufferBytes; - - rc = RTCircBufCreate(&pStreamDS->pCircBuf, pStreamDS->cbBufSize * 2 /* Use "double buffering" */); - AssertRC(rc); - - pThis->pDSStrmIn = pStreamDS; - - pCfgAcq->Backend.cFramesBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamDS->cbBufSize); - - } while (0); - - if (FAILED(hr)) - directSoundCaptureClose(pThis, pStreamDS); - - LogFlowFunc(("Returning %Rhrc\n", hr)); - return hr; -} - -static HRESULT directSoundCaptureStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush) -{ - AssertPtrReturn(pThis, E_POINTER); - AssertPtrReturn(pStreamDS, E_POINTER); - - RT_NOREF(pThis); - - HRESULT hr = S_OK; - - if (pStreamDS->In.pDSCB) - { - DSLOG(("DSound: Stopping capture\n")); - hr = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB); - } - - if (SUCCEEDED(hr)) - { - if (fFlush) - dsoundStreamReset(pThis, pStreamDS); - } - - if (FAILED(hr)) - DSLOGREL(("DSound: Stopping capture buffer failed with %Rhrc\n", hr)); - - return hr; -} - -static HRESULT directSoundCaptureStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - AssertPtrReturn(pThis, E_POINTER); - AssertPtrReturn(pStreamDS, E_POINTER); - - HRESULT hr; - if (pStreamDS->In.pDSCB) - { - DWORD dwStatus; - hr = IDirectSoundCaptureBuffer8_GetStatus(pStreamDS->In.pDSCB, &dwStatus); - if (FAILED(hr)) - { - DSLOGREL(("DSound: Retrieving capture status failed with %Rhrc\n", hr)); - } - else - { - if (dwStatus & DSCBSTATUS_CAPTURING) - { - DSLOG(("DSound: Already capturing\n")); - } - else - { - const DWORD fFlags = DSCBSTART_LOOPING; - - DSLOG(("DSound: Starting to capture\n")); - hr = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, fFlags); - if (FAILED(hr)) - DSLOGREL(("DSound: Starting to capture failed with %Rhrc\n", hr)); - } - } - } - else - hr = E_UNEXPECTED; - - LogFlowFunc(("Returning %Rhrc\n", hr)); - return hr; -} - -/** - * Callback for the playback device enumeration. - * - * @return TRUE if continuing enumeration, FALSE if not. - * @param pGUID Pointer to GUID of enumerated device. Can be NULL. - * @param pwszDescription Pointer to (friendly) description of enumerated device. - * @param pwszModule Pointer to module name of enumerated device. - * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information. - */ -static BOOL CALLBACK dsoundDevicesEnumCbPlayback(LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule, PVOID lpContext) -{ - RT_NOREF(pwszModule); - - PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX)lpContext; - AssertPtrReturn(pEnumCtx , FALSE); - - PPDMAUDIODEVICEENUM pDevEnm = pEnumCtx->pDevEnm; - AssertPtrReturn(pDevEnm, FALSE); - - /* pGUID can be NULL for default device(s). */ - AssertPtrReturn(pwszDescription, FALSE); - /* Do not care about pwszModule. */ - - int rc; - - PPDMAUDIODEVICE pDev = DrvAudioHlpDeviceAlloc(sizeof(DSOUNDDEV)); - if (pDev) - { - pDev->enmUsage = PDMAUDIODIR_OUT; - pDev->enmType = PDMAUDIODEVICETYPE_BUILTIN; - - if (pGUID == NULL) - pDev->fFlags = PDMAUDIODEV_FLAGS_DEFAULT; - - char *pszName; - rc = RTUtf16ToUtf8(pwszDescription, &pszName); - if (RT_SUCCESS(rc)) - { - RTStrCopy(pDev->szName, sizeof(pDev->szName), pszName); - RTStrFree(pszName); - - PDSOUNDDEV pDSoundDev = (PDSOUNDDEV)pDev->pvData; - - if (pGUID) - memcpy(&pDSoundDev->Guid, pGUID, sizeof(GUID)); - - LPDIRECTSOUND8 pDS; - HRESULT hr = directSoundPlayInterfaceCreate(pGUID, &pDS); - if (SUCCEEDED(hr)) - { - do - { - DSCAPS DSCaps; - RT_ZERO(DSCaps); - DSCaps.dwSize = sizeof(DSCAPS); - hr = IDirectSound_GetCaps(pDS, &DSCaps); - if (FAILED(hr)) - break; - - pDev->cMaxOutputChannels = DSCaps.dwFlags & DSCAPS_PRIMARYSTEREO ? 2 : 1; - - DWORD dwSpeakerCfg; - hr = IDirectSound_GetSpeakerConfig(pDS, &dwSpeakerCfg); - if (FAILED(hr)) - break; - - unsigned uSpeakerCount = 0; - switch (DSSPEAKER_CONFIG(dwSpeakerCfg)) - { - case DSSPEAKER_MONO: uSpeakerCount = 1; break; - case DSSPEAKER_HEADPHONE: uSpeakerCount = 2; break; - case DSSPEAKER_STEREO: uSpeakerCount = 2; break; - case DSSPEAKER_QUAD: uSpeakerCount = 4; break; - case DSSPEAKER_SURROUND: uSpeakerCount = 4; break; - case DSSPEAKER_5POINT1: uSpeakerCount = 6; break; - case DSSPEAKER_5POINT1_SURROUND: uSpeakerCount = 6; break; - case DSSPEAKER_7POINT1: uSpeakerCount = 8; break; - case DSSPEAKER_7POINT1_SURROUND: uSpeakerCount = 8; break; - default: break; - } - - if (uSpeakerCount) /* Do we need to update the channel count? */ - pDev->cMaxOutputChannels = uSpeakerCount; - - } while (0); - - directSoundPlayInterfaceDestroy(pDS); - - rc = VINF_SUCCESS; - } - else - rc = VERR_GENERAL_FAILURE; - - if (RT_SUCCESS(rc)) - rc = DrvAudioHlpDeviceEnumAdd(pDevEnm, pDev); - } - } - else - rc = VERR_NO_MEMORY; - - if (RT_FAILURE(rc)) - { - LogRel(("DSound: Error enumeration playback device '%ls', rc=%Rrc\n", pwszDescription, rc)); - return FALSE; /* Abort enumeration. */ - } - - return TRUE; -} - -/** - * Callback for the capture device enumeration. - * - * @return TRUE if continuing enumeration, FALSE if not. - * @param pGUID Pointer to GUID of enumerated device. Can be NULL. - * @param pwszDescription Pointer to (friendly) description of enumerated device. - * @param pwszModule Pointer to module name of enumerated device. - * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information. - */ -static BOOL CALLBACK dsoundDevicesEnumCbCapture(LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule, PVOID lpContext) -{ - RT_NOREF(pwszModule); - - PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX )lpContext; - AssertPtrReturn(pEnumCtx , FALSE); - - PPDMAUDIODEVICEENUM pDevEnm = pEnumCtx->pDevEnm; - AssertPtrReturn(pDevEnm, FALSE); - - /* pGUID can be NULL for default device(s). */ - AssertPtrReturn(pwszDescription, FALSE); - /* Do not care about pwszModule. */ - - int rc; - - PPDMAUDIODEVICE pDev = DrvAudioHlpDeviceAlloc(sizeof(DSOUNDDEV)); - if (pDev) - { - pDev->enmUsage = PDMAUDIODIR_IN; - pDev->enmType = PDMAUDIODEVICETYPE_BUILTIN; - - char *pszName; - rc = RTUtf16ToUtf8(pwszDescription, &pszName); - if (RT_SUCCESS(rc)) - { - RTStrCopy(pDev->szName, sizeof(pDev->szName), pszName); - RTStrFree(pszName); - - PDSOUNDDEV pDSoundDev = (PDSOUNDDEV)pDev->pvData; - - if (pGUID) - memcpy(&pDSoundDev->Guid, pGUID, sizeof(GUID)); - - LPDIRECTSOUNDCAPTURE8 pDSC; - HRESULT hr = directSoundCaptureInterfaceCreate(pGUID, &pDSC); - if (SUCCEEDED(hr)) - { - do - { - DSCCAPS DSCCaps; - RT_ZERO(DSCCaps); - DSCCaps.dwSize = sizeof(DSCCAPS); - hr = IDirectSoundCapture_GetCaps(pDSC, &DSCCaps); - if (FAILED(hr)) - break; - - pDev->cMaxInputChannels = DSCCaps.dwChannels; - - } while (0); - - directSoundCaptureInterfaceDestroy(pDSC); - - rc = VINF_SUCCESS; - } - else - rc = VERR_GENERAL_FAILURE; - - if (RT_SUCCESS(rc)) - rc = DrvAudioHlpDeviceEnumAdd(pDevEnm, pDev); - } - } - else - rc = VERR_NO_MEMORY; - - if (RT_FAILURE(rc)) - { - LogRel(("DSound: Error enumeration capture device '%ls', rc=%Rrc\n", pwszDescription, rc)); - return FALSE; /* Abort enumeration. */ - } - - return TRUE; -} - -/** - * Does a (Re-)enumeration of the host's playback + capturing devices. - * - * @return IPRT status code. - * @param pThis Host audio driver instance. - * @param pDevEnm Where to store the enumerated devices. - */ -static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PPDMAUDIODEVICEENUM pDevEnm) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - - DSLOG(("DSound: Enumerating devices ...\n")); - - RTLDRMOD hDSound = NULL; - int rc = RTLdrLoadSystem("dsound.dll", true /*fNoUnload*/, &hDSound); - if (RT_SUCCESS(rc)) - { - DSOUNDENUMCBCTX EnumCtx; - EnumCtx.fFlags = 0; - EnumCtx.pDevEnm = pDevEnm; - - PFNDIRECTSOUNDENUMERATEW pfnDirectSoundEnumerateW = NULL; - rc = RTLdrGetSymbol(hDSound, "DirectSoundEnumerateW", (void**)&pfnDirectSoundEnumerateW); - if (RT_SUCCESS(rc)) - { - HRESULT hr = pfnDirectSoundEnumerateW(&dsoundDevicesEnumCbPlayback, &EnumCtx); - if (FAILED(hr)) - LogRel(("DSound: Error enumerating host playback devices: %Rhrc\n", hr)); - } - else - LogRel(("DSound: Error starting to enumerate host playback devices: %Rrc\n", rc)); - - PFNDIRECTSOUNDCAPTUREENUMERATEW pfnDirectSoundCaptureEnumerateW = NULL; - rc = RTLdrGetSymbol(hDSound, "DirectSoundCaptureEnumerateW", (void**)&pfnDirectSoundCaptureEnumerateW); - if (RT_SUCCESS(rc)) - { - HRESULT hr = pfnDirectSoundCaptureEnumerateW(&dsoundDevicesEnumCbCapture, &EnumCtx); - if (FAILED(hr)) - LogRel(("DSound: Error enumerating host capture devices: %Rhrc\n", hr)); - } - else - LogRel(("DSound: Error starting to enumerate host capture devices: %Rrc\n", rc)); - - RTLdrClose(hDSound); - } - else - { - /* No dsound.dll on this system. */ - LogRel(("DSound: Could not load dsound.dll: %Rrc\n", rc)); - } - - DSLOG(("DSound: Enumerating devices done\n")); - - return rc; -} - -/** - * Updates this host driver's internal status, according to the global, overall input/output - * state and all connected (native) audio streams. - * - * @param pThis Host audio driver instance. - */ -static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis) -{ - AssertPtrReturnVoid(pThis); - /* pCfg is optional. */ - - LogFlowFuncEnter(); - - int rc = dsoundDevicesEnumerate(pThis, &pThis->DeviceEnum); - if (RT_SUCCESS(rc)) - { -#if 0 - if ( pThis->fEnabledOut != RT_BOOL(cbCtx.cDevOut) - || pThis->fEnabledIn != RT_BOOL(cbCtx.cDevIn)) - { - /** @todo Use a registered callback to the audio connector (e.g "OnConfigurationChanged") to - * let the connector know that something has changed within the host backend. */ - } -#endif - pThis->fEnabledIn = RT_BOOL(DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->DeviceEnum, PDMAUDIODIR_IN)); - pThis->fEnabledOut = RT_BOOL(DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->DeviceEnum, PDMAUDIODIR_OUT)); - } - - LogFlowFuncLeaveRC(rc); -} - -static int dsoundCreateStreamOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - LogFlowFunc(("pStreamDS=%p, pCfgReq=%p\n", pStreamDS, pCfgReq)); - - int rc = VINF_SUCCESS; - - /* Try to open playback in case the device is already there. */ - HRESULT hr = directSoundPlayOpen(pThis, pStreamDS, pCfgReq, pCfgAcq); - if (SUCCEEDED(hr)) - { - rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq); - if (RT_SUCCESS(rc)) - dsoundStreamReset(pThis, pStreamDS); - } - else - rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; - - LogFlowFuncLeaveRC(rc); - return rc; -} - -static int dsoundControlStreamOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - LogFlowFunc(("pStreamDS=%p, cmd=%d\n", pStreamDS, enmStreamCmd)); - - int rc = VINF_SUCCESS; - - HRESULT hr; - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - { - dsoundStreamEnable(pThis, pStreamDS, true /* fEnable */); - break; - } - - case PDMAUDIOSTREAMCMD_RESUME: - { - hr = directSoundPlayStart(pThis, pStreamDS); - if (FAILED(hr)) - rc = VERR_NOT_SUPPORTED; /** @todo Fix this. */ - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - { - dsoundStreamEnable(pThis, pStreamDS, false /* fEnable */); - hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */); - if (FAILED(hr)) - rc = VERR_NOT_SUPPORTED; - break; - } - - case PDMAUDIOSTREAMCMD_PAUSE: - { - hr = directSoundPlayStop(pThis, pStreamDS, false /* fFlush */); - if (FAILED(hr)) - rc = VERR_NOT_SUPPORTED; - break; - } - - case PDMAUDIOSTREAMCMD_DRAIN: - { - /* Make sure we transferred everything. */ - pStreamDS->fEnabled = true; - pStreamDS->Out.fDrain = true; - rc = dsoundPlayTransfer(pThis, pStreamDS); - if ( RT_SUCCESS(rc) - && pStreamDS->Out.fFirstTransfer) - { - /* If this was the first transfer ever for this stream, make sure to also play the (short) audio data. */ - DSLOG(("DSound: Started playing output (short sound)\n")); - - pStreamDS->Out.fFirstTransfer = false; - pStreamDS->Out.cbLastTransferred = pStreamDS->Out.cbTransferred; /* All transferred audio data must be played. */ - pStreamDS->Out.tsLastTransferredMs = RTTimeMilliTS(); - - hr = directSoundPlayStart(pThis, pStreamDS); - if (FAILED(hr)) - rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ - } - break; - } - - case PDMAUDIOSTREAMCMD_DROP: - { - pStreamDS->Out.cbLastTransferred = 0; - pStreamDS->Out.tsLastTransferredMs = 0; - RTCircBufReset(pStreamDS->pCircBuf); - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} - */ -static DECLCALLBACK(int) drvHostDSoundHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, - uint32_t uBufSize, uint32_t *puWritten) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(uBufSize, VERR_INVALID_PARAMETER); - /* puWritten is optional. */ - - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - int rc = VINF_SUCCESS; - - uint32_t cbWrittenTotal = 0; - - uint8_t *pbBuf = (uint8_t *)pvBuf; - PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf; - - uint32_t cbToPlay = RT_MIN(uBufSize, (uint32_t)RTCircBufFree(pCircBuf)); - while (cbToPlay) - { - void *pvChunk; - size_t cbChunk; - RTCircBufAcquireWriteBlock(pCircBuf, cbToPlay, &pvChunk, &cbChunk); - - if (cbChunk) - { - memcpy(pvChunk, pbBuf, cbChunk); - - pbBuf += cbChunk; - Assert(cbToPlay >= cbChunk); - cbToPlay -= (uint32_t)cbChunk; - - cbWrittenTotal += (uint32_t)cbChunk; - } - - RTCircBufReleaseWriteBlock(pCircBuf, cbChunk); - } - - Assert(cbWrittenTotal <= uBufSize); - Assert(cbWrittenTotal == uBufSize); - - pStreamDS->Out.cbWritten += cbWrittenTotal; - - if (RT_SUCCESS(rc)) - { - if (puWritten) - *puWritten = cbWrittenTotal; - } - else - dsoundUpdateStatusInternal(pThis); - - return rc; -} - -static int dsoundDestroyStreamOut(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDSTREAM pStream) -{ - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - LogFlowFuncEnter(); - - HRESULT hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */); - if (SUCCEEDED(hr)) - { - hr = directSoundPlayClose(pThis, pStreamDS); - if (FAILED(hr)) - return VERR_GENERAL_FAILURE; /** @todo Fix. */ - } - - return VINF_SUCCESS; -} - -static int dsoundCreateStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - LogFunc(("pStreamDS=%p, pCfgReq=%p, enmRecSource=%s\n", - pStreamDS, pCfgReq, DrvAudioHlpRecSrcToStr(pCfgReq->u.enmSrc))); - - int rc = VINF_SUCCESS; - - /* Try to open capture in case the device is already there. */ - HRESULT hr = directSoundCaptureOpen(pThis, pStreamDS, pCfgReq, pCfgAcq); - if (SUCCEEDED(hr)) - { - rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq); - if (RT_SUCCESS(rc)) - dsoundStreamReset(pThis, pStreamDS); - } - else - rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; - - return rc; -} - -static int dsoundControlStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - LogFlowFunc(("pStreamDS=%p, enmStreamCmd=%ld\n", pStreamDS, enmStreamCmd)); - - int rc = VINF_SUCCESS; - - HRESULT hr; - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - dsoundStreamEnable(pThis, pStreamDS, true /* fEnable */); - RT_FALL_THROUGH(); - case PDMAUDIOSTREAMCMD_RESUME: - { - /* Try to start capture. If it fails, then reopen and try again. */ - hr = directSoundCaptureStart(pThis, pStreamDS); - if (FAILED(hr)) - { - hr = directSoundCaptureClose(pThis, pStreamDS); - if (SUCCEEDED(hr)) - { - PDMAUDIOSTREAMCFG CfgAcq; - hr = directSoundCaptureOpen(pThis, pStreamDS, &pStreamDS->Cfg /* pCfgReq */, &CfgAcq); - if (SUCCEEDED(hr)) - { - rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, &CfgAcq); - if (RT_FAILURE(rc)) - break; - - /** @todo What to do if the format has changed? */ - - hr = directSoundCaptureStart(pThis, pStreamDS); - } - } - } - - if (FAILED(hr)) - rc = VERR_NOT_SUPPORTED; - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - dsoundStreamEnable(pThis, pStreamDS, false /* fEnable */); - RT_FALL_THROUGH(); - case PDMAUDIOSTREAMCMD_PAUSE: - { - directSoundCaptureStop(pThis, pStreamDS, - enmStreamCmd == PDMAUDIOSTREAMCMD_DISABLE /* fFlush */); - - /* Return success in any case, as stopping the capture can fail if - * the capture buffer is not around anymore. - * - * This can happen if the host's capturing device has been changed suddenly. */ - rc = VINF_SUCCESS; - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} - */ -static DECLCALLBACK(int) drvHostDSoundHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead) -{ - - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(uBufSize, VERR_INVALID_PARAMETER); - - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - int rc = VINF_SUCCESS; - - uint32_t cbReadTotal = 0; - - uint32_t cbToRead = RT_MIN((uint32_t)RTCircBufUsed(pStreamDS->pCircBuf), uBufSize); - while (cbToRead) - { - void *pvChunk; - size_t cbChunk; - RTCircBufAcquireReadBlock(pStreamDS->pCircBuf, cbToRead, &pvChunk, &cbChunk); - - if (cbChunk) - { - memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk); - cbReadTotal += (uint32_t)cbChunk; - Assert(cbToRead >= cbChunk); - cbToRead -= (uint32_t)cbChunk; - } - - RTCircBufReleaseReadBlock(pStreamDS->pCircBuf, cbChunk); - } - - if (RT_SUCCESS(rc)) - { - if (puRead) - *puRead = cbReadTotal; - } - else - dsoundUpdateStatusInternal(pThis); - - return rc; -} - -static int dsoundDestroyStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) -{ - LogFlowFuncEnter(); - - directSoundCaptureClose(pThis, pStreamDS); - - return VINF_SUCCESS; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} - */ -static DECLCALLBACK(int) drvHostDSoundHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); - - RT_BZERO(pBackendCfg, sizeof(PPDMAUDIOBACKENDCFG)); - - pBackendCfg->cbStreamOut = sizeof(DSOUNDSTREAM); - pBackendCfg->cbStreamIn = sizeof(DSOUNDSTREAM); - - RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "DirectSound"); - - pBackendCfg->cMaxStreamsIn = UINT32_MAX; - pBackendCfg->cMaxStreamsOut = UINT32_MAX; - - return VINF_SUCCESS; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} - */ -static DECLCALLBACK(int) drvHostDSoundHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIODEVICEENUM pDeviceEnum) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); - - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_SUCCESS(rc)) - { - rc = DrvAudioHlpDeviceEnumInit(pDeviceEnum); - if (RT_SUCCESS(rc)) - { - rc = dsoundDevicesEnumerate(pThis, pDeviceEnum); - if (RT_FAILURE(rc)) - DrvAudioHlpDeviceEnumFree(pDeviceEnum); - } - - int rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); - } - - LogFlowFunc(("Returning %Rrc\n", rc)); - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} - */ -static DECLCALLBACK(void) drvHostDSoundHA_Shutdown(PPDMIHOSTAUDIO pInterface) -{ - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - - LogFlowFuncEnter(); - - RT_NOREF(pThis); - - LogFlowFuncLeave(); -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} - */ -static DECLCALLBACK(int) drvHostDSoundHA_Init(PPDMIHOSTAUDIO pInterface) -{ - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - LogFlowFuncEnter(); - - int rc; - - /* Verify that IDirectSound is available. */ - LPDIRECTSOUND pDirectSound = NULL; - HRESULT hr = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound); - if (SUCCEEDED(hr)) - { - IDirectSound_Release(pDirectSound); - - rc = VINF_SUCCESS; - - dsoundUpdateStatusInternal(pThis); - } - else - { - DSLOGREL(("DSound: DirectSound not available: %Rhrc\n", hr)); - rc = VERR_NOT_SUPPORTED; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -static LPCGUID dsoundConfigQueryGUID(PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid) -{ - LPCGUID pGuid = NULL; - - char *pszGuid = NULL; - int rc = CFGMR3QueryStringAlloc(pCfg, pszName, &pszGuid); - if (RT_SUCCESS(rc)) - { - rc = RTUuidFromStr(pUuid, pszGuid); - if (RT_SUCCESS(rc)) - pGuid = (LPCGUID)&pUuid; - else - DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc)); - - RTStrFree(pszGuid); - } - - return pGuid; -} - -static int dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg) -{ - pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay); - pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture); - - DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n", - &pThis->Cfg.uuidPlay, - &pThis->Cfg.uuidCapture)); - - return VINF_SUCCESS; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDSoundHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) -{ - RT_NOREF(enmDir); - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); - - return PDMAUDIOBACKENDSTS_RUNNING; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} - */ -static DECLCALLBACK(int) drvHostDSoundHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - int rc; - if (pCfgReq->enmDir == PDMAUDIODIR_IN) - rc = dsoundCreateStreamIn(pThis, pStreamDS, pCfgReq, pCfgAcq); - else - rc = dsoundCreateStreamOut(pThis, pStreamDS, pCfgReq, pCfgAcq); - - if (RT_SUCCESS(rc)) - { - rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq); - if (RT_SUCCESS(rc)) - rc = RTCritSectInit(&pStreamDS->CritSect); - } - - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} - */ -static DECLCALLBACK(int) drvHostDSoundHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - int rc; - if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) - rc = dsoundDestroyStreamIn(pThis, pStreamDS); - else - rc = dsoundDestroyStreamOut(pThis, pStreamDS); - - if (RT_SUCCESS(rc)) - { - if (RTCritSectIsInitialized(&pStreamDS->CritSect)) - rc = RTCritSectDelete(&pStreamDS->CritSect); - } - - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} - */ -static DECLCALLBACK(int) drvHostDSoundHA_StreamControl(PPDMIHOSTAUDIO pInterface, - PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - int rc; - if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) - rc = dsoundControlStreamIn(pThis, pStreamDS, enmStreamCmd); - else - rc = dsoundControlStreamOut(pThis, pStreamDS, enmStreamCmd); - - return rc; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} - */ -static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAGS_NONE); - - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - if ( pStreamDS->fEnabled - && pStreamDS->pCircBuf) - { - return (uint32_t)RTCircBufUsed(pStreamDS->pCircBuf); - } - - return 0; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, PDMAUDIOSTREAMSTS_FLAGS_NONE); - AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAGS_NONE); - - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - if (pStreamDS->fEnabled) - return (uint32_t)RTCircBufFree(pStreamDS->pCircBuf); - - return 0; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} - */ -static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, 0); - - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) - { - uint32_t cbPending = 0; - - /* Any uncommitted data left? */ - if (pStreamDS->pCircBuf) - cbPending = (uint32_t)RTCircBufUsed(pStreamDS->pCircBuf); - - /* Check if we have committed data which still needs to be played by - * by DirectSound's streaming buffer. */ - if (!cbPending) - { - const uint64_t diffLastTransferredMs = RTTimeMilliTS() - pStreamDS->Out.tsLastTransferredMs; - const uint64_t uLastTranserredChunkMs = DrvAudioHlpBytesToMilli(pStreamDS->Out.cbLastTransferred, &pStreamDS->Cfg.Props); - if ( uLastTranserredChunkMs - && diffLastTransferredMs < uLastTranserredChunkMs) - cbPending = 1; - - Log3Func(("diffLastTransferredMs=%RU64ms, uLastTranserredChunkMs=%RU64ms (%RU32 bytes) -> cbPending=%RU32\n", - diffLastTransferredMs, uLastTranserredChunkMs, pStreamDS->Out.cbLastTransferred, cbPending)); - } - else - Log3Func(("cbPending=%RU32\n", cbPending)); - - return cbPending; - } - /* Note: For input streams we never have pending data left. */ - - return 0; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} - */ -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDSoundHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAGS_NONE); - - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - PDMAUDIOSTREAMSTS fStrmStatus = PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED; - - if (pStreamDS->fEnabled) - fStrmStatus |= PDMAUDIOSTREAMSTS_FLAGS_ENABLED; - - return fStrmStatus; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} - */ -static DECLCALLBACK(int) drvHostDSoundHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; - - if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) - { - return dsoundCaptureTransfer(pThis, pStreamDS); - } - else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) - { - return dsoundPlayTransfer(pThis, pStreamDS); - } - - return VINF_SUCCESS; -} - -#ifdef VBOX_WITH_AUDIO_CALLBACKS -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback} - */ -static DECLCALLBACK(int) drvHostDSoundHA_SetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - /* pfnCallback will be handled below. */ - - PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); - - int rc = RTCritSectEnter(&pThis->CritSect); - if (RT_SUCCESS(rc)) - { - LogFunc(("pfnCallback=%p\n", pfnCallback)); - - if (pfnCallback) /* Register. */ - { - Assert(pThis->pfnCallback == NULL); - pThis->pfnCallback = pfnCallback; - -#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT - if (pThis->m_pNotificationClient) - pThis->m_pNotificationClient->RegisterCallback(pThis->pDrvIns, pfnCallback); -#endif - } - else /* Unregister. */ - { - if (pThis->pfnCallback) - pThis->pfnCallback = NULL; - -#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT - if (pThis->m_pNotificationClient) - pThis->m_pNotificationClient->UnregisterCallback(); -#endif - } - - int rc2 = RTCritSectLeave(&pThis->CritSect); - AssertRC(rc2); - } - - return rc; -} -#endif - - -/********************************************************************************************************************************* -* PDMDRVINS::IBase Interface * -*********************************************************************************************************************************/ - -/** - * @callback_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID) -{ - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); - - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); - return NULL; -} - - -/********************************************************************************************************************************* -* PDMDRVREG Interface * -*********************************************************************************************************************************/ - -/** - * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct} - */ -static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns) -{ - PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); - PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); - - LogFlowFuncEnter(); - -#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT - if (pThis->m_pNotificationClient) - { - pThis->m_pNotificationClient->Dispose(); - pThis->m_pNotificationClient->Release(); - - pThis->m_pNotificationClient = NULL; - } -#endif - - DrvAudioHlpDeviceEnumFree(&pThis->DeviceEnum); - - if (pThis->pDrvIns) - CoUninitialize(); - - int rc2 = RTCritSectDelete(&pThis->CritSect); - AssertRC(rc2); - - LogFlowFuncLeave(); -} - -/** - * @callback_method_impl{FNPDMDRVCONSTRUCT, - * Construct a DirectSound Audio driver instance.} - */ -static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) -{ - RT_NOREF(fFlags); - PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); - - LogRel(("Audio: Initializing DirectSound audio driver\n")); - - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - - HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); - if (FAILED(hr)) - { - DSLOGREL(("DSound: CoInitializeEx failed with %Rhrc\n", hr)); - return VERR_NOT_SUPPORTED; - } - - /* - * Init basic data members and interfaces. - */ - pThis->pDrvIns = pDrvIns; - /* IBase */ - pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface; - /* IHostAudio */ - pThis->IHostAudio.pfnInit = drvHostDSoundHA_Init; - pThis->IHostAudio.pfnShutdown = drvHostDSoundHA_Shutdown; - pThis->IHostAudio.pfnGetConfig = drvHostDSoundHA_GetConfig; - pThis->IHostAudio.pfnGetStatus = drvHostDSoundHA_GetStatus; - pThis->IHostAudio.pfnStreamCreate = drvHostDSoundHA_StreamCreate; - pThis->IHostAudio.pfnStreamDestroy = drvHostDSoundHA_StreamDestroy; - pThis->IHostAudio.pfnStreamControl = drvHostDSoundHA_StreamControl; - pThis->IHostAudio.pfnStreamGetReadable = drvHostDSoundHA_StreamGetReadable; - pThis->IHostAudio.pfnStreamGetWritable = drvHostDSoundHA_StreamGetWritable; - pThis->IHostAudio.pfnStreamGetStatus = drvHostDSoundHA_StreamGetStatus; - pThis->IHostAudio.pfnStreamIterate = drvHostDSoundHA_StreamIterate; - pThis->IHostAudio.pfnStreamPlay = drvHostDSoundHA_StreamPlay; - pThis->IHostAudio.pfnStreamCapture = drvHostDSoundHA_StreamCapture; -#ifdef VBOX_WITH_AUDIO_CALLBACKS - pThis->IHostAudio.pfnSetCallback = drvHostDSoundHA_SetCallback; - pThis->pfnCallback = NULL; -#else - pThis->IHostAudio.pfnSetCallback = NULL; -#endif - pThis->IHostAudio.pfnGetDevices = drvHostDSoundHA_GetDevices; - pThis->IHostAudio.pfnStreamGetPending = drvHostDSoundHA_StreamGetPending; - pThis->IHostAudio.pfnStreamPlayBegin = NULL; - pThis->IHostAudio.pfnStreamPlayEnd = NULL; - pThis->IHostAudio.pfnStreamCaptureBegin = NULL; - pThis->IHostAudio.pfnStreamCaptureEnd = NULL; - - /* - * Init the static parts. - */ - DrvAudioHlpDeviceEnumInit(&pThis->DeviceEnum); - - pThis->fEnabledIn = false; - pThis->fEnabledOut = false; - - int rc = VINF_SUCCESS; - -#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT - bool fUseNotificationClient = false; - - char szOSVersion[32]; - rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion)); - if (RT_SUCCESS(rc)) - { - /* IMMNotificationClient is available starting at Windows Vista. */ - if (RTStrVersionCompare(szOSVersion, "6.0") >= 0) - fUseNotificationClient = true; - } - - if (fUseNotificationClient) - { - try - { - pThis->m_pNotificationClient = new VBoxMMNotificationClient(); - - HRESULT hr = pThis->m_pNotificationClient->Initialize(); - if (FAILED(hr)) - rc = VERR_AUDIO_BACKEND_INIT_FAILED; - } - catch (std::bad_alloc &ex) - { - NOREF(ex); - rc = VERR_NO_MEMORY; - } - } - - LogRel2(("DSound: Notification client is %s\n", fUseNotificationClient ? "enabled" : "disabled")); -#endif - - if (RT_SUCCESS(rc)) - { - /* - * Initialize configuration values. - */ - rc = dsoundConfigInit(pThis, pCfg); - if (RT_SUCCESS(rc)) - rc = RTCritSectInit(&pThis->CritSect); - } - - return rc; -} - - -/** - * PDM driver registration. - */ -const PDMDRVREG g_DrvHostDSound = -{ - /* u32Version */ - PDM_DRVREG_VERSION, - /* szName */ - "DSoundAudio", - /* szRCMod */ - "", - /* szR0Mod */ - "", - /* pszDescription */ - "DirectSound Audio host driver", - /* fFlags */ - PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, - /* fClass. */ - PDM_DRVREG_CLASS_AUDIO, - /* cMaxInstances */ - ~0U, - /* cbInstance */ - sizeof(DRVHOSTDSOUND), - /* pfnConstruct */ - drvHostDSoundConstruct, - /* pfnDestruct */ - drvHostDSoundDestruct, - /* pfnRelocate */ - NULL, - /* pfnIOCtl */ - NULL, - /* pfnPowerOn */ - NULL, - /* pfnReset */ - NULL, - /* pfnSuspend */ - NULL, - /* pfnResume */ - NULL, - /* pfnAttach */ - NULL, - /* pfnDetach */ - NULL, - /* pfnPowerOff */ - NULL, - /* pfnSoftReset */ - NULL, - /* u32EndVersion */ - PDM_DRVREG_VERSION -}; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostNullAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostNullAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostNullAudio.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostNullAudio.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,432 +0,0 @@ -/* $Id: DrvHostNullAudio.cpp $ */ -/** @file - * NULL audio driver. - * - * This also acts as a fallback if no other backend is available. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - * -------------------------------------------------------------------- - * - * This code is based on: noaudio.c QEMU based code. - * - * QEMU Timer based audio emulation - * - * Copyright (c) 2004-2005 Vassili Karpov (malc) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#include -#include /* For PDMIBASE_2_PDMDRV. */ - -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include - -#include "DrvAudio.h" -#include "VBoxDD.h" - - -/********************************************************************************************************************************* -* Structures and Typedefs * -*********************************************************************************************************************************/ -typedef struct NULLAUDIOSTREAM -{ - /** The stream's acquired configuration. */ - PPDMAUDIOSTREAMCFG pCfg; -} NULLAUDIOSTREAM, *PNULLAUDIOSTREAM; - -/** - * NULL audio driver instance data. - * @implements PDMIAUDIOCONNECTOR - */ -typedef struct DRVHOSTNULLAUDIO -{ - /** Pointer to the driver instance structure. */ - PPDMDRVINS pDrvIns; - /** Pointer to host audio interface. */ - PDMIHOSTAUDIO IHostAudio; -} DRVHOSTNULLAUDIO, *PDRVHOSTNULLAUDIO; - - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} - */ -static DECLCALLBACK(int) drvHostNullAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) -{ - NOREF(pInterface); - AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); - - RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "NULL audio"); - - pBackendCfg->cbStreamOut = sizeof(NULLAUDIOSTREAM); - pBackendCfg->cbStreamIn = sizeof(NULLAUDIOSTREAM); - - pBackendCfg->cMaxStreamsOut = 1; /* Output */ - pBackendCfg->cMaxStreamsIn = 2; /* Line input + microphone input. */ - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} - */ -static DECLCALLBACK(int) drvHostNullAudioHA_Init(PPDMIHOSTAUDIO pInterface) -{ - NOREF(pInterface); - - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} - */ -static DECLCALLBACK(void) drvHostNullAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostNullAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) -{ - RT_NOREF(enmDir); - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); - - return PDMAUDIOBACKENDSTS_RUNNING; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} - */ -static DECLCALLBACK(int) drvHostNullAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(uBufSize, VERR_INVALID_PARAMETER); - - RT_NOREF(pInterface, pStream, pvBuf); - - /* Note: No copying of samples needed here, as this a NULL backend. */ - - if (puWritten) - *puWritten = uBufSize; /* Return all bytes as written. */ - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} - */ -static DECLCALLBACK(int) drvHostNullAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead) -{ - RT_NOREF(pInterface, pStream); - - PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream; - - /* Return silence. */ - Assert(pStreamNull->pCfg); - DrvAudioHlpClearBuf(&pStreamNull->pCfg->Props, pvBuf, uBufSize, PDMAUDIOPCMPROPS_B2F(&pStreamNull->pCfg->Props, uBufSize)); - - if (puRead) - *puRead = uBufSize; - - return VINF_SUCCESS; -} - - -static int nullCreateStreamIn(PNULLAUDIOSTREAM pStreamNull, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - RT_NOREF(pStreamNull, pCfgReq, pCfgAcq); - - return VINF_SUCCESS; -} - - -static int nullCreateStreamOut(PNULLAUDIOSTREAM pStreamNull, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - RT_NOREF(pStreamNull, pCfgReq, pCfgAcq); - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} - */ -static DECLCALLBACK(int) drvHostNullAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream; - - int rc; - if (pCfgReq->enmDir == PDMAUDIODIR_IN) - rc = nullCreateStreamIn( pStreamNull, pCfgReq, pCfgAcq); - else - rc = nullCreateStreamOut(pStreamNull, pCfgReq, pCfgAcq); - - if (RT_SUCCESS(rc)) - { - pStreamNull->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); - if (!pStreamNull->pCfg) - rc = VERR_NO_MEMORY; - } - - return rc; -} - - -static int nullDestroyStreamIn(void) -{ - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; -} - - -static int nullDestroyStreamOut(PNULLAUDIOSTREAM pStreamNull) -{ - RT_NOREF(pStreamNull); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} - */ -static DECLCALLBACK(int) drvHostNullAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream; - - if (!pStreamNull->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamNull->pCfg->enmDir == PDMAUDIODIR_IN) - rc = nullDestroyStreamIn(); - else - rc = nullDestroyStreamOut(pStreamNull); - - if (RT_SUCCESS(rc)) - { - DrvAudioHlpStreamCfgFree(pStreamNull->pCfg); - pStreamNull->pCfg = NULL; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} - */ -static DECLCALLBACK(int) drvHostNullAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, - PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - RT_NOREF(pInterface, pStream, enmStreamCmd); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} - */ -static DECLCALLBACK(uint32_t) drvHostNullAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return UINT32_MAX; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(uint32_t) drvHostNullAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return UINT32_MAX; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} - */ -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostNullAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - return PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} - */ -static DECLCALLBACK(int) drvHostNullAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) drvHostNullAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) -{ - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVHOSTNULLAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTNULLAUDIO); - - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); - return NULL; -} - - -/** - * Constructs a Null audio driver instance. - * - * @copydoc FNPDMDRVCONSTRUCT - */ -static DECLCALLBACK(int) drvHostNullAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) -{ - RT_NOREF(pCfg, fFlags); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); - /* pCfg is optional. */ - - PDRVHOSTNULLAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTNULLAUDIO); - LogRel(("Audio: Initializing NULL driver\n")); - - /* - * Init the static parts. - */ - pThis->pDrvIns = pDrvIns; - /* IBase */ - pDrvIns->IBase.pfnQueryInterface = drvHostNullAudioQueryInterface; - /* IHostAudio */ - pThis->IHostAudio.pfnInit = drvHostNullAudioHA_Init; - pThis->IHostAudio.pfnShutdown = drvHostNullAudioHA_Shutdown; - pThis->IHostAudio.pfnGetConfig = drvHostNullAudioHA_GetConfig; - pThis->IHostAudio.pfnGetStatus = drvHostNullAudioHA_GetStatus; - pThis->IHostAudio.pfnStreamCreate = drvHostNullAudioHA_StreamCreate; - pThis->IHostAudio.pfnStreamDestroy = drvHostNullAudioHA_StreamDestroy; - pThis->IHostAudio.pfnStreamControl = drvHostNullAudioHA_StreamControl; - pThis->IHostAudio.pfnStreamGetReadable = drvHostNullAudioHA_StreamGetReadable; - pThis->IHostAudio.pfnStreamGetWritable = drvHostNullAudioHA_StreamGetWritable; - pThis->IHostAudio.pfnStreamGetStatus = drvHostNullAudioHA_StreamGetStatus; - pThis->IHostAudio.pfnStreamIterate = drvHostNullAudioHA_StreamIterate; - pThis->IHostAudio.pfnStreamPlay = drvHostNullAudioHA_StreamPlay; - pThis->IHostAudio.pfnStreamCapture = drvHostNullAudioHA_StreamCapture; - pThis->IHostAudio.pfnSetCallback = NULL; - pThis->IHostAudio.pfnGetDevices = NULL; - pThis->IHostAudio.pfnStreamGetPending = NULL; - pThis->IHostAudio.pfnStreamPlayBegin = NULL; - pThis->IHostAudio.pfnStreamPlayEnd = NULL; - pThis->IHostAudio.pfnStreamCaptureBegin = NULL; - pThis->IHostAudio.pfnStreamCaptureEnd = NULL; - - return VINF_SUCCESS; -} - - -/** - * Char driver registration record. - */ -const PDMDRVREG g_DrvHostNullAudio = -{ - /* u32Version */ - PDM_DRVREG_VERSION, - /* szName */ - "NullAudio", - /* szRCMod */ - "", - /* szR0Mod */ - "", - /* pszDescription */ - "NULL audio host driver", - /* fFlags */ - PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, - /* fClass. */ - PDM_DRVREG_CLASS_AUDIO, - /* cMaxInstances */ - ~0U, - /* cbInstance */ - sizeof(DRVHOSTNULLAUDIO), - /* pfnConstruct */ - drvHostNullAudioConstruct, - /* pfnDestruct */ - NULL, - /* pfnRelocate */ - NULL, - /* pfnIOCtl */ - NULL, - /* pfnPowerOn */ - NULL, - /* pfnReset */ - NULL, - /* pfnSuspend */ - NULL, - /* pfnResume */ - NULL, - /* pfnAttach */ - NULL, - /* pfnDetach */ - NULL, - /* pfnPowerOff */ - NULL, - /* pfnSoftReset */ - NULL, - /* u32EndVersion */ - PDM_DRVREG_VERSION -}; - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,1186 +0,0 @@ -/* $Id: DrvHostOSSAudio.cpp $ */ -/** @file - * OSS (Open Sound System) host audio backend. - */ - -/* - * Copyright (C) 2014-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include /* For PDMIBASE_2_PDMDRV. */ - -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include - -#include "DrvAudio.h" -#include "VBoxDD.h" - - -/********************************************************************************************************************************* -* Defines * -*********************************************************************************************************************************/ - -#if ((SOUND_VERSION > 360) && (defined(OSS_SYSINFO))) -/* OSS > 3.6 has a new syscall available for querying a bit more detailed information - * about OSS' audio capabilities. This is handy for e.g. Solaris. */ -# define VBOX_WITH_AUDIO_OSS_SYSINFO 1 -#endif - -/** Makes DRVHOSTOSSAUDIO out of PDMIHOSTAUDIO. */ -#define PDMIHOSTAUDIO_2_DRVHOSTOSSAUDIO(pInterface) \ - ( (PDRVHOSTOSSAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTOSSAUDIO, IHostAudio)) ) - - -/********************************************************************************************************************************* -* Structures * -*********************************************************************************************************************************/ - -/** - * OSS host audio driver instance data. - * @implements PDMIAUDIOCONNECTOR - */ -typedef struct DRVHOSTOSSAUDIO -{ - /** Pointer to the driver instance structure. */ - PPDMDRVINS pDrvIns; - /** Pointer to host audio interface. */ - PDMIHOSTAUDIO IHostAudio; - /** Error count for not flooding the release log. - * UINT32_MAX for unlimited logging. */ - uint32_t cLogErrors; -} DRVHOSTOSSAUDIO, *PDRVHOSTOSSAUDIO; - -typedef struct OSSAUDIOSTREAMCFG -{ - PDMAUDIOPCMPROPS Props; - uint16_t cFragments; - uint32_t cbFragmentSize; -} OSSAUDIOSTREAMCFG, *POSSAUDIOSTREAMCFG; - -typedef struct OSSAUDIOSTREAM -{ - /** The stream's acquired configuration. */ - PPDMAUDIOSTREAMCFG pCfg; - /** Buffer alignment. */ - uint8_t uAlign; - union - { - struct - { - - } In; - struct - { -#ifndef RT_OS_L4 - /** Whether we use a memory mapped file instead of our - * own allocated PCM buffer below. */ - /** @todo The memory mapped code seems to be utterly broken. - * Needs investigation! */ - bool fMMIO; -#endif - } Out; - }; - int hFile; - int cFragments; - int cbFragmentSize; - /** Own PCM buffer. */ - void *pvBuf; - /** Size (in bytes) of own PCM buffer. */ - size_t cbBuf; - int old_optr; -} OSSAUDIOSTREAM, *POSSAUDIOSTREAM; - -typedef struct OSSAUDIOCFG -{ -#ifndef RT_OS_L4 - bool try_mmap; -#endif - int nfrags; - int fragsize; - const char *devpath_out; - const char *devpath_in; - int debug; -} OSSAUDIOCFG, *POSSAUDIOCFG; - -static OSSAUDIOCFG s_OSSConf = -{ -#ifndef RT_OS_L4 - false, -#endif - 4, - 4096, - "/dev/dsp", - "/dev/dsp", - 0 -}; - - -/* http://www.df.lth.se/~john_e/gems/gem002d.html */ -static uint32_t popcount(uint32_t u) -{ - u = ((u&0x55555555) + ((u>>1)&0x55555555)); - u = ((u&0x33333333) + ((u>>2)&0x33333333)); - u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f)); - u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff)); - u = ( u&0x0000ffff) + (u>>16); - return u; -} - - -static uint32_t lsbindex(uint32_t u) -{ - return popcount ((u&-u)-1); -} - - -static int ossOSSToAudioProps(int fmt, PPDMAUDIOPCMPROPS pProps) -{ - RT_BZERO(pProps, sizeof(PDMAUDIOPCMPROPS)); - - /** @todo r=bird: What's the assumption about the incoming pProps? Code is - * clearly ASSUMING something about how it's initialized, but even so, - * the fSwapEndian isn't correct in a portable way. */ - switch (fmt) - { - case AFMT_S8: - pProps->cbSample = 1; - pProps->fSigned = true; - break; - - case AFMT_U8: - pProps->cbSample = 1; - pProps->fSigned = false; - break; - - case AFMT_S16_LE: - pProps->cbSample = 2; - pProps->fSigned = true; - break; - - case AFMT_U16_LE: - pProps->cbSample = 2; - pProps->fSigned = false; - break; - - case AFMT_S16_BE: - pProps->cbSample = 2; - pProps->fSigned = true; -#ifdef RT_LITTLE_ENDIAN - pProps->fSwapEndian = true; -#endif - break; - - case AFMT_U16_BE: - pProps->cbSample = 2; - pProps->fSigned = false; -#ifdef RT_LITTLE_ENDIAN - pProps->fSwapEndian = true; -#endif - break; - - default: - AssertMsgFailedReturn(("Format %d not supported\n", fmt), VERR_NOT_SUPPORTED); - } - - return VINF_SUCCESS; -} - - -static int ossStreamClose(int *phFile) -{ - if (!phFile || !*phFile || *phFile == -1) - return VINF_SUCCESS; - - int rc; - if (close(*phFile)) - { - LogRel(("OSS: Closing stream failed: %s\n", strerror(errno))); - rc = VERR_GENERAL_FAILURE; /** @todo */ - } - else - { - *phFile = -1; - rc = VINF_SUCCESS; - } - - return rc; -} - - -static int ossStreamOpen(const char *pszDev, int fOpen, POSSAUDIOSTREAMCFG pOSSReq, POSSAUDIOSTREAMCFG pOSSAcq, int *phFile) -{ - int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; - - int fdFile = -1; - do - { - fdFile = open(pszDev, fOpen); - if (fdFile == -1) - { - LogRel(("OSS: Failed to open %s: %s (%d)\n", pszDev, strerror(errno), errno)); - break; - } - - int iFormat; - switch (pOSSReq->Props.cbSample) - { - case 1: - iFormat = pOSSReq->Props.fSigned ? AFMT_S8 : AFMT_U8; - break; - - case 2: - /** @todo r=bird: You're ASSUMING stuff about pOSSReq->Props.fSwapEndian and - * the host endian here. */ - iFormat = pOSSReq->Props.fSigned ? AFMT_S16_LE : AFMT_U16_LE; - break; - - default: - rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; - break; - } - - if (RT_FAILURE(rc)) - break; - - if (ioctl(fdFile, SNDCTL_DSP_SAMPLESIZE, &iFormat)) - { - LogRel(("OSS: Failed to set audio format to %ld: %s (%d)\n", iFormat, strerror(errno), errno)); - break; - } - - int cChannels = pOSSReq->Props.cChannels; - if (ioctl(fdFile, SNDCTL_DSP_CHANNELS, &cChannels)) - { - LogRel(("OSS: Failed to set number of audio channels (%RU8): %s (%d)\n", - pOSSReq->Props.cChannels, strerror(errno), errno)); - break; - } - - int freq = pOSSReq->Props.uHz; - if (ioctl(fdFile, SNDCTL_DSP_SPEED, &freq)) - { - LogRel(("OSS: Failed to set audio frequency (%dHZ): %s (%d)\n", pOSSReq->Props.uHz, strerror(errno), errno)); - break; - } - - /* Obsolete on Solaris (using O_NONBLOCK is sufficient). */ -#if !(defined(VBOX) && defined(RT_OS_SOLARIS)) - if (ioctl(fdFile, SNDCTL_DSP_NONBLOCK)) - { - LogRel(("OSS: Failed to set non-blocking mode: %s (%d)\n", strerror(errno), errno)); - break; - } -#endif - - /* Check access mode (input or output). */ - bool fIn = ((fOpen & O_ACCMODE) == O_RDONLY); - - LogRel2(("OSS: Requested %RU16 %s fragments, %RU32 bytes each\n", - pOSSReq->cFragments, fIn ? "input" : "output", pOSSReq->cbFragmentSize)); - - int mmmmssss = (pOSSReq->cFragments << 16) | lsbindex(pOSSReq->cbFragmentSize); - if (ioctl(fdFile, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) - { - LogRel(("OSS: Failed to set %RU16 fragments to %RU32 bytes each: %s (%d)\n", - pOSSReq->cFragments, pOSSReq->cbFragmentSize, strerror(errno), errno)); - break; - } - - audio_buf_info abinfo; - if (ioctl(fdFile, fIn ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) - { - LogRel(("OSS: Failed to retrieve %s buffer length: %s (%d)\n", fIn ? "input" : "output", strerror(errno), errno)); - break; - } - - rc = ossOSSToAudioProps(iFormat, &pOSSAcq->Props); - if (RT_SUCCESS(rc)) - { - pOSSAcq->Props.cChannels = cChannels; - pOSSAcq->Props.uHz = freq; - pOSSAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pOSSAcq->Props.cbSample, pOSSAcq->Props.cChannels); - - pOSSAcq->cFragments = abinfo.fragstotal; - pOSSAcq->cbFragmentSize = abinfo.fragsize; - - LogRel2(("OSS: Got %RU16 %s fragments, %RU32 bytes each\n", - pOSSAcq->cFragments, fIn ? "input" : "output", pOSSAcq->cbFragmentSize)); - - *phFile = fdFile; - } - } - while (0); - - if (RT_FAILURE(rc)) - ossStreamClose(&fdFile); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int ossControlStreamIn(/*PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd*/ void) -{ - /** @todo Nothing to do here right now!? */ - - return VINF_SUCCESS; -} - - -static int ossControlStreamOut(PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; - - int rc = VINF_SUCCESS; - - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - case PDMAUDIOSTREAMCMD_RESUME: - { - DrvAudioHlpClearBuf(&pStreamOSS->pCfg->Props, pStreamOSS->pvBuf, pStreamOSS->cbBuf, - PDMAUDIOPCMPROPS_B2F(&pStreamOSS->pCfg->Props, pStreamOSS->cbBuf)); - - int mask = PCM_ENABLE_OUTPUT; - if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0) - { - LogRel(("OSS: Failed to enable output stream: %s\n", strerror(errno))); - rc = RTErrConvertFromErrno(errno); - } - - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - case PDMAUDIOSTREAMCMD_PAUSE: - { - int mask = 0; - if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0) - { - LogRel(("OSS: Failed to disable output stream: %s\n", strerror(errno))); - rc = RTErrConvertFromErrno(errno); - } - - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} - */ -static DECLCALLBACK(int) drvHostOssAudioHA_Init(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} - */ -static DECLCALLBACK(int) drvHostOssAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; - - int rc = VINF_SUCCESS; - - size_t cbToRead = RT_MIN(pStreamOSS->cbBuf, uBufSize); - - LogFlowFunc(("cbToRead=%zi\n", cbToRead)); - - uint32_t cbReadTotal = 0; - uint32_t cbTemp; - ssize_t cbRead; - size_t offWrite = 0; - - while (cbToRead) - { - cbTemp = RT_MIN(cbToRead, pStreamOSS->cbBuf); - AssertBreakStmt(cbTemp, rc = VERR_NO_DATA); - cbRead = read(pStreamOSS->hFile, (uint8_t *)pStreamOSS->pvBuf, cbTemp); - - LogFlowFunc(("cbRead=%zi, cbTemp=%RU32, cbToRead=%zu\n", cbRead, cbTemp, cbToRead)); - - if (cbRead < 0) - { - switch (errno) - { - case 0: - { - LogFunc(("Failed to read %z frames\n", cbRead)); - rc = VERR_ACCESS_DENIED; - break; - } - - case EINTR: - case EAGAIN: - rc = VERR_NO_DATA; - break; - - default: - LogFlowFunc(("Failed to read %zu input frames, rc=%Rrc\n", cbTemp, rc)); - rc = VERR_GENERAL_FAILURE; /** @todo Fix this. */ - break; - } - - if (RT_FAILURE(rc)) - break; - } - else if (cbRead) - { - memcpy((uint8_t *)pvBuf + offWrite, pStreamOSS->pvBuf, cbRead); - - Assert((ssize_t)cbToRead >= cbRead); - cbToRead -= cbRead; - offWrite += cbRead; - cbReadTotal += cbRead; - } - else /* No more data, try next round. */ - break; - } - - if (rc == VERR_NO_DATA) - rc = VINF_SUCCESS; - - if (RT_SUCCESS(rc)) - { - if (puRead) - *puRead = cbReadTotal; - } - - return rc; -} - - -static int ossDestroyStreamIn(PPDMAUDIOBACKENDSTREAM pStream) -{ - POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; - - LogFlowFuncEnter(); - - if (pStreamOSS->pvBuf) - { - Assert(pStreamOSS->cbBuf); - - RTMemFree(pStreamOSS->pvBuf); - pStreamOSS->pvBuf = NULL; - } - - pStreamOSS->cbBuf = 0; - - ossStreamClose(&pStreamOSS->hFile); - - return VINF_SUCCESS; -} - - -static int ossDestroyStreamOut(PPDMAUDIOBACKENDSTREAM pStream) -{ - POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; - -#ifndef RT_OS_L4 - if (pStreamOSS->Out.fMMIO) - { - if (pStreamOSS->pvBuf) - { - Assert(pStreamOSS->cbBuf); - - int rc2 = munmap(pStreamOSS->pvBuf, pStreamOSS->cbBuf); - if (rc2 == 0) - { - pStreamOSS->pvBuf = NULL; - pStreamOSS->cbBuf = 0; - - pStreamOSS->Out.fMMIO = false; - } - else - LogRel(("OSS: Failed to memory unmap playback buffer on close: %s\n", strerror(errno))); - } - } - else - { -#endif - if (pStreamOSS->pvBuf) - { - Assert(pStreamOSS->cbBuf); - - RTMemFree(pStreamOSS->pvBuf); - pStreamOSS->pvBuf = NULL; - } - - pStreamOSS->cbBuf = 0; -#ifndef RT_OS_L4 - } -#endif - - ossStreamClose(&pStreamOSS->hFile); - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} - */ -static DECLCALLBACK(int) drvHostOssAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) -{ - RT_NOREF(pInterface); - - RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "OSS"); - - pBackendCfg->cbStreamIn = sizeof(OSSAUDIOSTREAM); - pBackendCfg->cbStreamOut = sizeof(OSSAUDIOSTREAM); - - int hFile = open("/dev/dsp", O_WRONLY | O_NONBLOCK, 0); - if (hFile == -1) - { - /* Try opening the mixing device instead. */ - hFile = open("/dev/mixer", O_RDONLY | O_NONBLOCK, 0); - } - - int ossVer = -1; - -#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO - oss_sysinfo ossInfo; - RT_ZERO(ossInfo); -#endif - - if (hFile != -1) - { - int err = ioctl(hFile, OSS_GETVERSION, &ossVer); - if (err == 0) - { - LogRel2(("OSS: Using version: %d\n", ossVer)); -#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO - err = ioctl(hFile, OSS_SYSINFO, &ossInfo); - if (err == 0) - { - LogRel2(("OSS: Number of DSPs: %d\n", ossInfo.numaudios)); - LogRel2(("OSS: Number of mixers: %d\n", ossInfo.nummixers)); - - int cDev = ossInfo.nummixers; - if (!cDev) - cDev = ossInfo.numaudios; - - pBackendCfg->cMaxStreamsIn = UINT32_MAX; - pBackendCfg->cMaxStreamsOut = UINT32_MAX; - } - else - { -#endif - /* Since we cannot query anything, assume that we have at least - * one input and one output if we found "/dev/dsp" or "/dev/mixer". */ - - pBackendCfg->cMaxStreamsIn = UINT32_MAX; - pBackendCfg->cMaxStreamsOut = UINT32_MAX; -#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO - } -#endif - } - else - LogRel(("OSS: Unable to determine installed version: %s (%d)\n", strerror(err), err)); - } - else - LogRel(("OSS: No devices found, audio is not available\n")); - - if (hFile != -1) - close(hFile); - - return VINF_SUCCESS; -} - - -static int ossCreateStreamIn(POSSAUDIOSTREAM pStreamOSS, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - int rc; - - int hFile = -1; - - do - { - OSSAUDIOSTREAMCFG ossReq; - memcpy(&ossReq.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS)); - - ossReq.cFragments = s_OSSConf.nfrags; - ossReq.cbFragmentSize = s_OSSConf.fragsize; - - OSSAUDIOSTREAMCFG ossAcq; - RT_ZERO(ossAcq); - - rc = ossStreamOpen(s_OSSConf.devpath_in, O_RDONLY | O_NONBLOCK, &ossReq, &ossAcq, &hFile); - if (RT_SUCCESS(rc)) - { - memcpy(&pCfgAcq->Props, &ossAcq.Props, sizeof(PDMAUDIOPCMPROPS)); - - if (ossAcq.cFragments * ossAcq.cbFragmentSize & pStreamOSS->uAlign) - { - LogRel(("OSS: Warning: Misaligned capturing buffer: Size = %zu, Alignment = %u\n", - ossAcq.cFragments * ossAcq.cbFragmentSize, pStreamOSS->uAlign + 1)); - } - - if (RT_SUCCESS(rc)) - { - size_t cbBuf = PDMAUDIOSTREAMCFG_F2B(pCfgAcq, ossAcq.cFragments * ossAcq.cbFragmentSize); - void *pvBuf = RTMemAlloc(cbBuf); - if (!pvBuf) - { - LogRel(("OSS: Failed allocating capturing buffer with (%zu bytes)\n", cbBuf)); - rc = VERR_NO_MEMORY; - } - - pStreamOSS->hFile = hFile; - pStreamOSS->pvBuf = pvBuf; - pStreamOSS->cbBuf = cbBuf; - - pCfgAcq->Backend.cFramesPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, ossAcq.cbFragmentSize); - pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * 2; /* Use "double buffering". */ - /** @todo Pre-buffering required? */ - } - } - - } while (0); - - if (RT_FAILURE(rc)) - ossStreamClose(&hFile); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int ossCreateStreamOut(POSSAUDIOSTREAM pStreamOSS, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - int rc; - int hFile = -1; - - do - { - OSSAUDIOSTREAMCFG reqStream; - RT_ZERO(reqStream); - - OSSAUDIOSTREAMCFG obtStream; - RT_ZERO(obtStream); - - memcpy(&reqStream.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS)); - - reqStream.cFragments = s_OSSConf.nfrags; - reqStream.cbFragmentSize = s_OSSConf.fragsize; - - rc = ossStreamOpen(s_OSSConf.devpath_out, O_WRONLY, &reqStream, &obtStream, &hFile); - if (RT_SUCCESS(rc)) - { - memcpy(&pCfgAcq->Props, &obtStream.Props, sizeof(PDMAUDIOPCMPROPS)); - - if (obtStream.cFragments * obtStream.cbFragmentSize & pStreamOSS->uAlign) - { - LogRel(("OSS: Warning: Misaligned playback buffer: Size = %zu, Alignment = %u\n", - obtStream.cFragments * obtStream.cbFragmentSize, pStreamOSS->uAlign + 1)); - } - } - - if (RT_SUCCESS(rc)) - { - pStreamOSS->Out.fMMIO = false; - - size_t cbBuf = PDMAUDIOSTREAMCFG_F2B(pCfgAcq, obtStream.cFragments * obtStream.cbFragmentSize); - Assert(cbBuf); - -#ifndef RT_OS_L4 - if (s_OSSConf.try_mmap) - { - pStreamOSS->pvBuf = mmap(0, cbBuf, PROT_READ | PROT_WRITE, MAP_SHARED, hFile, 0); - if (pStreamOSS->pvBuf == MAP_FAILED) - { - LogRel(("OSS: Failed to memory map %zu bytes of playback buffer: %s\n", cbBuf, strerror(errno))); - rc = RTErrConvertFromErrno(errno); - break; - } - else - { - int mask = 0; - if (ioctl(hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0) - { - LogRel(("OSS: Failed to retrieve initial trigger mask for playback buffer: %s\n", strerror(errno))); - rc = RTErrConvertFromErrno(errno); - /* Note: No break here, need to unmap file first! */ - } - else - { - mask = PCM_ENABLE_OUTPUT; - if (ioctl (hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0) - { - LogRel(("OSS: Failed to retrieve PCM_ENABLE_OUTPUT mask: %s\n", strerror(errno))); - rc = RTErrConvertFromErrno(errno); - /* Note: No break here, need to unmap file first! */ - } - else - { - pStreamOSS->Out.fMMIO = true; - LogRel(("OSS: Using MMIO\n")); - } - } - - if (RT_FAILURE(rc)) - { - int rc2 = munmap(pStreamOSS->pvBuf, cbBuf); - if (rc2) - LogRel(("OSS: Failed to memory unmap playback buffer: %s\n", strerror(errno))); - break; - } - } - } -#endif /* !RT_OS_L4 */ - - /* Memory mapping failed above? Try allocating an own buffer. */ -#ifndef RT_OS_L4 - if (!pStreamOSS->Out.fMMIO) - { -#endif - void *pvBuf = RTMemAlloc(cbBuf); - if (!pvBuf) - { - LogRel(("OSS: Failed allocating playback buffer with %zu bytes\n", cbBuf)); - rc = VERR_NO_MEMORY; - break; - } - - pStreamOSS->hFile = hFile; - pStreamOSS->pvBuf = pvBuf; - pStreamOSS->cbBuf = cbBuf; -#ifndef RT_OS_L4 - } -#endif - pCfgAcq->Backend.cFramesPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, obtStream.cbFragmentSize); - pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * 2; /* Use "double buffering" */ - } - - } while (0); - - if (RT_FAILURE(rc)) - ossStreamClose(&hFile); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} - */ -static DECLCALLBACK(int) drvHostOssAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; - - int rc = VINF_SUCCESS; - uint32_t cbWrittenTotal = 0; - -#ifndef RT_OS_L4 - count_info cntinfo; -#endif - - do - { - uint32_t cbAvail = uBufSize; - uint32_t cbToWrite; - -#ifndef RT_OS_L4 - if (pStreamOSS->Out.fMMIO) - { - /* Get current playback pointer. */ - int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOPTR, &cntinfo); - if (!rc2) - { - LogRel(("OSS: Failed to retrieve current playback pointer: %s\n", strerror(errno))); - rc = RTErrConvertFromErrno(errno); - break; - } - - /* Nothing to play? */ - if (cntinfo.ptr == pStreamOSS->old_optr) - break; - - int cbData; - if (cntinfo.ptr > pStreamOSS->old_optr) - cbData = cntinfo.ptr - pStreamOSS->old_optr; - else - cbData = uBufSize + cntinfo.ptr - pStreamOSS->old_optr; - Assert(cbData >= 0); - - cbToWrite = RT_MIN((unsigned)cbData, cbAvail); - } - else - { -#endif - audio_buf_info abinfo; - int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &abinfo); - if (rc2 < 0) - { - LogRel(("OSS: Failed to retrieve current playback buffer: %s\n", strerror(errno))); - rc = RTErrConvertFromErrno(errno); - break; - } - - if ((size_t)abinfo.bytes > uBufSize) - { - LogRel2(("OSS: Warning: Too big output size (%d > %RU32), limiting to %RU32\n", abinfo.bytes, uBufSize, uBufSize)); - abinfo.bytes = uBufSize; - /* Keep going. */ - } - - if (abinfo.bytes < 0) - { - LogRel2(("OSS: Warning: Invalid available size (%d vs. %RU32)\n", abinfo.bytes, uBufSize)); - rc = VERR_INVALID_PARAMETER; - break; - } - - cbToWrite = RT_MIN(unsigned(abinfo.fragments * abinfo.fragsize), cbAvail); -#ifndef RT_OS_L4 - } -#endif - cbToWrite = RT_MIN(cbToWrite, pStreamOSS->cbBuf); - - while (cbToWrite) - { - uint32_t cbWritten = cbToWrite; - - memcpy(pStreamOSS->pvBuf, pvBuf, cbWritten); - - uint32_t cbChunk = cbWritten; - uint32_t cbChunkOff = 0; - while (cbChunk) - { - ssize_t cbChunkWritten = write(pStreamOSS->hFile, (uint8_t *)pStreamOSS->pvBuf + cbChunkOff, - RT_MIN(cbChunk, (unsigned)s_OSSConf.fragsize)); - if (cbChunkWritten < 0) - { - LogRel(("OSS: Failed writing output data: %s\n", strerror(errno))); - rc = RTErrConvertFromErrno(errno); - break; - } - - if (cbChunkWritten & pStreamOSS->uAlign) - { - LogRel(("OSS: Misaligned write (written %z, expected %RU32)\n", cbChunkWritten, cbChunk)); - break; - } - - cbChunkOff += (uint32_t)cbChunkWritten; - Assert(cbChunkOff <= cbWritten); - Assert(cbChunk >= (uint32_t)cbChunkWritten); - cbChunk -= (uint32_t)cbChunkWritten; - } - - Assert(cbToWrite >= cbWritten); - cbToWrite -= cbWritten; - cbWrittenTotal += cbWritten; - } - -#ifndef RT_OS_L4 - /* Update read pointer. */ - if (pStreamOSS->Out.fMMIO) - pStreamOSS->old_optr = cntinfo.ptr; -#endif - - } while(0); - - if (RT_SUCCESS(rc)) - { - if (puWritten) - *puWritten = cbWrittenTotal; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} - */ -static DECLCALLBACK(void) drvHostOssAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostOssAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) -{ - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); - RT_NOREF(enmDir); - - return PDMAUDIOBACKENDSTS_RUNNING; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} - */ -static DECLCALLBACK(int) drvHostOssAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; - - int rc; - if (pCfgReq->enmDir == PDMAUDIODIR_IN) - rc = ossCreateStreamIn (pStreamOSS, pCfgReq, pCfgAcq); - else - rc = ossCreateStreamOut(pStreamOSS, pCfgReq, pCfgAcq); - - if (RT_SUCCESS(rc)) - { - pStreamOSS->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); - if (!pStreamOSS->pCfg) - rc = VERR_NO_MEMORY; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} - */ -static DECLCALLBACK(int) drvHostOssAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; - - if (!pStreamOSS->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamOSS->pCfg->enmDir == PDMAUDIODIR_IN) - rc = ossDestroyStreamIn(pStream); - else - rc = ossDestroyStreamOut(pStream); - - if (RT_SUCCESS(rc)) - { - DrvAudioHlpStreamCfgFree(pStreamOSS->pCfg); - pStreamOSS->pCfg = NULL; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} - */ -static DECLCALLBACK(int) drvHostOssAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PDMAUDIOSTREAMCMD enmStreamCmd) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; - - if (!pStreamOSS->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamOSS->pCfg->enmDir == PDMAUDIODIR_IN) - rc = ossControlStreamIn(/*pInterface, pStream, enmStreamCmd*/); - else - rc = ossControlStreamOut(pStreamOSS, enmStreamCmd); - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} - */ -static DECLCALLBACK(int) drvHostOssAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - /* Nothing to do here for OSS. */ - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} - */ -static DECLCALLBACK(uint32_t) drvHostOssAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - return UINT32_MAX; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(uint32_t) drvHostOssAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - return UINT32_MAX; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} - */ -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostOssAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - return PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED; -} - -/** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) drvHostOSSAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) -{ - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO); - - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); - - return NULL; -} - -/** - * Constructs an OSS audio driver instance. - * - * @copydoc FNPDMDRVCONSTRUCT - */ -static DECLCALLBACK(int) drvHostOSSAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) -{ - RT_NOREF(pCfg, fFlags); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO); - LogRel(("Audio: Initializing OSS driver\n")); - - /* - * Init the static parts. - */ - pThis->pDrvIns = pDrvIns; - /* IBase */ - pDrvIns->IBase.pfnQueryInterface = drvHostOSSAudioQueryInterface; - /* IHostAudio */ - pThis->IHostAudio.pfnInit = drvHostOssAudioHA_Init; - pThis->IHostAudio.pfnShutdown = drvHostOssAudioHA_Shutdown; - pThis->IHostAudio.pfnGetConfig = drvHostOssAudioHA_GetConfig; - pThis->IHostAudio.pfnGetStatus = drvHostOssAudioHA_GetStatus; - pThis->IHostAudio.pfnStreamCreate = drvHostOssAudioHA_StreamCreate; - pThis->IHostAudio.pfnStreamDestroy = drvHostOssAudioHA_StreamDestroy; - pThis->IHostAudio.pfnStreamControl = drvHostOssAudioHA_StreamControl; - pThis->IHostAudio.pfnStreamGetReadable = drvHostOssAudioHA_StreamGetReadable; - pThis->IHostAudio.pfnStreamGetWritable = drvHostOssAudioHA_StreamGetWritable; - pThis->IHostAudio.pfnStreamGetStatus = drvHostOssAudioHA_StreamGetStatus; - pThis->IHostAudio.pfnStreamIterate = drvHostOssAudioHA_StreamIterate; - pThis->IHostAudio.pfnStreamPlay = drvHostOssAudioHA_StreamPlay; - pThis->IHostAudio.pfnStreamCapture = drvHostOssAudioHA_StreamCapture; - pThis->IHostAudio.pfnSetCallback = NULL; - pThis->IHostAudio.pfnGetDevices = NULL; - pThis->IHostAudio.pfnStreamGetPending = NULL; - pThis->IHostAudio.pfnStreamPlayBegin = NULL; - pThis->IHostAudio.pfnStreamPlayEnd = NULL; - pThis->IHostAudio.pfnStreamCaptureBegin = NULL; - pThis->IHostAudio.pfnStreamCaptureEnd = NULL; - - return VINF_SUCCESS; -} - -/** - * Char driver registration record. - */ -const PDMDRVREG g_DrvHostOSSAudio = -{ - /* u32Version */ - PDM_DRVREG_VERSION, - /* szName */ - "OSSAudio", - /* szRCMod */ - "", - /* szR0Mod */ - "", - /* pszDescription */ - "OSS audio host driver", - /* fFlags */ - PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, - /* fClass. */ - PDM_DRVREG_CLASS_AUDIO, - /* cMaxInstances */ - ~0U, - /* cbInstance */ - sizeof(DRVHOSTOSSAUDIO), - /* pfnConstruct */ - drvHostOSSAudioConstruct, - /* pfnDestruct */ - NULL, - /* pfnRelocate */ - NULL, - /* pfnIOCtl */ - NULL, - /* pfnPowerOn */ - NULL, - /* pfnReset */ - NULL, - /* pfnSuspend */ - NULL, - /* pfnResume */ - NULL, - /* pfnAttach */ - NULL, - /* pfnDetach */ - NULL, - /* pfnPowerOff */ - NULL, - /* pfnSoftReset */ - NULL, - /* u32EndVersion */ - PDM_DRVREG_VERSION -}; - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,1760 +0,0 @@ -/* $Id: DrvHostPulseAudio.cpp $ */ -/** @file - * VBox audio devices: Pulse Audio audio driver. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include - -#include - -#include -#include -#include - -RT_C_DECLS_BEGIN - #include "pulse_mangling.h" - #include "pulse_stubs.h" -RT_C_DECLS_END - -#include - -#include "DrvAudio.h" -#include "VBoxDD.h" - - -/********************************************************************************************************************************* -* Defines * -*********************************************************************************************************************************/ -#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 32 /** @todo Make this configurable thru driver options. */ - -#ifndef PA_STREAM_NOFLAGS -# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */ -#endif - -#ifndef PA_CONTEXT_NOFLAGS -# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */ -#endif - -/** No flags specified. */ -#define PULSEAUDIOENUMCBFLAGS_NONE 0 -/** (Release) log found devices. */ -#define PULSEAUDIOENUMCBFLAGS_LOG RT_BIT(0) - -/** Makes DRVHOSTPULSEAUDIO out of PDMIHOSTAUDIO. */ -#define PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface) \ - ( (PDRVHOSTPULSEAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTPULSEAUDIO, IHostAudio)) ) - - -/********************************************************************************************************************************* -* Structures * -*********************************************************************************************************************************/ - -/** - * Host Pulse audio driver instance data. - * @implements PDMIAUDIOCONNECTOR - */ -typedef struct DRVHOSTPULSEAUDIO -{ - /** Pointer to the driver instance structure. */ - PPDMDRVINS pDrvIns; - /** Pointer to PulseAudio's threaded main loop. */ - pa_threaded_mainloop *pMainLoop; - /** - * Pointer to our PulseAudio context. - * Note: We use a pMainLoop in a separate thread (pContext). - * So either use callback functions or protect these functions - * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock(). - */ - pa_context *pContext; - /** Shutdown indicator. */ - volatile bool fAbortLoop; - /** Enumeration operation successful? */ - volatile bool fEnumOpSuccess; - /** Pointer to host audio interface. */ - PDMIHOSTAUDIO IHostAudio; - /** Error count for not flooding the release log. - * Specify UINT32_MAX for unlimited logging. */ - uint32_t cLogErrors; - /** The stream (base) name; needed for distinguishing - * streams in the PulseAudio mixer controls if multiple - * VMs are running at the same time. */ - char szStreamName[64]; -} DRVHOSTPULSEAUDIO, *PDRVHOSTPULSEAUDIO; - -typedef struct PULSEAUDIOSTREAM -{ - /** The stream's acquired configuration. */ - PPDMAUDIOSTREAMCFG pCfg; - /** Pointer to driver instance. */ - PDRVHOSTPULSEAUDIO pDrv; - /** Pointer to opaque PulseAudio stream. */ - pa_stream *pStream; - /** Pulse sample format and attribute specification. */ - pa_sample_spec SampleSpec; - /** Pulse playback and buffer metrics. */ - pa_buffer_attr BufAttr; - int fOpSuccess; - /** Pointer to Pulse sample peeking buffer. */ - const uint8_t *pu8PeekBuf; - /** Current size (in bytes) of peeking data in - * buffer. */ - size_t cbPeekBuf; - /** Our offset (in bytes) in peeking buffer. */ - size_t offPeekBuf; - pa_operation *pDrainOp; - /** Number of occurred audio data underflows. */ - uint32_t cUnderflows; - /** Current latency (in us). */ - uint64_t curLatencyUs; -#ifdef LOG_ENABLED - /** Start time stamp (in us) of stream playback / recording. */ - pa_usec_t tsStartUs; - /** Time stamp (in us) when last read from / written to the stream. */ - pa_usec_t tsLastReadWrittenUs; -#endif -} PULSEAUDIOSTREAM, *PPULSEAUDIOSTREAM; - -/** - * Callback context for server enumeration callbacks. - */ -typedef struct PULSEAUDIOENUMCBCTX -{ - /** Pointer to host backend driver. */ - PDRVHOSTPULSEAUDIO pDrv; - /** Enumeration flags. */ - uint32_t fFlags; - /** Number of found input devices. */ - uint8_t cDevIn; - /** Number of found output devices. */ - uint8_t cDevOut; - /** Name of default sink being used. Must be free'd using RTStrFree(). */ - char *pszDefaultSink; - /** Name of default source being used. Must be free'd using RTStrFree(). */ - char *pszDefaultSource; -} PULSEAUDIOENUMCBCTX, *PPULSEAUDIOENUMCBCTX; - -#ifndef PA_CONTEXT_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */ -static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) { - return - x == PA_CONTEXT_CONNECTING || - x == PA_CONTEXT_AUTHORIZING || - x == PA_CONTEXT_SETTING_NAME || - x == PA_CONTEXT_READY; -} -#endif /* !PA_CONTEXT_IS_GOOD */ - -#ifndef PA_STREAM_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */ -static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) { - return - x == PA_STREAM_CREATING || - x == PA_STREAM_READY; -} -#endif /* !PA_STREAM_IS_GOOD */ - - -/********************************************************************************************************************************* -* Prototypes * -*********************************************************************************************************************************/ - -static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum); -static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg); -#ifdef DEBUG -static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext); -static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext); -#endif -static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvContext); - - -/** - * Signal the main loop to abort. Just signalling isn't sufficient as the - * mainloop might not have been entered yet. - */ -static void paSignalWaiter(PDRVHOSTPULSEAUDIO pThis) -{ - if (!pThis) - return; - - pThis->fAbortLoop = true; - pa_threaded_mainloop_signal(pThis->pMainLoop, 0); -} - - -static pa_sample_format_t paAudioPropsToPulse(PPDMAUDIOPCMPROPS pProps) -{ - switch (pProps->cbSample) - { - case 1: - if (!pProps->fSigned) - return PA_SAMPLE_U8; - break; - - case 2: - if (pProps->fSigned) - return PA_SAMPLE_S16LE; - break; - -#ifdef PA_SAMPLE_S32LE - case 4: - if (pProps->fSigned) - return PA_SAMPLE_S32LE; - break; -#endif - - default: - break; - } - - AssertMsgFailed(("%RU8%s not supported\n", pProps->cbSample, pProps->fSigned ? "S" : "U")); - return PA_SAMPLE_INVALID; -} - - -static int paPulseToAudioProps(pa_sample_format_t pulsefmt, PPDMAUDIOPCMPROPS pProps) -{ - /** @todo r=bird: You are assuming undocumented stuff about - * pProps->fSwapEndian. */ - switch (pulsefmt) - { - case PA_SAMPLE_U8: - pProps->cbSample = 1; - pProps->fSigned = false; - break; - - case PA_SAMPLE_S16LE: - pProps->cbSample = 2; - pProps->fSigned = true; - break; - - case PA_SAMPLE_S16BE: - pProps->cbSample = 2; - pProps->fSigned = true; - /** @todo Handle Endianess. */ - break; - -#ifdef PA_SAMPLE_S32LE - case PA_SAMPLE_S32LE: - pProps->cbSample = 4; - pProps->fSigned = true; - break; -#endif - -#ifdef PA_SAMPLE_S32BE - case PA_SAMPLE_S32BE: - pProps->cbSample = 4; - pProps->fSigned = true; - /** @todo Handle Endianess. */ - break; -#endif - - default: - AssertLogRelMsgFailed(("PulseAudio: Format (%ld) not supported\n", pulsefmt)); - return VERR_NOT_SUPPORTED; - } - - return VINF_SUCCESS; -} - - -/** - * Synchronously wait until an operation completed. - */ -static int paWaitForEx(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP, RTMSINTERVAL cMsTimeout) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pOP, VERR_INVALID_POINTER); - - int rc = VINF_SUCCESS; - - uint64_t u64StartMs = RTTimeMilliTS(); - while (pa_operation_get_state(pOP) == PA_OPERATION_RUNNING) - { - if (!pThis->fAbortLoop) - { - AssertPtr(pThis->pMainLoop); - pa_threaded_mainloop_wait(pThis->pMainLoop); - if ( !pThis->pContext - || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY) - { - LogRel(("PulseAudio: pa_context_get_state context not ready\n")); - break; - } - } - pThis->fAbortLoop = false; - - uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs; - if (u64ElapsedMs >= cMsTimeout) - { - rc = VERR_TIMEOUT; - break; - } - } - - pa_operation_unref(pOP); - - return rc; -} - - -static int paWaitFor(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP) -{ - return paWaitForEx(pThis, pOP, 10 * 1000 /* 10s timeout */); -} - - -/** - * Context status changed. - */ -static void paContextCbStateChanged(pa_context *pCtx, void *pvUser) -{ - AssertPtrReturnVoid(pCtx); - - PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser; - AssertPtrReturnVoid(pThis); - - switch (pa_context_get_state(pCtx)) - { - case PA_CONTEXT_READY: - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - paSignalWaiter(pThis); - break; - - default: - break; - } -} - - -/** - * Callback called when our pa_stream_drain operation was completed. - */ -static void paStreamCbDrain(pa_stream *pStream, int fSuccess, void *pvUser) -{ - AssertPtrReturnVoid(pStream); - - PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser; - AssertPtrReturnVoid(pStreamPA); - - pStreamPA->fOpSuccess = fSuccess; - if (fSuccess) - { - pa_operation_unref(pa_stream_cork(pStream, 1, - paStreamCbSuccess, pvUser)); - } - else - paError(pStreamPA->pDrv, "Failed to drain stream"); - - if (pStreamPA->pDrainOp) - { - pa_operation_unref(pStreamPA->pDrainOp); - pStreamPA->pDrainOp = NULL; - } -} - - -/** - * Stream status changed. - */ -static void paStreamCbStateChanged(pa_stream *pStream, void *pvUser) -{ - AssertPtrReturnVoid(pStream); - - PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser; - AssertPtrReturnVoid(pThis); - - switch (pa_stream_get_state(pStream)) - { - case PA_STREAM_READY: - case PA_STREAM_FAILED: - case PA_STREAM_TERMINATED: - paSignalWaiter(pThis); - break; - - default: - break; - } -} - - -#ifdef DEBUG -static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext) -{ - RT_NOREF(cbLen, pvContext); - - PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext; - AssertPtrReturnVoid(pStrm); - - pa_usec_t usec = 0; - int neg = 0; - pa_stream_get_latency(pStream, &usec, &neg); - - Log2Func(("Requested %zu bytes -- Current latency is %RU64ms\n", cbLen, usec / 1000)); -} - - -static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext) -{ - PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext; - AssertPtrReturnVoid(pStrm); - - pStrm->cUnderflows++; - - LogRel2(("PulseAudio: Warning: Hit underflow #%RU32\n", pStrm->cUnderflows)); - - if ( pStrm->cUnderflows >= 6 /** @todo Make this check configurable. */ - && pStrm->curLatencyUs < 2000000 /* 2s */) - { - pStrm->curLatencyUs = (pStrm->curLatencyUs * 3) / 2; - - LogRel2(("PulseAudio: Output latency increased to %RU64ms\n", pStrm->curLatencyUs / 1000 /* ms */)); - - pStrm->BufAttr.maxlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec); - pStrm->BufAttr.tlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec); - - pa_stream_set_buffer_attr(pStream, &pStrm->BufAttr, NULL, NULL); - - pStrm->cUnderflows = 0; - } - - pa_usec_t curLatencyUs = 0; - pa_stream_get_latency(pStream, &curLatencyUs, NULL /* Neg */); - - LogRel2(("PulseAudio: Latency now is %RU64ms\n", curLatencyUs / 1000 /* ms */)); - -# ifdef LOG_ENABLED - const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream); - const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream); - - pa_usec_t curPosWritesUs = pa_bytes_to_usec(pTInfo->write_index, pSpec); - pa_usec_t curPosReadsUs = pa_bytes_to_usec(pTInfo->read_index, pSpec); - pa_usec_t curTsUs = pa_rtclock_now() - pStrm->tsStartUs; - - Log2Func(("curPosWrite=%RU64ms, curPosRead=%RU64ms, curTs=%RU64ms, curLatency=%RU64ms (%RU32Hz, %RU8 channels)\n", - curPosWritesUs / RT_US_1MS_64, curPosReadsUs / RT_US_1MS_64, - curTsUs / RT_US_1MS_64, curLatencyUs / RT_US_1MS_64, pSpec->rate, pSpec->channels)); -# endif -} - - -static void paStreamCbOverflow(pa_stream *pStream, void *pvContext) -{ - RT_NOREF(pStream, pvContext); - - Log2Func(("Warning: Hit overflow\n")); -} -#endif /* DEBUG */ - - -static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvUser) -{ - AssertPtrReturnVoid(pStream); - - PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvUser; - AssertPtrReturnVoid(pStrm); - - pStrm->fOpSuccess = fSuccess; - - if (fSuccess) - paSignalWaiter(pStrm->pDrv); - else - paError(pStrm->pDrv, "Failed to finish stream operation"); -} - - -static int paStreamOpen(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, bool fIn, const char *pszName) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER); - AssertPtrReturn(pszName, VERR_INVALID_POINTER); - - int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; - - pa_stream *pStream = NULL; - uint32_t flags = PA_STREAM_NOFLAGS; - - pa_threaded_mainloop_lock(pThis->pMainLoop); - - do - { - pa_sample_spec *pSampleSpec = &pStreamPA->SampleSpec; - - LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n", - pszName, pSampleSpec->rate, pSampleSpec->channels, - pa_sample_format_to_string(pSampleSpec->format))); - - if (!pa_sample_spec_valid(pSampleSpec)) - { - LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", pszName)); - break; - } - - pa_buffer_attr *pBufAttr = &pStreamPA->BufAttr; - - /** @todo r=andy Use pa_stream_new_with_proplist instead. */ - if (!(pStream = pa_stream_new(pThis->pContext, pszName, pSampleSpec, NULL /* pa_channel_map */))) - { - LogRel(("PulseAudio: Could not create stream '%s'\n", pszName)); - rc = VERR_NO_MEMORY; - break; - } - -#ifdef DEBUG - pa_stream_set_write_callback (pStream, paStreamCbReqWrite, pStreamPA); - pa_stream_set_underflow_callback (pStream, paStreamCbUnderflow, pStreamPA); - if (!fIn) /* Only for output streams. */ - pa_stream_set_overflow_callback(pStream, paStreamCbOverflow, pStreamPA); -#endif - pa_stream_set_state_callback (pStream, paStreamCbStateChanged, pThis); - -#if PA_API_VERSION >= 12 - /* XXX */ - flags |= PA_STREAM_ADJUST_LATENCY; -#endif - /* For using pa_stream_get_latency() and pa_stream_get_time(). */ - flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; - - /* No input/output right away after the stream was started. */ - flags |= PA_STREAM_START_CORKED; - - if (fIn) - { - LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n", - pBufAttr->maxlength, pBufAttr->fragsize)); - - if (pa_stream_connect_record(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags) < 0) - { - LogRel(("PulseAudio: Could not connect input stream '%s': %s\n", - pszName, pa_strerror(pa_context_errno(pThis->pContext)))); - break; - } - } - else - { - LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n", - pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq)); - - if (pa_stream_connect_playback(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags, - /*cvolume=*/NULL, /*sync_stream=*/NULL) < 0) - { - LogRel(("PulseAudio: Could not connect playback stream '%s': %s\n", - pszName, pa_strerror(pa_context_errno(pThis->pContext)))); - break; - } - } - - /* Wait until the stream is ready. */ - for (;;) - { - if (!pThis->fAbortLoop) - pa_threaded_mainloop_wait(pThis->pMainLoop); - pThis->fAbortLoop = false; - - pa_stream_state_t streamSt = pa_stream_get_state(pStream); - if (streamSt == PA_STREAM_READY) - break; - else if ( streamSt == PA_STREAM_FAILED - || streamSt == PA_STREAM_TERMINATED) - { - LogRel(("PulseAudio: Failed to initialize stream '%s' (state %ld)\n", pszName, streamSt)); - break; - } - } - -#ifdef LOG_ENABLED - pStreamPA->tsStartUs = pa_rtclock_now(); -#endif - const pa_buffer_attr *pBufAttrObtained = pa_stream_get_buffer_attr(pStream); - AssertPtr(pBufAttrObtained); - memcpy(pBufAttr, pBufAttrObtained, sizeof(pa_buffer_attr)); - - LogFunc(("Obtained %s buffer attributes: tLength=%RU32, maxLength=%RU32, minReq=%RU32, fragSize=%RU32, preBuf=%RU32\n", - fIn ? "capture" : "playback", - pBufAttr->tlength, pBufAttr->maxlength, pBufAttr->minreq, pBufAttr->fragsize, pBufAttr->prebuf)); - - pStreamPA->pStream = pStream; - - rc = VINF_SUCCESS; - - } while (0); - - if ( RT_FAILURE(rc) - && pStream) - pa_stream_disconnect(pStream); - - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - if (RT_FAILURE(rc)) - { - if (pStream) - pa_stream_unref(pStream); - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} - */ -static DECLCALLBACK(int) drvHostPulseAudioHA_Init(PPDMIHOSTAUDIO pInterface) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - - LogFlowFuncEnter(); - - int rc = audioLoadPulseLib(); - if (RT_FAILURE(rc)) - { - LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc)); - return rc; - } - - LogRel(("PulseAudio: Using v%s\n", pa_get_library_version())); - - pThis->fAbortLoop = false; - pThis->pMainLoop = NULL; - - bool fLocked = false; - - do - { - if (!(pThis->pMainLoop = pa_threaded_mainloop_new())) - { - LogRel(("PulseAudio: Failed to allocate main loop: %s\n", - pa_strerror(pa_context_errno(pThis->pContext)))); - rc = VERR_NO_MEMORY; - break; - } - - if (!(pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox"))) - { - LogRel(("PulseAudio: Failed to allocate context: %s\n", - pa_strerror(pa_context_errno(pThis->pContext)))); - rc = VERR_NO_MEMORY; - break; - } - - if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0) - { - LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n", - pa_strerror(pa_context_errno(pThis->pContext)))); - break; - } - - /* Install a global callback to known if something happens to our acquired context. */ - pa_context_set_state_callback(pThis->pContext, paContextCbStateChanged, pThis /* pvUserData */); - - pa_threaded_mainloop_lock(pThis->pMainLoop); - fLocked = true; - - if (pa_context_connect(pThis->pContext, NULL /* pszServer */, - PA_CONTEXT_NOFLAGS, NULL) < 0) - { - LogRel(("PulseAudio: Failed to connect to server: %s\n", - pa_strerror(pa_context_errno(pThis->pContext)))); - break; - } - - /* Wait until the pThis->pContext is ready. */ - for (;;) - { - if (!pThis->fAbortLoop) - pa_threaded_mainloop_wait(pThis->pMainLoop); - pThis->fAbortLoop = false; - - pa_context_state_t cstate = pa_context_get_state(pThis->pContext); - if (cstate == PA_CONTEXT_READY) - break; - else if ( cstate == PA_CONTEXT_TERMINATED - || cstate == PA_CONTEXT_FAILED) - { - LogRel(("PulseAudio: Failed to initialize context (state %d)\n", cstate)); - break; - } - } - } - while (0); - - if (fLocked) - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - if (RT_FAILURE(rc)) - { - if (pThis->pMainLoop) - pa_threaded_mainloop_stop(pThis->pMainLoop); - - if (pThis->pContext) - { - pa_context_disconnect(pThis->pContext); - pa_context_unref(pThis->pContext); - pThis->pContext = NULL; - } - - if (pThis->pMainLoop) - { - pa_threaded_mainloop_free(pThis->pMainLoop); - pThis->pMainLoop = NULL; - } - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int paCreateStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - pStreamPA->pDrainOp = NULL; - - pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props); - pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz; - pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels; - - pStreamPA->curLatencyUs = DrvAudioHlpFramesToMilli(pCfgReq->Backend.cFramesBufferSize, &pCfgReq->Props) * RT_US_1MS; - - const uint32_t cbLatency = pa_usec_to_bytes(pStreamPA->curLatencyUs, &pStreamPA->SampleSpec); - - LogRel2(("PulseAudio: Initial output latency is %RU64ms (%RU32 bytes)\n", pStreamPA->curLatencyUs / RT_US_1MS, cbLatency)); - - pStreamPA->BufAttr.tlength = cbLatency; - pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */ - pStreamPA->BufAttr.prebuf = cbLatency; - pStreamPA->BufAttr.minreq = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cFramesPeriod, &pCfgReq->Props); - - LogFunc(("Requested: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n", - pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq)); - - Assert(pCfgReq->enmDir == PDMAUDIODIR_OUT); - - char szName[256]; - RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]", DrvAudioHlpPlaybackDstToStr(pCfgReq->u.enmDst), pThis->szStreamName); - - /* Note that the struct BufAttr is updated to the obtained values after this call! */ - int rc = paStreamOpen(pThis, pStreamPA, false /* fIn */, szName); - if (RT_FAILURE(rc)) - return rc; - - rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props); - if (RT_FAILURE(rc)) - { - LogRel(("PulseAudio: Cannot find audio output format %ld\n", pStreamPA->SampleSpec.format)); - return rc; - } - - pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate; - pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels; - pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cbSample, pCfgAcq->Props.cChannels); - - LogFunc(("Acquired: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n", - pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq)); - - pCfgAcq->Backend.cFramesPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.minreq); - pCfgAcq->Backend.cFramesBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.tlength); - pCfgAcq->Backend.cFramesPreBuffering = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.prebuf); - - pStreamPA->pDrv = pThis; - - return rc; -} - - -static int paCreateStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props); - pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz; - pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels; - - pStreamPA->BufAttr.fragsize = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cFramesPeriod, &pCfgReq->Props); - pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */ - - Assert(pCfgReq->enmDir == PDMAUDIODIR_IN); - - char szName[256]; - RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]", DrvAudioHlpRecSrcToStr(pCfgReq->u.enmSrc), pThis->szStreamName); - - /* Note: Other members of BufAttr are ignored for record streams. */ - int rc = paStreamOpen(pThis, pStreamPA, true /* fIn */, szName); - if (RT_FAILURE(rc)) - return rc; - - rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props); - if (RT_FAILURE(rc)) - { - LogRel(("PulseAudio: Cannot find audio capture format %ld\n", pStreamPA->SampleSpec.format)); - return rc; - } - - pStreamPA->pDrv = pThis; - pStreamPA->pu8PeekBuf = NULL; - - pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate; - pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels; - - pCfgAcq->Backend.cFramesPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.fragsize); - pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesBufferSize; - pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod; - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} - */ -static DECLCALLBACK(int) drvHostPulseAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead) -{ - RT_NOREF(pvBuf, uBufSize); - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(uBufSize, VERR_INVALID_PARAMETER); - /* pcbRead is optional. */ - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; - - /* We should only call pa_stream_readable_size() once and trust the first value. */ - pa_threaded_mainloop_lock(pThis->pMainLoop); - size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream); - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - if (cbAvail == (size_t)-1) - return paError(pStreamPA->pDrv, "Failed to determine input data size"); - - /* If the buffer was not dropped last call, add what remains. */ - if (pStreamPA->pu8PeekBuf) - { - Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf); - cbAvail += (pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf); - } - - Log3Func(("cbAvail=%zu\n", cbAvail)); - - if (!cbAvail) /* No data? Bail out. */ - { - if (puRead) - *puRead = 0; - return VINF_SUCCESS; - } - - int rc = VINF_SUCCESS; - - size_t cbToRead = RT_MIN(cbAvail, uBufSize); - - Log3Func(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n", - cbToRead, cbAvail, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf)); - - uint32_t cbReadTotal = 0; - - while (cbToRead) - { - /* If there is no data, do another peek. */ - if (!pStreamPA->pu8PeekBuf) - { - pa_threaded_mainloop_lock(pThis->pMainLoop); - pa_stream_peek(pStreamPA->pStream, - (const void**)&pStreamPA->pu8PeekBuf, &pStreamPA->cbPeekBuf); - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - pStreamPA->offPeekBuf = 0; - - /* No data anymore? - * Note: If there's a data hole (cbPeekBuf then contains the length of the hole) - * we need to drop the stream lateron. */ - if ( !pStreamPA->pu8PeekBuf - && !pStreamPA->cbPeekBuf) - { - break; - } - } - - Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf); - size_t cbToWrite = RT_MIN(pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf, cbToRead); - - Log3Func(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n", - cbToRead, cbToWrite, - pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->pu8PeekBuf)); - - if ( cbToWrite - /* Only copy data if it's not a data hole (see above). */ - && pStreamPA->pu8PeekBuf - && pStreamPA->cbPeekBuf) - { - memcpy((uint8_t *)pvBuf + cbReadTotal, pStreamPA->pu8PeekBuf + pStreamPA->offPeekBuf, cbToWrite); - - Assert(cbToRead >= cbToWrite); - cbToRead -= cbToWrite; - cbReadTotal += cbToWrite; - - pStreamPA->offPeekBuf += cbToWrite; - Assert(pStreamPA->offPeekBuf <= pStreamPA->cbPeekBuf); - } - - if (/* Nothing to write anymore? Drop the buffer. */ - !cbToWrite - /* Was there a hole in the peeking buffer? Drop it. */ - || !pStreamPA->pu8PeekBuf - /* If the buffer is done, drop it. */ - || pStreamPA->offPeekBuf == pStreamPA->cbPeekBuf) - { - pa_threaded_mainloop_lock(pThis->pMainLoop); - pa_stream_drop(pStreamPA->pStream); - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - pStreamPA->pu8PeekBuf = NULL; - } - } - - if (RT_SUCCESS(rc)) - { - if (puRead) - *puRead = cbReadTotal; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} - */ -static DECLCALLBACK(int) drvHostPulseAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(uBufSize, VERR_INVALID_PARAMETER); - /* puWritten is optional. */ - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - PPULSEAUDIOSTREAM pPAStream = (PPULSEAUDIOSTREAM)pStream; - - int rc = VINF_SUCCESS; - - uint32_t cbWrittenTotal = 0; - - pa_threaded_mainloop_lock(pThis->pMainLoop); - -#ifdef LOG_ENABLED - const pa_usec_t tsNowUs = pa_rtclock_now(); - const pa_usec_t tsDeltaPlayedUs = tsNowUs - pPAStream->tsLastReadWrittenUs; - - Log3Func(("tsDeltaPlayedMs=%RU64\n", tsDeltaPlayedUs / 1000 /* ms */)); - - pPAStream->tsLastReadWrittenUs = tsNowUs; -#endif - - do - { - size_t cbWriteable = pa_stream_writable_size(pPAStream->pStream); - if (cbWriteable == (size_t)-1) - { - rc = paError(pPAStream->pDrv, "Failed to determine output data size"); - break; - } - - size_t cbLeft = RT_MIN(cbWriteable, uBufSize); - Assert(cbLeft); /* At this point we better have *something* to write. */ - - while (cbLeft) - { - uint32_t cbChunk = cbLeft; /* Write all at once for now. */ - - if (pa_stream_write(pPAStream->pStream, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, NULL /* Cleanup callback */, - 0, PA_SEEK_RELATIVE) < 0) - { - rc = paError(pPAStream->pDrv, "Failed to write to output stream"); - break; - } - - Assert(cbLeft >= cbChunk); - cbLeft -= cbChunk; - cbWrittenTotal += cbChunk; - } - - } while (0); - - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - if (RT_SUCCESS(rc)) - { - if (puWritten) - *puWritten = cbWrittenTotal; - } - - return rc; -} - - -/** @todo Implement va handling. */ -static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(szMsg, VERR_INVALID_POINTER); - - if (pThis->cLogErrors++ < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS) - { - int rc2 = pa_context_errno(pThis->pContext); - LogRel2(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2))); - } - - /** @todo Implement some PulseAudio -> IPRT mapping here. */ - return VERR_GENERAL_FAILURE; -} - - -static void paEnumSinkCb(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData) -{ - if (eol > 0) - return; - - PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData; - AssertPtrReturnVoid(pCbCtx); - PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv; - AssertPtrReturnVoid(pThis); - if (eol < 0) - { - pThis->fEnumOpSuccess = false; - pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); - return; - } - - AssertPtrReturnVoid(pCtx); - AssertPtrReturnVoid(pInfo); - - LogRel2(("PulseAudio: Using output sink '%s'\n", pInfo->name)); - - /** @todo Store sinks + channel mapping in callback context as soon as we have surround support. */ - pCbCtx->cDevOut++; - - pThis->fEnumOpSuccess = true; - pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); -} - - -static void paEnumSourceCb(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData) -{ - if (eol > 0) - return; - - PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData; - AssertPtrReturnVoid(pCbCtx); - PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv; - AssertPtrReturnVoid(pThis); - if (eol < 0) - { - pThis->fEnumOpSuccess = false; - pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); - return; - } - - AssertPtrReturnVoid(pCtx); - AssertPtrReturnVoid(pInfo); - - LogRel2(("PulseAudio: Using input source '%s'\n", pInfo->name)); - - /** @todo Store sources + channel mapping in callback context as soon as we have surround support. */ - pCbCtx->cDevIn++; - - pThis->fEnumOpSuccess = true; - pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); -} - - -static void paEnumServerCb(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData) -{ - AssertPtrReturnVoid(pCtx); - PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData; - AssertPtrReturnVoid(pCbCtx); - PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv; - AssertPtrReturnVoid(pThis); - - if (!pInfo) - { - pThis->fEnumOpSuccess = false; - pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); - return; - } - - if (pInfo->default_sink_name) - { - Assert(RTStrIsValidEncoding(pInfo->default_sink_name)); - pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name); - } - - if (pInfo->default_sink_name) - { - Assert(RTStrIsValidEncoding(pInfo->default_source_name)); - pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name); - } - - pThis->fEnumOpSuccess = true; - pa_threaded_mainloop_signal(pThis->pMainLoop, 0); -} - - -static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - PDMAUDIOBACKENDCFG Cfg; - RT_ZERO(Cfg); - - RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "PulseAudio"); - - Cfg.cbStreamOut = sizeof(PULSEAUDIOSTREAM); - Cfg.cbStreamIn = sizeof(PULSEAUDIOSTREAM); - Cfg.cMaxStreamsOut = UINT32_MAX; - Cfg.cMaxStreamsIn = UINT32_MAX; - - PULSEAUDIOENUMCBCTX CbCtx; - RT_ZERO(CbCtx); - - CbCtx.pDrv = pThis; - CbCtx.fFlags = fEnum; - - bool fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG); - - pa_threaded_mainloop_lock(pThis->pMainLoop); - - pThis->fEnumOpSuccess = false; - - LogRel(("PulseAudio: Retrieving server information ...\n")); - - /* Check if server information is available and bail out early if it isn't. */ - pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, paEnumServerCb, &CbCtx); - if (!paOpServerInfo) - { - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - LogRel(("PulseAudio: Server information not available, skipping enumeration\n")); - return VINF_SUCCESS; - } - - int rc = paWaitFor(pThis, paOpServerInfo); - if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess) - rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */ - if (RT_SUCCESS(rc)) - { - if (CbCtx.pszDefaultSink) - { - if (fLog) - LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink)); - - pThis->fEnumOpSuccess = false; - rc = paWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink, - paEnumSinkCb, &CbCtx)); - if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess) - rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */ - if ( RT_FAILURE(rc) - && fLog) - { - LogRel(("PulseAudio: Error enumerating properties for default output sink '%s'\n", CbCtx.pszDefaultSink)); - } - } - else if (fLog) - LogRel2(("PulseAudio: No default output sink found\n")); - - if (RT_SUCCESS(rc)) - { - if (CbCtx.pszDefaultSource) - { - if (fLog) - LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource)); - - pThis->fEnumOpSuccess = false; - rc = paWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource, - paEnumSourceCb, &CbCtx)); - if ( (RT_FAILURE(rc) || !pThis->fEnumOpSuccess) - && fLog) - { - LogRel(("PulseAudio: Error enumerating properties for default input source '%s'\n", CbCtx.pszDefaultSource)); - } - } - else if (fLog) - LogRel2(("PulseAudio: No default input source found\n")); - } - - if (RT_SUCCESS(rc)) - { - if (fLog) - { - LogRel2(("PulseAudio: Found %RU8 host playback device(s)\n", CbCtx.cDevOut)); - LogRel2(("PulseAudio: Found %RU8 host capturing device(s)\n", CbCtx.cDevIn)); - } - - if (pCfg) - memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG)); - } - - if (CbCtx.pszDefaultSink) - { - RTStrFree(CbCtx.pszDefaultSink); - CbCtx.pszDefaultSink = NULL; - } - - if (CbCtx.pszDefaultSource) - { - RTStrFree(CbCtx.pszDefaultSource); - CbCtx.pszDefaultSource = NULL; - } - } - else if (fLog) - LogRel(("PulseAudio: Error enumerating PulseAudio server properties\n")); - - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int paDestroyStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA) -{ - LogFlowFuncEnter(); - - if (pStreamPA->pStream) - { - pa_threaded_mainloop_lock(pThis->pMainLoop); - - pa_stream_disconnect(pStreamPA->pStream); - pa_stream_unref(pStreamPA->pStream); - - pStreamPA->pStream = NULL; - - pa_threaded_mainloop_unlock(pThis->pMainLoop); - } - - return VINF_SUCCESS; -} - - -static int paDestroyStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA) -{ - if (pStreamPA->pStream) - { - pa_threaded_mainloop_lock(pThis->pMainLoop); - - /* Make sure to cancel a pending draining operation, if any. */ - if (pStreamPA->pDrainOp) - { - pa_operation_cancel(pStreamPA->pDrainOp); - pStreamPA->pDrainOp = NULL; - } - - pa_stream_disconnect(pStreamPA->pStream); - pa_stream_unref(pStreamPA->pStream); - - pStreamPA->pStream = NULL; - - pa_threaded_mainloop_unlock(pThis->pMainLoop); - } - - return VINF_SUCCESS; -} - - -static int paControlStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - int rc = VINF_SUCCESS; - - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - case PDMAUDIOSTREAMCMD_RESUME: - { - pa_threaded_mainloop_lock(pThis->pMainLoop); - - if ( pStreamPA->pDrainOp - && pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_DONE) - { - pa_operation_cancel(pStreamPA->pDrainOp); - pa_operation_unref(pStreamPA->pDrainOp); - - pStreamPA->pDrainOp = NULL; - } - else - { - /* Uncork (resume) stream. */ - rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Uncork */, paStreamCbSuccess, pStreamPA)); - } - - pa_threaded_mainloop_unlock(pThis->pMainLoop); - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - case PDMAUDIOSTREAMCMD_PAUSE: - { - /* Pause audio output (the Pause bit of the AC97 x_CR register is set). - * Note that we must return immediately from here! */ - pa_threaded_mainloop_lock(pThis->pMainLoop); - if (!pStreamPA->pDrainOp) - { - rc = paWaitFor(pThis, pa_stream_trigger(pStreamPA->pStream, paStreamCbSuccess, pStreamPA)); - if (RT_SUCCESS(rc)) - pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, paStreamCbDrain, pStreamPA); - } - pa_threaded_mainloop_unlock(pThis->pMainLoop); - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -static int paControlStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - int rc = VINF_SUCCESS; - - LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd)); - - switch (enmStreamCmd) - { - case PDMAUDIOSTREAMCMD_ENABLE: - case PDMAUDIOSTREAMCMD_RESUME: - { - pa_threaded_mainloop_lock(pThis->pMainLoop); - rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Play / resume */, paStreamCbSuccess, pStreamPA)); - pa_threaded_mainloop_unlock(pThis->pMainLoop); - break; - } - - case PDMAUDIOSTREAMCMD_DISABLE: - case PDMAUDIOSTREAMCMD_PAUSE: - { - pa_threaded_mainloop_lock(pThis->pMainLoop); - if (pStreamPA->pu8PeekBuf) /* Do we need to drop the peek buffer?*/ - { - pa_stream_drop(pStreamPA->pStream); - pStreamPA->pu8PeekBuf = NULL; - } - - rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 1 /* Stop / pause */, paStreamCbSuccess, pStreamPA)); - pa_threaded_mainloop_unlock(pThis->pMainLoop); - break; - } - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} - */ -static DECLCALLBACK(void) drvHostPulseAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface) -{ - AssertPtrReturnVoid(pInterface); - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - - LogFlowFuncEnter(); - - if (pThis->pMainLoop) - pa_threaded_mainloop_stop(pThis->pMainLoop); - - if (pThis->pContext) - { - pa_context_disconnect(pThis->pContext); - pa_context_unref(pThis->pContext); - pThis->pContext = NULL; - } - - if (pThis->pMainLoop) - { - pa_threaded_mainloop_free(pThis->pMainLoop); - pThis->pMainLoop = NULL; - } - - LogFlowFuncLeave(); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} - */ -static DECLCALLBACK(int) drvHostPulseAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - - return paEnumerate(pThis, pBackendCfg, PULSEAUDIOENUMCBFLAGS_LOG /* fEnum */); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostPulseAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) -{ - RT_NOREF(enmDir); - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); - - return PDMAUDIOBACKENDSTS_RUNNING; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} - */ -static DECLCALLBACK(int) drvHostPulseAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; - - int rc; - if (pCfgReq->enmDir == PDMAUDIODIR_IN) - rc = paCreateStreamIn (pThis, pStreamPA, pCfgReq, pCfgAcq); - else if (pCfgReq->enmDir == PDMAUDIODIR_OUT) - rc = paCreateStreamOut(pThis, pStreamPA, pCfgReq, pCfgAcq); - else - AssertFailedReturn(VERR_NOT_IMPLEMENTED); - - if (RT_SUCCESS(rc)) - { - pStreamPA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); - if (!pStreamPA->pCfg) - rc = VERR_NO_MEMORY; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} - */ -static DECLCALLBACK(int) drvHostPulseAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; - - if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN) - rc = paDestroyStreamIn (pThis, pStreamPA); - else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT) - rc = paDestroyStreamOut(pThis, pStreamPA); - else - AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); - - if (RT_SUCCESS(rc)) - { - DrvAudioHlpStreamCfgFree(pStreamPA->pCfg); - pStreamPA->pCfg = NULL; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} - */ -static DECLCALLBACK(int) drvHostPulseAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, - PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; - - if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN) - rc = paControlStreamIn (pThis, pStreamPA, enmStreamCmd); - else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT) - rc = paControlStreamOut(pThis, pStreamPA, enmStreamCmd); - else - AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); - - return rc; -} - - -static uint32_t paStreamGetAvail(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA) -{ - pa_threaded_mainloop_lock(pThis->pMainLoop); - - uint32_t cbAvail = 0; - - if (PA_STREAM_IS_GOOD(pa_stream_get_state(pStreamPA->pStream))) - { - if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN) - { - cbAvail = (uint32_t)pa_stream_readable_size(pStreamPA->pStream); - Log3Func(("cbReadable=%RU32\n", cbAvail)); - } - else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT) - { - size_t cbWritable = pa_stream_writable_size(pStreamPA->pStream); - - Log3Func(("cbWritable=%zu, maxLength=%RU32, minReq=%RU32\n", - cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq)); - - /* Don't report more writable than the PA server can handle. */ - if (cbWritable > pStreamPA->BufAttr.maxlength) - cbWritable = pStreamPA->BufAttr.maxlength; - - cbAvail = (uint32_t)cbWritable; - } - else - AssertFailed(); - } - - pa_threaded_mainloop_unlock(pThis->pMainLoop); - - return cbAvail; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} - */ -static DECLCALLBACK(uint32_t) drvHostPulseAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; - - return paStreamGetAvail(pThis, pStreamPA); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(uint32_t) drvHostPulseAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; - - return paStreamGetAvail(pThis, pStreamPA); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} - */ -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostPulseAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - RT_NOREF(pStream); - - PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); - - PDMAUDIOSTREAMSTS fStrmSts = PDMAUDIOSTREAMSTS_FLAGS_NONE; - - /* Check PulseAudio's general status. */ - if ( pThis->pContext - && PA_CONTEXT_IS_GOOD(pa_context_get_state(pThis->pContext))) - fStrmSts = PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED; - - return fStrmSts; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} - */ -static DECLCALLBACK(int) drvHostPulseAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - /* Nothing to do here for PulseAudio. */ - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) drvHostPulseAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) -{ - AssertPtrReturn(pInterface, NULL); - AssertPtrReturn(pszIID, NULL); - - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); - - return NULL; -} - - -/** - * Destructs a PulseAudio Audio driver instance. - * - * @copydoc FNPDMDRVDESTRUCT - */ -static DECLCALLBACK(void) drvHostPulseAudioDestruct(PPDMDRVINS pDrvIns) -{ - PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); - LogFlowFuncEnter(); -} - - -/** - * Constructs a PulseAudio Audio driver instance. - * - * @copydoc FNPDMDRVCONSTRUCT - */ -static DECLCALLBACK(int) drvHostPulseAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) -{ - RT_NOREF(pCfg, fFlags); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); - - PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO); - LogRel(("Audio: Initializing PulseAudio driver\n")); - - pThis->pDrvIns = pDrvIns; - /* IBase */ - pDrvIns->IBase.pfnQueryInterface = drvHostPulseAudioQueryInterface; - /* IHostAudio */ - pThis->IHostAudio.pfnInit = drvHostPulseAudioHA_Init; - pThis->IHostAudio.pfnShutdown = drvHostPulseAudioHA_Shutdown; - pThis->IHostAudio.pfnGetConfig = drvHostPulseAudioHA_GetConfig; - pThis->IHostAudio.pfnGetStatus = drvHostPulseAudioHA_GetStatus; - pThis->IHostAudio.pfnStreamCreate = drvHostPulseAudioHA_StreamCreate; - pThis->IHostAudio.pfnStreamDestroy = drvHostPulseAudioHA_StreamDestroy; - pThis->IHostAudio.pfnStreamControl = drvHostPulseAudioHA_StreamControl; - pThis->IHostAudio.pfnStreamGetReadable = drvHostPulseAudioHA_StreamGetReadable; - pThis->IHostAudio.pfnStreamGetWritable = drvHostPulseAudioHA_StreamGetWritable; - pThis->IHostAudio.pfnStreamGetStatus = drvHostPulseAudioHA_StreamGetStatus; - pThis->IHostAudio.pfnStreamIterate = drvHostPulseAudioHA_StreamIterate; - pThis->IHostAudio.pfnStreamPlay = drvHostPulseAudioHA_StreamPlay; - pThis->IHostAudio.pfnStreamCapture = drvHostPulseAudioHA_StreamCapture; - pThis->IHostAudio.pfnSetCallback = NULL; - pThis->IHostAudio.pfnGetDevices = NULL; - pThis->IHostAudio.pfnStreamGetPending = NULL; - pThis->IHostAudio.pfnStreamPlayBegin = NULL; - pThis->IHostAudio.pfnStreamPlayEnd = NULL; - pThis->IHostAudio.pfnStreamCaptureBegin = NULL; - pThis->IHostAudio.pfnStreamCaptureEnd = NULL; - - int rc2 = CFGMR3QueryString(pCfg, "StreamName", pThis->szStreamName, sizeof(pThis->szStreamName)); - AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"StreamName\" value, rc=%Rrc\n", rc2), rc2); - - return VINF_SUCCESS; -} - - -/** - * Pulse audio driver registration record. - */ -const PDMDRVREG g_DrvHostPulseAudio = -{ - /* u32Version */ - PDM_DRVREG_VERSION, - /* szName */ - "PulseAudio", - /* szRCMod */ - "", - /* szR0Mod */ - "", - /* pszDescription */ - "Pulse Audio host driver", - /* fFlags */ - PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, - /* fClass. */ - PDM_DRVREG_CLASS_AUDIO, - /* cMaxInstances */ - ~0U, - /* cbInstance */ - sizeof(DRVHOSTPULSEAUDIO), - /* pfnConstruct */ - drvHostPulseAudioConstruct, - /* pfnDestruct */ - drvHostPulseAudioDestruct, - /* pfnRelocate */ - NULL, - /* pfnIOCtl */ - NULL, - /* pfnPowerOn */ - NULL, - /* pfnReset */ - NULL, - /* pfnSuspend */ - NULL, - /* pfnResume */ - NULL, - /* pfnAttach */ - NULL, - /* pfnDetach */ - NULL, - /* pfnPowerOff */ - NULL, - /* pfnSoftReset */ - NULL, - /* u32EndVersion */ - PDM_DRVREG_VERSION -}; - -#if 0 /* unused */ -static struct audio_option pulse_options[] = -{ - {"DAC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_out, - "DAC period size in milliseconds", NULL, 0}, - {"ADC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_in, - "ADC period size in milliseconds", NULL, 0} -}; -#endif - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostValidationKit.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostValidationKit.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/DrvHostValidationKit.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/DrvHostValidationKit.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,488 +0,0 @@ -/* $Id: DrvHostValidationKit.cpp $ */ -/** @file - * ValidationKit audio driver - host backend for dumping and injecting audio data - * from/to the device emulation. - */ - -/* - * Copyright (C) 2016-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#include -#include /* For PDMIBASE_2_PDMDRV. */ - -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include - -#include "DrvAudio.h" -#include "VBoxDD.h" - - -/** - * Structure for keeping a VAKIT input/output stream. - */ -typedef struct VAKITAUDIOSTREAM -{ - /** The stream's acquired configuration. */ - PPDMAUDIOSTREAMCFG pCfg; - /** Audio file to dump output to or read input from. */ - PPDMAUDIOFILE pFile; - /** Text file to store timing of audio buffers submittions**/ - RTFILE hFileTiming; - /** Timestamp of the first play or record request**/ - uint64_t tsStarted; - /** Total number of samples played or recorded so far**/ - uint32_t uSamplesSinceStarted; - union - { - struct - { - /** Timestamp of last captured samples. */ - uint64_t tsLastCaptured; - } In; - struct - { - /** Timestamp of last played samples. */ - uint64_t tsLastPlayed; - uint8_t *pu8PlayBuffer; - uint32_t cbPlayBuffer; - } Out; - }; -} VAKITAUDIOSTREAM, *PVAKITAUDIOSTREAM; - -/** - * VAKIT audio driver instance data. - * @implements PDMIAUDIOCONNECTOR - */ -typedef struct DRVHOSTVAKITAUDIO -{ - /** Pointer to the driver instance structure. */ - PPDMDRVINS pDrvIns; - /** Pointer to host audio interface. */ - PDMIHOSTAUDIO IHostAudio; -} DRVHOSTVAKITAUDIO, *PDRVHOSTVAKITAUDIO; - -/*******************************************PDM_AUDIO_DRIVER******************************/ - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} - */ -static DECLCALLBACK(int) drvHostValKitAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) -{ - RT_NOREF(pInterface); - AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); - - RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Validation Kit"); - - pBackendCfg->cbStreamOut = sizeof(VAKITAUDIOSTREAM); - pBackendCfg->cbStreamIn = sizeof(VAKITAUDIOSTREAM); - - pBackendCfg->cMaxStreamsOut = 1; /* Output */ - pBackendCfg->cMaxStreamsIn = 0; /* No input supported yet. */ - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} - */ -static DECLCALLBACK(int) drvHostValKitAudioHA_Init(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); - - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} - */ -static DECLCALLBACK(void) drvHostValKitAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface) -{ - RT_NOREF(pInterface); -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} - */ -static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostValKitAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) -{ - RT_NOREF(enmDir); - AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); - - return PDMAUDIOBACKENDSTS_RUNNING; -} - - -static int drvHostValKitAudioCreateStreamIn(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - RT_NOREF(pDrv, pStreamDbg, pCfgReq, pCfgAcq); - - return VINF_SUCCESS; -} - - -static int drvHostValKitAudioCreateStreamOut(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - RT_NOREF(pDrv, pCfgAcq); - - pStreamDbg->tsStarted = 0; - pStreamDbg->uSamplesSinceStarted = 0; - pStreamDbg->Out.tsLastPlayed = 0; - pStreamDbg->Out.cbPlayBuffer = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cFramesBufferSize, &pCfgReq->Props); - pStreamDbg->Out.pu8PlayBuffer = (uint8_t *)RTMemAlloc(pStreamDbg->Out.cbPlayBuffer); - AssertReturn(pStreamDbg->Out.pu8PlayBuffer, VERR_NO_MEMORY); - - char szTemp[RTPATH_MAX]; - int rc = RTPathTemp(szTemp, sizeof(szTemp)); - if (RT_SUCCESS(rc)) - rc = RTPathAppend(szTemp, sizeof(szTemp), "VBoxTestTmp\\VBoxAudioValKit"); - if (RT_SUCCESS(rc)) - { - char szFile[RTPATH_MAX]; - rc = DrvAudioHlpFileNameGet(szFile, sizeof(szFile), szTemp, "VaKit", - 0 /* Instance */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - if (RT_SUCCESS(rc)) - { - rc = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAGS_NONE, &pStreamDbg->pFile); - if (RT_SUCCESS(rc)) - rc = DrvAudioHlpFileOpen(pStreamDbg->pFile, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, &pCfgReq->Props); - } - - if (RT_FAILURE(rc)) - LogRel(("VaKitAudio: Creating output file '%s' failed with %Rrc\n", szFile, rc)); - else - { - size_t cch; - char szTimingInfo[128]; - cch = RTStrPrintf(szTimingInfo, sizeof(szTimingInfo), "# %uHz %uch %ubit\n", - pCfgReq->Props.uHz, pCfgReq->Props.cChannels, pCfgReq->Props.cbSample * 8); - - RTFileWrite(pStreamDbg->hFileTiming, szTimingInfo, cch, NULL); - } - } - else - LogRel(("VaKitAudio: Unable to retrieve temp dir: %Rrc\n", rc)); - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} - */ -static DECLCALLBACK(int) drvHostValKitAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); - AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); - - PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio); - PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; - - int rc; - if (pCfgReq->enmDir == PDMAUDIODIR_IN) - rc = drvHostValKitAudioCreateStreamIn( pDrv, pStreamDbg, pCfgReq, pCfgAcq); - else - rc = drvHostValKitAudioCreateStreamOut(pDrv, pStreamDbg, pCfgReq, pCfgAcq); - - if (RT_SUCCESS(rc)) - { - pStreamDbg->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); - if (!pStreamDbg->pCfg) - rc = VERR_NO_MEMORY; - } - - return rc; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} - */ -static DECLCALLBACK(int) drvHostValKitAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten) -{ - PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio); - PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; - RT_NOREF(pDrv); - - uint64_t tsSinceStart; - size_t cch; - char szTimingInfo[128]; - - if (pStreamDbg->tsStarted == 0) - { - pStreamDbg->tsStarted = RTTimeNanoTS(); - tsSinceStart = 0; - } - else - { - tsSinceStart = RTTimeNanoTS() - pStreamDbg->tsStarted; - } - - // Microseconds are used everythere below - uint32_t sBuf = uBufSize >> pStreamDbg->pCfg->Props.cShift; - cch = RTStrPrintf(szTimingInfo, sizeof(szTimingInfo), "%d %d %d %d\n", - (uint32_t)(tsSinceStart / 1000), // Host time elapsed since Guest submitted the first buffer for playback - (uint32_t)(pStreamDbg->uSamplesSinceStarted * 1.0E6 / pStreamDbg->pCfg->Props.uHz), // how long all the samples submitted previously were played - (uint32_t)(sBuf * 1.0E6 / pStreamDbg->pCfg->Props.uHz), // how long a new uSamplesReady samples should\will be played - sBuf); - RTFileWrite(pStreamDbg->hFileTiming, szTimingInfo, cch, NULL); - pStreamDbg->uSamplesSinceStarted += sBuf; - - /* Remember when samples were consumed. */ - // pStreamDbg->Out.tsLastPlayed = PDMDrvHlpTMGetVirtualTime(pDrv->pDrvIns);; - - int rc2 = DrvAudioHlpFileWrite(pStreamDbg->pFile, pvBuf, uBufSize, 0 /* fFlags */); - if (RT_FAILURE(rc2)) - LogRel(("VaKitAudio: Writing output failed with %Rrc\n", rc2)); - - *puWritten = uBufSize; - - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} - */ -static DECLCALLBACK(int) drvHostValKitAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, - void *pvBuf, uint32_t uBufSize, uint32_t *puRead) -{ - RT_NOREF(pInterface, pStream, pvBuf, uBufSize); - - /* Never capture anything. */ - if (puRead) - *puRead = 0; - - return VINF_SUCCESS; -} - - -static int vakitDestroyStreamIn(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg) -{ - RT_NOREF(pDrv, pStreamDbg); - return VINF_SUCCESS; -} - - -static int vakitDestroyStreamOut(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg) -{ - RT_NOREF(pDrv); - - if (pStreamDbg->Out.pu8PlayBuffer) - { - RTMemFree(pStreamDbg->Out.pu8PlayBuffer); - pStreamDbg->Out.pu8PlayBuffer = NULL; - } - - if (pStreamDbg->pFile) - { - size_t cbDataSize = DrvAudioHlpFileGetDataSize(pStreamDbg->pFile); - if (cbDataSize) - LogRel(("VaKitAudio: Created output file '%s' (%zu bytes)\n", pStreamDbg->pFile->szName, cbDataSize)); - - DrvAudioHlpFileDestroy(pStreamDbg->pFile); - pStreamDbg->pFile = NULL; - } - - return VINF_SUCCESS; -} - - -static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - - PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio); - PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; - - if (!pStreamDbg->pCfg) /* Not (yet) configured? Skip. */ - return VINF_SUCCESS; - - int rc; - if (pStreamDbg->pCfg->enmDir == PDMAUDIODIR_IN) - rc = vakitDestroyStreamIn (pDrv, pStreamDbg); - else - rc = vakitDestroyStreamOut(pDrv, pStreamDbg); - - if (RT_SUCCESS(rc)) - { - DrvAudioHlpStreamCfgFree(pStreamDbg->pCfg); - pStreamDbg->pCfg = NULL; - } - - return rc; -} - -static DECLCALLBACK(int) drvHostValKitAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, - PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) -{ - RT_NOREF(enmStreamCmd); - AssertPtrReturn(pInterface, VERR_INVALID_POINTER); - AssertPtrReturn(pStream, VERR_INVALID_POINTER); - - return VINF_SUCCESS; -} - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} - */ -static DECLCALLBACK(uint32_t) drvHostValKitAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return UINT32_MAX; -} - - -/** - * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} - */ -static DECLCALLBACK(uint32_t) drvHostValKitAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return UINT32_MAX; -} - -static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostValKitAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, - PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - - return PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED; -} - -static DECLCALLBACK(int) drvHostValKitAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) -{ - RT_NOREF(pInterface, pStream); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMIBASE,pfnQueryInterface} - */ -static DECLCALLBACK(void *) drvHostValKitAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) -{ - PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); - PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO); - - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); - PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); - return NULL; -} - - -/** - * Constructs a VaKit audio driver instance. - * - * @copydoc FNPDMDRVCONSTRUCT - */ -static DECLCALLBACK(int) drvHostValKitAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) -{ - RT_NOREF(pCfg, fFlags); - PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); - PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO); - LogRel(("Audio: Initializing VAKIT driver\n")); - - /* - * Init the static parts. - */ - pThis->pDrvIns = pDrvIns; - /* IBase */ - pDrvIns->IBase.pfnQueryInterface = drvHostValKitAudioQueryInterface; - /* IHostAudio */ - pThis->IHostAudio.pfnInit = drvHostValKitAudioHA_Init; - pThis->IHostAudio.pfnShutdown = drvHostValKitAudioHA_Shutdown; - pThis->IHostAudio.pfnGetConfig = drvHostValKitAudioHA_GetConfig; - pThis->IHostAudio.pfnGetStatus = drvHostValKitAudioHA_GetStatus; - pThis->IHostAudio.pfnStreamCreate = drvHostValKitAudioHA_StreamCreate; - pThis->IHostAudio.pfnStreamDestroy = drvHostValKitAudioHA_StreamDestroy; - pThis->IHostAudio.pfnStreamControl = drvHostValKitAudioHA_StreamControl; - pThis->IHostAudio.pfnStreamGetReadable = drvHostValKitAudioHA_StreamGetReadable; - pThis->IHostAudio.pfnStreamGetWritable = drvHostValKitAudioHA_StreamGetWritable; - pThis->IHostAudio.pfnStreamGetStatus = drvHostValKitAudioHA_StreamGetStatus; - pThis->IHostAudio.pfnStreamIterate = drvHostValKitAudioHA_StreamIterate; - pThis->IHostAudio.pfnStreamPlay = drvHostValKitAudioHA_StreamPlay; - pThis->IHostAudio.pfnStreamCapture = drvHostValKitAudioHA_StreamCapture; - pThis->IHostAudio.pfnSetCallback = NULL; - pThis->IHostAudio.pfnGetDevices = NULL; - pThis->IHostAudio.pfnStreamGetPending = NULL; - pThis->IHostAudio.pfnStreamPlayBegin = NULL; - pThis->IHostAudio.pfnStreamPlayEnd = NULL; - pThis->IHostAudio.pfnStreamCaptureBegin = NULL; - pThis->IHostAudio.pfnStreamCaptureEnd = NULL; - - return VINF_SUCCESS; -} - -/** - * Char driver registration record. - */ -const PDMDRVREG g_DrvHostValidationKitAudio = -{ - /* u32Version */ - PDM_DRVREG_VERSION, - /* szName */ - "ValidationKitAudio", - /* szRCMod */ - "", - /* szR0Mod */ - "", - /* pszDescription */ - "ValidationKitAudio audio host driver", - /* fFlags */ - PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, - /* fClass. */ - PDM_DRVREG_CLASS_AUDIO, - /* cMaxInstances */ - ~0U, - /* cbInstance */ - sizeof(DRVHOSTVAKITAUDIO), - /* pfnConstruct */ - drvHostValKitAudioConstruct, - /* pfnDestruct */ - NULL, - /* pfnRelocate */ - NULL, - /* pfnIOCtl */ - NULL, - /* pfnPowerOn */ - NULL, - /* pfnReset */ - NULL, - /* pfnSuspend */ - NULL, - /* pfnResume */ - NULL, - /* pfnAttach */ - NULL, - /* pfnDetach */ - NULL, - /* pfnPowerOff */ - NULL, - /* pfnSoftReset */ - NULL, - /* u32EndVersion */ - PDM_DRVREG_VERSION -}; - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDACodec.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDACodec.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDACodec.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDACodec.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,3272 +0,0 @@ -/* $Id: HDACodec.cpp $ */ -/** @file - * HDACodec - VBox HD Audio Codec. - * - * Implemented based on the Intel HD Audio specification and the - * Sigmatel/IDT STAC9220 datasheet. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DEV_HDA_CODEC -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "VBoxDD.h" -#include "DrvAudio.h" -#include "HDACodec.h" -#include "DevHDACommon.h" -#include "AudioMixer.h" - - -/********************************************************************************************************************************* -* Defined Constants And Macros * -*********************************************************************************************************************************/ -/* PRM 5.3.1 */ -/** Codec address mask. */ -#define CODEC_CAD_MASK 0xF0000000 -/** Codec address shift. */ -#define CODEC_CAD_SHIFT 28 -#define CODEC_DIRECT_MASK RT_BIT(27) -/** Node ID mask. */ -#define CODEC_NID_MASK 0x07F00000 -/** Node ID shift. */ -#define CODEC_NID_SHIFT 20 -#define CODEC_VERBDATA_MASK 0x000FFFFF -#define CODEC_VERB_4BIT_CMD 0x000FFFF0 -#define CODEC_VERB_4BIT_DATA 0x0000000F -#define CODEC_VERB_8BIT_CMD 0x000FFF00 -#define CODEC_VERB_8BIT_DATA 0x000000FF -#define CODEC_VERB_16BIT_CMD 0x000F0000 -#define CODEC_VERB_16BIT_DATA 0x0000FFFF - -#define CODEC_CAD(cmd) (((cmd) & CODEC_CAD_MASK) >> CODEC_CAD_SHIFT) -#define CODEC_DIRECT(cmd) ((cmd) & CODEC_DIRECT_MASK) -#define CODEC_NID(cmd) ((((cmd) & CODEC_NID_MASK)) >> CODEC_NID_SHIFT) -#define CODEC_VERBDATA(cmd) ((cmd) & CODEC_VERBDATA_MASK) -#define CODEC_VERB_CMD(cmd, mask, x) (((cmd) & (mask)) >> (x)) -#define CODEC_VERB_CMD4(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_4BIT_CMD, 4)) -#define CODEC_VERB_CMD8(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_8BIT_CMD, 8)) -#define CODEC_VERB_CMD16(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_16BIT_CMD, 16)) -#define CODEC_VERB_PAYLOAD4(cmd) ((cmd) & CODEC_VERB_4BIT_DATA) -#define CODEC_VERB_PAYLOAD8(cmd) ((cmd) & CODEC_VERB_8BIT_DATA) -#define CODEC_VERB_PAYLOAD16(cmd) ((cmd) & CODEC_VERB_16BIT_DATA) - -#define CODEC_VERB_GET_AMP_DIRECTION RT_BIT(15) -#define CODEC_VERB_GET_AMP_SIDE RT_BIT(13) -#define CODEC_VERB_GET_AMP_INDEX 0x7 - -/* HDA spec 7.3.3.7 NoteA */ -#define CODEC_GET_AMP_DIRECTION(cmd) (((cmd) & CODEC_VERB_GET_AMP_DIRECTION) >> 15) -#define CODEC_GET_AMP_SIDE(cmd) (((cmd) & CODEC_VERB_GET_AMP_SIDE) >> 13) -#define CODEC_GET_AMP_INDEX(cmd) (CODEC_GET_AMP_DIRECTION(cmd) ? 0 : ((cmd) & CODEC_VERB_GET_AMP_INDEX)) - -/* HDA spec 7.3.3.7 NoteC */ -#define CODEC_VERB_SET_AMP_OUT_DIRECTION RT_BIT(15) -#define CODEC_VERB_SET_AMP_IN_DIRECTION RT_BIT(14) -#define CODEC_VERB_SET_AMP_LEFT_SIDE RT_BIT(13) -#define CODEC_VERB_SET_AMP_RIGHT_SIDE RT_BIT(12) -#define CODEC_VERB_SET_AMP_INDEX (0x7 << 8) -#define CODEC_VERB_SET_AMP_MUTE RT_BIT(7) -/** Note: 7-bit value [6:0]. */ -#define CODEC_VERB_SET_AMP_GAIN 0x7F - -#define CODEC_SET_AMP_IS_OUT_DIRECTION(cmd) (((cmd) & CODEC_VERB_SET_AMP_OUT_DIRECTION) != 0) -#define CODEC_SET_AMP_IS_IN_DIRECTION(cmd) (((cmd) & CODEC_VERB_SET_AMP_IN_DIRECTION) != 0) -#define CODEC_SET_AMP_IS_LEFT_SIDE(cmd) (((cmd) & CODEC_VERB_SET_AMP_LEFT_SIDE) != 0) -#define CODEC_SET_AMP_IS_RIGHT_SIDE(cmd) (((cmd) & CODEC_VERB_SET_AMP_RIGHT_SIDE) != 0) -#define CODEC_SET_AMP_INDEX(cmd) (((cmd) & CODEC_VERB_SET_AMP_INDEX) >> 7) -#define CODEC_SET_AMP_MUTE(cmd) ((cmd) & CODEC_VERB_SET_AMP_MUTE) -#define CODEC_SET_AMP_GAIN(cmd) ((cmd) & CODEC_VERB_SET_AMP_GAIN) - -/* HDA spec 7.3.3.1 defines layout of configuration registers/verbs (0xF00) */ -/* VendorID (7.3.4.1) */ -#define CODEC_MAKE_F00_00(vendorID, deviceID) (((vendorID) << 16) | (deviceID)) -#define CODEC_F00_00_VENDORID(f00_00) (((f00_00) >> 16) & 0xFFFF) -#define CODEC_F00_00_DEVICEID(f00_00) ((f00_00) & 0xFFFF) - -/** RevisionID (7.3.4.2). */ -#define CODEC_MAKE_F00_02(majRev, minRev, venFix, venProg, stepFix, stepProg) \ - ( (((majRev) & 0xF) << 20) \ - | (((minRev) & 0xF) << 16) \ - | (((venFix) & 0xF) << 12) \ - | (((venProg) & 0xF) << 8) \ - | (((stepFix) & 0xF) << 4) \ - | ((stepProg) & 0xF)) - -/** Subordinate node count (7.3.4.3). */ -#define CODEC_MAKE_F00_04(startNodeNumber, totalNodeNumber) ((((startNodeNumber) & 0xFF) << 16)|((totalNodeNumber) & 0xFF)) -#define CODEC_F00_04_TO_START_NODE_NUMBER(f00_04) (((f00_04) >> 16) & 0xFF) -#define CODEC_F00_04_TO_NODE_COUNT(f00_04) ((f00_04) & 0xFF) -/* - * Function Group Type (7.3.4.4) - * 0 & [0x3-0x7f] are reserved types - * [0x80 - 0xff] are vendor defined function groups - */ -#define CODEC_MAKE_F00_05(UnSol, NodeType) (((UnSol) << 8)|(NodeType)) -#define CODEC_F00_05_UNSOL RT_BIT(8) -#define CODEC_F00_05_AFG (0x1) -#define CODEC_F00_05_MFG (0x2) -#define CODEC_F00_05_IS_UNSOL(f00_05) RT_BOOL((f00_05) & RT_BIT(8)) -#define CODEC_F00_05_GROUP(f00_05) ((f00_05) & 0xff) -/* Audio Function Group capabilities (7.3.4.5). */ -#define CODEC_MAKE_F00_08(BeepGen, InputDelay, OutputDelay) ((((BeepGen) & 0x1) << 16)| (((InputDelay) & 0xF) << 8) | ((OutputDelay) & 0xF)) -#define CODEC_F00_08_BEEP_GEN(f00_08) ((f00_08) & RT_BIT(16) - -/* Converter Stream, Channel (7.3.3.11). */ -#define CODEC_F00_06_GET_STREAM_ID(cmd) (((cmd) >> 4) & 0x0F) -#define CODEC_F00_06_GET_CHANNEL_ID(cmd) (((cmd) & 0x0F)) - -/* Widget Capabilities (7.3.4.6). */ -#define CODEC_MAKE_F00_09(type, delay, chan_ext) \ - ( (((type) & 0xF) << 20) \ - | (((delay) & 0xF) << 16) \ - | (((chan_ext) & 0xF) << 13)) -/* note: types 0x8-0xe are reserved */ -#define CODEC_F00_09_TYPE_AUDIO_OUTPUT (0x0) -#define CODEC_F00_09_TYPE_AUDIO_INPUT (0x1) -#define CODEC_F00_09_TYPE_AUDIO_MIXER (0x2) -#define CODEC_F00_09_TYPE_AUDIO_SELECTOR (0x3) -#define CODEC_F00_09_TYPE_PIN_COMPLEX (0x4) -#define CODEC_F00_09_TYPE_POWER_WIDGET (0x5) -#define CODEC_F00_09_TYPE_VOLUME_KNOB (0x6) -#define CODEC_F00_09_TYPE_BEEP_GEN (0x7) -#define CODEC_F00_09_TYPE_VENDOR_DEFINED (0xF) - -#define CODEC_F00_09_CAP_CP RT_BIT(12) -#define CODEC_F00_09_CAP_L_R_SWAP RT_BIT(11) -#define CODEC_F00_09_CAP_POWER_CTRL RT_BIT(10) -#define CODEC_F00_09_CAP_DIGITAL RT_BIT(9) -#define CODEC_F00_09_CAP_CONNECTION_LIST RT_BIT(8) -#define CODEC_F00_09_CAP_UNSOL RT_BIT(7) -#define CODEC_F00_09_CAP_PROC_WIDGET RT_BIT(6) -#define CODEC_F00_09_CAP_STRIPE RT_BIT(5) -#define CODEC_F00_09_CAP_FMT_OVERRIDE RT_BIT(4) -#define CODEC_F00_09_CAP_AMP_FMT_OVERRIDE RT_BIT(3) -#define CODEC_F00_09_CAP_OUT_AMP_PRESENT RT_BIT(2) -#define CODEC_F00_09_CAP_IN_AMP_PRESENT RT_BIT(1) -#define CODEC_F00_09_CAP_STEREO RT_BIT(0) - -#define CODEC_F00_09_TYPE(f00_09) (((f00_09) >> 20) & 0xF) - -#define CODEC_F00_09_IS_CAP_CP(f00_09) RT_BOOL((f00_09) & RT_BIT(12)) -#define CODEC_F00_09_IS_CAP_L_R_SWAP(f00_09) RT_BOOL((f00_09) & RT_BIT(11)) -#define CODEC_F00_09_IS_CAP_POWER_CTRL(f00_09) RT_BOOL((f00_09) & RT_BIT(10)) -#define CODEC_F00_09_IS_CAP_DIGITAL(f00_09) RT_BOOL((f00_09) & RT_BIT(9)) -#define CODEC_F00_09_IS_CAP_CONNECTION_LIST(f00_09) RT_BOOL((f00_09) & RT_BIT(8)) -#define CODEC_F00_09_IS_CAP_UNSOL(f00_09) RT_BOOL((f00_09) & RT_BIT(7)) -#define CODEC_F00_09_IS_CAP_PROC_WIDGET(f00_09) RT_BOOL((f00_09) & RT_BIT(6)) -#define CODEC_F00_09_IS_CAP_STRIPE(f00_09) RT_BOOL((f00_09) & RT_BIT(5)) -#define CODEC_F00_09_IS_CAP_FMT_OVERRIDE(f00_09) RT_BOOL((f00_09) & RT_BIT(4)) -#define CODEC_F00_09_IS_CAP_AMP_OVERRIDE(f00_09) RT_BOOL((f00_09) & RT_BIT(3)) -#define CODEC_F00_09_IS_CAP_OUT_AMP_PRESENT(f00_09) RT_BOOL((f00_09) & RT_BIT(2)) -#define CODEC_F00_09_IS_CAP_IN_AMP_PRESENT(f00_09) RT_BOOL((f00_09) & RT_BIT(1)) -#define CODEC_F00_09_IS_CAP_LSB(f00_09) RT_BOOL((f00_09) & RT_BIT(0)) - -/* Supported PCM size, rates (7.3.4.7) */ -#define CODEC_F00_0A_32_BIT RT_BIT(19) -#define CODEC_F00_0A_24_BIT RT_BIT(18) -#define CODEC_F00_0A_16_BIT RT_BIT(17) -#define CODEC_F00_0A_8_BIT RT_BIT(16) - -#define CODEC_F00_0A_48KHZ_MULT_8X RT_BIT(11) -#define CODEC_F00_0A_48KHZ_MULT_4X RT_BIT(10) -#define CODEC_F00_0A_44_1KHZ_MULT_4X RT_BIT(9) -#define CODEC_F00_0A_48KHZ_MULT_2X RT_BIT(8) -#define CODEC_F00_0A_44_1KHZ_MULT_2X RT_BIT(7) -#define CODEC_F00_0A_48KHZ RT_BIT(6) -#define CODEC_F00_0A_44_1KHZ RT_BIT(5) -/* 2/3 * 48kHz */ -#define CODEC_F00_0A_48KHZ_2_3X RT_BIT(4) -/* 1/2 * 44.1kHz */ -#define CODEC_F00_0A_44_1KHZ_1_2X RT_BIT(3) -/* 1/3 * 48kHz */ -#define CODEC_F00_0A_48KHZ_1_3X RT_BIT(2) -/* 1/4 * 44.1kHz */ -#define CODEC_F00_0A_44_1KHZ_1_4X RT_BIT(1) -/* 1/6 * 48kHz */ -#define CODEC_F00_0A_48KHZ_1_6X RT_BIT(0) - -/* Supported streams formats (7.3.4.8) */ -#define CODEC_F00_0B_AC3 RT_BIT(2) -#define CODEC_F00_0B_FLOAT32 RT_BIT(1) -#define CODEC_F00_0B_PCM RT_BIT(0) - -/* Pin Capabilities (7.3.4.9)*/ -#define CODEC_MAKE_F00_0C(vref_ctrl) (((vref_ctrl) & 0xFF) << 8) -#define CODEC_F00_0C_CAP_HBR RT_BIT(27) -#define CODEC_F00_0C_CAP_DP RT_BIT(24) -#define CODEC_F00_0C_CAP_EAPD RT_BIT(16) -#define CODEC_F00_0C_CAP_HDMI RT_BIT(7) -#define CODEC_F00_0C_CAP_BALANCED_IO RT_BIT(6) -#define CODEC_F00_0C_CAP_INPUT RT_BIT(5) -#define CODEC_F00_0C_CAP_OUTPUT RT_BIT(4) -#define CODEC_F00_0C_CAP_HEADPHONE_AMP RT_BIT(3) -#define CODEC_F00_0C_CAP_PRESENCE_DETECT RT_BIT(2) -#define CODEC_F00_0C_CAP_TRIGGER_REQUIRED RT_BIT(1) -#define CODEC_F00_0C_CAP_IMPENDANCE_SENSE RT_BIT(0) - -#define CODEC_F00_0C_IS_CAP_HBR(f00_0c) ((f00_0c) & RT_BIT(27)) -#define CODEC_F00_0C_IS_CAP_DP(f00_0c) ((f00_0c) & RT_BIT(24)) -#define CODEC_F00_0C_IS_CAP_EAPD(f00_0c) ((f00_0c) & RT_BIT(16)) -#define CODEC_F00_0C_IS_CAP_HDMI(f00_0c) ((f00_0c) & RT_BIT(7)) -#define CODEC_F00_0C_IS_CAP_BALANCED_IO(f00_0c) ((f00_0c) & RT_BIT(6)) -#define CODEC_F00_0C_IS_CAP_INPUT(f00_0c) ((f00_0c) & RT_BIT(5)) -#define CODEC_F00_0C_IS_CAP_OUTPUT(f00_0c) ((f00_0c) & RT_BIT(4)) -#define CODEC_F00_0C_IS_CAP_HP(f00_0c) ((f00_0c) & RT_BIT(3)) -#define CODEC_F00_0C_IS_CAP_PRESENCE_DETECT(f00_0c) ((f00_0c) & RT_BIT(2)) -#define CODEC_F00_0C_IS_CAP_TRIGGER_REQUIRED(f00_0c) ((f00_0c) & RT_BIT(1)) -#define CODEC_F00_0C_IS_CAP_IMPENDANCE_SENSE(f00_0c) ((f00_0c) & RT_BIT(0)) - -/* Input Amplifier capabilities (7.3.4.10). */ -#define CODEC_MAKE_F00_0D(mute_cap, step_size, num_steps, offset) \ - ( (((mute_cap) & UINT32_C(0x1)) << 31) \ - | (((step_size) & UINT32_C(0xFF)) << 16) \ - | (((num_steps) & UINT32_C(0xFF)) << 8) \ - | ((offset) & UINT32_C(0xFF))) - -#define CODEC_F00_0D_CAP_MUTE RT_BIT(7) - -#define CODEC_F00_0D_IS_CAP_MUTE(f00_0d) ( ( f00_0d) & RT_BIT(31)) -#define CODEC_F00_0D_STEP_SIZE(f00_0d) ((( f00_0d) & (0x7F << 16)) >> 16) -#define CODEC_F00_0D_NUM_STEPS(f00_0d) ((((f00_0d) & (0x7F << 8)) >> 8) + 1) -#define CODEC_F00_0D_OFFSET(f00_0d) ( (f00_0d) & 0x7F) - -/** Indicates that the amplifier can be muted. */ -#define CODEC_AMP_CAP_MUTE 0x1 -/** The amplifier's maximum number of steps. We want - * a ~90dB dynamic range, so 64 steps with 1.25dB each - * should do the trick. - * - * As we want to map our range to [0..128] values we can avoid - * multiplication and simply doing a shift later. - * - * Produces -96dB to +0dB. - * "0" indicates a step of 0.25dB, "127" indicates a step of 32dB. - */ -#define CODEC_AMP_NUM_STEPS 0x7F -/** The initial gain offset (and when doing a node reset). */ -#define CODEC_AMP_OFF_INITIAL 0x7F -/** The amplifier's gain step size. */ -#define CODEC_AMP_STEP_SIZE 0x2 - -/* Output Amplifier capabilities (7.3.4.10) */ -#define CODEC_MAKE_F00_12 CODEC_MAKE_F00_0D - -#define CODEC_F00_12_IS_CAP_MUTE(f00_12) CODEC_F00_0D_IS_CAP_MUTE(f00_12) -#define CODEC_F00_12_STEP_SIZE(f00_12) CODEC_F00_0D_STEP_SIZE(f00_12) -#define CODEC_F00_12_NUM_STEPS(f00_12) CODEC_F00_0D_NUM_STEPS(f00_12) -#define CODEC_F00_12_OFFSET(f00_12) CODEC_F00_0D_OFFSET(f00_12) - -/* Connection list lenght (7.3.4.11). */ -#define CODEC_MAKE_F00_0E(long_form, length) \ - ( (((long_form) & 0x1) << 7) \ - | ((length) & 0x7F)) -/* Indicates short-form NIDs. */ -#define CODEC_F00_0E_LIST_NID_SHORT 0 -/* Indicates long-form NIDs. */ -#define CODEC_F00_0E_LIST_NID_LONG 1 -#define CODEC_F00_0E_IS_LONG(f00_0e) RT_BOOL((f00_0e) & RT_BIT(7)) -#define CODEC_F00_0E_COUNT(f00_0e) ((f00_0e) & 0x7F) -/* Supported Power States (7.3.4.12) */ -#define CODEC_F00_0F_EPSS RT_BIT(31) -#define CODEC_F00_0F_CLKSTOP RT_BIT(30) -#define CODEC_F00_0F_S3D3 RT_BIT(29) -#define CODEC_F00_0F_D3COLD RT_BIT(4) -#define CODEC_F00_0F_D3 RT_BIT(3) -#define CODEC_F00_0F_D2 RT_BIT(2) -#define CODEC_F00_0F_D1 RT_BIT(1) -#define CODEC_F00_0F_D0 RT_BIT(0) - -/* Processing capabilities 7.3.4.13 */ -#define CODEC_MAKE_F00_10(num, benign) ((((num) & 0xFF) << 8) | ((benign) & 0x1)) -#define CODEC_F00_10_NUM(f00_10) (((f00_10) & (0xFF << 8)) >> 8) -#define CODEC_F00_10_BENING(f00_10) ((f00_10) & 0x1) - -/* GPIO count (7.3.4.14). */ -#define CODEC_MAKE_F00_11(wake, unsol, numgpi, numgpo, numgpio) \ - ( (((wake) & UINT32_C(0x1)) << 31) \ - | (((unsol) & UINT32_C(0x1)) << 30) \ - | (((numgpi) & UINT32_C(0xFF)) << 16) \ - | (((numgpo) & UINT32_C(0xFF)) << 8) \ - | ((numgpio) & UINT32_C(0xFF))) - -/* Processing States (7.3.3.4). */ -#define CODEC_F03_OFF (0) -#define CODEC_F03_ON RT_BIT(0) -#define CODEC_F03_BENING RT_BIT(1) -/* Power States (7.3.3.10). */ -#define CODEC_MAKE_F05(reset, stopok, error, act, set) \ - ( (((reset) & 0x1) << 10) \ - | (((stopok) & 0x1) << 9) \ - | (((error) & 0x1) << 8) \ - | (((act) & 0xF) << 4) \ - | ((set) & 0xF)) -#define CODEC_F05_D3COLD (4) -#define CODEC_F05_D3 (3) -#define CODEC_F05_D2 (2) -#define CODEC_F05_D1 (1) -#define CODEC_F05_D0 (0) - -#define CODEC_F05_IS_RESET(value) (((value) & RT_BIT(10)) != 0) -#define CODEC_F05_IS_STOPOK(value) (((value) & RT_BIT(9)) != 0) -#define CODEC_F05_IS_ERROR(value) (((value) & RT_BIT(8)) != 0) -#define CODEC_F05_ACT(value) (((value) & 0xF0) >> 4) -#define CODEC_F05_SET(value) (((value) & 0xF)) - -#define CODEC_F05_GE(p0, p1) ((p0) <= (p1)) -#define CODEC_F05_LE(p0, p1) ((p0) >= (p1)) - -/* Converter Stream, Channel (7.3.3.11). */ -#define CODEC_MAKE_F06(stream, channel) \ - ( (((stream) & 0xF) << 4) \ - | ((channel) & 0xF)) -#define CODEC_F06_STREAM(value) ((value) & 0xF0) -#define CODEC_F06_CHANNEL(value) ((value) & 0xF) - -/* Pin Widged Control (7.3.3.13). */ -#define CODEC_F07_VREF_HIZ (0) -#define CODEC_F07_VREF_50 (0x1) -#define CODEC_F07_VREF_GROUND (0x2) -#define CODEC_F07_VREF_80 (0x4) -#define CODEC_F07_VREF_100 (0x5) -#define CODEC_F07_IN_ENABLE RT_BIT(5) -#define CODEC_F07_OUT_ENABLE RT_BIT(6) -#define CODEC_F07_OUT_H_ENABLE RT_BIT(7) - -/* Volume Knob Control (7.3.3.29). */ -#define CODEC_F0F_IS_DIRECT RT_BIT(7) -#define CODEC_F0F_VOLUME (0x7F) - -/* Unsolicited enabled (7.3.3.14). */ -#define CODEC_MAKE_F08(enable, tag) ((((enable) & 1) << 7) | ((tag) & 0x3F)) - -/* Converter formats (7.3.3.8) and (3.7.1). */ -/* This is the same format as SDnFMT. */ -#define CODEC_MAKE_A HDA_SDFMT_MAKE - -#define CODEC_A_TYPE HDA_SDFMT_TYPE -#define CODEC_A_TYPE_PCM HDA_SDFMT_TYPE_PCM -#define CODEC_A_TYPE_NON_PCM HDA_SDFMT_TYPE_NON_PCM - -#define CODEC_A_BASE HDA_SDFMT_BASE -#define CODEC_A_BASE_48KHZ HDA_SDFMT_BASE_48KHZ -#define CODEC_A_BASE_44KHZ HDA_SDFMT_BASE_44KHZ - -/* Pin Sense (7.3.3.15). */ -#define CODEC_MAKE_F09_ANALOG(fPresent, impedance) \ -( (((fPresent) & 0x1) << 31) \ - | (((impedance) & UINT32_C(0x7FFFFFFF)))) -#define CODEC_F09_ANALOG_NA UINT32_C(0x7FFFFFFF) -#define CODEC_MAKE_F09_DIGITAL(fPresent, fELDValid) \ -( (((fPresent) & UINT32_C(0x1)) << 31) \ - | (((fELDValid) & UINT32_C(0x1)) << 30)) - -#define CODEC_MAKE_F0C(lrswap, eapd, btl) ((((lrswap) & 1) << 2) | (((eapd) & 1) << 1) | ((btl) & 1)) -#define CODEC_FOC_IS_LRSWAP(f0c) RT_BOOL((f0c) & RT_BIT(2)) -#define CODEC_FOC_IS_EAPD(f0c) RT_BOOL((f0c) & RT_BIT(1)) -#define CODEC_FOC_IS_BTL(f0c) RT_BOOL((f0c) & RT_BIT(0)) -/* HDA spec 7.3.3.31 defines layout of configuration registers/verbs (0xF1C) */ -/* Configuration's port connection */ -#define CODEC_F1C_PORT_MASK (0x3) -#define CODEC_F1C_PORT_SHIFT (30) - -#define CODEC_F1C_PORT_COMPLEX (0x0) -#define CODEC_F1C_PORT_NO_PHYS (0x1) -#define CODEC_F1C_PORT_FIXED (0x2) -#define CODEC_F1C_BOTH (0x3) - -/* Configuration default: connection */ -#define CODEC_F1C_PORT_MASK (0x3) -#define CODEC_F1C_PORT_SHIFT (30) - -/* Connected to a jack (1/8", ATAPI, ...). */ -#define CODEC_F1C_PORT_COMPLEX (0x0) -/* No physical connection. */ -#define CODEC_F1C_PORT_NO_PHYS (0x1) -/* Fixed function device (integrated speaker, integrated mic, ...). */ -#define CODEC_F1C_PORT_FIXED (0x2) -/* Both, a jack and an internal device are attached. */ -#define CODEC_F1C_BOTH (0x3) - -/* Configuration default: Location */ -#define CODEC_F1C_LOCATION_MASK (0x3F) -#define CODEC_F1C_LOCATION_SHIFT (24) - -/* [4:5] bits of location region means chassis attachment */ -#define CODEC_F1C_LOCATION_PRIMARY_CHASSIS (0) -#define CODEC_F1C_LOCATION_INTERNAL RT_BIT(4) -#define CODEC_F1C_LOCATION_SECONDRARY_CHASSIS RT_BIT(5) -#define CODEC_F1C_LOCATION_OTHER RT_BIT(5) - -/* [0:3] bits of location region means geometry location attachment */ -#define CODEC_F1C_LOCATION_NA (0) -#define CODEC_F1C_LOCATION_REAR (0x1) -#define CODEC_F1C_LOCATION_FRONT (0x2) -#define CODEC_F1C_LOCATION_LEFT (0x3) -#define CODEC_F1C_LOCATION_RIGTH (0x4) -#define CODEC_F1C_LOCATION_TOP (0x5) -#define CODEC_F1C_LOCATION_BOTTOM (0x6) -#define CODEC_F1C_LOCATION_SPECIAL_0 (0x7) -#define CODEC_F1C_LOCATION_SPECIAL_1 (0x8) -#define CODEC_F1C_LOCATION_SPECIAL_2 (0x9) - -/* Configuration default: Device type */ -#define CODEC_F1C_DEVICE_MASK (0xF) -#define CODEC_F1C_DEVICE_SHIFT (20) -#define CODEC_F1C_DEVICE_LINE_OUT (0) -#define CODEC_F1C_DEVICE_SPEAKER (0x1) -#define CODEC_F1C_DEVICE_HP (0x2) -#define CODEC_F1C_DEVICE_CD (0x3) -#define CODEC_F1C_DEVICE_SPDIF_OUT (0x4) -#define CODEC_F1C_DEVICE_DIGITAL_OTHER_OUT (0x5) -#define CODEC_F1C_DEVICE_MODEM_LINE_SIDE (0x6) -#define CODEC_F1C_DEVICE_MODEM_HANDSET_SIDE (0x7) -#define CODEC_F1C_DEVICE_LINE_IN (0x8) -#define CODEC_F1C_DEVICE_AUX (0x9) -#define CODEC_F1C_DEVICE_MIC (0xA) -#define CODEC_F1C_DEVICE_PHONE (0xB) -#define CODEC_F1C_DEVICE_SPDIF_IN (0xC) -#define CODEC_F1C_DEVICE_RESERVED (0xE) -#define CODEC_F1C_DEVICE_OTHER (0xF) - -/* Configuration default: Connection type */ -#define CODEC_F1C_CONNECTION_TYPE_MASK (0xF) -#define CODEC_F1C_CONNECTION_TYPE_SHIFT (16) - -#define CODEC_F1C_CONNECTION_TYPE_UNKNOWN (0) -#define CODEC_F1C_CONNECTION_TYPE_1_8INCHES (0x1) -#define CODEC_F1C_CONNECTION_TYPE_1_4INCHES (0x2) -#define CODEC_F1C_CONNECTION_TYPE_ATAPI (0x3) -#define CODEC_F1C_CONNECTION_TYPE_RCA (0x4) -#define CODEC_F1C_CONNECTION_TYPE_OPTICAL (0x5) -#define CODEC_F1C_CONNECTION_TYPE_OTHER_DIGITAL (0x6) -#define CODEC_F1C_CONNECTION_TYPE_ANALOG (0x7) -#define CODEC_F1C_CONNECTION_TYPE_DIN (0x8) -#define CODEC_F1C_CONNECTION_TYPE_XLR (0x9) -#define CODEC_F1C_CONNECTION_TYPE_RJ_11 (0xA) -#define CODEC_F1C_CONNECTION_TYPE_COMBO (0xB) -#define CODEC_F1C_CONNECTION_TYPE_OTHER (0xF) - -/* Configuration's color */ -#define CODEC_F1C_COLOR_MASK (0xF) -#define CODEC_F1C_COLOR_SHIFT (12) -#define CODEC_F1C_COLOR_UNKNOWN (0) -#define CODEC_F1C_COLOR_BLACK (0x1) -#define CODEC_F1C_COLOR_GREY (0x2) -#define CODEC_F1C_COLOR_BLUE (0x3) -#define CODEC_F1C_COLOR_GREEN (0x4) -#define CODEC_F1C_COLOR_RED (0x5) -#define CODEC_F1C_COLOR_ORANGE (0x6) -#define CODEC_F1C_COLOR_YELLOW (0x7) -#define CODEC_F1C_COLOR_PURPLE (0x8) -#define CODEC_F1C_COLOR_PINK (0x9) -#define CODEC_F1C_COLOR_RESERVED_0 (0xA) -#define CODEC_F1C_COLOR_RESERVED_1 (0xB) -#define CODEC_F1C_COLOR_RESERVED_2 (0xC) -#define CODEC_F1C_COLOR_RESERVED_3 (0xD) -#define CODEC_F1C_COLOR_WHITE (0xE) -#define CODEC_F1C_COLOR_OTHER (0xF) - -/* Configuration's misc */ -#define CODEC_F1C_MISC_MASK (0xF) -#define CODEC_F1C_MISC_SHIFT (8) -#define CODEC_F1C_MISC_NONE 0 -#define CODEC_F1C_MISC_JACK_NO_PRESENCE_DETECT RT_BIT(0) -#define CODEC_F1C_MISC_RESERVED_0 RT_BIT(1) -#define CODEC_F1C_MISC_RESERVED_1 RT_BIT(2) -#define CODEC_F1C_MISC_RESERVED_2 RT_BIT(3) - -/* Configuration default: Association */ -#define CODEC_F1C_ASSOCIATION_MASK (0xF) -#define CODEC_F1C_ASSOCIATION_SHIFT (4) - -/** Reserved; don't use. */ -#define CODEC_F1C_ASSOCIATION_INVALID 0x0 -#define CODEC_F1C_ASSOCIATION_GROUP_0 0x1 -#define CODEC_F1C_ASSOCIATION_GROUP_1 0x2 -#define CODEC_F1C_ASSOCIATION_GROUP_2 0x3 -#define CODEC_F1C_ASSOCIATION_GROUP_3 0x4 -#define CODEC_F1C_ASSOCIATION_GROUP_4 0x5 -#define CODEC_F1C_ASSOCIATION_GROUP_5 0x6 -#define CODEC_F1C_ASSOCIATION_GROUP_6 0x7 -#define CODEC_F1C_ASSOCIATION_GROUP_7 0x8 -/* Note: Windows OSes will treat group 15 (0xF) as single PIN devices. - * The sequence number associated with that group then will be ignored. */ -#define CODEC_F1C_ASSOCIATION_GROUP_15 0xF - -/* Configuration default: Association Sequence. */ -#define CODEC_F1C_SEQ_MASK (0xF) -#define CODEC_F1C_SEQ_SHIFT (0) - -/* Implementation identification (7.3.3.30). */ -#define CODEC_MAKE_F20(bmid, bsku, aid) \ - ( (((bmid) & 0xFFFF) << 16) \ - | (((bsku) & 0xFF) << 8) \ - | (((aid) & 0xFF)) \ - ) - -/* Macro definition helping in filling the configuration registers. */ -#define CODEC_MAKE_F1C(port_connectivity, location, device, connection_type, color, misc, association, sequence) \ - ( (((port_connectivity) & 0xF) << CODEC_F1C_PORT_SHIFT) \ - | (((location) & 0xF) << CODEC_F1C_LOCATION_SHIFT) \ - | (((device) & 0xF) << CODEC_F1C_DEVICE_SHIFT) \ - | (((connection_type) & 0xF) << CODEC_F1C_CONNECTION_TYPE_SHIFT) \ - | (((color) & 0xF) << CODEC_F1C_COLOR_SHIFT) \ - | (((misc) & 0xF) << CODEC_F1C_MISC_SHIFT) \ - | (((association) & 0xF) << CODEC_F1C_ASSOCIATION_SHIFT) \ - | (((sequence) & 0xF))) - - -/********************************************************************************************************************************* -* Structures and Typedefs * -*********************************************************************************************************************************/ -/** The F00 parameter length (in dwords). */ -#define CODECNODE_F00_PARAM_LENGTH 20 -/** The F02 parameter length (in dwords). */ -#define CODECNODE_F02_PARAM_LENGTH 16 - -/** - * Common (or core) codec node structure. - */ -typedef struct CODECCOMMONNODE -{ - /** The node's ID. */ - uint8_t uID; - /** The node's name. */ - char const *pszName; - /** The SDn ID this node is assigned to. - * 0 means not assigned, 1 is SDn0. */ - uint8_t uSD; - /** The SDn's channel to use. - * Only valid if a valid SDn ID is set. */ - uint8_t uChannel; - /* PRM 5.3.6 */ - uint32_t au32F00_param[CODECNODE_F00_PARAM_LENGTH]; - uint32_t au32F02_param[CODECNODE_F02_PARAM_LENGTH]; -} CODECCOMMONNODE; -typedef CODECCOMMONNODE *PCODECCOMMONNODE; -AssertCompile(CODECNODE_F00_PARAM_LENGTH == 20); /* saved state */ -AssertCompile(CODECNODE_F02_PARAM_LENGTH == 16); /* saved state */ - -/** - * Compile time assertion on the expected node size. - */ -#define AssertNodeSize(a_Node, a_cParams) \ - AssertCompile((a_cParams) <= (60 + 6)); /* the max size - saved state */ \ - AssertCompile( sizeof(a_Node) - sizeof(CODECCOMMONNODE) \ - == (((a_cParams) * sizeof(uint32_t) + sizeof(void *) - 1) & ~(sizeof(void *) - 1)) ) - -typedef struct ROOTCODECNODE -{ - CODECCOMMONNODE node; -} ROOTCODECNODE, *PROOTCODECNODE; -AssertNodeSize(ROOTCODECNODE, 0); - -#define AMPLIFIER_SIZE 60 -typedef uint32_t AMPLIFIER[AMPLIFIER_SIZE]; -#define AMPLIFIER_IN 0 -#define AMPLIFIER_OUT 1 -#define AMPLIFIER_LEFT 1 -#define AMPLIFIER_RIGHT 0 -#define AMPLIFIER_REGISTER(amp, inout, side, index) ((amp)[30*(inout) + 15*(side) + (index)]) -typedef struct DACNODE -{ - CODECCOMMONNODE node; - uint32_t u32F0d_param; - uint32_t u32F04_param; - uint32_t u32F05_param; - uint32_t u32F06_param; - uint32_t u32F0c_param; - - uint32_t u32A_param; - AMPLIFIER B_params; - -} DACNODE, *PDACNODE; -AssertNodeSize(DACNODE, 6 + 60); - -typedef struct ADCNODE -{ - CODECCOMMONNODE node; - uint32_t u32F01_param; - uint32_t u32F03_param; - uint32_t u32F05_param; - uint32_t u32F06_param; - uint32_t u32F09_param; - - uint32_t u32A_param; - AMPLIFIER B_params; -} ADCNODE, *PADCNODE; -AssertNodeSize(DACNODE, 6 + 60); - -typedef struct SPDIFOUTNODE -{ - CODECCOMMONNODE node; - uint32_t u32F05_param; - uint32_t u32F06_param; - uint32_t u32F09_param; - uint32_t u32F0d_param; - - uint32_t u32A_param; - AMPLIFIER B_params; -} SPDIFOUTNODE, *PSPDIFOUTNODE; -AssertNodeSize(SPDIFOUTNODE, 5 + 60); - -typedef struct SPDIFINNODE -{ - CODECCOMMONNODE node; - uint32_t u32F05_param; - uint32_t u32F06_param; - uint32_t u32F09_param; - uint32_t u32F0d_param; - - uint32_t u32A_param; - AMPLIFIER B_params; -} SPDIFINNODE, *PSPDIFINNODE; -AssertNodeSize(SPDIFINNODE, 5 + 60); - -typedef struct AFGCODECNODE -{ - CODECCOMMONNODE node; - uint32_t u32F05_param; - uint32_t u32F08_param; - uint32_t u32F17_param; - uint32_t u32F20_param; -} AFGCODECNODE, *PAFGCODECNODE; -AssertNodeSize(AFGCODECNODE, 4); - -typedef struct PORTNODE -{ - CODECCOMMONNODE node; - uint32_t u32F01_param; - uint32_t u32F07_param; - uint32_t u32F08_param; - uint32_t u32F09_param; - uint32_t u32F1c_param; - AMPLIFIER B_params; -} PORTNODE, *PPORTNODE; -AssertNodeSize(PORTNODE, 5 + 60); - -typedef struct DIGOUTNODE -{ - CODECCOMMONNODE node; - uint32_t u32F01_param; - uint32_t u32F05_param; - uint32_t u32F07_param; - uint32_t u32F08_param; - uint32_t u32F09_param; - uint32_t u32F1c_param; -} DIGOUTNODE, *PDIGOUTNODE; -AssertNodeSize(DIGOUTNODE, 6); - -typedef struct DIGINNODE -{ - CODECCOMMONNODE node; - uint32_t u32F05_param; - uint32_t u32F07_param; - uint32_t u32F08_param; - uint32_t u32F09_param; - uint32_t u32F0c_param; - uint32_t u32F1c_param; - uint32_t u32F1e_param; -} DIGINNODE, *PDIGINNODE; -AssertNodeSize(DIGINNODE, 7); - -typedef struct ADCMUXNODE -{ - CODECCOMMONNODE node; - uint32_t u32F01_param; - - uint32_t u32A_param; - AMPLIFIER B_params; -} ADCMUXNODE, *PADCMUXNODE; -AssertNodeSize(ADCMUXNODE, 2 + 60); - -typedef struct PCBEEPNODE -{ - CODECCOMMONNODE node; - uint32_t u32F07_param; - uint32_t u32F0a_param; - - uint32_t u32A_param; - AMPLIFIER B_params; - uint32_t u32F1c_param; -} PCBEEPNODE, *PPCBEEPNODE; -AssertNodeSize(PCBEEPNODE, 3 + 60 + 1); - -typedef struct CDNODE -{ - CODECCOMMONNODE node; - uint32_t u32F07_param; - uint32_t u32F1c_param; -} CDNODE, *PCDNODE; -AssertNodeSize(CDNODE, 2); - -typedef struct VOLUMEKNOBNODE -{ - CODECCOMMONNODE node; - uint32_t u32F08_param; - uint32_t u32F0f_param; -} VOLUMEKNOBNODE, *PVOLUMEKNOBNODE; -AssertNodeSize(VOLUMEKNOBNODE, 2); - -typedef struct ADCVOLNODE -{ - CODECCOMMONNODE node; - uint32_t u32F0c_param; - uint32_t u32F01_param; - uint32_t u32A_params; - AMPLIFIER B_params; -} ADCVOLNODE, *PADCVOLNODE; -AssertNodeSize(ADCVOLNODE, 3 + 60); - -typedef struct RESNODE -{ - CODECCOMMONNODE node; - uint32_t u32F05_param; - uint32_t u32F06_param; - uint32_t u32F07_param; - uint32_t u32F1c_param; - - uint32_t u32A_param; -} RESNODE, *PRESNODE; -AssertNodeSize(RESNODE, 5); - -/** - * Used for the saved state. - */ -typedef struct CODECSAVEDSTATENODE -{ - CODECCOMMONNODE Core; - uint32_t au32Params[60 + 6]; -} CODECSAVEDSTATENODE; -AssertNodeSize(CODECSAVEDSTATENODE, 60 + 6); - -typedef union CODECNODE -{ - CODECCOMMONNODE node; - ROOTCODECNODE root; - AFGCODECNODE afg; - DACNODE dac; - ADCNODE adc; - SPDIFOUTNODE spdifout; - SPDIFINNODE spdifin; - PORTNODE port; - DIGOUTNODE digout; - DIGINNODE digin; - ADCMUXNODE adcmux; - PCBEEPNODE pcbeep; - CDNODE cdnode; - VOLUMEKNOBNODE volumeKnob; - ADCVOLNODE adcvol; - RESNODE reserved; - CODECSAVEDSTATENODE SavedState; -} CODECNODE, *PCODECNODE; -AssertNodeSize(CODECNODE, 60 + 6); - - -/********************************************************************************************************************************* -* Global Variables * -*********************************************************************************************************************************/ -/* STAC9220 - Nodes IDs / names. */ -#define STAC9220_NID_ROOT 0x0 /* Root node */ -#define STAC9220_NID_AFG 0x1 /* Audio Configuration Group */ -#define STAC9220_NID_DAC0 0x2 /* Out */ -#define STAC9220_NID_DAC1 0x3 /* Out */ -#define STAC9220_NID_DAC2 0x4 /* Out */ -#define STAC9220_NID_DAC3 0x5 /* Out */ -#define STAC9220_NID_ADC0 0x6 /* In */ -#define STAC9220_NID_ADC1 0x7 /* In */ -#define STAC9220_NID_SPDIF_OUT 0x8 /* Out */ -#define STAC9220_NID_SPDIF_IN 0x9 /* In */ -/** Also known as PIN_A. */ -#define STAC9220_NID_PIN_HEADPHONE0 0xA /* In, Out */ -#define STAC9220_NID_PIN_B 0xB /* In, Out */ -#define STAC9220_NID_PIN_C 0xC /* In, Out */ -/** Also known as PIN D. */ -#define STAC9220_NID_PIN_HEADPHONE1 0xD /* In, Out */ -#define STAC9220_NID_PIN_E 0xE /* In */ -#define STAC9220_NID_PIN_F 0xF /* In, Out */ -/** Also known as DIGOUT0. */ -#define STAC9220_NID_PIN_SPDIF_OUT 0x10 /* Out */ -/** Also known as DIGIN. */ -#define STAC9220_NID_PIN_SPDIF_IN 0x11 /* In */ -#define STAC9220_NID_ADC0_MUX 0x12 /* In */ -#define STAC9220_NID_ADC1_MUX 0x13 /* In */ -#define STAC9220_NID_PCBEEP 0x14 /* Out */ -#define STAC9220_NID_PIN_CD 0x15 /* In */ -#define STAC9220_NID_VOL_KNOB 0x16 -#define STAC9220_NID_AMP_ADC0 0x17 /* In */ -#define STAC9220_NID_AMP_ADC1 0x18 /* In */ -/* Only for STAC9221. */ -#define STAC9221_NID_ADAT_OUT 0x19 /* Out */ -#define STAC9221_NID_I2S_OUT 0x1A /* Out */ -#define STAC9221_NID_PIN_I2S_OUT 0x1B /* Out */ - -/** Number of total nodes emulated. */ -#define STAC9221_NUM_NODES 0x1C - -/* STAC9220 - Referenced through STAC9220WIDGET in the constructor below. */ -static uint8_t const g_abStac9220Ports[] = { STAC9220_NID_PIN_HEADPHONE0, STAC9220_NID_PIN_B, STAC9220_NID_PIN_C, STAC9220_NID_PIN_HEADPHONE1, STAC9220_NID_PIN_E, STAC9220_NID_PIN_F, 0 }; -static uint8_t const g_abStac9220Dacs[] = { STAC9220_NID_DAC0, STAC9220_NID_DAC1, STAC9220_NID_DAC2, STAC9220_NID_DAC3, 0 }; -static uint8_t const g_abStac9220Adcs[] = { STAC9220_NID_ADC0, STAC9220_NID_ADC1, 0 }; -static uint8_t const g_abStac9220SpdifOuts[] = { STAC9220_NID_SPDIF_OUT, 0 }; -static uint8_t const g_abStac9220SpdifIns[] = { STAC9220_NID_SPDIF_IN, 0 }; -static uint8_t const g_abStac9220DigOutPins[] = { STAC9220_NID_PIN_SPDIF_OUT, 0 }; -static uint8_t const g_abStac9220DigInPins[] = { STAC9220_NID_PIN_SPDIF_IN, 0 }; -static uint8_t const g_abStac9220AdcVols[] = { STAC9220_NID_AMP_ADC0, STAC9220_NID_AMP_ADC1, 0 }; -static uint8_t const g_abStac9220AdcMuxs[] = { STAC9220_NID_ADC0_MUX, STAC9220_NID_ADC1_MUX, 0 }; -static uint8_t const g_abStac9220Pcbeeps[] = { STAC9220_NID_PCBEEP, 0 }; -static uint8_t const g_abStac9220Cds[] = { STAC9220_NID_PIN_CD, 0 }; -static uint8_t const g_abStac9220VolKnobs[] = { STAC9220_NID_VOL_KNOB, 0 }; -/* STAC 9221. */ -/** @todo Is STAC9220_NID_SPDIF_IN really correct for reserved nodes? */ -static uint8_t const g_abStac9220Reserveds[] = { STAC9220_NID_SPDIF_IN, STAC9221_NID_ADAT_OUT, STAC9221_NID_I2S_OUT, STAC9221_NID_PIN_I2S_OUT, 0 }; - -/** SSM description of a CODECNODE. */ -static SSMFIELD const g_aCodecNodeFields[] = -{ - SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.uID), - SSMFIELD_ENTRY_PAD_HC_AUTO(3, 3), - SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F00_param), - SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F02_param), - SSMFIELD_ENTRY( CODECSAVEDSTATENODE, au32Params), - SSMFIELD_ENTRY_TERM() -}; - -/** Backward compatibility with v1 of the CODECNODE. */ -static SSMFIELD const g_aCodecNodeFieldsV1[] = -{ - SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.uID), - SSMFIELD_ENTRY_PAD_HC_AUTO(3, 7), - SSMFIELD_ENTRY_OLD_HCPTR(Core.name), - SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F00_param), - SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F02_param), - SSMFIELD_ENTRY( CODECSAVEDSTATENODE, au32Params), - SSMFIELD_ENTRY_TERM() -}; - - - -#if 0 /* unused */ -static DECLCALLBACK(void) stac9220DbgNodes(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - RT_NOREF(pszArgs); - for (uint8_t i = 1; i < pThis->cTotalNodes; i++) - { - PCODECNODE pNode = &pThis->paNodes[i]; - AMPLIFIER *pAmp = &pNode->dac.B_params; - - uint8_t lVol = AMPLIFIER_REGISTER(*pAmp, AMPLIFIER_OUT, AMPLIFIER_LEFT, 0) & 0x7f; - uint8_t rVol = AMPLIFIER_REGISTER(*pAmp, AMPLIFIER_OUT, AMPLIFIER_RIGHT, 0) & 0x7f; - - pHlp->pfnPrintf(pHlp, "0x%x: lVol=%RU8, rVol=%RU8\n", i, lVol, rVol); - } -} -#endif - -/** - * Resets the codec with all its connected nodes. - * - * @param pThis HDA codec to reset. - */ -static DECLCALLBACK(void) stac9220Reset(PHDACODEC pThis) -{ - AssertPtrReturnVoid(pThis->paNodes); - AssertPtrReturnVoid(pThis->pfnNodeReset); - - LogRel(("HDA: Codec reset\n")); - - pThis->fInReset = true; - - for (uint8_t i = 0; i < pThis->cTotalNodes; i++) - pThis->pfnNodeReset(pThis, i, &pThis->paNodes[i]); - - pThis->fInReset = false; -} - -/** - * Resets a single node of the codec. - * - * @returns IPRT status code. - * @param pThis HDA codec of node to reset. - * @param uNID Node ID to set node to. - * @param pNode Node to reset. - */ -static DECLCALLBACK(int) stac9220ResetNode(PHDACODEC pThis, uint8_t uNID, PCODECNODE pNode) -{ - LogFlowFunc(("NID=0x%x (%RU8)\n", uNID, uNID)); - - if ( !pThis->fInReset - && ( uNID != STAC9220_NID_ROOT - && uNID != STAC9220_NID_AFG) - ) - { - RT_ZERO(pNode->node); - } - - /* Set common parameters across all nodes. */ - pNode->node.uID = uNID; - pNode->node.uSD = 0; - - switch (uNID) - { - /* Root node. */ - case STAC9220_NID_ROOT: - { - /* Set the revision ID. */ - pNode->root.node.au32F00_param[0x02] = CODEC_MAKE_F00_02(0x1, 0x0, 0x3, 0x4, 0x0, 0x1); - break; - } - - /* - * AFG (Audio Function Group). - */ - case STAC9220_NID_AFG: - { - pNode->afg.node.au32F00_param[0x08] = CODEC_MAKE_F00_08(1, 0xd, 0xd); - /* We set the AFG's PCM capabitilies fixed to 44.1kHz, 16-bit signed. */ - pNode->afg.node.au32F00_param[0x0A] = CODEC_F00_0A_44_1KHZ | CODEC_F00_0A_16_BIT; - pNode->afg.node.au32F00_param[0x0B] = CODEC_F00_0B_PCM; - pNode->afg.node.au32F00_param[0x0C] = CODEC_MAKE_F00_0C(0x17) - | CODEC_F00_0C_CAP_BALANCED_IO - | CODEC_F00_0C_CAP_INPUT - | CODEC_F00_0C_CAP_OUTPUT - | CODEC_F00_0C_CAP_PRESENCE_DETECT - | CODEC_F00_0C_CAP_TRIGGER_REQUIRED - | CODEC_F00_0C_CAP_IMPENDANCE_SENSE; - - /* Default input amplifier capabilities. */ - pNode->node.au32F00_param[0x0D] = CODEC_MAKE_F00_0D(CODEC_AMP_CAP_MUTE, - CODEC_AMP_STEP_SIZE, - CODEC_AMP_NUM_STEPS, - CODEC_AMP_OFF_INITIAL); - /* Default output amplifier capabilities. */ - pNode->node.au32F00_param[0x12] = CODEC_MAKE_F00_12(CODEC_AMP_CAP_MUTE, - CODEC_AMP_STEP_SIZE, - CODEC_AMP_NUM_STEPS, - CODEC_AMP_OFF_INITIAL); - - pNode->afg.node.au32F00_param[0x11] = CODEC_MAKE_F00_11(1, 1, 0, 0, 4); - pNode->afg.node.au32F00_param[0x0F] = CODEC_F00_0F_D3 - | CODEC_F00_0F_D2 - | CODEC_F00_0F_D1 - | CODEC_F00_0F_D0; - - pNode->afg.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D2, CODEC_F05_D2); /* PS-Act: D2, PS->Set D2. */ - pNode->afg.u32F08_param = 0; - pNode->afg.u32F17_param = 0; - break; - } - - /* - * DACs. - */ - case STAC9220_NID_DAC0: /* DAC0: Headphones 0 + 1 */ - case STAC9220_NID_DAC1: /* DAC1: PIN C */ - case STAC9220_NID_DAC2: /* DAC2: PIN B */ - case STAC9220_NID_DAC3: /* DAC3: PIN F */ - { - pNode->dac.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, - HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_1X, HDA_SDFMT_16_BIT, - HDA_SDFMT_CHAN_STEREO); - - /* 7.3.4.6: Audio widget capabilities. */ - pNode->dac.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 13, 0) - | CODEC_F00_09_CAP_L_R_SWAP - | CODEC_F00_09_CAP_POWER_CTRL - | CODEC_F00_09_CAP_OUT_AMP_PRESENT - | CODEC_F00_09_CAP_STEREO; - - /* Connection list; must be 0 if the only connection for the widget is - * to the High Definition Audio Link. */ - pNode->dac.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 0 /* Entries */); - - pNode->dac.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); - - RT_ZERO(pNode->dac.B_params); - AMPLIFIER_REGISTER(pNode->dac.B_params, AMPLIFIER_OUT, AMPLIFIER_LEFT, 0) = 0x7F | RT_BIT(7); - AMPLIFIER_REGISTER(pNode->dac.B_params, AMPLIFIER_OUT, AMPLIFIER_RIGHT, 0) = 0x7F | RT_BIT(7); - break; - } - - /* - * ADCs. - */ - case STAC9220_NID_ADC0: /* Analog input. */ - { - pNode->node.au32F02_param[0] = STAC9220_NID_AMP_ADC0; - goto adc_init; - } - - case STAC9220_NID_ADC1: /* Analog input (CD). */ - { - pNode->node.au32F02_param[0] = STAC9220_NID_AMP_ADC1; - - /* Fall through is intentional. */ - adc_init: - - pNode->adc.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, - HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_1X, HDA_SDFMT_16_BIT, - HDA_SDFMT_CHAN_STEREO); - - pNode->adc.u32F03_param = RT_BIT(0); - pNode->adc.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); /* PS-Act: D3 Set: D3 */ - - pNode->adc.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_INPUT, 0xD, 0) - | CODEC_F00_09_CAP_POWER_CTRL - | CODEC_F00_09_CAP_CONNECTION_LIST - | CODEC_F00_09_CAP_PROC_WIDGET - | CODEC_F00_09_CAP_STEREO; - /* Connection list entries. */ - pNode->adc.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); - break; - } - - /* - * SP/DIF In/Out. - */ - case STAC9220_NID_SPDIF_OUT: - { - pNode->spdifout.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, - HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_1X, HDA_SDFMT_16_BIT, - HDA_SDFMT_CHAN_STEREO); - pNode->spdifout.u32F06_param = 0; - pNode->spdifout.u32F0d_param = 0; - - pNode->spdifout.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 4, 0) - | CODEC_F00_09_CAP_DIGITAL - | CODEC_F00_09_CAP_FMT_OVERRIDE - | CODEC_F00_09_CAP_STEREO; - - /* Use a fixed format from AFG. */ - pNode->spdifout.node.au32F00_param[0xA] = pThis->paNodes[STAC9220_NID_AFG].node.au32F00_param[0xA]; - pNode->spdifout.node.au32F00_param[0xB] = CODEC_F00_0B_PCM; - break; - } - - case STAC9220_NID_SPDIF_IN: - { - pNode->spdifin.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, - HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_1X, HDA_SDFMT_16_BIT, - HDA_SDFMT_CHAN_STEREO); - - pNode->spdifin.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_INPUT, 4, 0) - | CODEC_F00_09_CAP_DIGITAL - | CODEC_F00_09_CAP_CONNECTION_LIST - | CODEC_F00_09_CAP_FMT_OVERRIDE - | CODEC_F00_09_CAP_STEREO; - - /* Use a fixed format from AFG. */ - pNode->spdifin.node.au32F00_param[0xA] = pThis->paNodes[STAC9220_NID_AFG].node.au32F00_param[0xA]; - pNode->spdifin.node.au32F00_param[0xB] = CODEC_F00_0B_PCM; - - /* Connection list entries. */ - pNode->spdifin.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); - pNode->spdifin.node.au32F02_param[0] = 0x11; - break; - } - - /* - * PINs / Ports. - */ - case STAC9220_NID_PIN_HEADPHONE0: /* Port A: Headphone in/out (front). */ - { - pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(0 /*fPresent*/, CODEC_F09_ANALOG_NA); - - pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) - | CODEC_F00_0C_CAP_INPUT - | CODEC_F00_0C_CAP_OUTPUT - | CODEC_F00_0C_CAP_HEADPHONE_AMP - | CODEC_F00_0C_CAP_PRESENCE_DETECT - | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; - - /* Connection list entry 0: Goes to DAC0. */ - pNode->port.node.au32F02_param[0] = STAC9220_NID_DAC0; - - if (!pThis->fInReset) - pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, - CODEC_F1C_LOCATION_FRONT, - CODEC_F1C_DEVICE_HP, - CODEC_F1C_CONNECTION_TYPE_1_8INCHES, - CODEC_F1C_COLOR_GREEN, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_1, 0x0 /* Seq */); - goto port_init; - } - - case STAC9220_NID_PIN_B: /* Port B: Rear CLFE (Center / Subwoofer). */ - { - pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); - - pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) - | CODEC_F00_0C_CAP_INPUT - | CODEC_F00_0C_CAP_OUTPUT - | CODEC_F00_0C_CAP_PRESENCE_DETECT - | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; - - /* Connection list entry 0: Goes to DAC2. */ - pNode->port.node.au32F02_param[0] = STAC9220_NID_DAC2; - - if (!pThis->fInReset) - pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, - CODEC_F1C_LOCATION_REAR, - CODEC_F1C_DEVICE_SPEAKER, - CODEC_F1C_CONNECTION_TYPE_1_8INCHES, - CODEC_F1C_COLOR_BLACK, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_0, 0x1 /* Seq */); - goto port_init; - } - - case STAC9220_NID_PIN_C: /* Rear Speaker. */ - { - pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); - - pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) - | CODEC_F00_0C_CAP_INPUT - | CODEC_F00_0C_CAP_OUTPUT - | CODEC_F00_0C_CAP_PRESENCE_DETECT - | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; - - /* Connection list entry 0: Goes to DAC1. */ - pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC1; - - if (!pThis->fInReset) - pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, - CODEC_F1C_LOCATION_REAR, - CODEC_F1C_DEVICE_SPEAKER, - CODEC_F1C_CONNECTION_TYPE_1_8INCHES, - CODEC_F1C_COLOR_GREEN, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_0, 0x0 /* Seq */); - goto port_init; - } - - case STAC9220_NID_PIN_HEADPHONE1: /* Also known as PIN_D. */ - { - pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); - - pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) - | CODEC_F00_0C_CAP_INPUT - | CODEC_F00_0C_CAP_OUTPUT - | CODEC_F00_0C_CAP_HEADPHONE_AMP - | CODEC_F00_0C_CAP_PRESENCE_DETECT - | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; - - /* Connection list entry 0: Goes to DAC1. */ - pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC0; - - if (!pThis->fInReset) - pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, - CODEC_F1C_LOCATION_FRONT, - CODEC_F1C_DEVICE_MIC, - CODEC_F1C_CONNECTION_TYPE_1_8INCHES, - CODEC_F1C_COLOR_PINK, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_15, 0x0 /* Ignored */); - /* Fall through is intentional. */ - - port_init: - - pNode->port.u32F07_param = CODEC_F07_IN_ENABLE - | CODEC_F07_OUT_ENABLE; - pNode->port.u32F08_param = 0; - - pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) - | CODEC_F00_09_CAP_CONNECTION_LIST - | CODEC_F00_09_CAP_UNSOL - | CODEC_F00_09_CAP_STEREO; - /* Connection list entries. */ - pNode->port.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); - break; - } - - case STAC9220_NID_PIN_E: - { - pNode->port.u32F07_param = CODEC_F07_IN_ENABLE; - pNode->port.u32F08_param = 0; - /* If Line in is reported as enabled, OS X sees no speakers! Windows does - * not care either way, although Linux does. - */ - pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(0 /* fPresent */, 0); - - pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) - | CODEC_F00_09_CAP_UNSOL - | CODEC_F00_09_CAP_STEREO; - - pNode->port.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT - | CODEC_F00_0C_CAP_PRESENCE_DETECT; - - if (!pThis->fInReset) - pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, - CODEC_F1C_LOCATION_REAR, - CODEC_F1C_DEVICE_LINE_IN, - CODEC_F1C_CONNECTION_TYPE_1_8INCHES, - CODEC_F1C_COLOR_BLUE, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_4, 0x1 /* Seq */); - break; - } - - case STAC9220_NID_PIN_F: - { - pNode->port.u32F07_param = CODEC_F07_IN_ENABLE | CODEC_F07_OUT_ENABLE; - pNode->port.u32F08_param = 0; - pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /* fPresent */, CODEC_F09_ANALOG_NA); - - pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) - | CODEC_F00_09_CAP_CONNECTION_LIST - | CODEC_F00_09_CAP_UNSOL - | CODEC_F00_09_CAP_OUT_AMP_PRESENT - | CODEC_F00_09_CAP_STEREO; - - pNode->port.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT - | CODEC_F00_0C_CAP_OUTPUT; - - /* Connection list entry 0: Goes to DAC3. */ - pNode->port.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); - pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC3; - - if (!pThis->fInReset) - pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, - CODEC_F1C_LOCATION_INTERNAL, - CODEC_F1C_DEVICE_SPEAKER, - CODEC_F1C_CONNECTION_TYPE_1_8INCHES, - CODEC_F1C_COLOR_ORANGE, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_0, 0x2 /* Seq */); - break; - } - - case STAC9220_NID_PIN_SPDIF_OUT: /* Rear SPDIF Out. */ - { - pNode->digout.u32F07_param = CODEC_F07_OUT_ENABLE; - pNode->digout.u32F09_param = 0; - - pNode->digout.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) - | CODEC_F00_09_CAP_DIGITAL - | CODEC_F00_09_CAP_CONNECTION_LIST - | CODEC_F00_09_CAP_STEREO; - pNode->digout.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_OUTPUT; - - /* Connection list entries. */ - pNode->digout.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 3 /* Entries */); - pNode->digout.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_SPDIF_OUT, - STAC9220_NID_AMP_ADC0, STAC9221_NID_ADAT_OUT, 0); - if (!pThis->fInReset) - pNode->digout.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, - CODEC_F1C_LOCATION_REAR, - CODEC_F1C_DEVICE_SPDIF_OUT, - CODEC_F1C_CONNECTION_TYPE_DIN, - CODEC_F1C_COLOR_BLACK, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_2, 0x0 /* Seq */); - break; - } - - case STAC9220_NID_PIN_SPDIF_IN: - { - pNode->digin.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); /* PS-Act: D3 -> D3 */ - pNode->digin.u32F07_param = CODEC_F07_IN_ENABLE; - pNode->digin.u32F08_param = 0; - pNode->digin.u32F09_param = CODEC_MAKE_F09_DIGITAL(0, 0); - pNode->digin.u32F0c_param = 0; - - pNode->digin.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 3, 0) - | CODEC_F00_09_CAP_POWER_CTRL - | CODEC_F00_09_CAP_DIGITAL - | CODEC_F00_09_CAP_UNSOL - | CODEC_F00_09_CAP_STEREO; - - pNode->digin.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_EAPD - | CODEC_F00_0C_CAP_INPUT - | CODEC_F00_0C_CAP_PRESENCE_DETECT; - if (!pThis->fInReset) - pNode->digin.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, - CODEC_F1C_LOCATION_REAR, - CODEC_F1C_DEVICE_SPDIF_IN, - CODEC_F1C_CONNECTION_TYPE_OTHER_DIGITAL, - CODEC_F1C_COLOR_BLACK, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_5, 0x0 /* Seq */); - break; - } - - case STAC9220_NID_ADC0_MUX: - { - pNode->adcmux.u32F01_param = 0; /* Connection select control index (STAC9220_NID_PIN_E). */ - goto adcmux_init; - } - - case STAC9220_NID_ADC1_MUX: - { - pNode->adcmux.u32F01_param = 1; /* Connection select control index (STAC9220_NID_PIN_CD). */ - /* Fall through is intentional. */ - - adcmux_init: - - pNode->adcmux.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_SELECTOR, 0, 0) - | CODEC_F00_09_CAP_CONNECTION_LIST - | CODEC_F00_09_CAP_AMP_FMT_OVERRIDE - | CODEC_F00_09_CAP_OUT_AMP_PRESENT - | CODEC_F00_09_CAP_STEREO; - - pNode->adcmux.node.au32F00_param[0xD] = CODEC_MAKE_F00_0D(0, 27, 4, 0); - - /* Connection list entries. */ - pNode->adcmux.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 7 /* Entries */); - pNode->adcmux.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_PIN_E, - STAC9220_NID_PIN_CD, - STAC9220_NID_PIN_F, - STAC9220_NID_PIN_B); - pNode->adcmux.node.au32F02_param[0x4] = RT_MAKE_U32_FROM_U8(STAC9220_NID_PIN_C, - STAC9220_NID_PIN_HEADPHONE1, - STAC9220_NID_PIN_HEADPHONE0, - 0x0 /* Unused */); - - /* STAC 9220 v10 6.21-22.{4,5} both(left and right) out amplifiers initialized with 0. */ - RT_ZERO(pNode->adcmux.B_params); - break; - } - - case STAC9220_NID_PCBEEP: - { - pNode->pcbeep.u32F0a_param = 0; - - pNode->pcbeep.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_BEEP_GEN, 0, 0) - | CODEC_F00_09_CAP_AMP_FMT_OVERRIDE - | CODEC_F00_09_CAP_OUT_AMP_PRESENT; - pNode->pcbeep.node.au32F00_param[0xD] = CODEC_MAKE_F00_0D(0, 17, 3, 3); - - RT_ZERO(pNode->pcbeep.B_params); - break; - } - - case STAC9220_NID_PIN_CD: - { - pNode->cdnode.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) - | CODEC_F00_09_CAP_STEREO; - pNode->cdnode.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT; - - if (!pThis->fInReset) - pNode->cdnode.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_FIXED, - CODEC_F1C_LOCATION_INTERNAL, - CODEC_F1C_DEVICE_CD, - CODEC_F1C_CONNECTION_TYPE_ATAPI, - CODEC_F1C_COLOR_UNKNOWN, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_4, 0x2 /* Seq */); - break; - } - - case STAC9220_NID_VOL_KNOB: - { - pNode->volumeKnob.u32F08_param = 0; - pNode->volumeKnob.u32F0f_param = 0x7f; - - pNode->volumeKnob.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_VOLUME_KNOB, 0, 0); - pNode->volumeKnob.node.au32F00_param[0xD] = RT_BIT(7) | 0x7F; - - /* Connection list entries. */ - pNode->volumeKnob.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 4 /* Entries */); - pNode->volumeKnob.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_DAC0, - STAC9220_NID_DAC1, - STAC9220_NID_DAC2, - STAC9220_NID_DAC3); - break; - } - - case STAC9220_NID_AMP_ADC0: /* ADC0Vol */ - { - pNode->adcvol.node.au32F02_param[0] = STAC9220_NID_ADC0_MUX; - goto adcvol_init; - } - - case STAC9220_NID_AMP_ADC1: /* ADC1Vol */ - { - pNode->adcvol.node.au32F02_param[0] = STAC9220_NID_ADC1_MUX; - /* Fall through is intentional. */ - - adcvol_init: - - pNode->adcvol.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_SELECTOR, 0, 0) - | CODEC_F00_09_CAP_L_R_SWAP - | CODEC_F00_09_CAP_CONNECTION_LIST - | CODEC_F00_09_CAP_IN_AMP_PRESENT - | CODEC_F00_09_CAP_STEREO; - - - pNode->adcvol.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); - - RT_ZERO(pNode->adcvol.B_params); - AMPLIFIER_REGISTER(pNode->adcvol.B_params, AMPLIFIER_IN, AMPLIFIER_LEFT, 0) = RT_BIT(7); - AMPLIFIER_REGISTER(pNode->adcvol.B_params, AMPLIFIER_IN, AMPLIFIER_RIGHT, 0) = RT_BIT(7); - break; - } - - /* - * STAC9221 nodes. - */ - - case STAC9221_NID_ADAT_OUT: - { - pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_VENDOR_DEFINED, 3, 0) - | CODEC_F00_09_CAP_DIGITAL - | CODEC_F00_09_CAP_STEREO; - break; - } - - case STAC9221_NID_I2S_OUT: - { - pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 3, 0) - | CODEC_F00_09_CAP_DIGITAL - | CODEC_F00_09_CAP_STEREO; - break; - } - - case STAC9221_NID_PIN_I2S_OUT: - { - pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) - | CODEC_F00_09_CAP_DIGITAL - | CODEC_F00_09_CAP_CONNECTION_LIST - | CODEC_F00_09_CAP_STEREO; - - pNode->node.au32F00_param[0xC] = CODEC_F00_0C_CAP_OUTPUT; - - /* Connection list entries. */ - pNode->node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); - pNode->node.au32F02_param[0] = STAC9221_NID_I2S_OUT; - - if (!pThis->fInReset) - pNode->reserved.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_NO_PHYS, - CODEC_F1C_LOCATION_NA, - CODEC_F1C_DEVICE_LINE_OUT, - CODEC_F1C_CONNECTION_TYPE_UNKNOWN, - CODEC_F1C_COLOR_UNKNOWN, - CODEC_F1C_MISC_NONE, - CODEC_F1C_ASSOCIATION_GROUP_15, 0x0 /* Ignored */); - break; - } - - default: - AssertMsgFailed(("Node %RU8 not implemented\n", uNID)); - break; - } - - return VINF_SUCCESS; -} - -static int stac9220Construct(PHDACODEC pThis) -{ - unconst(pThis->cTotalNodes) = STAC9221_NUM_NODES; - - pThis->pfnReset = stac9220Reset; - pThis->pfnNodeReset = stac9220ResetNode; - - pThis->u16VendorId = 0x8384; /* SigmaTel */ - /* - * Note: The Linux kernel uses "patch_stac922x" for the fixups, - * which in turn uses "ref922x_pin_configs" for the configuration - * defaults tweaking in sound/pci/hda/patch_sigmatel.c. - */ - pThis->u16DeviceId = 0x7680; /* STAC9221 A1 */ - pThis->u8BSKU = 0x76; - pThis->u8AssemblyId = 0x80; - - pThis->paNodes = (PCODECNODE)RTMemAllocZ(sizeof(CODECNODE) * pThis->cTotalNodes); - if (!pThis->paNodes) - return VERR_NO_MEMORY; - - pThis->fInReset = false; - -#define STAC9220WIDGET(type) pThis->au8##type##s = g_abStac9220##type##s - STAC9220WIDGET(Port); - STAC9220WIDGET(Dac); - STAC9220WIDGET(Adc); - STAC9220WIDGET(AdcVol); - STAC9220WIDGET(AdcMux); - STAC9220WIDGET(Pcbeep); - STAC9220WIDGET(SpdifIn); - STAC9220WIDGET(SpdifOut); - STAC9220WIDGET(DigInPin); - STAC9220WIDGET(DigOutPin); - STAC9220WIDGET(Cd); - STAC9220WIDGET(VolKnob); - STAC9220WIDGET(Reserved); -#undef STAC9220WIDGET - - unconst(pThis->u8AdcVolsLineIn) = STAC9220_NID_AMP_ADC0; - unconst(pThis->u8DacLineOut) = STAC9220_NID_DAC1; - - /* - * Initialize all codec nodes. - * This is specific to the codec, so do this here. - * - * Note: Do *not* call stac9220Reset() here, as this would not - * initialize the node default configuration values then! - */ - AssertPtr(pThis->paNodes); - AssertPtr(pThis->pfnNodeReset); - - for (uint8_t i = 0; i < pThis->cTotalNodes; i++) - { - int rc2 = stac9220ResetNode(pThis, i, &pThis->paNodes[i]); - AssertRC(rc2); - } - - return VINF_SUCCESS; -} - - -/* - * Some generic predicate functions. - */ - -#define DECLISNODEOFTYPE(type) \ - DECLINLINE(bool) hdaCodecIs##type##Node(PHDACODEC pThis, uint8_t cNode) \ - { \ - Assert(pThis->au8##type##s); \ - for (int i = 0; pThis->au8##type##s[i] != 0; ++i) \ - if (pThis->au8##type##s[i] == cNode) \ - return true; \ - return false; \ - } -/* hdaCodecIsPortNode */ -DECLISNODEOFTYPE(Port) -/* hdaCodecIsDacNode */ -DECLISNODEOFTYPE(Dac) -/* hdaCodecIsAdcVolNode */ -DECLISNODEOFTYPE(AdcVol) -/* hdaCodecIsAdcNode */ -DECLISNODEOFTYPE(Adc) -/* hdaCodecIsAdcMuxNode */ -DECLISNODEOFTYPE(AdcMux) -/* hdaCodecIsPcbeepNode */ -DECLISNODEOFTYPE(Pcbeep) -/* hdaCodecIsSpdifOutNode */ -DECLISNODEOFTYPE(SpdifOut) -/* hdaCodecIsSpdifInNode */ -DECLISNODEOFTYPE(SpdifIn) -/* hdaCodecIsDigInPinNode */ -DECLISNODEOFTYPE(DigInPin) -/* hdaCodecIsDigOutPinNode */ -DECLISNODEOFTYPE(DigOutPin) -/* hdaCodecIsCdNode */ -DECLISNODEOFTYPE(Cd) -/* hdaCodecIsVolKnobNode */ -DECLISNODEOFTYPE(VolKnob) -/* hdaCodecIsReservedNode */ -DECLISNODEOFTYPE(Reserved) - - -/* - * Misc helpers. - */ -static int hdaCodecToAudVolume(PHDACODEC pThis, PCODECNODE pNode, AMPLIFIER *pAmp, PDMAUDIOMIXERCTL enmMixerCtl) -{ - RT_NOREF(pNode); - - uint8_t iDir; - switch (enmMixerCtl) - { - case PDMAUDIOMIXERCTL_VOLUME_MASTER: - case PDMAUDIOMIXERCTL_FRONT: - iDir = AMPLIFIER_OUT; - break; - case PDMAUDIOMIXERCTL_LINE_IN: - case PDMAUDIOMIXERCTL_MIC_IN: - iDir = AMPLIFIER_IN; - break; - default: - AssertMsgFailedReturn(("Invalid mixer control %RU32\n", enmMixerCtl), VERR_INVALID_PARAMETER); - break; - } - - int iMute; - iMute = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & RT_BIT(7); - iMute |= AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_RIGHT, 0) & RT_BIT(7); - iMute >>=7; - iMute &= 0x1; - - uint8_t lVol = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & 0x7f; - uint8_t rVol = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_RIGHT, 0) & 0x7f; - - /* - * The STAC9220 volume controls have 0 to -96dB attenuation range in 128 steps. - * We have 0 to -96dB range in 256 steps. HDA volume setting of 127 must map - * to 255 internally (0dB), while HDA volume setting of 0 (-96dB) should map - * to 1 (rather than zero) internally. - */ - lVol = (lVol + 1) * (2 * 255) / 256; - rVol = (rVol + 1) * (2 * 255) / 256; - - PDMAUDIOVOLUME Vol = { RT_BOOL(iMute), lVol, rVol }; - - LogFunc(("[NID0x%02x] %RU8/%RU8 (%s)\n", - pNode->node.uID, lVol, rVol, RT_BOOL(iMute) ? "Muted" : "Unmuted")); - - LogRel2(("HDA: Setting volume for mixer control '%s' to %RU8/%RU8 (%s)\n", - DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), lVol, rVol, RT_BOOL(iMute) ? "Muted" : "Unmuted")); - - return pThis->pfnCbMixerSetVolume(pThis->pDevIns, enmMixerCtl, &Vol); -} - -DECLINLINE(void) hdaCodecSetRegister(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset, uint32_t mask) -{ - Assert((pu32Reg && u8Offset < 32)); - *pu32Reg &= ~(mask << u8Offset); - *pu32Reg |= (u32Cmd & mask) << u8Offset; -} - -DECLINLINE(void) hdaCodecSetRegisterU8(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset) -{ - hdaCodecSetRegister(pu32Reg, u32Cmd, u8Offset, CODEC_VERB_8BIT_DATA); -} - -DECLINLINE(void) hdaCodecSetRegisterU16(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset) -{ - hdaCodecSetRegister(pu32Reg, u32Cmd, u8Offset, CODEC_VERB_16BIT_DATA); -} - - -/* - * Verb processor functions. - */ -#if 0 /* unused */ - -static DECLCALLBACK(int) vrbProcUnimplemented(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - RT_NOREF(pThis, cmd); - LogFlowFunc(("cmd(raw:%x: cad:%x, d:%c, nid:%x, verb:%x)\n", cmd, - CODEC_CAD(cmd), CODEC_DIRECT(cmd) ? 'N' : 'Y', CODEC_NID(cmd), CODEC_VERBDATA(cmd))); - *pResp = 0; - return VINF_SUCCESS; -} - -static DECLCALLBACK(int) vrbProcBreak(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - int rc; - rc = vrbProcUnimplemented(pThis, cmd, pResp); - *pResp |= CODEC_RESPONSE_UNSOLICITED; - return rc; -} - -#endif /* unused */ - -/* B-- */ -static DECLCALLBACK(int) vrbProcGetAmplifier(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - /* HDA spec 7.3.3.7 Note A */ - /** @todo If index out of range response should be 0. */ - uint8_t u8Index = CODEC_GET_AMP_DIRECTION(cmd) == AMPLIFIER_OUT ? 0 : CODEC_GET_AMP_INDEX(cmd); - - PCODECNODE pNode = &pThis->paNodes[CODEC_NID(cmd)]; - if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - *pResp = AMPLIFIER_REGISTER(pNode->dac.B_params, - CODEC_GET_AMP_DIRECTION(cmd), - CODEC_GET_AMP_SIDE(cmd), - u8Index); - else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) - *pResp = AMPLIFIER_REGISTER(pNode->adcvol.B_params, - CODEC_GET_AMP_DIRECTION(cmd), - CODEC_GET_AMP_SIDE(cmd), - u8Index); - else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd))) - *pResp = AMPLIFIER_REGISTER(pNode->adcmux.B_params, - CODEC_GET_AMP_DIRECTION(cmd), - CODEC_GET_AMP_SIDE(cmd), - u8Index); - else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) - *pResp = AMPLIFIER_REGISTER(pNode->pcbeep.B_params, - CODEC_GET_AMP_DIRECTION(cmd), - CODEC_GET_AMP_SIDE(cmd), - u8Index); - else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - *pResp = AMPLIFIER_REGISTER(pNode->port.B_params, - CODEC_GET_AMP_DIRECTION(cmd), - CODEC_GET_AMP_SIDE(cmd), - u8Index); - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - *pResp = AMPLIFIER_REGISTER(pNode->adc.B_params, - CODEC_GET_AMP_DIRECTION(cmd), - CODEC_GET_AMP_SIDE(cmd), - u8Index); - else - LogRel2(("HDA: Warning: Unhandled get amplifier command: 0x%x (NID=0x%x [%RU8])\n", cmd, CODEC_NID(cmd), CODEC_NID(cmd))); - - return VINF_SUCCESS; -} - -/* 3-- */ -static DECLCALLBACK(int) vrbProcSetAmplifier(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - PCODECNODE pNode = &pThis->paNodes[CODEC_NID(cmd)]; - AMPLIFIER *pAmplifier = NULL; - if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - pAmplifier = &pNode->dac.B_params; - else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) - pAmplifier = &pNode->adcvol.B_params; - else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd))) - pAmplifier = &pNode->adcmux.B_params; - else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) - pAmplifier = &pNode->pcbeep.B_params; - else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - pAmplifier = &pNode->port.B_params; - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - pAmplifier = &pNode->adc.B_params; - else - LogRel2(("HDA: Warning: Unhandled set amplifier command: 0x%x (Payload=%RU16, NID=0x%x [%RU8])\n", - cmd, CODEC_VERB_PAYLOAD16(cmd), CODEC_NID(cmd), CODEC_NID(cmd))); - - if (!pAmplifier) - return VINF_SUCCESS; - - bool fIsOut = CODEC_SET_AMP_IS_OUT_DIRECTION(cmd); - bool fIsIn = CODEC_SET_AMP_IS_IN_DIRECTION(cmd); - bool fIsLeft = CODEC_SET_AMP_IS_LEFT_SIDE(cmd); - bool fIsRight = CODEC_SET_AMP_IS_RIGHT_SIDE(cmd); - uint8_t u8Index = CODEC_SET_AMP_INDEX(cmd); - - if ( (!fIsLeft && !fIsRight) - || (!fIsOut && !fIsIn)) - return VINF_SUCCESS; - - LogFunc(("[NID0x%02x] fIsOut=%RTbool, fIsIn=%RTbool, fIsLeft=%RTbool, fIsRight=%RTbool, Idx=%RU8\n", - CODEC_NID(cmd), fIsOut, fIsIn, fIsLeft, fIsRight, u8Index)); - - if (fIsIn) - { - if (fIsLeft) - hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_LEFT, u8Index), cmd, 0); - if (fIsRight) - hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_RIGHT, u8Index), cmd, 0); - - // if (CODEC_NID(cmd) == pThis->u8AdcVolsLineIn) - // { - hdaCodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_LINE_IN); - // } - } - if (fIsOut) - { - if (fIsLeft) - hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_LEFT, u8Index), cmd, 0); - if (fIsRight) - hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_RIGHT, u8Index), cmd, 0); - - if (CODEC_NID(cmd) == pThis->u8DacLineOut) - hdaCodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_FRONT); - } - - return VINF_SUCCESS; -} - -static DECLCALLBACK(int) vrbProcGetParameter(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - Assert((cmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F00_PARAM_LENGTH); - if ((cmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F00_PARAM_LENGTH) - { - *pResp = 0; - - LogFlowFunc(("invalid F00 parameter %d\n", (cmd & CODEC_VERB_8BIT_DATA))); - return VINF_SUCCESS; - } - - *pResp = pThis->paNodes[CODEC_NID(cmd)].node.au32F00_param[cmd & CODEC_VERB_8BIT_DATA]; - return VINF_SUCCESS; -} - -/* F01 */ -static DECLCALLBACK(int) vrbProcGetConSelectCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].adcmux.u32F01_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F01_param; - else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F01_param; - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F01_param; - else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F01_param; - else - LogRel2(("HDA: Warning: Unhandled get connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* 701 */ -static DECLCALLBACK(int) vrbProcSetConSelectCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcmux.u32F01_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F01_param; - else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F01_param; - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F01_param; - else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F01_param; - else - LogRel2(("HDA: Warning: Unhandled set connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, 0); - - return VINF_SUCCESS; -} - -/* F07 */ -static DECLCALLBACK(int) vrbProcGetPinCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F07_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F07_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F07_param; - else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F07_param; - else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F07_param; - else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F07_param; - else - LogRel2(("HDA: Warning: Unhandled get pin control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* 707 */ -static DECLCALLBACK(int) vrbProcSetPinCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F07_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F07_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F07_param; - else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F07_param; - else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F07_param; - else if ( hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)) - && CODEC_NID(cmd) == 0x1b) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F07_param; - else - LogRel2(("HDA: Warning: Unhandled set pin control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, 0); - - return VINF_SUCCESS; -} - -/* F08 */ -static DECLCALLBACK(int) vrbProcGetUnsolicitedEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F08_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param; - else if ((cmd) == STAC9220_NID_AFG) - *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F08_param; - else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F08_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F08_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param; - else - LogRel2(("HDA: Warning: Unhandled get unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* 708 */ -static DECLCALLBACK(int) vrbProcSetUnsolicitedEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F08_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param; - else if (CODEC_NID(cmd) == STAC9220_NID_AFG) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F08_param; - else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F08_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F08_param; - else - LogRel2(("HDA: Warning: Unhandled set unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, 0); - - return VINF_SUCCESS; -} - -/* F09 */ -static DECLCALLBACK(int) vrbProcGetPinSense(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F09_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F09_param; - else - { - AssertFailed(); - LogRel2(("HDA: Warning: Unhandled get pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - } - - return VINF_SUCCESS; -} - -/* 709 */ -static DECLCALLBACK(int) vrbProcSetPinSense(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F09_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F09_param; - else - LogRel2(("HDA: Warning: Unhandled set pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, 0); - - return VINF_SUCCESS; -} - -static DECLCALLBACK(int) vrbProcGetConnectionListEntry(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - Assert((cmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F02_PARAM_LENGTH); - if ((cmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F02_PARAM_LENGTH) - { - LogFlowFunc(("access to invalid F02 index %d\n", (cmd & CODEC_VERB_8BIT_DATA))); - return VINF_SUCCESS; - } - *pResp = pThis->paNodes[CODEC_NID(cmd)].node.au32F02_param[cmd & CODEC_VERB_8BIT_DATA]; - return VINF_SUCCESS; -} - -/* F03 */ -static DECLCALLBACK(int) vrbProcGetProcessingState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F03_param; - - return VINF_SUCCESS; -} - -/* 703 */ -static DECLCALLBACK(int) vrbProcSetProcessingState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].adc.u32F03_param, cmd, 0); - return VINF_SUCCESS; -} - -/* F0D */ -static DECLCALLBACK(int) vrbProcGetDigitalConverter(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F0d_param; - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F0d_param; - - return VINF_SUCCESS; -} - -static int codecSetDigitalConverter(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F0d_param, cmd, u8Offset); - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F0d_param, cmd, u8Offset); - return VINF_SUCCESS; -} - -/* 70D */ -static DECLCALLBACK(int) vrbProcSetDigitalConverter1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - return codecSetDigitalConverter(pThis, cmd, 0, pResp); -} - -/* 70E */ -static DECLCALLBACK(int) vrbProcSetDigitalConverter2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - return codecSetDigitalConverter(pThis, cmd, 8, pResp); -} - -/* F20 */ -static DECLCALLBACK(int) vrbProcGetSubId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - Assert(CODEC_CAD(cmd) == pThis->id); - Assert(CODEC_NID(cmd) < pThis->cTotalNodes); - if (CODEC_NID(cmd) >= pThis->cTotalNodes) - { - LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd))); - return VINF_SUCCESS; - } - if (CODEC_NID(cmd) == STAC9220_NID_AFG) - *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F20_param; - else - *pResp = 0; - return VINF_SUCCESS; -} - -static int codecSetSubIdX(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset) -{ - Assert(CODEC_CAD(cmd) == pThis->id); - Assert(CODEC_NID(cmd) < pThis->cTotalNodes); - if (CODEC_NID(cmd) >= pThis->cTotalNodes) - { - LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd))); - return VINF_SUCCESS; - } - uint32_t *pu32Reg; - if (CODEC_NID(cmd) == STAC9220_NID_AFG) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F20_param; - else - AssertFailedReturn(VINF_SUCCESS); - hdaCodecSetRegisterU8(pu32Reg, cmd, u8Offset); - return VINF_SUCCESS; -} - -/* 720 */ -static DECLCALLBACK(int) vrbProcSetSubId0(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - return codecSetSubIdX(pThis, cmd, 0); -} - -/* 721 */ -static DECLCALLBACK(int) vrbProcSetSubId1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - return codecSetSubIdX(pThis, cmd, 8); -} - -/* 722 */ -static DECLCALLBACK(int) vrbProcSetSubId2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - return codecSetSubIdX(pThis, cmd, 16); -} - -/* 723 */ -static DECLCALLBACK(int) vrbProcSetSubId3(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - return codecSetSubIdX(pThis, cmd, 24); -} - -static DECLCALLBACK(int) vrbProcReset(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - Assert(CODEC_CAD(cmd) == pThis->id); - Assert(CODEC_NID(cmd) == STAC9220_NID_AFG); - - if ( CODEC_NID(cmd) == STAC9220_NID_AFG - && pThis->pfnReset) - { - pThis->pfnReset(pThis); - } - - *pResp = 0; - return VINF_SUCCESS; -} - -/* F05 */ -static DECLCALLBACK(int) vrbProcGetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (CODEC_NID(cmd) == STAC9220_NID_AFG) - *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param; - else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param; - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F05_param; - else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param; - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param; - else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param; - else - LogRel2(("HDA: Warning: Unhandled get power state command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - LogFunc(("[NID0x%02x]: fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n", - CODEC_NID(cmd), CODEC_F05_IS_RESET(*pResp), CODEC_F05_IS_STOPOK(*pResp), CODEC_F05_ACT(*pResp), CODEC_F05_SET(*pResp))); - return VINF_SUCCESS; -} - -/* 705 */ -#if 1 -static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (CODEC_NID(cmd) == STAC9220_NID_AFG) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param; - else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F05_param; - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param; - else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param; - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param; - else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param; - else - { - LogRel2(("HDA: Warning: Unhandled set power state command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - } - - if (!pu32Reg) - return VINF_SUCCESS; - - uint8_t uPwrCmd = CODEC_F05_SET (cmd); - bool fReset = CODEC_F05_IS_RESET (*pu32Reg); - bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg); -#ifdef LOG_ENABLED - bool fError = CODEC_F05_IS_ERROR (*pu32Reg); - uint8_t uPwrAct = CODEC_F05_ACT (*pu32Reg); - uint8_t uPwrSet = CODEC_F05_SET (*pu32Reg); - LogFunc(("[NID0x%02x] Cmd=D%RU8, fReset=%RTbool, fStopOk=%RTbool, fError=%RTbool, uPwrAct=D%RU8, uPwrSet=D%RU8\n", - CODEC_NID(cmd), uPwrCmd, fReset, fStopOk, fError, uPwrAct, uPwrSet)); - LogFunc(("AFG: Act=D%RU8, Set=D%RU8\n", - CODEC_F05_ACT(pThis->paNodes[STAC9220_NID_AFG].afg.u32F05_param), - CODEC_F05_SET(pThis->paNodes[STAC9220_NID_AFG].afg.u32F05_param))); -#endif - - if (CODEC_NID(cmd) == STAC9220_NID_AFG) - *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uPwrCmd /* PS-Act */, uPwrCmd /* PS-Set */); - - const uint8_t uAFGPwrAct = CODEC_F05_ACT(pThis->paNodes[STAC9220_NID_AFG].afg.u32F05_param); - if (uAFGPwrAct == CODEC_F05_D0) /* Only propagate power state if AFG is on (D0). */ - { - /* Propagate to all other nodes under this AFG. */ - LogFunc(("Propagating Act=D%RU8 (AFG), Set=D%RU8 to all AFG child nodes ...\n", uAFGPwrAct, uPwrCmd)); - -#define PROPAGATE_PWR_STATE(_aList, _aMember) \ - { \ - const uint8_t *pu8NodeIndex = &_aList[0]; \ - while (*(++pu8NodeIndex)) \ - { \ - pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param = \ - CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd); \ - LogFunc(("\t[NID0x%02x]: Act=D%RU8, Set=D%RU8\n", *pu8NodeIndex, \ - CODEC_F05_ACT(pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param), \ - CODEC_F05_SET(pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param))); \ - } \ - } - - PROPAGATE_PWR_STATE(pThis->au8Dacs, dac); - PROPAGATE_PWR_STATE(pThis->au8Adcs, adc); - PROPAGATE_PWR_STATE(pThis->au8DigInPins, digin); - PROPAGATE_PWR_STATE(pThis->au8DigOutPins, digout); - PROPAGATE_PWR_STATE(pThis->au8SpdifIns, spdifin); - PROPAGATE_PWR_STATE(pThis->au8SpdifOuts, spdifout); - PROPAGATE_PWR_STATE(pThis->au8Reserveds, reserved); - -#undef PROPAGATE_PWR_STATE - } - /* - * If this node is a reqular node (not the AFG one), adopt PS-Set of the AFG node - * as PS-Set of this node. PS-Act always is one level under PS-Set here. - */ - else - { - *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd); - } - - LogFunc(("[NID0x%02x] fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n", - CODEC_NID(cmd), - CODEC_F05_IS_RESET(*pu32Reg), CODEC_F05_IS_STOPOK(*pu32Reg), CODEC_F05_ACT(*pu32Reg), CODEC_F05_SET(*pu32Reg))); - - return VINF_SUCCESS; -} -#else -DECLINLINE(void) codecPropogatePowerState(uint32_t *pu32F05_param) -{ - Assert(pu32F05_param); - if (!pu32F05_param) - return; - bool fReset = CODEC_F05_IS_RESET(*pu32F05_param); - bool fStopOk = CODEC_F05_IS_STOPOK(*pu32F05_param); - uint8_t u8SetPowerState = CODEC_F05_SET(*pu32F05_param); - *pu32F05_param = CODEC_MAKE_F05(fReset, fStopOk, 0, u8SetPowerState, u8SetPowerState); -} - -static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - Assert(CODEC_CAD(cmd) == pThis->id); - Assert(CODEC_NID(cmd) < pThis->cTotalNodes); - if (CODEC_NID(cmd) >= pThis->cTotalNodes) - { - LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd))); - return VINF_SUCCESS; - } - *pResp = 0; - uint32_t *pu32Reg; - if (CODEC_NID(cmd) == 1 /* AFG */) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param; - else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param; - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param; - else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param; - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param; - else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param; - else - AssertFailedReturn(VINF_SUCCESS); - - bool fReset = CODEC_F05_IS_RESET(*pu32Reg); - bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg); - - if (CODEC_NID(cmd) != 1 /* AFG */) - { - /* - * We shouldn't propogate actual power state, which actual for AFG - */ - *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, - CODEC_F05_ACT(pThis->paNodes[1].afg.u32F05_param), - CODEC_F05_SET(cmd)); - } - - /* Propagate next power state only if AFG is on or verb modifies AFG power state */ - if ( CODEC_NID(cmd) == 1 /* AFG */ - || !CODEC_F05_ACT(pThis->paNodes[1].afg.u32F05_param)) - { - *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, CODEC_F05_SET(cmd), CODEC_F05_SET(cmd)); - if ( CODEC_NID(cmd) == 1 /* AFG */ - && (CODEC_F05_SET(cmd)) == CODEC_F05_D0) - { - /* now we're powered on AFG and may propogate power states on nodes */ - const uint8_t *pu8NodeIndex = &pThis->au8Dacs[0]; - while (*(++pu8NodeIndex)) - codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].dac.u32F05_param); - - pu8NodeIndex = &pThis->au8Adcs[0]; - while (*(++pu8NodeIndex)) - codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].adc.u32F05_param); - - pu8NodeIndex = &pThis->au8DigInPins[0]; - while (*(++pu8NodeIndex)) - codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].digin.u32F05_param); - } - } - return VINF_SUCCESS; -} -#endif - -/* F06 */ -static DECLCALLBACK(int) vrbProcGetStreamId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F06_param; - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F06_param; - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F06_param; - else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F06_param; - else if (CODEC_NID(cmd) == STAC9221_NID_I2S_OUT) - *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F06_param; - else - LogRel2(("HDA: Warning: Unhandled get stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - LogFlowFunc(("[NID0x%02x] Stream ID=%RU8, channel=%RU8\n", - CODEC_NID(cmd), CODEC_F00_06_GET_STREAM_ID(cmd), CODEC_F00_06_GET_CHANNEL_ID(cmd))); - - return VINF_SUCCESS; -} - -/* 706 */ -static DECLCALLBACK(int) vrbProcSetStreamId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint8_t uSD = CODEC_F00_06_GET_STREAM_ID(cmd); - uint8_t uChannel = CODEC_F00_06_GET_CHANNEL_ID(cmd); - - LogFlowFunc(("[NID0x%02x] Setting to stream ID=%RU8, channel=%RU8\n", - CODEC_NID(cmd), uSD, uChannel)); - - ASSERT_GUEST_LOGREL_MSG_RETURN(uSD < HDA_MAX_STREAMS, - ("Setting stream ID #%RU8 is invalid\n", uSD), VERR_INVALID_PARAMETER); - - PDMAUDIODIR enmDir; - uint32_t *pu32Addr = NULL; - if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - { - pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F06_param; - enmDir = PDMAUDIODIR_OUT; - } - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - { - pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F06_param; - enmDir = PDMAUDIODIR_IN; - } - else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - { - pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F06_param; - enmDir = PDMAUDIODIR_OUT; - } - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - { - pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F06_param; - enmDir = PDMAUDIODIR_IN; - } - else - { - enmDir = PDMAUDIODIR_UNKNOWN; - LogRel2(("HDA: Warning: Unhandled set stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - } - - /* Do we (re-)assign our input/output SDn (SDI/SDO) IDs? */ - if (enmDir != PDMAUDIODIR_UNKNOWN) - { - pThis->paNodes[CODEC_NID(cmd)].node.uSD = uSD; - pThis->paNodes[CODEC_NID(cmd)].node.uChannel = uChannel; - - if (enmDir == PDMAUDIODIR_OUT) - { - /** @todo Check if non-interleaved streams need a different channel / SDn? */ - - /* Propagate to the controller. */ - pThis->pfnCbMixerControl(pThis->pDevIns, PDMAUDIOMIXERCTL_FRONT, uSD, uChannel); -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - pThis->pfnCbMixerControl(pThis->pDevIns, PDMAUDIOMIXERCTL_CENTER_LFE, uSD, uChannel); - pThis->pfnCbMixerControl(pThis->pDevIns, PDMAUDIOMIXERCTL_REAR, uSD, uChannel); -#endif - } - else if (enmDir == PDMAUDIODIR_IN) - { - pThis->pfnCbMixerControl(pThis->pDevIns, PDMAUDIOMIXERCTL_LINE_IN, uSD, uChannel); -#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - pThis->pfnCbMixerControl(pThis->pDevIns, PDMAUDIOMIXERCTL_MIC_IN, uSD, uChannel); -#endif - } - } - - if (pu32Addr) - hdaCodecSetRegisterU8(pu32Addr, cmd, 0); - - return VINF_SUCCESS; -} - -/* A0 */ -static DECLCALLBACK(int) vrbProcGetConverterFormat(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32A_param; - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32A_param; - else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32A_param; - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32A_param; - else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32A_param; - else - LogRel2(("HDA: Warning: Unhandled get converter format command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* Also see section 3.7.1. */ -static DECLCALLBACK(int) vrbProcSetConverterFormat(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].dac.u32A_param, cmd, 0); - else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) - hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].adc.u32A_param, cmd, 0); - else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) - hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].spdifout.u32A_param, cmd, 0); - else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) - hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].spdifin.u32A_param, cmd, 0); - else - LogRel2(("HDA: Warning: Unhandled set converter format command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* F0C */ -static DECLCALLBACK(int) vrbProcGetEAPD_BTLEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F0c_param; - else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F0c_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F0c_param; - else - LogRel2(("HDA: Warning: Unhandled get EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* 70C */ -static DECLCALLBACK(int) vrbProcSetEAPD_BTLEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F0c_param; - else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F0c_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F0c_param; - else - LogRel2(("HDA: Warning: Unhandled set EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, 0); - - return VINF_SUCCESS; -} - -/* F0F */ -static DECLCALLBACK(int) vrbProcGetVolumeKnobCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F0f_param; - else - LogRel2(("HDA: Warning: Unhandled get volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* 70F */ -static DECLCALLBACK(int) vrbProcSetVolumeKnobCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F0f_param; - else - LogRel2(("HDA: Warning: Unhandled set volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, 0); - - return VINF_SUCCESS; -} - -/* F15 */ -static DECLCALLBACK(int) vrbProcGetGPIOData(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - RT_NOREF(pThis, cmd); - *pResp = 0; - return VINF_SUCCESS; -} - -/* 715 */ -static DECLCALLBACK(int) vrbProcSetGPIOData(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - RT_NOREF(pThis, cmd); - *pResp = 0; - return VINF_SUCCESS; -} - -/* F16 */ -static DECLCALLBACK(int) vrbProcGetGPIOEnableMask(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - RT_NOREF(pThis, cmd); - *pResp = 0; - return VINF_SUCCESS; -} - -/* 716 */ -static DECLCALLBACK(int) vrbProcSetGPIOEnableMask(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - RT_NOREF(pThis, cmd); - *pResp = 0; - return VINF_SUCCESS; -} - -/* F17 */ -static DECLCALLBACK(int) vrbProcGetGPIODirection(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - /* Note: this is true for ALC885. */ - if (CODEC_NID(cmd) == STAC9220_NID_AFG) - *pResp = pThis->paNodes[1].afg.u32F17_param; - else - LogRel2(("HDA: Warning: Unhandled get GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* 717 */ -static DECLCALLBACK(int) vrbProcSetGPIODirection(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (CODEC_NID(cmd) == STAC9220_NID_AFG) - pu32Reg = &pThis->paNodes[1].afg.u32F17_param; - else - LogRel2(("HDA: Warning: Unhandled set GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, 0); - - return VINF_SUCCESS; -} - -/* F1C */ -static DECLCALLBACK(int) vrbProcGetConfig(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F1c_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F1c_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F1c_param; - else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F1c_param; - else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F1c_param; - else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F1c_param; - else - LogRel2(("HDA: Warning: Unhandled get config command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -static int codecSetConfigX(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset) -{ - uint32_t *pu32Reg = NULL; - if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F1c_param; - else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F1c_param; - else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F1c_param; - else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F1c_param; - else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F1c_param; - else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F1c_param; - else - LogRel2(("HDA: Warning: Unhandled set config command (%RU8) for NID0x%02x: 0x%x\n", u8Offset, CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, u8Offset); - - return VINF_SUCCESS; -} - -/* 71C */ -static DECLCALLBACK(int) vrbProcSetConfig0(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - return codecSetConfigX(pThis, cmd, 0); -} - -/* 71D */ -static DECLCALLBACK(int) vrbProcSetConfig1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - return codecSetConfigX(pThis, cmd, 8); -} - -/* 71E */ -static DECLCALLBACK(int) vrbProcSetConfig2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - return codecSetConfigX(pThis, cmd, 16); -} - -/* 71E */ -static DECLCALLBACK(int) vrbProcSetConfig3(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - return codecSetConfigX(pThis, cmd, 24); -} - -/* F04 */ -static DECLCALLBACK(int) vrbProcGetSDISelect(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F04_param; - else - LogRel2(("HDA: Warning: Unhandled get SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - return VINF_SUCCESS; -} - -/* 704 */ -static DECLCALLBACK(int) vrbProcSetSDISelect(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) -{ - *pResp = 0; - - uint32_t *pu32Reg = NULL; - if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) - pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F04_param; - else - LogRel2(("HDA: Warning: Unhandled set SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); - - if (pu32Reg) - hdaCodecSetRegisterU8(pu32Reg, cmd, 0); - - return VINF_SUCCESS; -} - -/** - * HDA codec verb map. - * @todo Any reason not to use binary search here? - * bird: because you'd need to sort the entries first... - */ -static const CODECVERB g_aCodecVerbs[] = -{ - /* Verb Verb mask Callback Name - * ---------- --------------------- ---------------------------------------------------------- - */ - { 0x000F0000, CODEC_VERB_8BIT_CMD , vrbProcGetParameter , "GetParameter " }, - { 0x000F0100, CODEC_VERB_8BIT_CMD , vrbProcGetConSelectCtrl , "GetConSelectCtrl " }, - { 0x00070100, CODEC_VERB_8BIT_CMD , vrbProcSetConSelectCtrl , "SetConSelectCtrl " }, - { 0x000F0600, CODEC_VERB_8BIT_CMD , vrbProcGetStreamId , "GetStreamId " }, - { 0x00070600, CODEC_VERB_8BIT_CMD , vrbProcSetStreamId , "SetStreamId " }, - { 0x000F0700, CODEC_VERB_8BIT_CMD , vrbProcGetPinCtrl , "GetPinCtrl " }, - { 0x00070700, CODEC_VERB_8BIT_CMD , vrbProcSetPinCtrl , "SetPinCtrl " }, - { 0x000F0800, CODEC_VERB_8BIT_CMD , vrbProcGetUnsolicitedEnabled , "GetUnsolicitedEnabled " }, - { 0x00070800, CODEC_VERB_8BIT_CMD , vrbProcSetUnsolicitedEnabled , "SetUnsolicitedEnabled " }, - { 0x000F0900, CODEC_VERB_8BIT_CMD , vrbProcGetPinSense , "GetPinSense " }, - { 0x00070900, CODEC_VERB_8BIT_CMD , vrbProcSetPinSense , "SetPinSense " }, - { 0x000F0200, CODEC_VERB_8BIT_CMD , vrbProcGetConnectionListEntry , "GetConnectionListEntry" }, - { 0x000F0300, CODEC_VERB_8BIT_CMD , vrbProcGetProcessingState , "GetProcessingState " }, - { 0x00070300, CODEC_VERB_8BIT_CMD , vrbProcSetProcessingState , "SetProcessingState " }, - { 0x000F0D00, CODEC_VERB_8BIT_CMD , vrbProcGetDigitalConverter , "GetDigitalConverter " }, - { 0x00070D00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter1 , "SetDigitalConverter1 " }, - { 0x00070E00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter2 , "SetDigitalConverter2 " }, - { 0x000F2000, CODEC_VERB_8BIT_CMD , vrbProcGetSubId , "GetSubId " }, - { 0x00072000, CODEC_VERB_8BIT_CMD , vrbProcSetSubId0 , "SetSubId0 " }, - { 0x00072100, CODEC_VERB_8BIT_CMD , vrbProcSetSubId1 , "SetSubId1 " }, - { 0x00072200, CODEC_VERB_8BIT_CMD , vrbProcSetSubId2 , "SetSubId2 " }, - { 0x00072300, CODEC_VERB_8BIT_CMD , vrbProcSetSubId3 , "SetSubId3 " }, - { 0x0007FF00, CODEC_VERB_8BIT_CMD , vrbProcReset , "Reset " }, - { 0x000F0500, CODEC_VERB_8BIT_CMD , vrbProcGetPowerState , "GetPowerState " }, - { 0x00070500, CODEC_VERB_8BIT_CMD , vrbProcSetPowerState , "SetPowerState " }, - { 0x000F0C00, CODEC_VERB_8BIT_CMD , vrbProcGetEAPD_BTLEnabled , "GetEAPD_BTLEnabled " }, - { 0x00070C00, CODEC_VERB_8BIT_CMD , vrbProcSetEAPD_BTLEnabled , "SetEAPD_BTLEnabled " }, - { 0x000F0F00, CODEC_VERB_8BIT_CMD , vrbProcGetVolumeKnobCtrl , "GetVolumeKnobCtrl " }, - { 0x00070F00, CODEC_VERB_8BIT_CMD , vrbProcSetVolumeKnobCtrl , "SetVolumeKnobCtrl " }, - { 0x000F1500, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOData , "GetGPIOData " }, - { 0x00071500, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOData , "SetGPIOData " }, - { 0x000F1600, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOEnableMask , "GetGPIOEnableMask " }, - { 0x00071600, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOEnableMask , "SetGPIOEnableMask " }, - { 0x000F1700, CODEC_VERB_8BIT_CMD , vrbProcGetGPIODirection , "GetGPIODirection " }, - { 0x00071700, CODEC_VERB_8BIT_CMD , vrbProcSetGPIODirection , "SetGPIODirection " }, - { 0x000F1C00, CODEC_VERB_8BIT_CMD , vrbProcGetConfig , "GetConfig " }, - { 0x00071C00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig0 , "SetConfig0 " }, - { 0x00071D00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig1 , "SetConfig1 " }, - { 0x00071E00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig2 , "SetConfig2 " }, - { 0x00071F00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig3 , "SetConfig3 " }, - { 0x000A0000, CODEC_VERB_16BIT_CMD, vrbProcGetConverterFormat , "GetConverterFormat " }, - { 0x00020000, CODEC_VERB_16BIT_CMD, vrbProcSetConverterFormat , "SetConverterFormat " }, - { 0x000B0000, CODEC_VERB_16BIT_CMD, vrbProcGetAmplifier , "GetAmplifier " }, - { 0x00030000, CODEC_VERB_16BIT_CMD, vrbProcSetAmplifier , "SetAmplifier " }, - { 0x000F0400, CODEC_VERB_8BIT_CMD , vrbProcGetSDISelect , "GetSDISelect " }, - { 0x00070400, CODEC_VERB_8BIT_CMD , vrbProcSetSDISelect , "SetSDISelect " } - /** @todo Implement 0x7e7: IDT Set GPIO (STAC922x only). */ -}; - - -/** - * CODEC debug info item printing state. - */ -typedef struct CODECDEBUG -{ - /** DBGF info helpers. */ - PCDBGFINFOHLP pHlp; - /** Current recursion level. */ - uint8_t uLevel; - /** Pointer to codec state. */ - PHDACODEC pThis; -} CODECDEBUG; -/** Pointer to the debug info item printing state for the codec. */ -typedef CODECDEBUG *PCODECDEBUG; - -#define CODECDBG_INDENT pInfo->uLevel++; -#define CODECDBG_UNINDENT if (pInfo->uLevel) pInfo->uLevel--; - -#define CODECDBG_PRINT(...) pInfo->pHlp->pfnPrintf(pInfo->pHlp, __VA_ARGS__) -#define CODECDBG_PRINTI(...) codecDbgPrintf(pInfo, __VA_ARGS__) - -/** Wrapper around DBGFINFOHLP::pfnPrintf that adds identation. */ -static void codecDbgPrintf(PCODECDEBUG pInfo, const char *pszFormat, ...) -{ - va_list va; - va_start(va, pszFormat); - pInfo->pHlp->pfnPrintf(pInfo->pHlp, "%*s%N", pInfo->uLevel * 4, "", pszFormat, &va); - va_end(va); -} - -/** Power state */ -static void codecDbgPrintNodeRegF05(PCODECDEBUG pInfo, uint32_t u32Reg) -{ - codecDbgPrintf(pInfo, "Power (F05): fReset=%RTbool, fStopOk=%RTbool, Set=%RU8, Act=%RU8\n", - CODEC_F05_IS_RESET(u32Reg), CODEC_F05_IS_STOPOK(u32Reg), CODEC_F05_SET(u32Reg), CODEC_F05_ACT(u32Reg)); -} - -static void codecDbgPrintNodeRegA(PCODECDEBUG pInfo, uint32_t u32Reg) -{ - codecDbgPrintf(pInfo, "RegA: %x\n", u32Reg); -} - -static void codecDbgPrintNodeRegF00(PCODECDEBUG pInfo, uint32_t *paReg00) -{ - codecDbgPrintf(pInfo, "Parameters (F00):\n"); - - CODECDBG_INDENT - codecDbgPrintf(pInfo, "Connections: %RU8\n", CODEC_F00_0E_COUNT(paReg00[0xE])); - codecDbgPrintf(pInfo, "Amplifier Caps:\n"); - uint32_t uReg = paReg00[0xD]; - CODECDBG_INDENT - codecDbgPrintf(pInfo, "Input Steps=%02RU8, StepSize=%02RU8, StepOff=%02RU8, fCanMute=%RTbool\n", - CODEC_F00_0D_NUM_STEPS(uReg), - CODEC_F00_0D_STEP_SIZE(uReg), - CODEC_F00_0D_OFFSET(uReg), - RT_BOOL(CODEC_F00_0D_IS_CAP_MUTE(uReg))); - - uReg = paReg00[0x12]; - codecDbgPrintf(pInfo, "Output Steps=%02RU8, StepSize=%02RU8, StepOff=%02RU8, fCanMute=%RTbool\n", - CODEC_F00_12_NUM_STEPS(uReg), - CODEC_F00_12_STEP_SIZE(uReg), - CODEC_F00_12_OFFSET(uReg), - RT_BOOL(CODEC_F00_12_IS_CAP_MUTE(uReg))); - CODECDBG_UNINDENT - CODECDBG_UNINDENT -} - -static void codecDbgPrintNodeAmp(PCODECDEBUG pInfo, uint32_t *paReg, uint8_t uIdx, uint8_t uDir) -{ -# define CODECDBG_AMP(reg, chan) \ - codecDbgPrintf(pInfo, "Amp %RU8 %s %s: In=%RTbool, Out=%RTbool, Left=%RTbool, Right=%RTbool, Idx=%RU8, fMute=%RTbool, uGain=%RU8\n", \ - uIdx, chan, uDir == AMPLIFIER_IN ? "In" : "Out", \ - RT_BOOL(CODEC_SET_AMP_IS_IN_DIRECTION(reg)), RT_BOOL(CODEC_SET_AMP_IS_OUT_DIRECTION(reg)), \ - RT_BOOL(CODEC_SET_AMP_IS_LEFT_SIDE(reg)), RT_BOOL(CODEC_SET_AMP_IS_RIGHT_SIDE(reg)), \ - CODEC_SET_AMP_INDEX(reg), RT_BOOL(CODEC_SET_AMP_MUTE(reg)), CODEC_SET_AMP_GAIN(reg)) - - uint32_t regAmp = AMPLIFIER_REGISTER(paReg, uDir, AMPLIFIER_LEFT, uIdx); - CODECDBG_AMP(regAmp, "Left"); - regAmp = AMPLIFIER_REGISTER(paReg, uDir, AMPLIFIER_RIGHT, uIdx); - CODECDBG_AMP(regAmp, "Right"); - -# undef CODECDBG_AMP -} - -# if 0 /* unused */ -static void codecDbgPrintNodeConnections(PCODECDEBUG pInfo, PCODECNODE pNode) -{ - if (pNode->node.au32F00_param[0xE] == 0) /* Directly connected to HDA link. */ - { - codecDbgPrintf(pInfo, "[HDA LINK]\n"); - return; - } -} -# endif - -static void codecDbgPrintNode(PCODECDEBUG pInfo, PCODECNODE pNode, bool fRecursive) -{ - codecDbgPrintf(pInfo, "Node 0x%02x (%02RU8): ", pNode->node.uID, pNode->node.uID); - - if (pNode->node.uID == STAC9220_NID_ROOT) - { - CODECDBG_PRINT("ROOT\n"); - } - else if (pNode->node.uID == STAC9220_NID_AFG) - { - CODECDBG_PRINT("AFG\n"); - CODECDBG_INDENT - codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); - codecDbgPrintNodeRegF05(pInfo, pNode->afg.u32F05_param); - CODECDBG_UNINDENT - } - else if (hdaCodecIsPortNode(pInfo->pThis, pNode->node.uID)) - { - CODECDBG_PRINT("PORT\n"); - } - else if (hdaCodecIsDacNode(pInfo->pThis, pNode->node.uID)) - { - CODECDBG_PRINT("DAC\n"); - CODECDBG_INDENT - codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); - codecDbgPrintNodeRegF05(pInfo, pNode->dac.u32F05_param); - codecDbgPrintNodeRegA (pInfo, pNode->dac.u32A_param); - codecDbgPrintNodeAmp (pInfo, pNode->dac.B_params, 0, AMPLIFIER_OUT); - CODECDBG_UNINDENT - } - else if (hdaCodecIsAdcVolNode(pInfo->pThis, pNode->node.uID)) - { - CODECDBG_PRINT("ADC VOLUME\n"); - CODECDBG_INDENT - codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); - codecDbgPrintNodeRegA (pInfo, pNode->adcvol.u32A_params); - codecDbgPrintNodeAmp (pInfo, pNode->adcvol.B_params, 0, AMPLIFIER_IN); - CODECDBG_UNINDENT - } - else if (hdaCodecIsAdcNode(pInfo->pThis, pNode->node.uID)) - { - CODECDBG_PRINT("ADC\n"); - CODECDBG_INDENT - codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); - codecDbgPrintNodeRegF05(pInfo, pNode->adc.u32F05_param); - codecDbgPrintNodeRegA (pInfo, pNode->adc.u32A_param); - codecDbgPrintNodeAmp (pInfo, pNode->adc.B_params, 0, AMPLIFIER_IN); - CODECDBG_UNINDENT - } - else if (hdaCodecIsAdcMuxNode(pInfo->pThis, pNode->node.uID)) - { - CODECDBG_PRINT("ADC MUX\n"); - CODECDBG_INDENT - codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); - codecDbgPrintNodeRegA (pInfo, pNode->adcmux.u32A_param); - codecDbgPrintNodeAmp (pInfo, pNode->adcmux.B_params, 0, AMPLIFIER_IN); - CODECDBG_UNINDENT - } - else if (hdaCodecIsPcbeepNode(pInfo->pThis, pNode->node.uID)) - CODECDBG_PRINT("PC BEEP\n"); - else if (hdaCodecIsSpdifOutNode(pInfo->pThis, pNode->node.uID)) - CODECDBG_PRINT("SPDIF OUT\n"); - else if (hdaCodecIsSpdifInNode(pInfo->pThis, pNode->node.uID)) - CODECDBG_PRINT("SPDIF IN\n"); - else if (hdaCodecIsDigInPinNode(pInfo->pThis, pNode->node.uID)) - CODECDBG_PRINT("DIGITAL IN PIN\n"); - else if (hdaCodecIsDigOutPinNode(pInfo->pThis, pNode->node.uID)) - CODECDBG_PRINT("DIGITAL OUT PIN\n"); - else if (hdaCodecIsCdNode(pInfo->pThis, pNode->node.uID)) - CODECDBG_PRINT("CD\n"); - else if (hdaCodecIsVolKnobNode(pInfo->pThis, pNode->node.uID)) - CODECDBG_PRINT("VOLUME KNOB\n"); - else if (hdaCodecIsReservedNode(pInfo->pThis, pNode->node.uID)) - CODECDBG_PRINT("RESERVED\n"); - else - CODECDBG_PRINT("UNKNOWN TYPE 0x%x\n", pNode->node.uID); - - if (fRecursive) - { -# define CODECDBG_PRINT_CONLIST_ENTRY(_aNode, _aEntry) \ - if (cCnt >= _aEntry) \ - { \ - const uint8_t uID = RT_BYTE##_aEntry(_aNode->node.au32F02_param[0x0]); \ - if (pNode->node.uID == uID) \ - codecDbgPrintNode(pInfo, _aNode, false /* fRecursive */); \ - } - - /* Slow recursion, but this is debug stuff anyway. */ - for (uint8_t i = 0; i < pInfo->pThis->cTotalNodes; i++) - { - const PCODECNODE pSubNode = &pInfo->pThis->paNodes[i]; - if (pSubNode->node.uID == pNode->node.uID) - continue; - - const uint8_t cCnt = CODEC_F00_0E_COUNT(pSubNode->node.au32F00_param[0xE]); - if (cCnt == 0) /* No connections present? Skip. */ - continue; - - CODECDBG_INDENT - CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 1) - CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 2) - CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 3) - CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 4) - CODECDBG_UNINDENT - } - -# undef CODECDBG_PRINT_CONLIST_ENTRY - } -} - -static DECLCALLBACK(void) codecDbgListNodes(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - RT_NOREF(pszArgs); - pHlp->pfnPrintf(pHlp, "HDA LINK / INPUTS\n"); - - CODECDEBUG dbgInfo; - dbgInfo.pHlp = pHlp; - dbgInfo.pThis = pThis; - dbgInfo.uLevel = 0; - - PCODECDEBUG pInfo = &dbgInfo; - - CODECDBG_INDENT - for (uint8_t i = 0; i < pThis->cTotalNodes; i++) - { - PCODECNODE pNode = &pThis->paNodes[i]; - - /* Start with all nodes which have connection entries set. */ - if (CODEC_F00_0E_COUNT(pNode->node.au32F00_param[0xE])) - codecDbgPrintNode(&dbgInfo, pNode, true /* fRecursive */); - } - CODECDBG_UNINDENT -} - -#ifdef DEBUG - -static DECLCALLBACK(void) codecDbgSelector(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) -{ - RT_NOREF(pThis, pHlp, pszArgs); -} - -#endif /* DEBUG */ - -static DECLCALLBACK(int) codecLookup(PHDACODEC pThis, uint32_t cmd, uint64_t *puResp) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(puResp, VERR_INVALID_POINTER); - STAM_COUNTER_INC(&pThis->StatLookups); - - if (CODEC_CAD(cmd) != pThis->id) - { - *puResp = 0; - AssertMsgFailed(("Unknown codec address 0x%x\n", CODEC_CAD(cmd))); - return VERR_INVALID_PARAMETER; - } - - if ( CODEC_VERBDATA(cmd) == 0 - || CODEC_NID(cmd) >= pThis->cTotalNodes) - { - *puResp = 0; - AssertMsgFailed(("[NID0x%02x] Unknown / invalid node or data (0x%x)\n", CODEC_NID(cmd), CODEC_VERBDATA(cmd))); - return VERR_INVALID_PARAMETER; - } - - /** @todo r=andy Implement a binary search here. */ - for (size_t i = 0; i < pThis->cVerbs; i++) - { - if ((CODEC_VERBDATA(cmd) & pThis->paVerbs[i].mask) == pThis->paVerbs[i].verb) - { - int rc2 = pThis->paVerbs[i].pfn(pThis, cmd, puResp); - AssertRC(rc2); - Log3Func(("[NID0x%02x] (0x%x) %s: 0x%x -> 0x%x\n", - CODEC_NID(cmd), pThis->paVerbs[i].verb, pThis->paVerbs[i].pszName, CODEC_VERB_PAYLOAD8(cmd), *puResp)); - return rc2; - } - } - - *puResp = 0; - LogFunc(("[NID0x%02x] Callback for %x not found\n", CODEC_NID(cmd), CODEC_VERBDATA(cmd))); - return VERR_NOT_FOUND; -} - -/* - * APIs exposed to DevHDA. - */ - -int hdaCodecAddStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - int rc = VINF_SUCCESS; - - switch (enmMixerCtl) - { - case PDMAUDIOMIXERCTL_VOLUME_MASTER: - case PDMAUDIOMIXERCTL_FRONT: -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - case PDMAUDIOMIXERCTL_CENTER_LFE: - case PDMAUDIOMIXERCTL_REAR: -#endif - break; - - case PDMAUDIOMIXERCTL_LINE_IN: -#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - case PDMAUDIOMIXERCTL_MIC_IN: -#endif - break; - - default: - AssertMsgFailed(("Mixer control %d not implemented\n", enmMixerCtl)); - rc = VERR_NOT_IMPLEMENTED; - break; - } - - if (RT_SUCCESS(rc)) - rc = pThis->pfnCbMixerAddStream(pThis->pDevIns, enmMixerCtl, pCfg); - - LogFlowFuncLeaveRC(rc); - return rc; -} - -int hdaCodecRemoveStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl) -{ - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - - int rc = pThis->pfnCbMixerRemoveStream(pThis->pDevIns, enmMixerCtl); - - LogFlowFuncLeaveRC(rc); - return rc; -} - -int hdaCodecSaveState(PPDMDEVINS pDevIns, PHDACODEC pThis, PSSMHANDLE pSSM) -{ - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - AssertLogRelMsgReturn(pThis->cTotalNodes == STAC9221_NUM_NODES, ("cTotalNodes=%#x, should be 0x1c", pThis->cTotalNodes), - VERR_INTERNAL_ERROR); - pHlp->pfnSSMPutU32(pSSM, pThis->cTotalNodes); - for (unsigned idxNode = 0; idxNode < pThis->cTotalNodes; ++idxNode) - pHlp->pfnSSMPutStructEx(pSSM, &pThis->paNodes[idxNode].SavedState, sizeof(pThis->paNodes[idxNode].SavedState), - 0 /*fFlags*/, g_aCodecNodeFields, NULL /*pvUser*/); - return VINF_SUCCESS; -} - -int hdaCodecLoadState(PPDMDEVINS pDevIns, PHDACODEC pThis, PSSMHANDLE pSSM, uint32_t uVersion) -{ - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - PCSSMFIELD pFields = NULL; - uint32_t fFlags = 0; - if (uVersion >= HDA_SAVED_STATE_VERSION_4) - { - /* Since version 4 a flexible node count is supported. */ - uint32_t cNodes; - int rc2 = pHlp->pfnSSMGetU32(pSSM, &cNodes); - AssertRCReturn(rc2, rc2); - AssertReturn(cNodes == 0x1c, VERR_SSM_DATA_UNIT_FORMAT_CHANGED); - AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); - - pFields = g_aCodecNodeFields; - fFlags = 0; - } - else if (uVersion >= HDA_SAVED_STATE_VERSION_2) - { - AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); - pFields = g_aCodecNodeFields; - fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED; - } - else if (uVersion >= HDA_SAVED_STATE_VERSION_1) - { - AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); - pFields = g_aCodecNodeFieldsV1; - fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED; - } - else - AssertFailedReturn(VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); - - for (unsigned idxNode = 0; idxNode < pThis->cTotalNodes; ++idxNode) - { - uint8_t idOld = pThis->paNodes[idxNode].SavedState.Core.uID; - int rc = pHlp->pfnSSMGetStructEx(pSSM, &pThis->paNodes[idxNode].SavedState, sizeof(pThis->paNodes[idxNode].SavedState), - fFlags, pFields, NULL); - AssertRCReturn(rc, rc); - AssertLogRelMsgReturn(idOld == pThis->paNodes[idxNode].SavedState.Core.uID, - ("loaded %#x, expected %#x\n", pThis->paNodes[idxNode].SavedState.Core.uID, idOld), - VERR_SSM_DATA_UNIT_FORMAT_CHANGED); - } - - /* - * Update stuff after changing the state. - */ - PCODECNODE pNode; - if (hdaCodecIsDacNode(pThis, pThis->u8DacLineOut)) - { - pNode = &pThis->paNodes[pThis->u8DacLineOut]; - hdaCodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT); - } - else if (hdaCodecIsSpdifOutNode(pThis, pThis->u8DacLineOut)) - { - pNode = &pThis->paNodes[pThis->u8DacLineOut]; - hdaCodecToAudVolume(pThis, pNode, &pNode->spdifout.B_params, PDMAUDIOMIXERCTL_FRONT); - } - - pNode = &pThis->paNodes[pThis->u8AdcVolsLineIn]; - hdaCodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN); - - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; -} - -/** - * Powers off the codec. - * - * @param pThis Codec to power off. - */ -void hdaCodecPowerOff(PHDACODEC pThis) -{ - if (!pThis) - return; - - LogFlowFuncEnter(); - - LogRel2(("HDA: Powering off codec ...\n")); - - int rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_FRONT); - AssertRC(rc2); -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_CENTER_LFE); - AssertRC(rc2); - rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_REAR); - AssertRC(rc2); -#endif - -#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN - rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_MIC_IN); - AssertRC(rc2); -#endif - rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_LINE_IN); - AssertRC(rc2); -} - -void hdaCodecDestruct(PHDACODEC pThis) -{ - if (!pThis) - return; - - LogFlowFuncEnter(); - - if (pThis->paNodes) - { - RTMemFree(pThis->paNodes); - pThis->paNodes = NULL; - } -} - -int hdaCodecConstruct(PPDMDEVINS pDevIns, PHDACODEC pThis, - uint16_t uLUN, PCFGMNODE pCfg) -{ - AssertPtrReturn(pDevIns, VERR_INVALID_POINTER); - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pCfg, VERR_INVALID_POINTER); - - pThis->id = uLUN; - pThis->paVerbs = &g_aCodecVerbs[0]; - pThis->cVerbs = RT_ELEMENTS(g_aCodecVerbs); - -#ifdef DEBUG - pThis->pfnDbgSelector = codecDbgSelector; -#endif - pThis->pfnDbgListNodes = codecDbgListNodes; - pThis->pfnLookup = codecLookup; - - int rc = stac9220Construct(pThis); - AssertRCReturn(rc, rc); - - /* Common root node initializers. */ - pThis->paNodes[STAC9220_NID_ROOT].root.node.au32F00_param[0] = CODEC_MAKE_F00_00(pThis->u16VendorId, pThis->u16DeviceId); - pThis->paNodes[STAC9220_NID_ROOT].root.node.au32F00_param[4] = CODEC_MAKE_F00_04(0x1, 0x1); - - /* Common AFG node initializers. */ - pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x4] = CODEC_MAKE_F00_04(0x2, pThis->cTotalNodes - 2); - pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x5] = CODEC_MAKE_F00_05(1, CODEC_F00_05_AFG); - pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0xA] = CODEC_F00_0A_44_1KHZ | CODEC_F00_0A_16_BIT; - pThis->paNodes[STAC9220_NID_AFG].afg.u32F20_param = CODEC_MAKE_F20(pThis->u16VendorId, pThis->u8BSKU, pThis->u8AssemblyId); - - /* - * Set initial volume. - */ - PCODECNODE pNode = &pThis->paNodes[pThis->u8DacLineOut]; - hdaCodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT); - - pNode = &pThis->paNodes[pThis->u8AdcVolsLineIn]; - hdaCodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN); -#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN -# error "Implement mic-in support!" -#endif - - /* - * Statistics - */ -#ifdef VBOX_WITH_STATISTICS - PDMDevHlpSTAMRegister(pDevIns, &pThis->StatLookups, STAMTYPE_COUNTER, "Codec/Lookups", STAMUNIT_OCCURENCES, "Number of codecLookup calls"); -#endif - - LogFlowFuncLeaveRC(rc); - return rc; -} - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDACodec.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDACodec.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDACodec.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDACodec.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,200 +0,0 @@ -/* $Id: HDACodec.h $ */ -/** @file - * HDACodec - VBox HD Audio Codec. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_HDACodec_h -#define VBOX_INCLUDED_SRC_Audio_HDACodec_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include - -#include "AudioMixer.h" - -/** Pointer to a shared HDA device state. */ -typedef struct HDASTATE *PHDASTATE; -/** Pointer to a ring-3 HDA device state. */ -typedef struct HDASTATER3 *PHDASTATER3; -/** The ICH HDA (Intel) codec state. */ -typedef struct HDACODEC *PHDACODEC; -/** The HDA host driver backend. */ -typedef struct HDADRIVER *PHDADRIVER; - -/** - * Verb processor method. - */ -typedef DECLCALLBACK(int) FNHDACODECVERBPROCESSOR(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp); -typedef FNHDACODECVERBPROCESSOR *PFNHDACODECVERBPROCESSOR; - -/* PRM 5.3.1 */ -#define CODEC_RESPONSE_UNSOLICITED RT_BIT_64(34) - -typedef struct CODECVERB -{ - /** Verb. */ - uint32_t verb; - /** Verb mask. */ - uint32_t mask; - /** Function pointer for implementation callback. */ - PFNHDACODECVERBPROCESSOR pfn; - /** Friendly name, for debugging. */ - const char *pszName; -} CODECVERB; - -union CODECNODE; -typedef union CODECNODE CODECNODE, *PCODECNODE; - -/** - * HDA codec state. - */ -typedef struct HDACODEC -{ - uint16_t id; - uint16_t u16VendorId; - uint16_t u16DeviceId; - uint8_t u8BSKU; - uint8_t u8AssemblyId; - - /** List of assigned HDA drivers to this codec. - * A driver only can be assigned to one codec at a time. */ - RTLISTANCHOR lstDrv; - - CODECVERB const *paVerbs; - size_t cVerbs; - - PCODECNODE paNodes; - - bool fInReset; - uint8_t abPadding1[3]; - - const uint8_t cTotalNodes; - const uint8_t u8AdcVolsLineIn; - const uint8_t u8DacLineOut; - uint8_t bPadding2; - const uint8_t *au8Ports; - const uint8_t *au8Dacs; - const uint8_t *au8AdcVols; - const uint8_t *au8Adcs; - const uint8_t *au8AdcMuxs; - const uint8_t *au8Pcbeeps; - const uint8_t *au8SpdifIns; - const uint8_t *au8SpdifOuts; - const uint8_t *au8DigInPins; - const uint8_t *au8DigOutPins; - const uint8_t *au8Cds; - const uint8_t *au8VolKnobs; - const uint8_t *au8Reserveds; - - /** @name Public codec functions. - * @{ */ - DECLR3CALLBACKMEMBER(int, pfnLookup, (PHDACODEC pThis, uint32_t uVerb, uint64_t *puResp)); - DECLR3CALLBACKMEMBER(void, pfnReset, (PHDACODEC pThis)); - DECLR3CALLBACKMEMBER(int, pfnNodeReset, (PHDACODEC pThis, uint8_t, PCODECNODE)); - DECLR3CALLBACKMEMBER(void, pfnDbgListNodes, (PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)); - DECLR3CALLBACKMEMBER(void, pfnDbgSelector, (PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)); - /** @} */ - - /** The parent device instance. */ - PPDMDEVINS pDevIns; - - /** @name Callbacks to the HDA controller, mostly used for multiplexing to the - * various host backends. - * @{ */ - /** - * - * Adds a new audio stream to a specific mixer control. - * - * Depending on the mixer control the stream then gets assigned to one of the - * internal mixer sinks, which in turn then handle the mixing of all connected - * streams to that sink. - * - * @return VBox status code. - * @param pDevIns The device instance. - * @param enmMixerCtl Mixer control to assign new stream to. - * @param pCfg Stream configuration for the new stream. - */ - DECLR3CALLBACKMEMBER(int, pfnCbMixerAddStream, (PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg)); - /** - * Removes a specified mixer control from the HDA's mixer. - * - * @return VBox status code. - * @param pDevIns The device instance. - * @param enmMixerCtl Mixer control to remove. - */ - DECLR3CALLBACKMEMBER(int, pfnCbMixerRemoveStream, (PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl)); - /** - * Controls an input / output converter widget, that is, which converter is - * connected to which stream (and channel). - * - * @return VBox status code. - * @param pDevIns The device instance. - * @param enmMixerCtl Mixer control to set SD stream number and channel for. - * @param uSD SD stream number (number + 1) to set. Set to 0 for unassign. - * @param uChannel Channel to set. Only valid if a valid SD stream number is specified. - */ - DECLR3CALLBACKMEMBER(int, pfnCbMixerControl, (PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel)); - /** - * Sets the volume of a specified mixer control. - * - * @return IPRT status code. - * @param pDevIns The device instance. - * @param enmMixerCtl Mixer control to set volume for. - * @param pVol Pointer to volume data to set. - */ - DECLR3CALLBACKMEMBER(int, pfnCbMixerSetVolume, (PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol)); - /** @} */ - -#ifdef VBOX_WITH_STATISTICS - STAMCOUNTER StatLookups; -#endif -} HDACODEC; - -int hdaCodecConstruct(PPDMDEVINS pDevIns, PHDACODEC pThis, uint16_t uLUN, PCFGMNODE pCfg); -void hdaCodecDestruct(PHDACODEC pThis); -void hdaCodecPowerOff(PHDACODEC pThis); -int hdaCodecSaveState(PPDMDEVINS pDevIns, PHDACODEC pThis, PSSMHANDLE pSSM); -int hdaCodecLoadState(PPDMDEVINS pDevIns, PHDACODEC pThis, PSSMHANDLE pSSM, uint32_t uVersion); -int hdaCodecAddStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg); -int hdaCodecRemoveStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl); - -/** @name DevHDA saved state versions - * @{ */ -/** Added (Controller): Current wall clock value (this independent from WALCLK register value). - * Added (Controller): Current IRQ level. - * Added (Per stream): Ring buffer. This is optional and can be skipped if (not) needed. - * Added (Per stream): Struct g_aSSMStreamStateFields7. - * Added (Per stream): Struct g_aSSMStreamPeriodFields7. - * Added (Current BDLE per stream): Struct g_aSSMBDLEDescFields7. - * Added (Current BDLE per stream): Struct g_aSSMBDLEStateFields7. */ -#define HDA_SAVED_STATE_VERSION 7 -/** Saves the current BDLE state. - * @since 5.0.14 (r104839) */ -#define HDA_SAVED_STATE_VERSION_6 6 -/** Introduced dynamic number of streams + stream identifiers for serialization. - * Bug: Did not save the BDLE states correctly. - * Those will be skipped on load then. - * @since 5.0.12 (r104520) */ -#define HDA_SAVED_STATE_VERSION_5 5 -/** Since this version the number of MMIO registers can be flexible. */ -#define HDA_SAVED_STATE_VERSION_4 4 -#define HDA_SAVED_STATE_VERSION_3 3 -#define HDA_SAVED_STATE_VERSION_2 2 -#define HDA_SAVED_STATE_VERSION_1 1 -/** @} */ - -#endif /* !VBOX_INCLUDED_SRC_Audio_HDACodec_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamChannel.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamChannel.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamChannel.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamChannel.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -/* $Id: HDAStreamChannel.cpp $ */ -/** @file - * HDAStreamChannel.cpp - Stream channel functions for HD Audio. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DEV_HDA -#include - -#include -#include - -#include "HDAStreamChannel.h" - -/** - * Initializes a stream channel data structure. - * - * @returns IPRT status code. - * @param pChanData Channel data to initialize. - * @param fFlags - */ -int hdaR3StreamChannelDataInit(PPDMAUDIOSTREAMCHANNELDATA pChanData, uint32_t fFlags) -{ - int rc = RTCircBufCreate(&pChanData->pCircBuf, 256); /** @todo Make this configurable? */ - if (RT_SUCCESS(rc)) - { - pChanData->fFlags = fFlags; - } - - return rc; -} - -/** - * Destroys a stream channel data structure. - * - * @param pChanData Channel data to destroy. - */ -void hdaR3StreamChannelDataDestroy(PPDMAUDIOSTREAMCHANNELDATA pChanData) -{ - if (!pChanData) - return; - - if (pChanData->pCircBuf) - { - RTCircBufDestroy(pChanData->pCircBuf); - pChanData->pCircBuf = NULL; - } - - pChanData->fFlags = PDMAUDIOSTREAMCHANNELDATA_FLAGS_NONE; -} - -/** - * Acquires (reads) audio channel data. - * Must be released when done with hdaR3StreamChannelReleaseData(). - * - * @returns IPRT status code. - * @param pChanData Channel data to acquire audio channel data from. - * @param ppvData Where to store the pointer to the acquired data. - * @param pcbData Size (in bytes) of acquired data. - */ -int hdaR3StreamChannelAcquireData(PPDMAUDIOSTREAMCHANNELDATA pChanData, void **ppvData, size_t *pcbData) -{ - AssertPtrReturn(pChanData, VERR_INVALID_POINTER); - AssertPtrReturn(ppvData, VERR_INVALID_POINTER); - AssertPtrReturn(pcbData, VERR_INVALID_POINTER); - - RTCircBufAcquireReadBlock(pChanData->pCircBuf, 256 /** @todo Make this configurarble? */, ppvData, &pChanData->cbAcq); - - *pcbData = pChanData->cbAcq; - return VINF_SUCCESS; -} - -/** - * Releases formerly acquired data by hdaR3StreamChannelAcquireData(). - * - * @returns IPRT status code. - * @param pChanData Channel data to release formerly acquired data for. - */ -int hdaR3StreamChannelReleaseData(PPDMAUDIOSTREAMCHANNELDATA pChanData) -{ - AssertPtrReturn(pChanData, VERR_INVALID_POINTER); - RTCircBufReleaseReadBlock(pChanData->pCircBuf, pChanData->cbAcq); - - return VINF_SUCCESS; -} - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamChannel.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamChannel.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamChannel.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamChannel.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -/* $Id: HDAStreamChannel.h $ */ -/** @file - * HDAStreamChannel.h - Stream channel functions for HD Audio. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h -#define VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -int hdaR3StreamChannelDataInit(PPDMAUDIOSTREAMCHANNELDATA pChanData, uint32_t fFlags); -void hdaR3StreamChannelDataDestroy(PPDMAUDIOSTREAMCHANNELDATA pChanData); -int hdaR3StreamChannelAcquireData(PPDMAUDIOSTREAMCHANNELDATA pChanData, void *ppvData, size_t *pcbData); -int hdaR3StreamChannelReleaseData(PPDMAUDIOSTREAMCHANNELDATA pChanData); - -#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStream.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStream.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStream.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStream.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,2046 +0,0 @@ -/* $Id: HDAStream.cpp $ */ -/** @file - * HDAStream.cpp - Stream functions for HD Audio. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DEV_HDA -#include - -#include -#include - -#include -#include -#include - -#include "DrvAudio.h" - -#include "DevHDA.h" -#include "HDAStream.h" - -#ifdef IN_RING3 /* whole file */ - - -/********************************************************************************************************************************* -* Internal Functions * -*********************************************************************************************************************************/ -static void hdaR3StreamSetPosition(PHDASTREAM pStreamShared, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t u32LPIB); - -static int hdaR3StreamAsyncIODestroy(PHDASTREAMR3 pStreamR3); -static int hdaR3StreamAsyncIONotify(PHDASTREAMR3 pStreamR3); - - - -/** - * Creates an HDA stream. - * - * @returns IPRT status code. - * @param pStreamShared The HDA stream to construct - shared bits. - * @param pStreamR3 The HDA stream to construct - ring-3 bits. - * @param pThis The shared HDA device instance. - * @param pThisCC The ring-3 HDA device instance. - * @param uSD Stream descriptor number to assign. - */ -int hdaR3StreamConstruct(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PHDASTATE pThis, PHDASTATER3 pThisCC, uint8_t uSD) -{ - int rc; - - pStreamR3->u8SD = uSD; - pStreamShared->u8SD = uSD; - pStreamR3->pMixSink = NULL; - pStreamR3->pHDAStateShared = pThis; - pStreamR3->pHDAStateR3 = pThisCC; - Assert(pStreamShared->hTimer != NIL_TMTIMERHANDLE); /* hdaR3Construct initalized this one already. */ - - pStreamShared->State.fInReset = false; - pStreamShared->State.fRunning = false; -#ifdef HDA_USE_DMA_ACCESS_HANDLER - RTListInit(&pStreamR3->State.lstDMAHandlers); -#endif - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - rc = RTCritSectInit(&pStreamR3->CritSect); - AssertRCReturn(rc, rc); -# endif - - rc = hdaR3StreamPeriodCreate(&pStreamShared->State.Period); - AssertRCReturn(rc, rc); - - pStreamShared->State.tsLastUpdateNs = 0; - -#ifdef DEBUG - rc = RTCritSectInit(&pStreamR3->Dbg.CritSect); - AssertRCReturn(rc, rc); -#endif - - const bool fIsInput = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN; - - if (fIsInput) - { - pStreamShared->State.Cfg.u.enmSrc = PDMAUDIORECSRC_UNKNOWN; - pStreamShared->State.Cfg.enmDir = PDMAUDIODIR_IN; - } - else - { - pStreamShared->State.Cfg.u.enmDst = PDMAUDIOPLAYBACKDST_UNKNOWN; - pStreamShared->State.Cfg.enmDir = PDMAUDIODIR_OUT; - } - - pStreamR3->Dbg.Runtime.fEnabled = pThisCC->Dbg.fEnabled; - - if (pStreamR3->Dbg.Runtime.fEnabled) - { - char szFile[64]; - char szPath[RTPATH_MAX]; - - /* pFileStream */ - if (fIsInput) - RTStrPrintf(szFile, sizeof(szFile), "hdaStreamWriteSD%RU8", uSD); - else - RTStrPrintf(szFile, sizeof(szFile), "hdaStreamReadSD%RU8", uSD); - - int rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThisCC->Dbg.pszOutPath, szFile, - 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - AssertRC(rc2); - - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAGS_NONE, &pStreamR3->Dbg.Runtime.pFileStream); - AssertRC(rc2); - - /* pFileDMARaw */ - if (fIsInput) - RTStrPrintf(szFile, sizeof(szFile), "hdaDMARawWriteSD%RU8", uSD); - else - RTStrPrintf(szFile, sizeof(szFile), "hdaDMARawReadSD%RU8", uSD); - - rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThisCC->Dbg.pszOutPath, szFile, - 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - AssertRC(rc2); - - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAGS_NONE, &pStreamR3->Dbg.Runtime.pFileDMARaw); - AssertRC(rc2); - - /* pFileDMAMapped */ - if (fIsInput) - RTStrPrintf(szFile, sizeof(szFile), "hdaDMAWriteMappedSD%RU8", uSD); - else - RTStrPrintf(szFile, sizeof(szFile), "hdaDMAReadMappedSD%RU8", uSD); - - rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThisCC->Dbg.pszOutPath, szFile, - 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE); - AssertRC(rc2); - - rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAGS_NONE, &pStreamR3->Dbg.Runtime.pFileDMAMapped); - AssertRC(rc2); - - /* Delete stale debugging files from a former run. */ - DrvAudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileStream); - DrvAudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileDMARaw); - DrvAudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileDMAMapped); - } - - return rc; -} - -/** - * Destroys an HDA stream. - * - * @param pStreamShared The HDA stream to destroy - shared bits. - * @param pStreamR3 The HDA stream to destroy - ring-3 bits. - */ -void hdaR3StreamDestroy(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3) -{ - LogFlowFunc(("[SD%RU8] Destroying ...\n", pStreamShared->u8SD)); - - hdaR3StreamMapDestroy(&pStreamR3->State.Mapping); - - int rc2; - -#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - rc2 = hdaR3StreamAsyncIODestroy(pStreamR3); - AssertRC(rc2); -#endif - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - if (RTCritSectIsInitialized(&pStreamR3->CritSect)) - { - rc2 = RTCritSectDelete(&pStreamR3->CritSect); - AssertRC(rc2); - } -# endif - - if (pStreamR3->State.pCircBuf) - { - RTCircBufDestroy(pStreamR3->State.pCircBuf); - pStreamR3->State.pCircBuf = NULL; - } - - hdaR3StreamPeriodDestroy(&pStreamShared->State.Period); - -#ifdef DEBUG - if (RTCritSectIsInitialized(&pStreamR3->Dbg.CritSect)) - { - rc2 = RTCritSectDelete(&pStreamR3->Dbg.CritSect); - AssertRC(rc2); - } -#endif - - if (pStreamR3->Dbg.Runtime.fEnabled) - { - DrvAudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileStream); - pStreamR3->Dbg.Runtime.pFileStream = NULL; - - DrvAudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileDMARaw); - pStreamR3->Dbg.Runtime.pFileDMARaw = NULL; - - DrvAudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileDMAMapped); - pStreamR3->Dbg.Runtime.pFileDMAMapped = NULL; - } - - LogFlowFuncLeave(); -} - -/** - * Sets up ((re-)iniitalizes) an HDA stream. - * - * @returns IPRT status code. VINF_NO_CHANGE if the stream does not need - * be set-up again because the stream's (hardware) parameters did - * not change. - * @param pDevIns The device instance. - * @param pThis The shared HDA device state (for HW register - * parameters). - * @param pStreamShared HDA stream to set up, shared portion. - * @param pStreamR3 HDA stream to set up, ring-3 portion. - * @param uSD Stream descriptor number to assign it. - */ -int hdaR3StreamSetUp(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD) -{ - /* This must be valid all times. */ - AssertReturn(uSD < HDA_MAX_STREAMS, VERR_INVALID_PARAMETER); - - /* These member can only change on data corruption, despite what the code does further down (bird). */ - AssertReturn(pStreamShared->u8SD == uSD, VERR_WRONG_ORDER); - AssertReturn(pStreamR3->u8SD == uSD, VERR_WRONG_ORDER); - - const uint64_t u64BDLBase = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, uSD), - HDA_STREAM_REG(pThis, BDPU, uSD)); - const uint16_t u16LVI = HDA_STREAM_REG(pThis, LVI, uSD); - const uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, uSD); - const uint16_t u16FIFOS = HDA_STREAM_REG(pThis, FIFOS, uSD) + 1; - const uint16_t u16FMT = HDA_STREAM_REG(pThis, FMT, uSD); - - /* Is the bare minimum set of registers configured for the stream? - * If not, bail out early, as there's nothing to do here for us (yet). */ - if ( !u64BDLBase - || !u16LVI - || !u32CBL - || !u16FIFOS - || !u16FMT) - { - LogFunc(("[SD%RU8] Registers not set up yet, skipping (re-)initialization\n", uSD)); - return VINF_SUCCESS; - } - - PDMAUDIOPCMPROPS Props; - int rc = hdaR3SDFMTToPCMProps(u16FMT, &Props); - if (RT_FAILURE(rc)) - { - LogRel(("HDA: Warning: Format 0x%x for stream #%RU8 not supported\n", HDA_STREAM_REG(pThis, FMT, uSD), uSD)); - return rc; - } - - /* Reset (any former) stream map. */ - hdaR3StreamMapReset(&pStreamR3->State.Mapping); - - /* - * Initialize the stream mapping in any case, regardless if - * we support surround audio or not. This is needed to handle - * the supported channels within a single audio stream, e.g. mono/stereo. - * - * In other words, the stream mapping *always* knows the real - * number of channels in a single audio stream. - */ - rc = hdaR3StreamMapInit(&pStreamR3->State.Mapping, &Props); - AssertRCReturn(rc, rc); - - ASSERT_GUEST_LOGREL_MSG_RETURN( pStreamR3->State.Mapping.cbFrameSize > 0 - && u32CBL % pStreamR3->State.Mapping.cbFrameSize == 0, - ("CBL for stream #%RU8 does not align to frame size (u32CBL=%u cbFrameSize=%u)\n", - uSD, u32CBL, pStreamR3->State.Mapping.cbFrameSize), - VERR_INVALID_PARAMETER); - - /* - * Set the stream's timer Hz rate, based on the stream channel count. - * Currently this is just a rough guess and we might want to optimize this further. - * - * In any case, more channels per SDI/SDO means that we have to drive data more frequently. - */ - if (pThis->uTimerHz == HDA_TIMER_HZ_DEFAULT) /* Make sure that we don't have any custom Hz rate set we want to enforce */ - { - if (Props.cChannels >= 5) - pStreamShared->State.uTimerHz = 300; - else if (Props.cChannels == 4) - pStreamShared->State.uTimerHz = 150; - else - pStreamShared->State.uTimerHz = 100; - } - else - pStreamShared->State.uTimerHz = pThis->uTimerHz; - -#ifndef VBOX_WITH_AUDIO_HDA_51_SURROUND - if (Props.cChannels > 2) - { - /* - * When not running with surround support enabled, override the audio channel count - * with stereo (2) channels so that we at least can properly work with those. - * - * Note: This also involves dealing with surround setups the guest might has set up for us. - */ - LogRel2(("HDA: More than stereo (2) channels are not supported (%RU8 requested), " - "falling back to stereo channels for stream #%RU8\n", Props.cChannels, uSD)); - Props.cChannels = 2; - Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Props.cbSample, Props.cChannels); - } -#endif - - /* Did some of the vital / critical parameters change? - * If not, we can skip a lot of the (re-)initialization and just (re-)use the existing stuff. - * Also, tell the caller so that further actions can be taken. */ - if ( uSD == pStreamShared->u8SD /* paranoia OFC */ - && u64BDLBase == pStreamShared->u64BDLBase - && u16LVI == pStreamShared->u16LVI - && u32CBL == pStreamShared->u32CBL - && u16FIFOS == pStreamShared->u16FIFOS - && u16FMT == pStreamShared->u16FMT) - { - LogFunc(("[SD%RU8] No format change, skipping (re-)initialization\n", uSD)); - return VINF_NO_CHANGE; - } - - pStreamShared->u8SD = uSD; - - /* Update all register copies so that we later know that something has changed. */ - pStreamShared->u64BDLBase = u64BDLBase; - pStreamShared->u16LVI = u16LVI; - pStreamShared->u32CBL = u32CBL; - pStreamShared->u16FIFOS = u16FIFOS; - pStreamShared->u16FMT = u16FMT; - - PPDMAUDIOSTREAMCFG pCfg = &pStreamShared->State.Cfg; - pCfg->Props = Props; - - /* (Re-)Allocate the stream's internal DMA buffer, based on the PCM properties we just got above. */ - if (pStreamR3->State.pCircBuf) - { - RTCircBufDestroy(pStreamR3->State.pCircBuf); - pStreamR3->State.pCircBuf = NULL; - } - - /* By default we allocate an internal buffer of 100ms. */ - rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, - DrvAudioHlpMilliToBytes(100 /* ms */, &pCfg->Props)); /** @todo Make this configurable. */ - AssertRCReturn(rc, rc); - - /* Set the stream's direction. */ - pCfg->enmDir = hdaGetDirFromSD(uSD); - - /* The the stream's name, based on the direction. */ - switch (pCfg->enmDir) - { - case PDMAUDIODIR_IN: -# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN -# error "Implement me!" -# else - pCfg->u.enmSrc = PDMAUDIORECSRC_LINE; - pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; - RTStrCopy(pCfg->szName, sizeof(pCfg->szName), "Line In"); -# endif - break; - - case PDMAUDIODIR_OUT: - /* Destination(s) will be set in hdaAddStreamOut(), - * based on the channels / stream layout. */ - break; - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - /* Set scheduling hint (if available). */ - if (pStreamShared->State.uTimerHz) - pCfg->Device.cMsSchedulingHint = 1000 /* ms */ / pStreamShared->State.uTimerHz; - - LogFunc(("[SD%RU8] DMA @ 0x%x (%RU32 bytes), LVI=%RU16, FIFOS=%RU16\n", - uSD, pStreamShared->u64BDLBase, pStreamShared->u32CBL, pStreamShared->u16LVI, pStreamShared->u16FIFOS)); - - if (RT_SUCCESS(rc)) - { - /* Make sure that the chosen Hz rate dividable by the stream's rate. */ - if (pStreamShared->State.Cfg.Props.uHz % pStreamShared->State.uTimerHz != 0) - LogRel(("HDA: Stream timer Hz rate (%RU32) does not fit to stream #%RU8 timing (%RU32)\n", - pStreamShared->State.uTimerHz, uSD, pStreamShared->State.Cfg.Props.uHz)); - - /* Figure out how many transfer fragments we're going to use for this stream. */ - /** @todo Use a more dynamic fragment size? */ - uint8_t cFragments = pStreamShared->u16LVI + 1; - if (cFragments <= 1) - cFragments = 2; /* At least two fragments (BDLEs) must be present. */ - - /* - * Handle the stream's position adjustment. - */ - uint32_t cfPosAdjust = 0; - - LogFunc(("[SD%RU8] fPosAdjustEnabled=%RTbool, cPosAdjustFrames=%RU16\n", - uSD, pThis->fPosAdjustEnabled, pThis->cPosAdjustFrames)); - - if (pThis->fPosAdjustEnabled) /* Is the position adjustment enabled at all? */ - { - HDABDLE BDLE; - RT_ZERO(BDLE); - - int rc2 = hdaR3BDLEFetch(pDevIns, &BDLE, pStreamShared->u64BDLBase, 0 /* Entry */); - AssertRC(rc2); - - /* Note: Do *not* check if this BDLE aligns to the stream's frame size. - * It can happen that this isn't the case on some guests, e.g. - * on Windows with a 5.1 speaker setup. - * - * The only thing which counts is that the stream's CBL value - * properly aligns to the stream's frame size. - */ - - /* If no custom set position adjustment is set, apply some - * simple heuristics to detect the appropriate position adjustment. */ - if ( !pThis->cPosAdjustFrames - /* Position adjustmenet buffer *must* have the IOC bit set! */ - && hdaR3BDLENeedsInterrupt(&BDLE)) - { - /** @todo Implement / use a (dynamic) table once this gets more complicated. */ -#ifdef VBOX_WITH_INTEL_HDA - /* Intel ICH / PCH: 1 frame. */ - if (BDLE.Desc.u32BufSize == (uint32_t)(1 * pStreamR3->State.Mapping.cbFrameSize)) - { - cfPosAdjust = 1; - } - /* Intel Baytrail / Braswell: 32 frames. */ - else if (BDLE.Desc.u32BufSize == (uint32_t)(32 * pStreamR3->State.Mapping.cbFrameSize)) - { - cfPosAdjust = 32; - } -#endif - } - else /* Go with the set default. */ - cfPosAdjust = pThis->cPosAdjustFrames; - - if (cfPosAdjust) - { - /* Also adjust the number of fragments, as the position adjustment buffer - * does not count as an own fragment as such. - * - * This e.g. can happen on (newer) Ubuntu guests which use - * 4 (IOC) + 4408 (IOC) + 4408 (IOC) + 4408 (IOC) + 4404 (= 17632) bytes, - * where the first buffer (4) is used as position adjustment. - * - * Only skip a fragment if the whole buffer fragment is used for - * position adjustment. - */ - if ( (cfPosAdjust * pStreamR3->State.Mapping.cbFrameSize) == BDLE.Desc.u32BufSize - && cFragments) - { - cFragments--; - } - - /* Initialize position adjustment counter. */ - pStreamShared->State.cfPosAdjustDefault = cfPosAdjust; - pStreamShared->State.cfPosAdjustLeft = pStreamShared->State.cfPosAdjustDefault; - - LogRel2(("HDA: Position adjustment for stream #%RU8 active (%RU32 frames)\n", - uSD, pStreamShared->State.cfPosAdjustDefault)); - } - } - - LogFunc(("[SD%RU8] cfPosAdjust=%RU32, cFragments=%RU8\n", uSD, cfPosAdjust, cFragments)); - - /* - * Set up data transfer stuff. - */ - - /* Calculate the fragment size the guest OS expects interrupt delivery at. */ - pStreamShared->State.cbTransferSize = pStreamShared->u32CBL / cFragments; - Assert(pStreamShared->State.cbTransferSize); - Assert(pStreamShared->State.cbTransferSize % pStreamR3->State.Mapping.cbFrameSize == 0); - ASSERT_GUEST_LOGREL_MSG_STMT(pStreamShared->State.cbTransferSize, - ("Transfer size for stream #%RU8 is invalid\n", uSD), rc = VERR_INVALID_PARAMETER); - if (RT_SUCCESS(rc)) - { - /* Calculate the bytes we need to transfer to / from the stream's DMA per iteration. - * This is bound to the device's Hz rate and thus to the (virtual) timing the device expects. */ - pStreamShared->State.cbTransferChunk = (pStreamShared->State.Cfg.Props.uHz / pStreamShared->State.uTimerHz) * pStreamR3->State.Mapping.cbFrameSize; - Assert(pStreamShared->State.cbTransferChunk); - Assert(pStreamShared->State.cbTransferChunk % pStreamR3->State.Mapping.cbFrameSize == 0); - ASSERT_GUEST_LOGREL_MSG_STMT(pStreamShared->State.cbTransferChunk, - ("Transfer chunk for stream #%RU8 is invalid\n", uSD), - rc = VERR_INVALID_PARAMETER); - if (RT_SUCCESS(rc)) - { - /* Make sure that the transfer chunk does not exceed the overall transfer size. */ - if (pStreamShared->State.cbTransferChunk > pStreamShared->State.cbTransferSize) - pStreamShared->State.cbTransferChunk = pStreamShared->State.cbTransferSize; - - const uint64_t cTicksPerHz = PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer) / pStreamShared->State.uTimerHz; - - /* Calculate the timer ticks per byte for this stream. */ - pStreamShared->State.cTicksPerByte = cTicksPerHz / pStreamShared->State.cbTransferChunk; - Assert(pStreamShared->State.cTicksPerByte); - - /* Calculate timer ticks per transfer. */ - pStreamShared->State.cTransferTicks = pStreamShared->State.cbTransferChunk * pStreamShared->State.cTicksPerByte; - Assert(pStreamShared->State.cTransferTicks); - - LogFunc(("[SD%RU8] Timer %uHz (%RU64 ticks per Hz), cTicksPerByte=%RU64, cbTransferChunk=%RU32, " \ - "cTransferTicks=%RU64, cbTransferSize=%RU32\n", - uSD, pStreamShared->State.uTimerHz, cTicksPerHz, pStreamShared->State.cTicksPerByte, - pStreamShared->State.cbTransferChunk, pStreamShared->State.cTransferTicks, pStreamShared->State.cbTransferSize)); - - /* Make sure to also update the stream's DMA counter (based on its current LPIB value). */ - hdaR3StreamSetPosition(pStreamShared, pDevIns, pThis, HDA_STREAM_REG(pThis, LPIB, uSD)); - -#ifdef LOG_ENABLED - hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); -#endif - } - } - } - - if (RT_FAILURE(rc)) - LogRel(("HDA: Initializing stream #%RU8 failed with %Rrc\n", uSD, rc)); - - return rc; -} - -/** - * Resets an HDA stream. - * - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - * @param pStreamShared HDA stream to reset (shared). - * @param pStreamR3 HDA stream to reset (ring-3). - * @param uSD Stream descriptor (SD) number to use for this stream. - */ -void hdaR3StreamReset(PHDASTATE pThis, PHDASTATER3 pThisCC, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD) -{ - AssertPtr(pThis); - AssertPtr(pStreamShared); - AssertPtr(pStreamR3); - Assert(uSD < HDA_MAX_STREAMS); - AssertMsg(!pStreamShared->State.fRunning, ("[SD%RU8] Cannot reset stream while in running state\n", uSD)); - - LogFunc(("[SD%RU8] Reset\n", uSD)); - - /* - * Set reset state. - */ - Assert(ASMAtomicReadBool(&pStreamShared->State.fInReset) == false); /* No nested calls. */ - ASMAtomicXchgBool(&pStreamShared->State.fInReset, true); - - /* - * Second, initialize the registers. - */ - HDA_STREAM_REG(pThis, STS, uSD) = HDA_SDSTS_FIFORDY; - /* According to the ICH6 datasheet, 0x40000 is the default value for stream descriptor register 23:20 - * bits are reserved for stream number 18.2.33, resets SDnCTL except SRST bit. */ - HDA_STREAM_REG(pThis, CTL, uSD) = 0x40000 | (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_SRST); - /* ICH6 defines default values (120 bytes for input and 192 bytes for output descriptors) of FIFO size. 18.2.39. */ - HDA_STREAM_REG(pThis, FIFOS, uSD) = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN ? HDA_SDIFIFO_120B : HDA_SDOFIFO_192B; - /* See 18.2.38: Always defaults to 0x4 (32 bytes). */ - HDA_STREAM_REG(pThis, FIFOW, uSD) = HDA_SDFIFOW_32B; - HDA_STREAM_REG(pThis, LPIB, uSD) = 0; - HDA_STREAM_REG(pThis, CBL, uSD) = 0; - HDA_STREAM_REG(pThis, LVI, uSD) = 0; - HDA_STREAM_REG(pThis, FMT, uSD) = 0; - HDA_STREAM_REG(pThis, BDPU, uSD) = 0; - HDA_STREAM_REG(pThis, BDPL, uSD) = 0; - -#ifdef HDA_USE_DMA_ACCESS_HANDLER - hdaR3StreamUnregisterDMAHandlers(pThis, pStream); -#endif - - /* Assign the default mixer sink to the stream. */ - pStreamR3->pMixSink = hdaR3GetDefaultSink(pThisCC, uSD); - - /* Reset position adjustment counter. */ - pStreamShared->State.cfPosAdjustLeft = pStreamShared->State.cfPosAdjustDefault; - - /* Reset transfer stuff. */ - pStreamShared->State.cbTransferProcessed = 0; - pStreamShared->State.cTransferPendingInterrupts = 0; - pStreamShared->State.tsTransferLast = 0; - pStreamShared->State.tsTransferNext = 0; - - /* Initialize other timestamps. */ - pStreamShared->State.tsLastUpdateNs = 0; - - RT_ZERO(pStreamShared->State.BDLE); - pStreamShared->State.uCurBDLE = 0; - - if (pStreamR3->State.pCircBuf) - RTCircBufReset(pStreamR3->State.pCircBuf); - - /* Reset the stream's period. */ - hdaR3StreamPeriodReset(&pStreamShared->State.Period); - -#ifdef DEBUG - pStreamR3->Dbg.cReadsTotal = 0; - pStreamR3->Dbg.cbReadTotal = 0; - pStreamR3->Dbg.tsLastReadNs = 0; - pStreamR3->Dbg.cWritesTotal = 0; - pStreamR3->Dbg.cbWrittenTotal = 0; - pStreamR3->Dbg.cWritesHz = 0; - pStreamR3->Dbg.cbWrittenHz = 0; - pStreamR3->Dbg.tsWriteSlotBegin = 0; -#endif - - /* Report that we're done resetting this stream. */ - HDA_STREAM_REG(pThis, CTL, uSD) = 0; - - LogFunc(("[SD%RU8] Reset\n", uSD)); - - /* Exit reset mode. */ - ASMAtomicXchgBool(&pStreamShared->State.fInReset, false); -} - -/** - * Enables or disables an HDA audio stream. - * - * @returns IPRT status code. - * @param pStreamShared HDA stream to enable or disable - shared bits. - * @param pStreamR3 HDA stream to enable or disable - ring-3 bits. - * @param fEnable Whether to enable or disble the stream. - */ -int hdaR3StreamEnable(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fEnable) -{ - AssertPtr(pStreamR3); - AssertPtr(pStreamShared); - - LogFunc(("[SD%RU8] fEnable=%RTbool, pMixSink=%p\n", pStreamShared->u8SD, fEnable, pStreamR3->pMixSink)); - - int rc = VINF_SUCCESS; - - AUDMIXSINKCMD enmCmd = fEnable - ? AUDMIXSINKCMD_ENABLE : AUDMIXSINKCMD_DISABLE; - - /* First, enable or disable the stream and the stream's sink, if any. */ - if ( pStreamR3->pMixSink - && pStreamR3->pMixSink->pMixSink) - rc = AudioMixerSinkCtl(pStreamR3->pMixSink->pMixSink, enmCmd); - - if ( RT_SUCCESS(rc) - && fEnable - && pStreamR3->Dbg.Runtime.fEnabled) - { - Assert(DrvAudioHlpPCMPropsAreValid(&pStreamShared->State.Cfg.Props)); - - if (fEnable) - { - if (!DrvAudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileStream)) - { - int rc2 = DrvAudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileStream, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStreamShared->State.Cfg.Props); - AssertRC(rc2); - } - - if (!DrvAudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileDMARaw)) - { - int rc2 = DrvAudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileDMARaw, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStreamShared->State.Cfg.Props); - AssertRC(rc2); - } - - if (!DrvAudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileDMAMapped)) - { - int rc2 = DrvAudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileDMAMapped, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, - &pStreamShared->State.Cfg.Props); - AssertRC(rc2); - } - } - } - - if (RT_SUCCESS(rc)) - { - pStreamShared->State.fRunning = fEnable; - } - - LogFunc(("[SD%RU8] rc=%Rrc\n", pStreamShared->u8SD, rc)); - return rc; -} - -static uint32_t hdaR3StreamGetPosition(PHDASTATE pThis, PHDASTREAM pStreamShared) -{ - return HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD); -} - -/* - * Updates an HDA stream's current read or write buffer position (depending on the stream type) by - * updating its associated LPIB register and DMA position buffer (if enabled). - * - * @param pStreamShared HDA stream to update read / write position for (shared). - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param u32LPIB Absolute position (in bytes) to set current read / write position to. - */ -static void hdaR3StreamSetPosition(PHDASTREAM pStreamShared, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t u32LPIB) -{ - AssertPtrReturnVoid(pStreamShared); - - Log3Func(("[SD%RU8] LPIB=%RU32 (DMA Position Buffer Enabled: %RTbool)\n", pStreamShared->u8SD, u32LPIB, pThis->fDMAPosition)); - - /* Update LPIB in any case. */ - HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD) = u32LPIB; - - /* Do we need to tell the current DMA position? */ - if (pThis->fDMAPosition) - { - int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, - pThis->u64DPBase + (pStreamShared->u8SD * 2 * sizeof(uint32_t)), - (void *)&u32LPIB, sizeof(uint32_t)); - AssertRC(rc2); - } -} - -/** - * Retrieves the available size of (buffered) audio data (in bytes) of a given HDA stream. - * - * @returns Available data (in bytes). - * @param pStreamR3 HDA stream to retrieve size for (ring-3). - */ -static uint32_t hdaR3StreamGetUsed(PHDASTREAMR3 pStreamR3) -{ - AssertPtrReturn(pStreamR3, 0); - - if (pStreamR3->State.pCircBuf) - return (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); - return 0; -} - -/** - * Retrieves the free size of audio data (in bytes) of a given HDA stream. - * - * @returns Free data (in bytes). - * @param pStreamR3 HDA stream to retrieve size for (ring-3). - */ -static uint32_t hdaR3StreamGetFree(PHDASTREAMR3 pStreamR3) -{ - AssertPtrReturn(pStreamR3, 0); - - if (pStreamR3->State.pCircBuf) - return (uint32_t)RTCircBufFree(pStreamR3->State.pCircBuf); - return 0; -} - -/** - * Returns whether a next transfer for a given stream is scheduled or not. - * - * This takes pending stream interrupts into account as well as the next scheduled - * transfer timestamp. - * - * @returns True if a next transfer is scheduled, false if not. - * @param pStreamShared HDA stream to retrieve schedule status for (shared). - * @param tsNow The current time. - */ -bool hdaR3StreamTransferIsScheduled(PHDASTREAM pStreamShared, uint64_t tsNow) -{ - if (pStreamShared) - { - if (pStreamShared->State.fRunning) - { - if (pStreamShared->State.cTransferPendingInterrupts) - { - Log3Func(("[SD%RU8] Scheduled (%RU8 IRQs pending)\n", pStreamShared->u8SD, pStreamShared->State.cTransferPendingInterrupts)); - return true; - } - - if (pStreamShared->State.tsTransferNext > tsNow) - { - Log3Func(("[SD%RU8] Scheduled in %RU64\n", pStreamShared->u8SD, pStreamShared->State.tsTransferNext - tsNow)); - return true; - } - } - } - return false; -} - -/** - * Returns the (virtual) clock timestamp of the next transfer, if any. - * Will return 0 if no new transfer is scheduled. - * - * @returns The (virtual) clock timestamp of the next transfer. - * @param pStreamShared HDA stream to retrieve timestamp for (shared). - */ -uint64_t hdaR3StreamTransferGetNext(PHDASTREAM pStreamShared) -{ - return pStreamShared->State.tsTransferNext; -} - -/** - * Writes audio data from a mixer sink into an HDA stream's DMA buffer. - * - * @returns IPRT status code. - * @param pStreamR3 HDA stream to write to (ring-3). - * @param pvBuf Data buffer to write. - * If NULL, silence will be written. - * @param cbBuf Number of bytes of data buffer to write. - * @param pcbWritten Number of bytes written. Optional. - */ -static int hdaR3StreamWrite(PHDASTREAMR3 pStreamR3, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) -{ - Assert(cbBuf); - - PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf; - AssertPtr(pCircBuf); - - uint32_t cbWrittenTotal = 0; - uint32_t cbLeft = RT_MIN(cbBuf, (uint32_t)RTCircBufFree(pCircBuf)); - - while (cbLeft) - { - void *pvDst; - size_t cbDst; - RTCircBufAcquireWriteBlock(pCircBuf, cbLeft, &pvDst, &cbDst); - - if (cbDst) - { - if (pvBuf) - memcpy(pvDst, (uint8_t *)pvBuf + cbWrittenTotal, cbDst); - else /* Send silence. */ - { - /** @todo Use a sample spec for "silence" based on the PCM parameters. - * For now we ASSUME that silence equals NULLing the data. */ - RT_BZERO(pvDst, cbDst); - } - - if (RT_LIKELY(!pStreamR3->Dbg.Runtime.fEnabled)) - { /* likely */ } - else - DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileStream, pvDst, cbDst, 0 /* fFlags */); - } - - RTCircBufReleaseWriteBlock(pCircBuf, cbDst); - - Assert(cbLeft >= (uint32_t)cbDst); - cbLeft -= (uint32_t)cbDst; - cbWrittenTotal += (uint32_t)cbDst; - } - - Log3Func(("cbWrittenTotal=%RU32\n", cbWrittenTotal)); - - if (pcbWritten) - *pcbWritten = cbWrittenTotal; - - return VINF_SUCCESS; -} - - -/** - * Reads audio data from an HDA stream's DMA buffer and writes into a specified mixer sink. - * - * @returns IPRT status code. - * @param pStreamR3 HDA stream to read audio data from (ring-3). - * @param cbToRead Number of bytes to read. - * @param pcbRead Number of bytes read. Optional. - */ -static int hdaR3StreamRead(PHDASTREAMR3 pStreamR3, uint32_t cbToRead, uint32_t *pcbRead) -{ - Assert(cbToRead); - - PHDAMIXERSINK pSink = pStreamR3->pMixSink; - AssertMsgReturnStmt(pSink, ("[SD%RU8] Can't read from a stream with no sink attached\n", pStreamR3->u8SD), - if (pcbRead) *pcbRead = 0, - VINF_SUCCESS); - - PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf; - AssertPtr(pCircBuf); - - int rc = VINF_SUCCESS; - - uint32_t cbReadTotal = 0; - uint32_t cbLeft = RT_MIN(cbToRead, (uint32_t)RTCircBufUsed(pCircBuf)); - - while (cbLeft) - { - void *pvSrc; - size_t cbSrc; - - uint32_t cbWritten = 0; - - RTCircBufAcquireReadBlock(pCircBuf, cbLeft, &pvSrc, &cbSrc); - - if (cbSrc) - { - if (pStreamR3->Dbg.Runtime.fEnabled) - DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileStream, pvSrc, cbSrc, 0 /* fFlags */); - - rc = AudioMixerSinkWrite(pSink->pMixSink, AUDMIXOP_COPY, pvSrc, (uint32_t)cbSrc, &cbWritten); - AssertRC(rc); - - Assert(cbSrc >= cbWritten); - Log2Func(("[SD%RU8] %RU32/%zu bytes read\n", pStreamR3->u8SD, cbWritten, cbSrc)); - } - - RTCircBufReleaseReadBlock(pCircBuf, cbWritten); - - if ( !cbWritten /* Nothing written? */ - || RT_FAILURE(rc)) - break; - - Assert(cbLeft >= cbWritten); - cbLeft -= cbWritten; - - cbReadTotal += cbWritten; - } - - if (pcbRead) - *pcbRead = cbReadTotal; - - return rc; -} - -/** - * Transfers data of an HDA stream according to its usage (input / output). - * - * For an SDO (output) stream this means reading DMA data from the device to - * the HDA stream's internal FIFO buffer. - * - * For an SDI (input) stream this is reading audio data from the HDA stream's - * internal FIFO buffer and writing it as DMA data to the device. - * - * @returns IPRT status code. - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - * @param pStreamShared HDA stream to update (shared). - * @param pStreamR3 HDA stream to update (ring-3). - * @param cbToProcessMax How much data (in bytes) to process as maximum. - * @param fInTimer Set if we're in the timer callout. - */ -static int hdaR3StreamTransfer(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PHDASTREAM pStreamShared, - PHDASTREAMR3 pStreamR3, uint32_t cbToProcessMax, bool fInTimer) -{ - uint8_t const uSD = pStreamShared->u8SD; - hdaR3StreamLock(pStreamR3); - - PHDASTREAMPERIOD pPeriod = &pStreamShared->State.Period; - hdaR3StreamPeriodLock(pPeriod); - - bool fProceed = true; - - /* Stream not running? */ - if (!pStreamShared->State.fRunning) - { - Log3Func(("[SD%RU8] Not running\n", uSD)); - fProceed = false; - } - else if (HDA_STREAM_REG(pThis, STS, uSD) & HDA_SDSTS_BCIS) - { - Log3Func(("[SD%RU8] BCIS bit set\n", uSD)); - fProceed = false; - } - - if (!fProceed) - { - hdaR3StreamPeriodUnlock(pPeriod); - hdaR3StreamUnlock(pStreamR3); - return VINF_SUCCESS; - } - - const uint64_t tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); - - if (!pStreamShared->State.tsTransferLast) - pStreamShared->State.tsTransferLast = tsNow; - -#ifdef DEBUG - const int64_t iTimerDelta = tsNow - pStreamShared->State.tsTransferLast; - Log3Func(("[SD%RU8] Time now=%RU64, last=%RU64 -> %RI64 ticks delta\n", - uSD, tsNow, pStreamShared->State.tsTransferLast, iTimerDelta)); -#endif - - pStreamShared->State.tsTransferLast = tsNow; - - /* Sanity checks. */ - Assert(uSD < HDA_MAX_STREAMS); - Assert(pStreamShared->u64BDLBase); - Assert(pStreamShared->u32CBL); - Assert(pStreamShared->u16FIFOS); - - /* State sanity checks. */ - Assert(ASMAtomicReadBool(&pStreamShared->State.fInReset) == false); - - int rc = VINF_SUCCESS; - - /* Fetch first / next BDL entry. */ - PHDABDLE pBDLE = &pStreamShared->State.BDLE; - if (hdaR3BDLEIsComplete(pBDLE)) - { - rc = hdaR3BDLEFetch(pDevIns, pBDLE, pStreamShared->u64BDLBase, pStreamShared->State.uCurBDLE); - AssertRC(rc); - } - - uint32_t cbToProcess = RT_MIN(pStreamShared->State.cbTransferSize - pStreamShared->State.cbTransferProcessed, - pStreamShared->State.cbTransferChunk); - - Log3Func(("[SD%RU8] cbToProcess=%RU32, cbToProcessMax=%RU32\n", uSD, cbToProcess, cbToProcessMax)); - - if (cbToProcess > cbToProcessMax) - { - LogFunc(("[SD%RU8] Limiting transfer (cbToProcess=%RU32, cbToProcessMax=%RU32)\n", uSD, cbToProcess, cbToProcessMax)); - - /* Never process more than a stream currently can handle. */ - cbToProcess = cbToProcessMax; - } - - uint32_t cbProcessed = 0; - uint32_t cbLeft = cbToProcess; - - uint8_t abChunk[HDA_FIFO_MAX + 1]; - while (cbLeft) - { - /* Limit the chunk to the stream's FIFO size and what's left to process. */ - uint32_t cbChunk = RT_MIN(cbLeft, pStreamShared->u16FIFOS); - - /* Limit the chunk to the remaining data of the current BDLE. */ - cbChunk = RT_MIN(cbChunk, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff); - - /* If there are position adjustment frames left to be processed, - * make sure that we process them first as a whole. */ - if (pStreamShared->State.cfPosAdjustLeft) - cbChunk = RT_MIN(cbChunk, uint32_t(pStreamShared->State.cfPosAdjustLeft * pStreamR3->State.Mapping.cbFrameSize)); - - Log3Func(("[SD%RU8] cbChunk=%RU32, cPosAdjustFramesLeft=%RU16\n", - uSD, cbChunk, pStreamShared->State.cfPosAdjustLeft)); - - if (!cbChunk) - break; - - uint32_t cbDMA = 0; - PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf; - - if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN) /* Input (SDI). */ - { - STAM_PROFILE_START(&pThis->StatIn, a); - - uint32_t cbDMAWritten = 0; - uint32_t cbDMAToWrite = cbChunk; - - /** @todo Do we need interleaving streams support here as well? - * Never saw anything else besides mono/stereo mics (yet). */ - while (cbDMAToWrite) - { - void *pvBuf; size_t cbBuf; - RTCircBufAcquireReadBlock(pCircBuf, cbDMAToWrite, &pvBuf, &cbBuf); - - if ( !cbBuf - && !RTCircBufUsed(pCircBuf)) - break; - - memcpy(abChunk + cbDMAWritten, pvBuf, cbBuf); - - RTCircBufReleaseReadBlock(pCircBuf, cbBuf); - - Assert(cbDMAToWrite >= cbBuf); - cbDMAToWrite -= (uint32_t)cbBuf; - cbDMAWritten += (uint32_t)cbBuf; - Assert(cbDMAWritten <= cbChunk); - } - - if (cbDMAToWrite) - { - LogRel2(("HDA: FIFO underflow for stream #%RU8 (%RU32 bytes outstanding)\n", uSD, cbDMAToWrite)); - - Assert(cbChunk == cbDMAWritten + cbDMAToWrite); - memset((uint8_t *)abChunk + cbDMAWritten, 0, cbDMAToWrite); - cbDMAWritten = cbChunk; - } - - rc = hdaR3DMAWrite(pDevIns, pThis, pStreamShared, pStreamR3, abChunk, cbDMAWritten, &cbDMA /* pcbWritten */); - if (RT_FAILURE(rc)) - LogRel(("HDA: Writing to stream #%RU8 DMA failed with %Rrc\n", uSD, rc)); - - STAM_PROFILE_STOP(&pThis->StatIn, a); - } - else if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) /* Output (SDO). */ - { - STAM_PROFILE_START(&pThis->StatOut, a); - - rc = hdaR3DMARead(pDevIns, pThis, pStreamShared, pStreamR3, abChunk, cbChunk, &cbDMA /* pcbRead */); - if (RT_SUCCESS(rc)) - { - const uint32_t cbFree = (uint32_t)RTCircBufFree(pCircBuf); - - /* - * Most guests don't use different stream frame sizes than - * the default one, so save a bit of CPU time and don't go into - * the frame extraction code below. - * - * Only macOS guests need the frame extraction branch below at the moment AFAIK. - */ - if (pStreamR3->State.Mapping.cbFrameSize == HDA_FRAME_SIZE_DEFAULT) - { - uint32_t cbDMARead = 0; - uint32_t cbDMALeft = RT_MIN(cbDMA, cbFree); - - while (cbDMALeft) - { - void *pvBuf; size_t cbBuf; - RTCircBufAcquireWriteBlock(pCircBuf, cbDMALeft, &pvBuf, &cbBuf); - - if (cbBuf) - { - memcpy(pvBuf, abChunk + cbDMARead, cbBuf); - cbDMARead += (uint32_t)cbBuf; - cbDMALeft -= (uint32_t)cbBuf; - } - - RTCircBufReleaseWriteBlock(pCircBuf, cbBuf); - } - } - else - { - /* - * The following code extracts the required audio stream (channel) data - * of non-interleaved *and* interleaved audio streams. - * - * We by default only support 2 channels with 16-bit samples (HDA_FRAME_SIZE), - * but an HDA audio stream can have interleaved audio data of multiple audio - * channels in such a single stream ("AA,AA,AA vs. AA,BB,AA,BB"). - * - * So take this into account by just handling the first channel in such a stream ("A") - * and just discard the other channel's data. - * - * I know, the following code is horribly slow, but seems to work for now. - */ - /** @todo Optimize channel data extraction! Use some SSE(3) / intrinsics? */ - for (unsigned m = 0; m < pStreamR3->State.Mapping.cMappings; m++) - { - const uint32_t cbFrame = pStreamR3->State.Mapping.cbFrameSize; - - Assert(cbFree >= cbDMA); - - PPDMAUDIOSTREAMMAP pMap = &pStreamR3->State.Mapping.paMappings[m]; - AssertPtr(pMap); - - Log3Func(("Mapping #%u: Start (cbDMA=%RU32, cbFrame=%RU32, offNext=%RU32)\n", - m, cbDMA, cbFrame, pMap->offNext)); - - - /* Skip the current DMA chunk if the chunk is smaller than what the current stream mapping needs to read - * the next associated frame (pointed to at pMap->cbOff). - * - * This can happen if the guest did not come up with enough data within a certain time period, especially - * when using multi-channel speaker (> 2 channels [stereo]) setups. */ - if (pMap->offNext > cbChunk) - { - Log2Func(("Mapping #%u: Skipped (cbChunk=%RU32, cbMapOff=%RU32)\n", m, cbChunk, pMap->offNext)); - continue; - } - - uint8_t *pbSrcBuf = abChunk; - size_t cbSrcOff = pMap->offNext; - - for (unsigned i = 0; i < cbDMA / cbFrame; i++) - { - void *pvDstBuf; size_t cbDstBuf; - RTCircBufAcquireWriteBlock(pCircBuf, pMap->cbStep, &pvDstBuf, &cbDstBuf); - - Assert(cbDstBuf >= pMap->cbStep); - - if (cbDstBuf) - { - Log3Func(("Mapping #%u: Frame #%02u: cbStep=%u, offFirst=%u, offNext=%u, cbDstBuf=%u, cbSrcOff=%u\n", - m, i, pMap->cbStep, pMap->offFirst, pMap->offNext, cbDstBuf, cbSrcOff)); - - memcpy(pvDstBuf, pbSrcBuf + cbSrcOff, cbDstBuf); - -#if 0 /* Too slow, even for release builds, so disabled it. */ - if (pStreamR3->Dbg.Runtime.fEnabled) - DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMAMapped, pvDstBuf, cbDstBuf, - 0 /* fFlags */); -#endif - Assert(cbSrcOff <= cbDMA); - if (cbSrcOff + cbFrame + pMap->offFirst<= cbDMA) - cbSrcOff += cbFrame + pMap->offFirst; - - Log3Func(("Mapping #%u: Frame #%02u: -> cbSrcOff=%zu\n", m, i, cbSrcOff)); - } - - RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf); - } - - Log3Func(("Mapping #%u: End cbSize=%u, cbDMA=%RU32, cbSrcOff=%zu\n", - m, pMap->cbStep, cbDMA, cbSrcOff)); - - Assert(cbSrcOff <= cbDMA); - - const uint32_t cbSrcLeft = cbDMA - (uint32_t)cbSrcOff; - if (cbSrcLeft) - { - Log3Func(("Mapping #%u: cbSrcLeft=%RU32\n", m, cbSrcLeft)); - - if (cbSrcLeft >= pMap->cbStep) - { - void *pvDstBuf; size_t cbDstBuf; - RTCircBufAcquireWriteBlock(pCircBuf, pMap->cbStep, &pvDstBuf, &cbDstBuf); - - Assert(cbDstBuf >= pMap->cbStep); - - if (cbDstBuf) - { - memcpy(pvDstBuf, pbSrcBuf + cbSrcOff, cbDstBuf); - } - - RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf); - } - - Assert(pMap->cbFrame >= cbSrcLeft); - pMap->offNext = pMap->cbFrame - cbSrcLeft; - } - else - pMap->offNext = 0; - - Log3Func(("Mapping #%u finish (cbSrcOff=%zu, offNext=%zu)\n", m, cbSrcOff, pMap->offNext)); - } - } - } - else - LogRel(("HDA: Reading from stream #%RU8 DMA failed with %Rrc\n", uSD, rc)); - - STAM_PROFILE_STOP(&pThis->StatOut, a); - } - - else /** @todo Handle duplex streams? */ - AssertFailed(); - - if (cbDMA) - { - /* We always increment the position of DMA buffer counter because we're always reading - * into an intermediate DMA buffer. */ - pBDLE->State.u32BufOff += (uint32_t)cbDMA; - Assert(pBDLE->State.u32BufOff <= pBDLE->Desc.u32BufSize); - - /* Are we done doing the position adjustment? - * Only then do the transfer accounting .*/ - if (pStreamShared->State.cfPosAdjustLeft == 0) - { - Assert(cbLeft >= cbDMA); - cbLeft -= cbDMA; - - cbProcessed += cbDMA; - } - - /* - * Update the stream's current position. - * Do this as accurate and close to the actual data transfer as possible. - * All guetsts rely on this, depending on the mechanism they use (LPIB register or DMA counters). - */ - uint32_t cbStreamPos = hdaR3StreamGetPosition(pThis, pStreamShared); - if (cbStreamPos == pStreamShared->u32CBL) - cbStreamPos = 0; - - hdaR3StreamSetPosition(pStreamShared, pDevIns, pThis, cbStreamPos + cbDMA); - } - - if (hdaR3BDLEIsComplete(pBDLE)) - { - Log3Func(("[SD%RU8] Complete: %R[bdle]\n", uSD, pBDLE)); - - /* Does the current BDLE require an interrupt to be sent? */ - if ( hdaR3BDLENeedsInterrupt(pBDLE) - /* Are we done doing the position adjustment? - * It can happen that a BDLE which is handled while doing the - * position adjustment requires an interrupt on completion (IOC) being set. - * - * In such a case we need to skip such an interrupt and just move on. */ - && pStreamShared->State.cfPosAdjustLeft == 0) - { - /* If the IOCE ("Interrupt On Completion Enable") bit of the SDCTL register is set - * we need to generate an interrupt. - */ - if (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_IOCE) - { - pStreamShared->State.cTransferPendingInterrupts++; - - AssertMsg(pStreamShared->State.cTransferPendingInterrupts <= 32, - ("Too many pending interrupts (%RU8) for stream #%RU8\n", - pStreamShared->State.cTransferPendingInterrupts, uSD)); - } - } - - if (pStreamShared->State.uCurBDLE == pStreamShared->u16LVI) - { - pStreamShared->State.uCurBDLE = 0; - } - else - pStreamShared->State.uCurBDLE++; - - /* Fetch the next BDLE entry. */ - hdaR3BDLEFetch(pDevIns, pBDLE, pStreamShared->u64BDLBase, pStreamShared->State.uCurBDLE); - } - - /* Do the position adjustment accounting. */ - pStreamShared->State.cfPosAdjustLeft -= - RT_MIN(pStreamShared->State.cfPosAdjustLeft, cbDMA / pStreamR3->State.Mapping.cbFrameSize); - - if (RT_FAILURE(rc)) - break; - } - - Log3Func(("[SD%RU8] cbToProcess=%RU32, cbProcessed=%RU32, cbLeft=%RU32, %R[bdle], rc=%Rrc\n", - uSD, cbToProcess, cbProcessed, cbLeft, pBDLE, rc)); - - /* Sanity. */ - Assert(cbProcessed == cbToProcess); - Assert(cbLeft == 0); - - /* Only do the data accounting if we don't have to do any position - * adjustment anymore. */ - if (pStreamShared->State.cfPosAdjustLeft == 0) - { - hdaR3StreamPeriodInc(pPeriod, RT_MIN(cbProcessed / pStreamR3->State.Mapping.cbFrameSize, - hdaR3StreamPeriodGetRemainingFrames(pPeriod))); - - pStreamShared->State.cbTransferProcessed += cbProcessed; - } - - /* Make sure that we never report more stuff processed than initially announced. */ - if (pStreamShared->State.cbTransferProcessed > pStreamShared->State.cbTransferSize) - pStreamShared->State.cbTransferProcessed = pStreamShared->State.cbTransferSize; - - uint32_t cbTransferLeft = pStreamShared->State.cbTransferSize - pStreamShared->State.cbTransferProcessed; - bool fTransferComplete = !cbTransferLeft; - uint64_t tsTransferNext = 0; - - if (fTransferComplete) - { - /* - * Try updating the wall clock. - * - * Note 1) Only certain guests (like Linux' snd_hda_intel) rely on the WALCLK register - * in order to determine the correct timing of the sound device. Other guests - * like Windows 7 + 10 (or even more exotic ones like Haiku) will completely - * ignore this. - * - * Note 2) When updating the WALCLK register too often / early (or even in a non-monotonic - * fashion) this *will* upset guest device drivers and will completely fuck up the - * sound output. Running VLC on the guest will tell! - */ - const bool fWalClkSet = hdaR3WalClkSet(pThis, pThisCC, - hdaWalClkGetCurrent(pThis) - + hdaR3StreamPeriodFramesToWalClk(pPeriod, - pStreamShared->State.cbTransferProcessed - / pStreamR3->State.Mapping.cbFrameSize), - false /* fForce */); - RT_NOREF(fWalClkSet); - } - - /* Does the period have any interrupts outstanding? */ - if (pStreamShared->State.cTransferPendingInterrupts) - { - Log3Func(("[SD%RU8] Scheduling interrupt\n", uSD)); - - /* - * Set the stream's BCIS bit. - * - * Note: This only must be done if the whole period is complete, and not if only - * one specific BDL entry is complete (if it has the IOC bit set). - * - * This will otherwise confuses the guest when it 1) deasserts the interrupt, - * 2) reads SDSTS (with BCIS set) and then 3) too early reads a (wrong) WALCLK value. - * - * snd_hda_intel on Linux will tell. - */ - HDA_STREAM_REG(pThis, STS, uSD) |= HDA_SDSTS_BCIS; - - /* Trigger an interrupt first and let hdaRegWriteSDSTS() deal with - * ending / beginning a period. */ - HDA_PROCESS_INTERRUPT(pDevIns, pThis); - } - else /* Transfer still in-flight -- schedule the next timing slot. */ - { - uint32_t cbTransferNext = cbTransferLeft; - - /* No data left to transfer anymore or do we have more data left - * than we can transfer per timing slot? Clamp. */ - if ( !cbTransferNext - || cbTransferNext > pStreamShared->State.cbTransferChunk) - { - cbTransferNext = pStreamShared->State.cbTransferChunk; - } - - tsTransferNext = tsNow + (cbTransferNext * pStreamShared->State.cTicksPerByte); - - /* - * If the current transfer is complete, reset our counter. - * - * This can happen for examlpe if the guest OS (like macOS) sets up - * big BDLEs without IOC bits set (but for the last one) and the - * transfer is complete before we reach such a BDL entry. - */ - if (fTransferComplete) - pStreamShared->State.cbTransferProcessed = 0; - } - - /* If we need to do another transfer, (re-)arm the device timer. */ - if (tsTransferNext) /* Can be 0 if no next transfer is needed. */ - { - Log3Func(("[SD%RU8] Scheduling timer\n", uSD)); - - LogFunc(("Timer set SD%RU8\n", uSD)); - Assert(!fInTimer || tsNow == PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer)); - hdaR3TimerSet(pDevIns, pStreamShared, tsTransferNext, - true /* fForce - skip tsTransferNext check */, fInTimer ? tsNow : 0); - - pStreamShared->State.tsTransferNext = tsTransferNext; - } - - pStreamShared->State.tsTransferLast = tsNow; - - Log3Func(("[SD%RU8] cbTransferLeft=%RU32 -- %RU32/%RU32\n", - uSD, cbTransferLeft, pStreamShared->State.cbTransferProcessed, pStreamShared->State.cbTransferSize)); - Log3Func(("[SD%RU8] fTransferComplete=%RTbool, cTransferPendingInterrupts=%RU8\n", - uSD, fTransferComplete, pStreamShared->State.cTransferPendingInterrupts)); - Log3Func(("[SD%RU8] tsNow=%RU64, tsTransferNext=%RU64 (in %RU64 ticks)\n", - uSD, tsNow, tsTransferNext, tsTransferNext - tsNow)); - - hdaR3StreamPeriodUnlock(pPeriod); - hdaR3StreamUnlock(pStreamR3); - - return VINF_SUCCESS; -} - -/** - * Updates a HDA stream by doing its required data transfers. - * The host sink(s) set the overall pace. - * - * This routine is called by both, the synchronous and the asynchronous, implementations. - * - * This routine is called by both, the synchronous and the asynchronous - * (VBOX_WITH_AUDIO_HDA_ASYNC_IO), implementations. - * - * When running synchronously, the device DMA transfers *and* the mixer sink - * processing is within the device timer. - * - * When running asynchronously, only the device DMA transfers are done in the - * device timer, whereas the mixer sink processing then is done in the stream's - * own async I/O thread. This thread also will call this function - * (with fInTimer set to @c false). - * - * @param pDevIns The device instance. - * @param pThis The shared HDA device state. - * @param pThisCC The ring-3 HDA device state. - * @param pStreamShared HDA stream to update (shared bits). - * @param pStreamR3 HDA stream to update (ring-3 bits). - * @param fInTimer Whether to this function was called from the timer - * context or an asynchronous I/O stream thread (if supported). - */ -void hdaR3StreamUpdate(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, - PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fInTimer) -{ - if (!pStreamShared) - return; - - PAUDMIXSINK pSink = NULL; - if (pStreamR3->pMixSink) - pSink = pStreamR3->pMixSink->pMixSink; - - if (!AudioMixerSinkIsActive(pSink)) /* No sink available? Bail out. */ - return; - - int rc2; - - if (hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_OUT) /* Output (SDO). */ - { - bool fDoRead = false; /* Whether to read from the HDA stream or not. */ - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - if (fInTimer) -# endif - { - const uint32_t cbStreamFree = hdaR3StreamGetFree(pStreamR3); - if (cbStreamFree) - { - /* Do the DMA transfer. */ - rc2 = hdaR3StreamTransfer(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3, cbStreamFree, fInTimer); - AssertRC(rc2); - } - - /* Only read from the HDA stream at the given scheduling rate. */ - const uint64_t tsNowNs = RTTimeNanoTS(); - if (tsNowNs - pStreamShared->State.tsLastUpdateNs >= pStreamShared->State.Cfg.Device.cMsSchedulingHint * RT_NS_1MS) - { - fDoRead = true; - pStreamShared->State.tsLastUpdateNs = tsNowNs; - } - } - - Log3Func(("[SD%RU8] fInTimer=%RTbool, fDoRead=%RTbool\n", pStreamShared->u8SD, fInTimer, fDoRead)); - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - if (fDoRead) - { - rc2 = hdaR3StreamAsyncIONotify(pStreamR3); - AssertRC(rc2); - } -# endif - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - if (!fInTimer) /* In async I/O thread */ - { -# else - if (fDoRead) - { -# endif - const uint32_t cbSinkWritable = AudioMixerSinkGetWritable(pSink); - const uint32_t cbStreamReadable = hdaR3StreamGetUsed(pStreamR3); - const uint32_t cbToReadFromStream = RT_MIN(cbStreamReadable, cbSinkWritable); - - Log3Func(("[SD%RU8] cbSinkWritable=%RU32, cbStreamReadable=%RU32\n", pStreamShared->u8SD, cbSinkWritable, cbStreamReadable)); - - if (cbToReadFromStream) - { - /* Read (guest output) data and write it to the stream's sink. */ - rc2 = hdaR3StreamRead(pStreamR3, cbToReadFromStream, NULL /* pcbRead */); - AssertRC(rc2); - } - - /* When running synchronously, update the associated sink here. - * Otherwise this will be done in the async I/O thread. */ - rc2 = AudioMixerSinkUpdate(pSink); - AssertRC(rc2); - } - } - else /* Input (SDI). */ - { -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - if (!fInTimer) - { -# endif - rc2 = AudioMixerSinkUpdate(pSink); - AssertRC(rc2); - - /* Is the sink ready to be read (host input data) from? If so, by how much? */ - uint32_t cbSinkReadable = AudioMixerSinkGetReadable(pSink); - - /* How much (guest input) data is available for writing at the moment for the HDA stream? */ - const uint32_t cbStreamFree = hdaR3StreamGetFree(pStreamR3); - - Log3Func(("[SD%RU8] cbSinkReadable=%RU32, cbStreamFree=%RU32\n", pStreamShared->u8SD, cbSinkReadable, cbStreamFree)); - - /* Do not read more than the HDA stream can hold at the moment. - * The host sets the overall pace. */ - if (cbSinkReadable > cbStreamFree) - cbSinkReadable = cbStreamFree; - - if (cbSinkReadable) - { - uint8_t abFIFO[HDA_FIFO_MAX + 1]; - while (cbSinkReadable) - { - uint32_t cbRead; - rc2 = AudioMixerSinkRead(pSink, AUDMIXOP_COPY, - abFIFO, RT_MIN(cbSinkReadable, (uint32_t)sizeof(abFIFO)), &cbRead); - AssertRCBreak(rc2); - - if (!cbRead) - { - AssertMsgFailed(("Nothing read from sink, even if %RU32 bytes were (still) announced\n", cbSinkReadable)); - break; - } - - /* Write (guest input) data to the stream which was read from stream's sink before. */ - uint32_t cbWritten; - rc2 = hdaR3StreamWrite(pStreamR3, abFIFO, cbRead, &cbWritten); - AssertRCBreak(rc2); - AssertBreak(cbWritten > 0); /* Should never happen, as we know how much we can write. */ - - Assert(cbSinkReadable >= cbRead); - cbSinkReadable -= cbRead; - } - } -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - } - else /* fInTimer */ - { -# endif - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - const uint64_t tsNowNs = RTTimeNanoTS(); - if (tsNowNs - pStreamShared->State.tsLastUpdateNs >= pStreamShared->State.Cfg.Device.cMsSchedulingHint * RT_NS_1MS) - { - rc2 = hdaR3StreamAsyncIONotify(pStreamR3); - AssertRC(rc2); - - pStreamShared->State.tsLastUpdateNs = tsNowNs; - } -# endif - const uint32_t cbStreamUsed = hdaR3StreamGetUsed(pStreamR3); - if (cbStreamUsed) - { - rc2 = hdaR3StreamTransfer(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3, cbStreamUsed, fInTimer); - AssertRC(rc2); - } -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - } -# endif - } -} - -/** - * Locks an HDA stream for serialized access. - * - * @returns IPRT status code. - * @param pStreamR3 HDA stream to lock (ring-3 bits). - */ -void hdaR3StreamLock(PHDASTREAMR3 pStreamR3) -{ - AssertPtrReturnVoid(pStreamR3); -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - int rc2 = RTCritSectEnter(&pStreamR3->CritSect); - AssertRC(rc2); -# else - Assert(PDMDevHlpCritSectIsOwner(pStream->pHDAState->pDevInsR3, pStream->pHDAState->CritSect)); -# endif -} - -/** - * Unlocks a formerly locked HDA stream. - * - * @returns IPRT status code. - * @param pStreamR3 HDA stream to unlock (ring-3 bits). - */ -void hdaR3StreamUnlock(PHDASTREAMR3 pStreamR3) -{ - AssertPtrReturnVoid(pStreamR3); -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - int rc2 = RTCritSectLeave(&pStreamR3->CritSect); - AssertRC(rc2); -# endif -} - -#if 0 /* unused - no prototype even */ -/** - * Updates an HDA stream's current read or write buffer position (depending on the stream type) by - * updating its associated LPIB register and DMA position buffer (if enabled). - * - * @returns Set LPIB value. - * @param pDevIns The device instance. - * @param pStream HDA stream to update read / write position for. - * @param u32LPIB New LPIB (position) value to set. - */ -uint32_t hdaR3StreamUpdateLPIB(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, uint32_t u32LPIB) -{ - AssertMsg(u32LPIB <= pStreamShared->u32CBL, - ("[SD%RU8] New LPIB (%RU32) exceeds CBL (%RU32)\n", pStreamShared->u8SD, u32LPIB, pStreamShared->u32CBL)); - - u32LPIB = RT_MIN(u32LPIB, pStreamShared->u32CBL); - - LogFlowFunc(("[SD%RU8] LPIB=%RU32 (DMA Position Buffer Enabled: %RTbool)\n", - pStreamShared->u8SD, u32LPIB, pThis->fDMAPosition)); - - /* Update LPIB in any case. */ - HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD) = u32LPIB; - - /* Do we need to tell the current DMA position? */ - if (pThis->fDMAPosition) - { - int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, - pThis->u64DPBase + (pStreamShared->u8SD * 2 * sizeof(uint32_t)), - (void *)&u32LPIB, sizeof(uint32_t)); - AssertRC(rc2); - } - - return u32LPIB; -} -#endif - -# ifdef HDA_USE_DMA_ACCESS_HANDLER -/** - * Registers access handlers for a stream's BDLE DMA accesses. - * - * @returns true if registration was successful, false if not. - * @param pStream HDA stream to register BDLE access handlers for. - */ -bool hdaR3StreamRegisterDMAHandlers(PHDASTREAM pStream) -{ - /* At least LVI and the BDL base must be set. */ - if ( !pStreamShared->u16LVI - || !pStreamShared->u64BDLBase) - { - return false; - } - - hdaR3StreamUnregisterDMAHandlers(pStream); - - LogFunc(("Registering ...\n")); - - int rc = VINF_SUCCESS; - - /* - * Create BDLE ranges. - */ - - struct BDLERANGE - { - RTGCPHYS uAddr; - uint32_t uSize; - } arrRanges[16]; /** @todo Use a define. */ - - size_t cRanges = 0; - - for (uint16_t i = 0; i < pStreamShared->u16LVI + 1; i++) - { - HDABDLE BDLE; - rc = hdaR3BDLEFetch(pDevIns, &BDLE, pStreamShared->u64BDLBase, i /* Index */); - if (RT_FAILURE(rc)) - break; - - bool fAddRange = true; - BDLERANGE *pRange; - - if (cRanges) - { - pRange = &arrRanges[cRanges - 1]; - - /* Is the current range a direct neighbor of the current BLDE? */ - if ((pRange->uAddr + pRange->uSize) == BDLE.Desc.u64BufAddr) - { - /* Expand the current range by the current BDLE's size. */ - pRange->uSize += BDLE.Desc.u32BufSize; - - /* Adding a new range in this case is not needed anymore. */ - fAddRange = false; - - LogFunc(("Expanding range %zu by %RU32 (%RU32 total now)\n", cRanges - 1, BDLE.Desc.u32BufSize, pRange->uSize)); - } - } - - /* Do we need to add a new range? */ - if ( fAddRange - && cRanges < RT_ELEMENTS(arrRanges)) - { - pRange = &arrRanges[cRanges]; - - pRange->uAddr = BDLE.Desc.u64BufAddr; - pRange->uSize = BDLE.Desc.u32BufSize; - - LogFunc(("Adding range %zu - 0x%x (%RU32)\n", cRanges, pRange->uAddr, pRange->uSize)); - - cRanges++; - } - } - - LogFunc(("%zu ranges total\n", cRanges)); - - /* - * Register all ranges as DMA access handlers. - */ - - for (size_t i = 0; i < cRanges; i++) - { - BDLERANGE *pRange = &arrRanges[i]; - - PHDADMAACCESSHANDLER pHandler = (PHDADMAACCESSHANDLER)RTMemAllocZ(sizeof(HDADMAACCESSHANDLER)); - if (!pHandler) - { - rc = VERR_NO_MEMORY; - break; - } - - RTListAppend(&pStream->State.lstDMAHandlers, &pHandler->Node); - - pHandler->pStream = pStream; /* Save a back reference to the owner. */ - - char szDesc[32]; - RTStrPrintf(szDesc, sizeof(szDesc), "HDA[SD%RU8 - RANGE%02zu]", pStream->u8SD, i); - - int rc2 = PGMR3HandlerPhysicalTypeRegister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3), PGMPHYSHANDLERKIND_WRITE, - hdaDMAAccessHandler, - NULL, NULL, NULL, - NULL, NULL, NULL, - szDesc, &pHandler->hAccessHandlerType); - AssertRCBreak(rc2); - - pHandler->BDLEAddr = pRange->uAddr; - pHandler->BDLESize = pRange->uSize; - - /* Get first and last pages of the BDLE range. */ - RTGCPHYS pgFirst = pRange->uAddr & ~PAGE_OFFSET_MASK; - RTGCPHYS pgLast = RT_ALIGN(pgFirst + pRange->uSize, PAGE_SIZE); - - /* Calculate the region size (in pages). */ - RTGCPHYS regionSize = RT_ALIGN(pgLast - pgFirst, PAGE_SIZE); - - pHandler->GCPhysFirst = pgFirst; - pHandler->GCPhysLast = pHandler->GCPhysFirst + (regionSize - 1); - - LogFunc(("\tRegistering region '%s': 0x%x - 0x%x (region size: %zu)\n", - szDesc, pHandler->GCPhysFirst, pHandler->GCPhysLast, regionSize)); - LogFunc(("\tBDLE @ 0x%x - 0x%x (%RU32)\n", - pHandler->BDLEAddr, pHandler->BDLEAddr + pHandler->BDLESize, pHandler->BDLESize)); - - rc2 = PGMHandlerPhysicalRegister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3), - pHandler->GCPhysFirst, pHandler->GCPhysLast, - pHandler->hAccessHandlerType, pHandler, NIL_RTR0PTR, NIL_RTRCPTR, - szDesc); - AssertRCBreak(rc2); - - pHandler->fRegistered = true; - } - - LogFunc(("Registration ended with rc=%Rrc\n", rc)); - - return RT_SUCCESS(rc); -} - -/** - * Unregisters access handlers of a stream's BDLEs. - * - * @param pStream HDA stream to unregister BDLE access handlers for. - */ -void hdaR3StreamUnregisterDMAHandlers(PHDASTREAM pStream) -{ - LogFunc(("\n")); - - PHDADMAACCESSHANDLER pHandler, pHandlerNext; - RTListForEachSafe(&pStream->State.lstDMAHandlers, pHandler, pHandlerNext, HDADMAACCESSHANDLER, Node) - { - if (!pHandler->fRegistered) /* Handler not registered? Skip. */ - continue; - - LogFunc(("Unregistering 0x%x - 0x%x (%zu)\n", - pHandler->GCPhysFirst, pHandler->GCPhysLast, pHandler->GCPhysLast - pHandler->GCPhysFirst)); - - int rc2 = PGMHandlerPhysicalDeregister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3), - pHandler->GCPhysFirst); - AssertRC(rc2); - - RTListNodeRemove(&pHandler->Node); - - RTMemFree(pHandler); - pHandler = NULL; - } - - Assert(RTListIsEmpty(&pStream->State.lstDMAHandlers)); -} -# endif /* HDA_USE_DMA_ACCESS_HANDLER */ - -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO -/** - * @callback_method_impl{FNRTTHREAD, - * Asynchronous I/O thread for a HDA stream. - * - * This will do the heavy lifting work for us as soon as it's getting notified - * by another thread.} - */ -static DECLCALLBACK(int) hdaR3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser) -{ - PHDASTREAMR3 const pStreamR3 = (PHDASTREAMR3)pvUser; - PHDASTREAMSTATEAIO const pAIO = &pStreamR3->State.AIO; - PHDASTATE const pThis = pStreamR3->pHDAStateShared; - PHDASTATER3 const pThisCC = pStreamR3->pHDAStateR3; - PPDMDEVINS const pDevIns = pThisCC->pDevIns; - PHDASTREAM const pStreamShared = &pThis->aStreams[pStreamR3 - &pThisCC->aStreams[0]]; - Assert(pStreamR3 - &pThisCC->aStreams[0] == pStreamR3->u8SD); - Assert(pStreamShared->u8SD == pStreamR3->u8SD); - - /* Signal parent thread that we've started */ - ASMAtomicXchgBool(&pAIO->fStarted, true); - RTThreadUserSignal(hThreadSelf); - - LogFunc(("[SD%RU8] Started\n", pStreamShared->u8SD)); - - for (;;) - { - int rc2 = RTSemEventWait(pAIO->hEvent, RT_INDEFINITE_WAIT); - if (RT_FAILURE(rc2)) - break; - - if (ASMAtomicReadBool(&pAIO->fShutdown)) - break; - - rc2 = RTCritSectEnter(&pAIO->CritSect); - AssertRC(rc2); - if (RT_SUCCESS(rc2)) - { - if (!pAIO->fEnabled) - { - RTCritSectLeave(&pAIO->CritSect); - continue; - } - - hdaR3StreamUpdate(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3, false /* fInTimer */); - - int rc3 = RTCritSectLeave(&pAIO->CritSect); - AssertRC(rc3); - } - } - - LogFunc(("[SD%RU8] Ended\n", pStreamShared->u8SD)); - ASMAtomicXchgBool(&pAIO->fStarted, false); - - return VINF_SUCCESS; -} - -/** - * Creates the async I/O thread for a specific HDA audio stream. - * - * @returns IPRT status code. - * @param pStreamR3 HDA audio stream to create the async I/O thread for. - */ -int hdaR3StreamAsyncIOCreate(PHDASTREAMR3 pStreamR3) -{ - PHDASTREAMSTATEAIO pAIO = &pStreamR3->State.AIO; - - int rc; - - if (!ASMAtomicReadBool(&pAIO->fStarted)) - { - pAIO->fShutdown = false; - pAIO->fEnabled = true; /* Enabled by default. */ - - rc = RTSemEventCreate(&pAIO->hEvent); - if (RT_SUCCESS(rc)) - { - rc = RTCritSectInit(&pAIO->CritSect); - if (RT_SUCCESS(rc)) - { - rc = RTThreadCreateF(&pAIO->hThread, hdaR3StreamAsyncIOThread, pStreamR3, 0 /*cbStack*/, - RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "hdaAIO%RU8", pStreamR3->u8SD); - if (RT_SUCCESS(rc)) - rc = RTThreadUserWait(pAIO->hThread, 10 * 1000 /* 10s timeout */); - } - } - } - else - rc = VINF_SUCCESS; - - LogFunc(("[SD%RU8] Returning %Rrc\n", pStreamR3->u8SD, rc)); - return rc; -} - -/** - * Destroys the async I/O thread of a specific HDA audio stream. - * - * @returns IPRT status code. - * @param pStreamR3 HDA audio stream to destroy the async I/O thread for. - */ -static int hdaR3StreamAsyncIODestroy(PHDASTREAMR3 pStreamR3) -{ - PHDASTREAMSTATEAIO pAIO = &pStreamR3->State.AIO; - - if (!ASMAtomicReadBool(&pAIO->fStarted)) - return VINF_SUCCESS; - - ASMAtomicWriteBool(&pAIO->fShutdown, true); - - int rc = hdaR3StreamAsyncIONotify(pStreamR3); - AssertRC(rc); - - int rcThread; - rc = RTThreadWait(pAIO->hThread, 30 * 1000 /* 30s timeout */, &rcThread); - LogFunc(("Async I/O thread ended with %Rrc (%Rrc)\n", rc, rcThread)); - - if (RT_SUCCESS(rc)) - { - pAIO->hThread = NIL_RTTHREAD; - - rc = RTCritSectDelete(&pAIO->CritSect); - AssertRC(rc); - - rc = RTSemEventDestroy(pAIO->hEvent); - AssertRC(rc); - pAIO->hEvent = NIL_RTSEMEVENT; - - pAIO->fStarted = false; - pAIO->fShutdown = false; - pAIO->fEnabled = false; - } - - LogFunc(("[SD%RU8] Returning %Rrc\n", pStreamR3->u8SD, rc)); - return rc; -} - -/** - * Lets the stream's async I/O thread know that there is some data to process. - * - * @returns IPRT status code. - * @param pStreamR3 HDA stream to notify async I/O thread for. - */ -static int hdaR3StreamAsyncIONotify(PHDASTREAMR3 pStreamR3) -{ - return RTSemEventSignal(pStreamR3->State.AIO.hEvent); -} - -/** - * Locks the async I/O thread of a specific HDA audio stream. - * - * @param pStreamR3 HDA stream to lock async I/O thread for. - */ -void hdaR3StreamAsyncIOLock(PHDASTREAMR3 pStreamR3) -{ - PHDASTREAMSTATEAIO pAIO = &pStreamR3->State.AIO; - - if (!ASMAtomicReadBool(&pAIO->fStarted)) - return; - - int rc2 = RTCritSectEnter(&pAIO->CritSect); - AssertRC(rc2); -} - -/** - * Unlocks the async I/O thread of a specific HDA audio stream. - * - * @param pStreamR3 HDA stream to unlock async I/O thread for. - */ -void hdaR3StreamAsyncIOUnlock(PHDASTREAMR3 pStreamR3) -{ - PHDASTREAMSTATEAIO pAIO = &pStreamR3->State.AIO; - - if (!ASMAtomicReadBool(&pAIO->fStarted)) - return; - - int rc2 = RTCritSectLeave(&pAIO->CritSect); - AssertRC(rc2); -} - -/** - * Enables (resumes) or disables (pauses) the async I/O thread. - * - * @param pStreamR3 HDA stream to enable/disable async I/O thread for. - * @param fEnable Whether to enable or disable the I/O thread. - * - * @remarks Does not do locking. - */ -void hdaR3StreamAsyncIOEnable(PHDASTREAMR3 pStreamR3, bool fEnable) -{ - PHDASTREAMSTATEAIO pAIO = &pStreamR3->State.AIO; - ASMAtomicXchgBool(&pAIO->fEnabled, fEnable); -} -# endif /* VBOX_WITH_AUDIO_HDA_ASYNC_IO */ - -#endif /* IN_RING3 */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStream.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStream.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStream.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStream.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,312 +0,0 @@ -/* $Id: HDAStream.h $ */ -/** @file - * HDAStream.h - Streams for HD Audio. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_HDAStream_h -#define VBOX_INCLUDED_SRC_Audio_HDAStream_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include "DevHDACommon.h" -#include "HDAStreamMap.h" -#include "HDAStreamPeriod.h" - - -#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO -/** - * HDA stream's state for asynchronous I/O. - */ -typedef struct HDASTREAMSTATEAIO -{ - /** Thread handle for the actual I/O thread. */ - RTTHREAD hThread; - /** Event for letting the thread know there is some data to process. */ - RTSEMEVENT hEvent; - /** Critical section for synchronizing access. */ - RTCRITSECT CritSect; - /** Started indicator. */ - volatile bool fStarted; - /** Shutdown indicator. */ - volatile bool fShutdown; - /** Whether the thread should do any data processing or not. */ - volatile bool fEnabled; - bool afPadding[1+4]; -} HDASTREAMSTATEAIO; -/** Pointer to a HDA stream's asynchronous I/O state. */ -typedef HDASTREAMSTATEAIO *PHDASTREAMSTATEAIO; -#endif - -/** - * Structure containing HDA stream debug stuff, configurable at runtime. - */ -typedef struct HDASTREAMDEBUGRT -{ - /** Whether debugging is enabled or not. */ - bool fEnabled; - uint8_t Padding[7]; - /** File for dumping stream reads / writes. - * For input streams, this dumps data being written to the device FIFO, - * whereas for output streams this dumps data being read from the device FIFO. */ - R3PTRTYPE(PPDMAUDIOFILE) pFileStream; - /** File for dumping raw DMA reads / writes. - * For input streams, this dumps data being written to the device DMA, - * whereas for output streams this dumps data being read from the device DMA. */ - R3PTRTYPE(PPDMAUDIOFILE) pFileDMARaw; - /** File for dumping mapped (that is, extracted) DMA reads / writes. */ - R3PTRTYPE(PPDMAUDIOFILE) pFileDMAMapped; -} HDASTREAMDEBUGRT; - -/** - * Structure containing HDA stream debug information. - */ -typedef struct HDASTREAMDEBUG -{ -#ifdef DEBUG - /** Critical section to serialize access if needed. */ - RTCRITSECT CritSect; - uint32_t Padding0[2]; - /** Number of total read accesses. */ - uint64_t cReadsTotal; - /** Number of total DMA bytes read. */ - uint64_t cbReadTotal; - /** Timestamp (in ns) of last read access. */ - uint64_t tsLastReadNs; - /** Number of total write accesses. */ - uint64_t cWritesTotal; - /** Number of total DMA bytes written. */ - uint64_t cbWrittenTotal; - /** Number of total write accesses since last iteration (Hz). */ - uint64_t cWritesHz; - /** Number of total DMA bytes written since last iteration (Hz). */ - uint64_t cbWrittenHz; - /** Timestamp (in ns) of beginning a new write slot. */ - uint64_t tsWriteSlotBegin; - /** Number of current silence samples in a (consecutive) row. */ - uint64_t csSilence; - /** Number of silent samples in a row to consider an audio block as audio gap (silence). */ - uint64_t cSilenceThreshold; - /** How many bytes to skip in an audio stream before detecting silence. - * (useful for intros and silence at the beginning of a song). */ - uint64_t cbSilenceReadMin; -#endif - /** Runtime debug info. */ - HDASTREAMDEBUGRT Runtime; -} HDASTREAMDEBUG; -typedef HDASTREAMDEBUG *PHDASTREAMDEBUG; - -/** - * Internal state of a HDA stream. - */ -typedef struct HDASTREAMSTATE -{ - /** Current BDLE to use. Wraps around to 0 if - * maximum (cBDLE) is reached. */ - uint16_t uCurBDLE; - /** Flag indicating whether this stream currently is - * in reset mode and therefore not acccessible by the guest. */ - volatile bool fInReset; - /** Flag indicating if the stream is in running state or not. */ - volatile bool fRunning; - /** Unused, padding. */ - uint8_t abPadding0[4]; - /** Current BDLE (Buffer Descriptor List Entry). */ - HDABDLE BDLE; - /** Timestamp of the last DMA data transfer. */ - uint64_t tsTransferLast; - /** Timestamp of the next DMA data transfer. - * Next for determining the next scheduling window. - * Can be 0 if no next transfer is scheduled. */ - uint64_t tsTransferNext; - /** Total transfer size (in bytes) of a transfer period. */ - uint32_t cbTransferSize; - /** Transfer chunk size (in bytes) of a transfer period. */ - uint32_t cbTransferChunk; - /** How many bytes already have been processed in within - * the current transfer period. */ - uint32_t cbTransferProcessed; - /** How many interrupts are pending due to - * BDLE interrupt-on-completion (IOC) bits set. */ - uint8_t cTransferPendingInterrupts; - uint8_t abPadding2[3]; - /** The stream's timer Hz rate. - * This value can can be different from the device's default Hz rate, - * depending on the rate the stream expects (e.g. for 5.1 speaker setups). - * Set in hdaR3StreamInit(). */ - uint16_t uTimerHz; - /** Number of audio data frames for the position adjustment. - * 0 if no position adjustment is needed. */ - uint16_t cfPosAdjustDefault; - /** How many audio data frames are left to be processed - * for the position adjustment handling. - * - * 0 if position adjustment handling is done or inactive. */ - uint16_t cfPosAdjustLeft; - uint16_t u16Padding3; - /** (Virtual) clock ticks per byte. */ - uint64_t cTicksPerByte; - /** (Virtual) clock ticks per transfer. */ - uint64_t cTransferTicks; - /** The stream's period. Need for timing. */ - HDASTREAMPERIOD Period; - /** The stream's current configuration. - * Should match SDFMT. */ - PDMAUDIOSTREAMCFG Cfg; - /** Timestamp (in ns) of last stream update. */ - uint64_t tsLastUpdateNs; -} HDASTREAMSTATE; -AssertCompileSizeAlignment(HDASTREAMSTATE, 8); - -/** - * An HDA stream (SDI / SDO) - shared. - * - * @note This HDA stream has nothing to do with a regular audio stream handled - * by the audio connector or the audio mixer. This HDA stream is a serial - * data in/out stream (SDI/SDO) defined in hardware and can contain - * multiple audio streams in one single SDI/SDO (interleaving streams). - * - * How a specific SDI/SDO is mapped to our internal audio streams relies on the - * stream channel mappings. - * - * Contains only register values which do *not* change until a stream reset - * occurs. - */ -typedef struct HDASTREAM -{ - /** Stream descriptor number (SDn). */ - uint8_t u8SD; - /** Current channel index. - * For a stereo stream, this is u8Channel + 1. */ - uint8_t u8Channel; - uint8_t abPadding0[6]; - /** DMA base address (SDnBDPU - SDnBDPL). - * Will be updated in hdaR3StreamInit(). */ - uint64_t u64BDLBase; - /** Cyclic Buffer Length (SDnCBL). - * Represents the size of the ring buffer. - * Will be updated in hdaR3StreamInit(). */ - uint32_t u32CBL; - /** Format (SDnFMT). - * Will be updated in hdaR3StreamInit(). */ - uint16_t u16FMT; - /** FIFO Size (FIFOS). - * Maximum number of bytes that may have been DMA'd into - * memory but not yet transmitted on the link. - * - * Will be updated in hdaR3StreamInit(). */ - uint16_t u16FIFOS; - /** FIFO Watermark. */ - uint16_t u16FIFOW; - /** Last Valid Index (SDnLVI). - * Will be updated in hdaR3StreamInit(). */ - uint16_t u16LVI; - uint16_t au16Padding1[2]; - /** The timer for pumping data thru the attached LUN drivers. */ - TMTIMERHANDLE hTimer; - /** Internal state of this stream. */ - HDASTREAMSTATE State; -} HDASTREAM; -/** Pointer to an HDA stream (SDI / SDO). */ -typedef HDASTREAM *PHDASTREAM; - - -/** - * An HDA stream (SDI / SDO) - ring-3 bits. - */ -typedef struct HDASTREAMR3 -{ - /** Stream descriptor number (SDn). */ - uint8_t u8SD; - uint8_t abPadding[7]; - /** The shared state for the parent HDA device. */ - R3PTRTYPE(PHDASTATE) pHDAStateShared; - /** The ring-3 state for the parent HDA device. */ - R3PTRTYPE(PHDASTATER3) pHDAStateR3; - /** Pointer to HDA sink this stream is attached to. */ - R3PTRTYPE(PHDAMIXERSINK) pMixSink; -#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - /** The stream's critical section to serialize access between the async I/O - * thread and (basically) the guest. */ - RTCRITSECT CritSect; -#endif - /** Internal state of this stream. */ - struct - { - /** This stream's data mapping. */ - HDASTREAMMAP Mapping; - /** Circular buffer (FIFO) for holding DMA'ed data. */ - R3PTRTYPE(PRTCIRCBUF) pCircBuf; -#ifdef HDA_USE_DMA_ACCESS_HANDLER - /** List of DMA handlers. */ - RTLISTANCHORR3 lstDMAHandlers; -#endif -#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO - /** Asynchronous I/O state members. */ - HDASTREAMSTATEAIO AIO; -#endif - } State; - /** Debug bits. */ - HDASTREAMDEBUG Dbg; -} HDASTREAMR3; -/** Pointer to an HDA stream (SDI / SDO). */ -typedef HDASTREAMR3 *PHDASTREAMR3; - -#ifdef IN_RING3 - -/** @name Stream functions. - * @{ - */ -int hdaR3StreamConstruct(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PHDASTATE pThis, - PHDASTATER3 pThisCC, uint8_t uSD); -void hdaR3StreamDestroy(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3); -int hdaR3StreamSetUp(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, - PHDASTREAMR3 pStreamR3, uint8_t uSD); -void hdaR3StreamReset(PHDASTATE pThis, PHDASTATER3 pThisCC, - PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD); -int hdaR3StreamEnable(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fEnable); -/* uint32_t hdaR3StreamGetPosition(PHDASTATE pThis, PHDASTREAMR3 pStreamShared); - only used in HDAStream.cpp */ -/*void hdaR3StreamSetPosition(PHDASTREAM pStream, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t u32LPIB); - only used in HDAStream.cpp */ -/*uint32_t hdaR3StreamGetFree(PHDASTREAM pStream); - only used in HDAStream.cpp */ -/*uint32_t hdaR3StreamGetUsed(PHDASTREAM pStream); - only used in HDAStream.cpp */ -bool hdaR3StreamTransferIsScheduled(PHDASTREAM pStreamShared, uint64_t tsNow); -uint64_t hdaR3StreamTransferGetNext(PHDASTREAM pStreamShared); -void hdaR3StreamLock(PHDASTREAMR3 pStreamR3); -void hdaR3StreamUnlock(PHDASTREAMR3 pStreamR3); -/* int hdaR3StreamRead(PHDASTREAM pStream, uint32_t cbToRead, uint32_t *pcbRead); - only used in HDAStream.cpp */ -/*int hdaR3StreamWrite(PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten); - only used in HDAStream.cpp */ -void hdaR3StreamUpdate(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, - PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fInTimer); -PHDASTREAM hdaR3StreamR3ToShared(PHDASTREAMR3 pStreamCC); -# ifdef HDA_USE_DMA_ACCESS_HANDLER -bool hdaR3StreamRegisterDMAHandlers(PHDASTREAM pStream); -void hdaR3StreamUnregisterDMAHandlers(PHDASTREAM pStream); -# endif -/** @} */ - -/** @name Async I/O stream functions. - * @{ - */ -# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO -int hdaR3StreamAsyncIOCreate(PHDASTREAMR3 pStreamR3); -void hdaR3StreamAsyncIOLock(PHDASTREAMR3 pStreamR3); -void hdaR3StreamAsyncIOUnlock(PHDASTREAMR3 pStreamR3); -void hdaR3StreamAsyncIOEnable(PHDASTREAMR3 pStreamR3, bool fEnable); -# endif /* VBOX_WITH_AUDIO_HDA_ASYNC_IO */ -/** @} */ - -#endif /* IN_RING3 */ -#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStream_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamMap.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamMap.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamMap.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamMap.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,174 +0,0 @@ -/* $Id: HDAStreamMap.cpp $ */ -/** @file - * HDAStreamMap.cpp - Stream mapping functions for HD Audio. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DEV_HDA -#include - -#include - -#include -#include - -#include "DrvAudio.h" - -#include "HDAStreamChannel.h" -#include "HDAStreamMap.h" - -#ifdef IN_RING3 - -static int hdaR3StreamMapSetup(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps); - -/** - * Initializes a stream mapping structure according to the given PCM properties. - * - * @return IPRT status code. - * @param pMap Pointer to mapping to initialize. - * @param pProps Pointer to PCM properties to use for initialization. - */ -int hdaR3StreamMapInit(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps) -{ - AssertPtrReturn(pMap, VERR_INVALID_POINTER); - AssertPtrReturn(pProps, VERR_INVALID_POINTER); - - if (!DrvAudioHlpPCMPropsAreValid(pProps)) - return VERR_INVALID_PARAMETER; - - hdaR3StreamMapReset(pMap); - - int rc = hdaR3StreamMapSetup(pMap, pProps); - if (RT_FAILURE(rc)) - return rc; - -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - if ( RT_SUCCESS(rc) - /* Create circular buffer if not created yet. */ - && !pMap->pCircBuf) - { - rc = RTCircBufCreate(&pMap->pCircBuf, _4K); /** @todo Make size configurable? */ - } -#endif - - if (RT_SUCCESS(rc)) - { - pMap->cbFrameSize = pProps->cChannels * pProps->cbSample; - - LogFunc(("cChannels=%RU8, cBytes=%RU8 -> cbFrameSize=%RU32\n", - pProps->cChannels, pProps->cbSample, pMap->cbFrameSize)); - - Assert(pMap->cbFrameSize); /* Frame size must not be 0. */ - - pMap->enmLayout = PDMAUDIOSTREAMLAYOUT_INTERLEAVED; - } - - return rc; -} - - -/** - * Destroys a given stream mapping. - * - * @param pMap Pointer to mapping to destroy. - */ -void hdaR3StreamMapDestroy(PHDASTREAMMAP pMap) -{ - hdaR3StreamMapReset(pMap); - -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - if (pMap->pCircBuf) - { - RTCircBufDestroy(pMap->pCircBuf); - pMap->pCircBuf = NULL; - } -#endif -} - - -/** - * Resets a given stream mapping. - * - * @param pMap Pointer to mapping to reset. - */ -void hdaR3StreamMapReset(PHDASTREAMMAP pMap) -{ - AssertPtrReturnVoid(pMap); - - pMap->enmLayout = PDMAUDIOSTREAMLAYOUT_UNKNOWN; - - if (pMap->paMappings) - { - for (uint8_t i = 0; i < pMap->cMappings; i++) - hdaR3StreamChannelDataDestroy(&pMap->paMappings[i].Data); - - RTMemFree(pMap->paMappings); - pMap->paMappings = NULL; - - pMap->cMappings = 0; - } -} - - -/** - * Sets up a stream mapping according to the given properties / configuration. - * - * @return VBox status code, or VERR_NOT_SUPPORTED if the channel setup is not supported (yet). - * @param pMap Pointer to mapping to set up. - * @param pProps PCM audio properties to use for lookup. - */ -static int hdaR3StreamMapSetup(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps) -{ - int rc; - - /** @todo We ASSUME that all channels in a stream ... - * - have the same format - * - are in a consecutive order with no gaps in between - * - have a simple (raw) data layout - * - work in a non-striped fashion, e.g. interleaved (only on one SDn, not spread over multiple SDns) */ - if ( pProps->cChannels == 1 /* Mono */ - || pProps->cChannels == 2 /* Stereo */ - || pProps->cChannels == 4 /* Quadrophonic */ - || pProps->cChannels == 6) /* Surround (5.1) */ - { - /* For now we don't have anything other as mono / stereo channels being covered by the backends. - * So just set up one channel covering those and skipping the rest (like configured rear or center/LFE outputs). */ - pMap->cMappings = 1; - pMap->paMappings = (PPDMAUDIOSTREAMMAP)RTMemAlloc(sizeof(PDMAUDIOSTREAMMAP) * pMap->cMappings); - if (!pMap->paMappings) - return VERR_NO_MEMORY; - - PPDMAUDIOSTREAMMAP pMapLR = &pMap->paMappings[0]; - - pMapLR->aenmIDs[0] = PDMAUDIOSTREAMCHANNELID_FRONT_LEFT; - pMapLR->aenmIDs[1] = PDMAUDIOSTREAMCHANNELID_FRONT_RIGHT; - pMapLR->cbFrame = pProps->cbSample * pProps->cChannels; - pMapLR->cbStep = pProps->cbSample * 2 /* Front left + Front right channels */; - pMapLR->offFirst = 0; - pMapLR->offNext = pMapLR->offFirst; - - rc = hdaR3StreamChannelDataInit(&pMapLR->Data, PDMAUDIOSTREAMCHANNELDATA_FLAGS_NONE); - AssertRC(rc); - } - else - rc = VERR_NOT_SUPPORTED; /** @todo r=andy Support more setups. */ - - return rc; -} -#endif /* IN_RING3 */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamMap.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamMap.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamMap.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamMap.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -/* $Id: HDAStreamMap.h $ */ -/** @file - * HDAStreamMap.h - Stream map functions for HD Audio. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h -#define VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -/** - * Audio stream data mapping. - */ -typedef struct HDASTREAMMAP -{ - /** The stream's layout. */ - PDMAUDIOSTREAMLAYOUT enmLayout; - uint8_t cbFrameSize; - /** Number of mappings in paMappings. */ - uint8_t cMappings; - uint8_t aPadding[2]; - /** Array of stream mappings. - * Note: The mappings *must* be layed out in an increasing order, e.g. - * how the data appears in the given data block. */ - R3PTRTYPE(PPDMAUDIOSTREAMMAP) paMappings; -#if HC_ARCH_BITS == 32 - RTR3PTR Padding1; -#endif -#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND - /** Circular buffer holding for holding audio data for this mapping. */ - R3PTRTYPE(PRTCIRCBUF) pCircBuf; -#endif -} HDASTREAMMAP; -AssertCompileSizeAlignment(HDASTREAMMAP, 8); -/** Pointer to an audio stream data mapping. */ -typedef HDASTREAMMAP *PHDASTREAMMAP; - -/** @name Stream mapping functions. - * @{ - */ -#ifdef IN_RING3 -int hdaR3StreamMapInit(PHDASTREAMMAP pMapping, PPDMAUDIOPCMPROPS pProps); -void hdaR3StreamMapDestroy(PHDASTREAMMAP pMapping); -void hdaR3StreamMapReset(PHDASTREAMMAP pMapping); -#endif -/** @} */ - -#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamPeriod.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamPeriod.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamPeriod.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamPeriod.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,445 +0,0 @@ -/* $Id: HDAStreamPeriod.cpp $ */ -/** @file - * HDAStreamPeriod.cpp - Stream period functions for HD Audio. - * - * Utility functions for handling HDA audio stream periods. Stream period - * handling is needed in order to keep track of a stream's timing - * and processed audio data. - * - * As the HDA device only has one bit clock (WALCLK) but audio streams can be - * processed at certain points in time, these functions can be used to estimate - * and schedule the wall clock (WALCLK) for all streams accordingly. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_DEV_HDA -#include - -#include /* For ASMMultU64ByU32DivByU32(). */ - -#include -#include - -#include "DrvAudio.h" -#include "HDAStreamPeriod.h" - - -#ifdef IN_RING3 /* entire file currently */ - -/** - * Creates a stream period. - * - * @return IPRT status code. - * @param pPeriod Stream period to initialize. - */ -int hdaR3StreamPeriodCreate(PHDASTREAMPERIOD pPeriod) -{ - Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_F_VALID)); - -# ifdef HDA_STREAM_PERIOD_WITH_LOCKING - int rc = RTCritSectInit(&pPeriod->CritSect); - AssertRCReturnStmt(rc, pPeriod->fStatus = 0, rc); -# endif - pPeriod->fStatus = HDASTREAMPERIOD_F_VALID; - - return VINF_SUCCESS; -} - -/** - * Destroys a formerly created stream period. - * - * @param pPeriod Stream period to destroy. - */ -void hdaR3StreamPeriodDestroy(PHDASTREAMPERIOD pPeriod) -{ - if (pPeriod->fStatus & HDASTREAMPERIOD_F_VALID) - { -# ifdef HDA_STREAM_PERIOD_WITH_LOCKING - RTCritSectDelete(&pPeriod->CritSect); -# endif - pPeriod->fStatus = HDASTREAMPERIOD_F_NONE; - } -} - -/** - * Initializes a given stream period with needed parameters. - * - * @return VBox status code. - * @param pPeriod Stream period to (re-)initialize. Must be created with hdaR3StreamPeriodCreate() first. - * @param u8SD Stream descriptor (serial data #) number to assign this stream period to. - * @param u16LVI The HDA stream's LVI value to use for the period calculation. - * @param u32CBL The HDA stream's CBL value to use for the period calculation. - * @param pStreamCfg Audio stream configuration to use for this period. - */ -int hdaR3StreamPeriodInit(PHDASTREAMPERIOD pPeriod, - uint8_t u8SD, uint16_t u16LVI, uint32_t u32CBL, PPDMAUDIOSTREAMCFG pStreamCfg) -{ - if ( !u16LVI - || !u32CBL - || !DrvAudioHlpPCMPropsAreValid(&pStreamCfg->Props)) - { - return VERR_INVALID_PARAMETER; - } - - /* - * Linux guests (at least Ubuntu): - * 17632 bytes (CBL) / 4 (frame size) = 4408 frames / 4 (LVI) = 1102 frames per period - * - * Windows guests (Win10 AU): - * 3584 bytes (CBL) / 4 (frame size) = 896 frames / 2 (LVI) = 448 frames per period - */ - unsigned cTotalPeriods = u16LVI + 1; - - if (cTotalPeriods <= 1) - cTotalPeriods = 2; /* At least two periods *must* be present (LVI >= 1). */ - - uint32_t cFramesToTransfer = (u32CBL / 4 /** @todo Define frame size? */) / cTotalPeriods; - - pPeriod->u8SD = u8SD; - pPeriod->u64StartWalClk = 0; - pPeriod->u32Hz = pStreamCfg->Props.uHz; - pPeriod->u64DurationWalClk = hdaR3StreamPeriodFramesToWalClk(pPeriod, cFramesToTransfer); - pPeriod->u64ElapsedWalClk = 0; - pPeriod->i64DelayWalClk = 0; - pPeriod->cFramesToTransfer = cFramesToTransfer; - pPeriod->cFramesTransferred = 0; - pPeriod->cIntPending = 0; - - Log3Func(("[SD%RU8] %RU64 long, Hz=%RU32, CBL=%RU32, LVI=%RU16 -> %u periods, %RU32 frames each\n", - pPeriod->u8SD, pPeriod->u64DurationWalClk, pPeriod->u32Hz, u32CBL, u16LVI, - cTotalPeriods, pPeriod->cFramesToTransfer)); - - return VINF_SUCCESS; -} - -/** - * Resets a stream period to its initial state. - * - * @param pPeriod Stream period to reset. - */ -void hdaR3StreamPeriodReset(PHDASTREAMPERIOD pPeriod) -{ - Log3Func(("[SD%RU8]\n", pPeriod->u8SD)); - - if (pPeriod->cIntPending) - LogRelMax(50, ("HDA: Warning: %RU8 interrupts for stream #%RU8 still pending -- so a period reset might trigger audio hangs\n", - pPeriod->cIntPending, pPeriod->u8SD)); - - pPeriod->fStatus &= ~HDASTREAMPERIOD_F_ACTIVE; - pPeriod->u64StartWalClk = 0; - pPeriod->u64ElapsedWalClk = 0; - pPeriod->cFramesTransferred = 0; - pPeriod->cIntPending = 0; -# ifdef LOG_ENABLED - pPeriod->Dbg.tsStartNs = 0; -# endif -} - -/** - * Begins a new period life span of a given period. - * - * @return IPRT status code. - * @param pPeriod Stream period to begin new life span for. - * @param u64WalClk Wall clock (WALCLK) value to set for the period's starting point. - */ -int hdaR3StreamPeriodBegin(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk) -{ - Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE)); /* No nested calls. */ - - pPeriod->fStatus |= HDASTREAMPERIOD_F_ACTIVE; - pPeriod->u64StartWalClk = u64WalClk; - pPeriod->u64ElapsedWalClk = 0; - pPeriod->cFramesTransferred = 0; - pPeriod->cIntPending = 0; -# ifdef LOG_ENABLED - pPeriod->Dbg.tsStartNs = RTTimeNanoTS(); -# endif - - Log3Func(("[SD%RU8] Starting @ %RU64 (%RU64 long)\n", pPeriod->u8SD, pPeriod->u64StartWalClk, pPeriod->u64DurationWalClk)); - return VINF_SUCCESS; -} - -/** - * Ends a formerly begun period life span. - * - * @param pPeriod Stream period to end life span for. - */ -void hdaR3StreamPeriodEnd(PHDASTREAMPERIOD pPeriod) -{ - Log3Func(("[SD%RU8] Took %zuus\n", pPeriod->u8SD, (RTTimeNanoTS() - pPeriod->Dbg.tsStartNs) / 1000)); - - if (!(pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE)) - return; - - /* Sanity. */ - AssertMsg(pPeriod->cIntPending == 0, - ("%RU8 interrupts for stream #%RU8 still pending -- so ending a period might trigger audio hangs\n", - pPeriod->cIntPending, pPeriod->u8SD)); - Assert(hdaR3StreamPeriodIsComplete(pPeriod)); - - pPeriod->fStatus &= ~HDASTREAMPERIOD_F_ACTIVE; -} - -/** - * Pauses a period. All values remain intact. - * - * @param pPeriod Stream period to pause. - */ -void hdaR3StreamPeriodPause(PHDASTREAMPERIOD pPeriod) -{ - AssertMsg((pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE), ("Period %p already in inactive state\n", pPeriod)); - - pPeriod->fStatus &= ~HDASTREAMPERIOD_F_ACTIVE; - - Log3Func(("[SD%RU8]\n", pPeriod->u8SD)); -} - -/** - * Resumes a formerly paused period. - * - * @param pPeriod Stream period to resume. - */ -void hdaR3StreamPeriodResume(PHDASTREAMPERIOD pPeriod) -{ - AssertMsg(!(pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE), ("Period %p already in active state\n", pPeriod)); - - pPeriod->fStatus |= HDASTREAMPERIOD_F_ACTIVE; - - Log3Func(("[SD%RU8]\n", pPeriod->u8SD)); -} - -/** - * Locks a stream period for serializing access. - * - * @returns IPRT status code (safe to ignore, asserted). - * @param pPeriod Stream period to lock. - */ -int hdaR3StreamPeriodLock(PHDASTREAMPERIOD pPeriod) -{ -# ifdef HDA_STREAM_PERIOD_WITH_LOCKING - int rc = RTCritSectEnter(&pPeriod->CritSect); - AssertRC(rc); - return rc; -# else - RT_NOREF(pPeriod); - return VINF_SUCCESS; -# endif -} - -/** - * Unlocks a formerly locked stream period. - * - * @param pPeriod Stream period to unlock. - */ -void hdaR3StreamPeriodUnlock(PHDASTREAMPERIOD pPeriod) -{ -# ifdef HDA_STREAM_PERIOD_WITH_LOCKING - int rc2 = RTCritSectLeave(&pPeriod->CritSect); - AssertRC(rc2); -# else - RT_NOREF(pPeriod); -# endif -} - -/** - * Returns the wall clock (WALCLK) value for a given amount of stream period audio frames. - * - * @return Calculated wall clock value. - * @param pPeriod Stream period to calculate wall clock value for. - * @param uFrames Number of audio frames to calculate wall clock value for. - * - * @remark Calculation depends on the given stream period and assumes a 24 MHz wall clock counter (WALCLK). - */ -uint64_t hdaR3StreamPeriodFramesToWalClk(PHDASTREAMPERIOD pPeriod, uint32_t uFrames) -{ - /* Prevent division by zero. */ - const uint32_t uHz = pPeriod->u32Hz ? pPeriod->u32Hz : 1; - - /* 24 MHz wall clock (WALCLK): 42ns resolution. */ - return ASMMultU64ByU32DivByU32(uFrames, 24000000, uHz); -} - -/** - * Returns the absolute wall clock (WALCLK) value for the already elapsed time of - * a given stream period. - * - * @return Absolute elapsed time as wall clock (WALCLK) value. - * @param pPeriod Stream period to use. - */ -uint64_t hdaR3StreamPeriodGetAbsElapsedWalClk(PHDASTREAMPERIOD pPeriod) -{ - return pPeriod->u64StartWalClk - + pPeriod->u64ElapsedWalClk - + pPeriod->i64DelayWalClk; -} - -/** - * Returns the absolute wall clock (WALCLK) value for the calculated end time of - * a given stream period. - * - * @return Absolute end time as wall clock (WALCLK) value. - * @param pPeriod Stream period to use. - */ -uint64_t hdaR3StreamPeriodGetAbsEndWalClk(PHDASTREAMPERIOD pPeriod) -{ - return pPeriod->u64StartWalClk + pPeriod->u64DurationWalClk; -} - -/** - * Returns the remaining audio frames to process for a given stream period. - * - * @return Number of remaining audio frames to process. 0 if all were processed. - * @param pPeriod Stream period to return value for. - */ -uint32_t hdaR3StreamPeriodGetRemainingFrames(PHDASTREAMPERIOD pPeriod) -{ - Assert(pPeriod->cFramesToTransfer >= pPeriod->cFramesTransferred); - return pPeriod->cFramesToTransfer - pPeriod->cFramesTransferred; -} - -/** - * Tells whether a given stream period has elapsed (time-wise) or not. - * - * @return true if the stream period has elapsed, false if not. - * @param pPeriod Stream period to get status for. - */ -bool hdaR3StreamPeriodHasElapsed(PHDASTREAMPERIOD pPeriod) -{ - return (pPeriod->u64ElapsedWalClk >= pPeriod->u64DurationWalClk); -} - -/** - * Tells whether a given stream period has passed the given absolute wall clock (WALCLK) - * time or not - * - * @return true if the stream period has passed the given time, false if not. - * @param pPeriod Stream period to get status for. - * @param u64WalClk Absolute wall clock (WALCLK) time to check for. - */ -bool hdaR3StreamPeriodHasPassedAbsWalClk(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk) -{ - /* Period not in use? */ - if (!(pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE)) - return true; /* ... implies that it has passed. */ - - if (hdaR3StreamPeriodHasElapsed(pPeriod)) - return true; /* Period already has elapsed. */ - - return (pPeriod->u64StartWalClk + pPeriod->u64ElapsedWalClk) >= u64WalClk; -} - -/** - * Tells whether a given stream period has some required interrupts pending or not. - * - * @return true if period has interrupts pending, false if not. - * @param pPeriod Stream period to get status for. - */ -bool hdaR3StreamPeriodNeedsInterrupt(PHDASTREAMPERIOD pPeriod) -{ - return pPeriod->cIntPending > 0; -} - -/** - * Acquires (references) an (pending) interrupt for a given stream period. - * - * @param pPeriod Stream period to acquire interrupt for. - * - * @remark This routine does not do any actual interrupt processing; it only - * keeps track of the required (pending) interrupts for a stream period. - */ -void hdaR3StreamPeriodAcquireInterrupt(PHDASTREAMPERIOD pPeriod) -{ - uint32_t cIntPending = pPeriod->cIntPending; - if (cIntPending) - { - Log3Func(("[SD%RU8] Already pending\n", pPeriod->u8SD)); - return; - } - - pPeriod->cIntPending++; - - Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending)); -} - -/** - * Releases (dereferences) a pending interrupt. - * - * @param pPeriod Stream period to release pending interrupt for. - */ -void hdaR3StreamPeriodReleaseInterrupt(PHDASTREAMPERIOD pPeriod) -{ - Assert(pPeriod->cIntPending); - pPeriod->cIntPending--; - - Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending)); -} - -/** - * Adds an amount of (processed) audio frames to a given stream period. - * - * @return IPRT status code. - * @param pPeriod Stream period to add audio frames to. - * @param framesInc Audio frames to add. - */ -void hdaR3StreamPeriodInc(PHDASTREAMPERIOD pPeriod, uint32_t framesInc) -{ - pPeriod->cFramesTransferred += framesInc; - Assert(pPeriod->cFramesTransferred <= pPeriod->cFramesToTransfer); - - pPeriod->u64ElapsedWalClk = hdaR3StreamPeriodFramesToWalClk(pPeriod, pPeriod->cFramesTransferred); - Assert(pPeriod->u64ElapsedWalClk <= pPeriod->u64DurationWalClk); - - Log3Func(("[SD%RU8] cbTransferred=%RU32, u64ElapsedWalClk=%RU64\n", - pPeriod->u8SD, pPeriod->cFramesTransferred, pPeriod->u64ElapsedWalClk)); -} - -/** - * Tells whether a given stream period is considered as complete or not. - * - * @return true if stream period is complete, false if not. - * @param pPeriod Stream period to report status for. - * - * @remark A stream period is considered complete if it has 1) passed (elapsed) its calculated period time - * and 2) processed all required audio frames. - */ -bool hdaR3StreamPeriodIsComplete(PHDASTREAMPERIOD pPeriod) -{ - const bool fIsComplete = /* Has the period elapsed time-wise? */ - hdaR3StreamPeriodHasElapsed(pPeriod) - /* All frames transferred? */ - && pPeriod->cFramesTransferred >= pPeriod->cFramesToTransfer; -# ifdef VBOX_STRICT - if (fIsComplete) - { - Assert(pPeriod->cFramesTransferred == pPeriod->cFramesToTransfer); - Assert(pPeriod->u64ElapsedWalClk == pPeriod->u64DurationWalClk); - } -# endif - - Log3Func(("[SD%RU8] Period %s - runtime %RU64 / %RU64 (abs @ %RU64, starts @ %RU64, ends @ %RU64), %RU8 IRQs pending\n", - pPeriod->u8SD, - fIsComplete ? "COMPLETE" : "NOT COMPLETE YET", - pPeriod->u64ElapsedWalClk, pPeriod->u64DurationWalClk, - hdaR3StreamPeriodGetAbsElapsedWalClk(pPeriod), pPeriod->u64StartWalClk, - hdaR3StreamPeriodGetAbsEndWalClk(pPeriod), pPeriod->cIntPending)); - - return fIsComplete; -} - -#endif /* IN_RING3 */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamPeriod.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamPeriod.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/HDAStreamPeriod.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/HDAStreamPeriod.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,117 +0,0 @@ -/* $Id: HDAStreamPeriod.h $ */ -/** @file - * HDAStreamPeriod.h - Stream period functions for HD Audio. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h -#define VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include -#ifdef DEBUG -# include -#endif -#include /* LOG_ENABLED */ - - -#ifdef LOG_ENABLED -/** - * Debug stuff for a HDA stream's period. - */ -typedef struct HDASTREAMPERIODDEBUG -{ - /** Host start time (in ns) of the period. */ - uint64_t tsStartNs; -} HDASTREAMPERIODDEBUG; -#endif - -/** No flags set. */ -#define HDASTREAMPERIOD_F_NONE 0 -/** The stream period has been initialized and is in a valid state. */ -#define HDASTREAMPERIOD_F_VALID RT_BIT(0) -/** The stream period is active. */ -#define HDASTREAMPERIOD_F_ACTIVE RT_BIT(1) - -/** - * HDA stream's time period. - * - * This is needed in order to keep track of stream timing and interrupt delivery. - */ -typedef struct HDASTREAMPERIOD -{ -#ifdef HDA_STREAM_PERIOD_WITH_LOCKING - /** Critical section for serializing access. - * @todo r=bird: This is not needed. The stream lock is held the two places - * this critsect is entered. */ - RTCRITSECT CritSect; -#endif - /** Associated HDA stream descriptor (SD) number. */ - uint8_t u8SD; - /** The period's status flags. */ - uint8_t fStatus; - /** Number of pending interrupts required for this period. */ - uint8_t cIntPending; - uint8_t bPadding0; - /** Hertz (Hz) rate this period runs with. */ - uint32_t u32Hz; - /** Period start time (in wall clock counts). */ - uint64_t u64StartWalClk; - /** Period duration (in wall clock counts). */ - uint64_t u64DurationWalClk; - /** The period's (relative) elapsed time (in wall clock counts). */ - uint64_t u64ElapsedWalClk; - /** Delay (in wall clock counts) for tweaking the period timing. Optional. */ - int64_t i64DelayWalClk; - /** Number of audio frames to transfer for this period. */ - uint32_t cFramesToTransfer; - /** Number of audio frames already transfered. */ - uint32_t cFramesTransferred; -#ifdef LOG_ENABLED - /** Debugging state. */ - HDASTREAMPERIODDEBUG Dbg; -#endif -} HDASTREAMPERIOD; -AssertCompileSizeAlignment(HDASTREAMPERIOD, 8); -/** Pointer to a HDA stream's time period keeper. */ -typedef HDASTREAMPERIOD *PHDASTREAMPERIOD; - -#ifdef IN_RING3 -int hdaR3StreamPeriodCreate(PHDASTREAMPERIOD pPeriod); -void hdaR3StreamPeriodDestroy(PHDASTREAMPERIOD pPeriod); -int hdaR3StreamPeriodInit(PHDASTREAMPERIOD pPeriod, uint8_t u8SD, uint16_t u16LVI, uint32_t u32CBL, PPDMAUDIOSTREAMCFG pStreamCfg); -void hdaR3StreamPeriodReset(PHDASTREAMPERIOD pPeriod); -int hdaR3StreamPeriodBegin(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk); -void hdaR3StreamPeriodEnd(PHDASTREAMPERIOD pPeriod); -void hdaR3StreamPeriodPause(PHDASTREAMPERIOD pPeriod); -void hdaR3StreamPeriodResume(PHDASTREAMPERIOD pPeriod); -int hdaR3StreamPeriodLock(PHDASTREAMPERIOD pPeriod); -void hdaR3StreamPeriodUnlock(PHDASTREAMPERIOD pPeriod); -uint64_t hdaR3StreamPeriodFramesToWalClk(PHDASTREAMPERIOD pPeriod, uint32_t uFrames); -uint64_t hdaR3StreamPeriodGetAbsEndWalClk(PHDASTREAMPERIOD pPeriod); -uint64_t hdaR3StreamPeriodGetAbsElapsedWalClk(PHDASTREAMPERIOD pPeriod); -uint32_t hdaR3StreamPeriodGetRemainingFrames(PHDASTREAMPERIOD pPeriod); -bool hdaR3StreamPeriodHasElapsed(PHDASTREAMPERIOD pPeriod); -bool hdaR3StreamPeriodHasPassedAbsWalClk(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk); -bool hdaR3StreamPeriodNeedsInterrupt(PHDASTREAMPERIOD pPeriod); -void hdaR3StreamPeriodAcquireInterrupt(PHDASTREAMPERIOD pPeriod); -void hdaR3StreamPeriodReleaseInterrupt(PHDASTREAMPERIOD pPeriod); -void hdaR3StreamPeriodInc(PHDASTREAMPERIOD pPeriod, uint32_t framesInc); -bool hdaR3StreamPeriodIsComplete(PHDASTREAMPERIOD pPeriod); -#endif /* IN_RING3 */ - -#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/pulse_mangling.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/pulse_mangling.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/pulse_mangling.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/pulse_mangling.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -/* $Id: pulse_mangling.h $ */ -/** @file - * Mangle libpulse symbols. - * - * This is necessary on hosts which don't support the -fvisibility gcc switch. - */ - -/* - * Copyright (C) 2013-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_pulse_mangling_h -#define VBOX_INCLUDED_SRC_Audio_pulse_mangling_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#define PULSE_MANGLER(symbol) VBox_##symbol - -#define pa_bytes_per_second PULSE_MANGLER(pa_bytes_per_second) -#define pa_bytes_to_usec PULSE_MANGLER(pa_bytes_to_usec) -#define pa_channel_map_init_auto PULSE_MANGLER(pa_channel_map_init_auto) - -#define pa_context_connect PULSE_MANGLER(pa_context_connect) -#define pa_context_disconnect PULSE_MANGLER(pa_context_disconnect) -#define pa_context_get_server_info PULSE_MANGLER(pa_context_get_server_info) -#define pa_context_get_sink_info_by_name PULSE_MANGLER(pa_context_get_sink_info_by_name) -#define pa_context_get_source_info_by_name PULSE_MANGLER(pa_context_get_source_info_by_name) -#define pa_context_get_state PULSE_MANGLER(pa_context_get_state) -#define pa_context_unref PULSE_MANGLER(pa_context_unref) -#define pa_context_errno PULSE_MANGLER(pa_context_errno) -#define pa_context_new PULSE_MANGLER(pa_context_new) -#define pa_context_set_state_callback PULSE_MANGLER(pa_context_set_state_callback) - -#define pa_frame_size PULSE_MANGLER(pa_frame_size) -#define pa_get_library_version PULSE_MANGLER(pa_get_library_version) -#define pa_operation_unref PULSE_MANGLER(pa_operation_unref) -#define pa_operation_get_state PULSE_MANGLER(pa_operation_get_state) -#define pa_operation_cancel PULSE_MANGLER(pa_operation_cancel) -#define pa_rtclock_now PULSE_MANGLER(pa_rtclock_now) -#define pa_sample_format_to_string PULSE_MANGLER(pa_sample_format_to_string) -#define pa_sample_spec_valid PULSE_MANGLER(pa_sample_spec_valid) - -#define pa_stream_connect_playback PULSE_MANGLER(pa_stream_connect_playback) -#define pa_stream_connect_record PULSE_MANGLER(pa_stream_connect_record) -#define pa_stream_cork PULSE_MANGLER(pa_stream_cork) -#define pa_stream_disconnect PULSE_MANGLER(pa_stream_disconnect) -#define pa_stream_drop PULSE_MANGLER(pa_stream_drop) -#define pa_stream_get_sample_spec PULSE_MANGLER(pa_stream_get_sample_spec) -#define pa_stream_set_latency_update_callback PULSE_MANGLER(pa_stream_set_latency_update_callback) -#define pa_stream_write PULSE_MANGLER(pa_stream_write) -#define pa_stream_unref PULSE_MANGLER(pa_stream_unref) -#define pa_stream_get_state PULSE_MANGLER(pa_stream_get_state) -#define pa_stream_get_latency PULSE_MANGLER(pa_stream_get_latency) -#define pa_stream_get_timing_info PULSE_MANGLER(pa_stream_get_timing_info) -#define pa_stream_set_buffer_attr PULSE_MANGLER(pa_stream_set_buffer_attr) -#define pa_stream_set_state_callback PULSE_MANGLER(pa_stream_set_state_callback) -#define pa_stream_set_underflow_callback PULSE_MANGLER(pa_stream_set_underflow_callback) -#define pa_stream_set_overflow_callback PULSE_MANGLER(pa_stream_set_overflow_callback) -#define pa_stream_set_write_callback PULSE_MANGLER(pa_stream_set_write_callback) -#define pa_stream_flush PULSE_MANGLER(pa_stream_flush) -#define pa_stream_drain PULSE_MANGLER(pa_stream_drain) -#define pa_stream_trigger PULSE_MANGLER(pa_stream_trigger) -#define pa_stream_new PULSE_MANGLER(pa_stream_new) -#define pa_stream_get_buffer_attr PULSE_MANGLER(pa_stream_get_buffer_attr) -#define pa_stream_peek PULSE_MANGLER(pa_stream_peek) -#define pa_stream_readable_size PULSE_MANGLER(pa_stream_readable_size) -#define pa_stream_writable_size PULSE_MANGLER(pa_stream_writable_size) - -#define pa_strerror PULSE_MANGLER(pa_strerror) - -#define pa_threaded_mainloop_stop PULSE_MANGLER(pa_threaded_mainloop_stop) -#define pa_threaded_mainloop_get_api PULSE_MANGLER(pa_threaded_mainloop_get_api) -#define pa_threaded_mainloop_free PULSE_MANGLER(pa_threaded_mainloop_free) -#define pa_threaded_mainloop_signal PULSE_MANGLER(pa_threaded_mainloop_signal) -#define pa_threaded_mainloop_unlock PULSE_MANGLER(pa_threaded_mainloop_unlock) -#define pa_threaded_mainloop_new PULSE_MANGLER(pa_threaded_mainloop_new) -#define pa_threaded_mainloop_wait PULSE_MANGLER(pa_threaded_mainloop_wait) -#define pa_threaded_mainloop_start PULSE_MANGLER(pa_threaded_mainloop_start) -#define pa_threaded_mainloop_lock PULSE_MANGLER(pa_threaded_mainloop_lock) - -#define pa_usec_to_bytes PULSE_MANGLER(pa_usec_to_bytes) - -#endif /* !VBOX_INCLUDED_SRC_Audio_pulse_mangling_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/pulse_stubs.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/pulse_stubs.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/pulse_stubs.c 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/pulse_stubs.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,348 +0,0 @@ -/* $Id: pulse_stubs.c $ */ -/** @file - * Stubs for libpulse. - */ - -/* - * Copyright (C) 2006-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include -#include -#include -#include - -#include - -#include "pulse_stubs.h" - -#define VBOX_PULSE_LIB "libpulse.so.0" - -#define PROXY_STUB(function, rettype, signature, shortsig) \ - static rettype (*g_pfn_ ## function) signature; \ - \ - rettype VBox_##function signature; \ - rettype VBox_##function signature \ - { \ - return g_pfn_ ## function shortsig; \ - } - -#define PROXY_STUB_VOID(function, signature, shortsig) \ - static void (*g_pfn_ ## function) signature; \ - \ - void VBox_##function signature; \ - void VBox_##function signature \ - { \ - g_pfn_ ## function shortsig; \ - } - -PROXY_STUB (pa_bytes_per_second, size_t, - (const pa_sample_spec *spec), - (spec)) -PROXY_STUB (pa_bytes_to_usec, pa_usec_t, - (uint64_t l, const pa_sample_spec *spec), - (l, spec)) -PROXY_STUB (pa_channel_map_init_auto, pa_channel_map*, - (pa_channel_map *m, unsigned channels, pa_channel_map_def_t def), - (m, channels, def)) - -PROXY_STUB (pa_context_connect, int, - (pa_context *c, const char *server, pa_context_flags_t flags, - const pa_spawn_api *api), - (c, server, flags, api)) -PROXY_STUB_VOID(pa_context_disconnect, - (pa_context *c), - (c)) -PROXY_STUB (pa_context_get_server_info, pa_operation*, - (pa_context *c, pa_server_info_cb_t cb, void *userdata), - (c, cb, userdata)) -PROXY_STUB (pa_context_get_sink_info_by_name, pa_operation*, - (pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata), - (c, name, cb, userdata)) -PROXY_STUB (pa_context_get_source_info_by_name, pa_operation*, - (pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata), - (c, name, cb, userdata)) -PROXY_STUB (pa_context_get_state, pa_context_state_t, - (pa_context *c), - (c)) -PROXY_STUB_VOID(pa_context_unref, - (pa_context *c), - (c)) -PROXY_STUB (pa_context_errno, int, - (pa_context *c), - (c)) -PROXY_STUB (pa_context_new, pa_context*, - (pa_mainloop_api *mainloop, const char *name), - (mainloop, name)) -PROXY_STUB_VOID(pa_context_set_state_callback, - (pa_context *c, pa_context_notify_cb_t cb, void *userdata), - (c, cb, userdata)) - -PROXY_STUB (pa_frame_size, size_t, - (const pa_sample_spec *spec), - (spec)) -PROXY_STUB (pa_get_library_version, const char *, (void), ()) -PROXY_STUB_VOID(pa_operation_unref, - (pa_operation *o), - (o)) -PROXY_STUB (pa_operation_get_state, pa_operation_state_t, - (pa_operation *o), - (o)) -PROXY_STUB_VOID(pa_operation_cancel, - (pa_operation *o), - (o)) - -PROXY_STUB (pa_rtclock_now, pa_usec_t, - (void), - ()) -PROXY_STUB (pa_sample_format_to_string, const char*, - (pa_sample_format_t f), - (f)) -PROXY_STUB (pa_sample_spec_valid, int, - (const pa_sample_spec *spec), - (spec)) -PROXY_STUB (pa_strerror, const char*, - (int error), - (error)) - -#if PA_PROTOCOL_VERSION >= 16 -PROXY_STUB (pa_stream_connect_playback, int, - (pa_stream *s, const char *dev, const pa_buffer_attr *attr, - pa_stream_flags_t flags, const pa_cvolume *volume, pa_stream *sync_stream), - (s, dev, attr, flags, volume, sync_stream)) -#else -PROXY_STUB (pa_stream_connect_playback, int, - (pa_stream *s, const char *dev, const pa_buffer_attr *attr, - pa_stream_flags_t flags, pa_cvolume *volume, pa_stream *sync_stream), - (s, dev, attr, flags, volume, sync_stream)) -#endif -PROXY_STUB (pa_stream_connect_record, int, - (pa_stream *s, const char *dev, const pa_buffer_attr *attr, - pa_stream_flags_t flags), - (s, dev, attr, flags)) -PROXY_STUB (pa_stream_disconnect, int, - (pa_stream *s), - (s)) -PROXY_STUB (pa_stream_get_sample_spec, const pa_sample_spec*, - (pa_stream *s), - (s)) -PROXY_STUB_VOID(pa_stream_set_latency_update_callback, - (pa_stream *p, pa_stream_notify_cb_t cb, void *userdata), - (p, cb, userdata)) -PROXY_STUB (pa_stream_write, int, - (pa_stream *p, const void *data, size_t bytes, pa_free_cb_t free_cb, - int64_t offset, pa_seek_mode_t seek), - (p, data, bytes, free_cb, offset, seek)) -PROXY_STUB_VOID(pa_stream_unref, - (pa_stream *s), - (s)) -PROXY_STUB (pa_stream_get_state, pa_stream_state_t, - (pa_stream *p), - (p)) -PROXY_STUB (pa_stream_get_latency, int, - (pa_stream *s, pa_usec_t *r_usec, int *negative), - (s, r_usec, negative)) -PROXY_STUB (pa_stream_get_timing_info, pa_timing_info*, - (pa_stream *s), - (s)) -PROXY_STUB (pa_stream_readable_size, size_t, - (pa_stream *p), - (p)) -PROXY_STUB (pa_stream_set_buffer_attr, pa_operation *, - (pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata), - (s, attr, cb, userdata)) -PROXY_STUB_VOID(pa_stream_set_state_callback, - (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), - (s, cb, userdata)) -PROXY_STUB_VOID(pa_stream_set_underflow_callback, - (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), - (s, cb, userdata)) -PROXY_STUB_VOID(pa_stream_set_overflow_callback, - (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), - (s, cb, userdata)) -PROXY_STUB_VOID(pa_stream_set_write_callback, - (pa_stream *s, pa_stream_request_cb_t cb, void *userdata), - (s, cb, userdata)) -PROXY_STUB (pa_stream_flush, pa_operation*, - (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), - (s, cb, userdata)) -PROXY_STUB (pa_stream_drain, pa_operation*, - (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), - (s, cb, userdata)) -PROXY_STUB (pa_stream_trigger, pa_operation*, - (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), - (s, cb, userdata)) -PROXY_STUB (pa_stream_new, pa_stream*, - (pa_context *c, const char *name, const pa_sample_spec *ss, - const pa_channel_map *map), - (c, name, ss, map)) -PROXY_STUB (pa_stream_get_buffer_attr, const pa_buffer_attr*, - (pa_stream *s), - (s)) -PROXY_STUB (pa_stream_peek, int, - (pa_stream *p, const void **data, size_t *bytes), - (p, data, bytes)) -PROXY_STUB (pa_stream_cork, pa_operation*, - (pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata), - (s, b, cb, userdata)) -PROXY_STUB (pa_stream_drop, int, - (pa_stream *p), - (p)) -PROXY_STUB (pa_stream_writable_size, size_t, - (pa_stream *p), - (p)) - -PROXY_STUB_VOID(pa_threaded_mainloop_stop, - (pa_threaded_mainloop *m), - (m)) -PROXY_STUB (pa_threaded_mainloop_get_api, pa_mainloop_api*, - (pa_threaded_mainloop *m), - (m)) -PROXY_STUB_VOID(pa_threaded_mainloop_free, - (pa_threaded_mainloop* m), - (m)) -PROXY_STUB_VOID(pa_threaded_mainloop_signal, - (pa_threaded_mainloop *m, int wait_for_accept), - (m, wait_for_accept)) -PROXY_STUB_VOID(pa_threaded_mainloop_unlock, - (pa_threaded_mainloop *m), - (m)) -PROXY_STUB (pa_threaded_mainloop_new, pa_threaded_mainloop *, - (void), - ()) -PROXY_STUB_VOID(pa_threaded_mainloop_wait, - (pa_threaded_mainloop *m), - (m)) -PROXY_STUB (pa_threaded_mainloop_start, int, - (pa_threaded_mainloop *m), - (m)) -PROXY_STUB_VOID(pa_threaded_mainloop_lock, - (pa_threaded_mainloop *m), - (m)) - -PROXY_STUB (pa_usec_to_bytes, size_t, - (pa_usec_t t, const pa_sample_spec *spec), - (t, spec)) - -typedef struct -{ - const char *name; - void (**fn)(void); -} SHARED_FUNC; - -#define ELEMENT(function) { #function , (void (**)(void)) & g_pfn_ ## function } -static SHARED_FUNC SharedFuncs[] = -{ - ELEMENT(pa_bytes_per_second), - ELEMENT(pa_bytes_to_usec), - ELEMENT(pa_channel_map_init_auto), - - ELEMENT(pa_context_connect), - ELEMENT(pa_context_disconnect), - ELEMENT(pa_context_get_server_info), - ELEMENT(pa_context_get_sink_info_by_name), - ELEMENT(pa_context_get_source_info_by_name), - ELEMENT(pa_context_get_state), - ELEMENT(pa_context_unref), - ELEMENT(pa_context_errno), - ELEMENT(pa_context_new), - ELEMENT(pa_context_set_state_callback), - - ELEMENT(pa_frame_size), - ELEMENT(pa_get_library_version), - ELEMENT(pa_operation_unref), - ELEMENT(pa_operation_get_state), - ELEMENT(pa_operation_cancel), - ELEMENT(pa_rtclock_now), - ELEMENT(pa_sample_format_to_string), - ELEMENT(pa_sample_spec_valid), - ELEMENT(pa_strerror), - - ELEMENT(pa_stream_connect_playback), - ELEMENT(pa_stream_connect_record), - ELEMENT(pa_stream_disconnect), - ELEMENT(pa_stream_get_sample_spec), - ELEMENT(pa_stream_set_latency_update_callback), - ELEMENT(pa_stream_write), - ELEMENT(pa_stream_unref), - ELEMENT(pa_stream_get_state), - ELEMENT(pa_stream_get_latency), - ELEMENT(pa_stream_get_timing_info), - ELEMENT(pa_stream_readable_size), - ELEMENT(pa_stream_set_buffer_attr), - ELEMENT(pa_stream_set_state_callback), - ELEMENT(pa_stream_set_underflow_callback), - ELEMENT(pa_stream_set_overflow_callback), - ELEMENT(pa_stream_set_write_callback), - ELEMENT(pa_stream_flush), - ELEMENT(pa_stream_drain), - ELEMENT(pa_stream_trigger), - ELEMENT(pa_stream_new), - ELEMENT(pa_stream_get_buffer_attr), - ELEMENT(pa_stream_peek), - ELEMENT(pa_stream_cork), - ELEMENT(pa_stream_drop), - ELEMENT(pa_stream_writable_size), - - ELEMENT(pa_threaded_mainloop_stop), - ELEMENT(pa_threaded_mainloop_get_api), - ELEMENT(pa_threaded_mainloop_free), - ELEMENT(pa_threaded_mainloop_signal), - ELEMENT(pa_threaded_mainloop_unlock), - ELEMENT(pa_threaded_mainloop_new), - ELEMENT(pa_threaded_mainloop_wait), - ELEMENT(pa_threaded_mainloop_start), - ELEMENT(pa_threaded_mainloop_lock), - - ELEMENT(pa_usec_to_bytes) -}; -#undef ELEMENT - -/** - * Try to dynamically load the PulseAudio libraries. This function is not - * thread-safe, and should be called before attempting to use any of the - * PulseAudio functions. - * - * @returns iprt status code - */ -int audioLoadPulseLib(void) -{ - int rc = VINF_SUCCESS; - unsigned i; - static enum { NO = 0, YES, FAIL } isLibLoaded = NO; - RTLDRMOD hLib; - - LogFlowFunc(("\n")); - /* If this is not NO then the function has obviously been called twice, - which is likely to be a bug. */ - if (NO != isLibLoaded) - { - AssertMsgFailed(("isLibLoaded == %s\n", YES == isLibLoaded ? "YES" : "NO")); - return YES == isLibLoaded ? VINF_SUCCESS : VERR_NOT_SUPPORTED; - } - isLibLoaded = FAIL; - rc = RTLdrLoad(VBOX_PULSE_LIB, &hLib); - if (RT_FAILURE(rc)) - { - LogRelFunc(("Failed to load library %s\n", VBOX_PULSE_LIB)); - return rc; - } - for (i=0; i #include #include +#include #include #include #include +#include #include "../AudioMixBuffer.h" -#include "../DrvAudio.h" +#include "../AudioHlp.h" + +#define _USE_MATH_DEFINES +#include /* sin, M_PI */ /********************************************************************************************************************************* -* Structures and Typedefs * +* Global Variables * *********************************************************************************************************************************/ +#ifdef RT_LITTLE_ENDIAN +bool const g_fLittleEndian = true; +#else +bool const g_fLittleEndian = false; +#endif + + +static void tstBasics(RTTEST hTest) +{ + RTTestSub(hTest, "Basics"); + + const PDMAUDIOPCMPROPS Cfg441StereoS16 = PDMAUDIOPCMPROPS_INITIALIZER( + /* a_cb: */ 2, + /* a_fSigned: */ true, + /* a_cChannels: */ 2, + /* a_uHz: */ 44100, + /* a_fSwapEndian: */ false + ); + const PDMAUDIOPCMPROPS Cfg441StereoU16 = PDMAUDIOPCMPROPS_INITIALIZER( + /* a_cb: */ 2, + /* a_fSigned: */ false, + /* a_cChannels: */ 2, + /* a_uHz: */ 44100, + /* a_fSwapEndian: */ false + ); + const PDMAUDIOPCMPROPS Cfg441StereoU32 = PDMAUDIOPCMPROPS_INITIALIZER( + /* a_cb: */ 4, + /* a_fSigned: */ false, + /* a_cChannels: */ 2, + /* a_uHz: */ 44100, + /* a_fSwapEndian: */ false + ); + + RTTESTI_CHECK(PDMAudioPropsGetBitrate(&Cfg441StereoS16) == 44100*4*8); + RTTESTI_CHECK(PDMAudioPropsGetBitrate(&Cfg441StereoU16) == 44100*4*8); + RTTESTI_CHECK(PDMAudioPropsGetBitrate(&Cfg441StereoU32) == 44100*8*8); + + RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&Cfg441StereoS16)); + RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&Cfg441StereoU16) == false); /* go figure */ + RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&Cfg441StereoU32) == false); /* go figure */ + + + RTTESTI_CHECK_MSG(PDMAUDIOPCMPROPS_F2B(&Cfg441StereoS16, 1) == 4, + ("got %x, expected 4\n", PDMAUDIOPCMPROPS_F2B(&Cfg441StereoS16, 1))); + RTTESTI_CHECK_MSG(PDMAUDIOPCMPROPS_F2B(&Cfg441StereoU16, 1) == 4, + ("got %x, expected 4\n", PDMAUDIOPCMPROPS_F2B(&Cfg441StereoU16, 1))); + RTTESTI_CHECK_MSG(PDMAUDIOPCMPROPS_F2B(&Cfg441StereoU32, 1) == 8, + ("got %x, expected 4\n", PDMAUDIOPCMPROPS_F2B(&Cfg441StereoU32, 1))); + + RTTESTI_CHECK_MSG(PDMAudioPropsBytesPerFrame(&Cfg441StereoS16) == 4, + ("got %x, expected 4\n", PDMAudioPropsBytesPerFrame(&Cfg441StereoS16))); + RTTESTI_CHECK_MSG(PDMAudioPropsBytesPerFrame(&Cfg441StereoU16) == 4, + ("got %x, expected 4\n", PDMAudioPropsBytesPerFrame(&Cfg441StereoU16))); + RTTESTI_CHECK_MSG(PDMAudioPropsBytesPerFrame(&Cfg441StereoU32) == 8, + ("got %x, expected 4\n", PDMAudioPropsBytesPerFrame(&Cfg441StereoU32))); + + uint32_t u32; + for (uint32_t i = 0; i < 256; i += 8) + { + RTTESTI_CHECK(PDMAudioPropsIsSizeAligned(&Cfg441StereoU32, i) == true); + for (uint32_t j = 1; j < 8; j++) + RTTESTI_CHECK(PDMAudioPropsIsSizeAligned(&Cfg441StereoU32, i + j) == false); + for (uint32_t j = 0; j < 8; j++) + RTTESTI_CHECK(PDMAudioPropsFloorBytesToFrame(&Cfg441StereoU32, i + j) == i); + } + for (uint32_t i = 0; i < 4096; i += 4) + { + RTTESTI_CHECK(PDMAudioPropsIsSizeAligned(&Cfg441StereoS16, i) == true); + for (uint32_t j = 1; j < 4; j++) + RTTESTI_CHECK(PDMAudioPropsIsSizeAligned(&Cfg441StereoS16, i + j) == false); + for (uint32_t j = 0; j < 4; j++) + RTTESTI_CHECK(PDMAudioPropsFloorBytesToFrame(&Cfg441StereoS16, i + j) == i); + } + + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoS16, 44100)) == 44100 * 2 * 2, + ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoS16, 2)) == 2 * 2 * 2, + ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoS16, 1)) == 4, + ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoU16, 1)) == 4, + ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoU32, 1)) == 8, + ("cb=%RU32\n", u32)); + + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsBytesToFrames(&Cfg441StereoS16, 4)) == 1, ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsBytesToFrames(&Cfg441StereoU16, 4)) == 1, ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsBytesToFrames(&Cfg441StereoU32, 8)) == 1, ("cb=%RU32\n", u32)); + + uint64_t u64; + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsBytesToNano(&Cfg441StereoS16, 44100 * 2 * 2)) == RT_NS_1SEC, + ("ns=%RU64\n", u64)); + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsBytesToMicro(&Cfg441StereoS16, 44100 * 2 * 2)) == RT_US_1SEC, + ("us=%RU64\n", u64)); + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsBytesToMilli(&Cfg441StereoS16, 44100 * 2 * 2)) == RT_MS_1SEC, + ("ms=%RU64\n", u64)); + + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToNano(&Cfg441StereoS16, 44100)) == RT_NS_1SEC, ("ns=%RU64\n", u64)); + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToNano(&Cfg441StereoS16, 1)) == 22675, ("ns=%RU64\n", u64)); + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToNano(&Cfg441StereoS16, 31)) == 702947, ("ns=%RU64\n", u64)); + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToNano(&Cfg441StereoS16, 255)) == 5782312, ("ns=%RU64\n", u64)); + //RTTESTI_CHECK_MSG((u64 = DrvAudioHlpFramesToMicro(&Cfg441StereoS16, 44100)) == RT_US_1SEC, + // ("us=%RU64\n", u64)); + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToMilli(&Cfg441StereoS16, 44100)) == RT_MS_1SEC, ("ms=%RU64\n", u64)); + RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToMilli(&Cfg441StereoS16, 255)) == 5, ("ms=%RU64\n", u64)); + + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsNanoToFrames(&Cfg441StereoS16, RT_NS_1SEC)) == 44100, ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsNanoToFrames(&Cfg441StereoS16, 215876)) == 10, ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsMilliToFrames(&Cfg441StereoS16, RT_MS_1SEC)) == 44100, ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsMilliToFrames(&Cfg441StereoU32, 6)) == 265, ("cb=%RU32\n", u32)); + + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsNanoToBytes(&Cfg441StereoS16, RT_NS_1SEC)) == 44100*2*2, ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsNanoToBytes(&Cfg441StereoS16, 702947)) == 31*2*2, ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsMilliToBytes(&Cfg441StereoS16, RT_MS_1SEC)) == 44100*2*2, ("cb=%RU32\n", u32)); + RTTESTI_CHECK_MSG((u32 = PDMAudioPropsMilliToBytes(&Cfg441StereoS16, 5)) == 884, ("cb=%RU32\n", u32)); + + /* DrvAudioHlpClearBuf: */ + uint8_t *pbPage; + int rc = RTTestGuardedAlloc(hTest, PAGE_SIZE, 0, false /*fHead*/, (void **)&pbPage); + RTTESTI_CHECK_RC_OK_RETV(rc); + + memset(pbPage, 0x42, PAGE_SIZE); + PDMAudioPropsClearBuffer(&Cfg441StereoS16, pbPage, PAGE_SIZE, PAGE_SIZE / 4); + RTTESTI_CHECK(ASMMemIsZero(pbPage, PAGE_SIZE)); + + memset(pbPage, 0x42, PAGE_SIZE); + PDMAudioPropsClearBuffer(&Cfg441StereoU16, pbPage, PAGE_SIZE, PAGE_SIZE / 4); + for (uint32_t off = 0; off < PAGE_SIZE; off += 2) + RTTESTI_CHECK_MSG(pbPage[off] == 0 && pbPage[off + 1] == 0x80, ("off=%#x: %#x %x\n", off, pbPage[off], pbPage[off + 1])); + + memset(pbPage, 0x42, PAGE_SIZE); + PDMAudioPropsClearBuffer(&Cfg441StereoU32, pbPage, PAGE_SIZE, PAGE_SIZE / 8); + for (uint32_t off = 0; off < PAGE_SIZE; off += 4) + RTTESTI_CHECK(pbPage[off] == 0 && pbPage[off + 1] == 0 && pbPage[off + 2] == 0 && pbPage[off + 3] == 0x80); + + + RTTestDisableAssertions(hTest); + memset(pbPage, 0x42, PAGE_SIZE); + PDMAudioPropsClearBuffer(&Cfg441StereoS16, pbPage, PAGE_SIZE, PAGE_SIZE); /* should adjust down the frame count. */ + RTTESTI_CHECK(ASMMemIsZero(pbPage, PAGE_SIZE)); + + memset(pbPage, 0x42, PAGE_SIZE); + PDMAudioPropsClearBuffer(&Cfg441StereoU16, pbPage, PAGE_SIZE, PAGE_SIZE); /* should adjust down the frame count. */ + for (uint32_t off = 0; off < PAGE_SIZE; off += 2) + RTTESTI_CHECK_MSG(pbPage[off] == 0 && pbPage[off + 1] == 0x80, ("off=%#x: %#x %x\n", off, pbPage[off], pbPage[off + 1])); + + memset(pbPage, 0x42, PAGE_SIZE); + PDMAudioPropsClearBuffer(&Cfg441StereoU32, pbPage, PAGE_SIZE, PAGE_SIZE); /* should adjust down the frame count. */ + for (uint32_t off = 0; off < PAGE_SIZE; off += 4) + RTTESTI_CHECK(pbPage[off] == 0 && pbPage[off + 1] == 0 && pbPage[off + 2] == 0 && pbPage[off + 3] == 0x80); + RTTestRestoreAssertions(hTest); + + RTTestGuardedFree(hTest, pbPage); +} + -static int tstSingle(RTTEST hTest) +static void tstSimple(RTTEST hTest) { - RTTestSubF(hTest, "Single buffer"); + RTTestSub(hTest, "Simple"); /* 44100Hz, 2 Channels, S16 */ - PDMAUDIOPCMPROPS config = PDMAUDIOPCMPROPS_INITIALIZOR( + PDMAUDIOPCMPROPS config = PDMAUDIOPCMPROPS_INITIALIZER( 2, /* Bytes */ true, /* Signed */ 2, /* Channels */ 44100, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ false /* Swap Endian */ ); - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&config)); + RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&config)); uint32_t cBufSize = _1K; /* * General stuff. */ - PDMAUDIOMIXBUF mb; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&mb, "Single", &config, cBufSize)); + AUDIOMIXBUF mb; + RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInit(&mb, "Single", &config, cBufSize)); RTTESTI_CHECK(AudioMixBufSize(&mb) == cBufSize); RTTESTI_CHECK(AUDIOMIXBUF_B2F(&mb, AudioMixBufSizeBytes(&mb)) == cBufSize); RTTESTI_CHECK(AUDIOMIXBUF_F2B(&mb, AudioMixBufSize(&mb)) == AudioMixBufSizeBytes(&mb)); RTTESTI_CHECK(AudioMixBufFree(&mb) == cBufSize); RTTESTI_CHECK(AUDIOMIXBUF_F2B(&mb, AudioMixBufFree(&mb)) == AudioMixBufFreeBytes(&mb)); + AUDIOMIXBUFWRITESTATE WriteState; + RTTESTI_CHECK_RC(AudioMixBufInitWriteState(&mb, &WriteState, &config), VINF_SUCCESS); + + AUDIOMIXBUFPEEKSTATE PeekState; + RTTESTI_CHECK_RC(AudioMixBufInitPeekState(&mb, &PeekState, &config), VINF_SUCCESS); + /* - * Absolute writes. + * A few writes (used to be the weird absolute writes). */ uint32_t cFramesRead = 0, cFramesWritten = 0, cFramesWrittenAbs = 0; - int8_t aFrames8 [2] = { 0x12, 0x34 }; int16_t aFrames16[2] = { 0xAA, 0xBB }; int32_t aFrames32[2] = { 0xCC, 0xDD }; - RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, &aFrames8, sizeof(aFrames8), &cFramesWritten)); - RTTESTI_CHECK(cFramesWritten == 0 /* Frames */); RTTESTI_CHECK(AudioMixBufUsed(&mb) == 0); - RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, &aFrames16, sizeof(aFrames16), &cFramesWritten)); + AudioMixBufWrite(&mb, &WriteState, &aFrames16, sizeof(aFrames16), 0 /*offDstFrame*/, cBufSize / 4, &cFramesWritten); RTTESTI_CHECK(cFramesWritten == 1 /* Frames */); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == 0); + AudioMixBufCommit(&mb, cFramesWritten); RTTESTI_CHECK(AudioMixBufUsed(&mb) == 1); + RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0); + RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 1); - RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 2 /* Offset */, &aFrames32, sizeof(aFrames32), &cFramesWritten)); + AudioMixBufWrite(&mb, &WriteState, &aFrames32, sizeof(aFrames32), 0 /*offDstFrame*/, cBufSize / 4, &cFramesWritten); RTTESTI_CHECK(cFramesWritten == 2 /* Frames */); - RTTESTI_CHECK(AudioMixBufUsed(&mb) == 2); + AudioMixBufCommit(&mb, cFramesWritten); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == 3); + RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0); + RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 3); - /* Beyond buffer. */ - RTTESTI_CHECK_RC(AudioMixBufWriteAt(&mb, AudioMixBufSize(&mb) + 1, &aFrames16, sizeof(aFrames16), - &cFramesWritten), VERR_BUFFER_OVERFLOW); - - /* Offset wrap-around: When writing as much (or more) frames the mixing buffer can hold. */ - uint32_t cbSamples = cBufSize * sizeof(int16_t) * 2 /* Channels */; - RTTESTI_CHECK(cbSamples); + /* Pretend we read the frames.*/ + AudioMixBufAdvance(&mb, 3); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == 0); + RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 3); + RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 3); + + /* Fill up the buffer completely and check wraps. */ + + uint32_t cbSamples = PDMAudioPropsFramesToBytes(&config, cBufSize); uint16_t *paSamples = (uint16_t *)RTMemAlloc(cbSamples); - RTTESTI_CHECK(paSamples); - RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, paSamples, cbSamples, &cFramesWritten)); - RTTESTI_CHECK(cFramesWritten == cBufSize /* Frames */); + RTTESTI_CHECK_RETV(paSamples); + AudioMixBufWrite(&mb, &WriteState, paSamples, cbSamples, 0 /*offDstFrame*/, cBufSize, &cFramesWritten); + RTTESTI_CHECK(cFramesWritten == cBufSize); + AudioMixBufCommit(&mb, cFramesWritten); RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize); - RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0); - RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 0); + RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 3); + RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 3); RTMemFree(paSamples); cbSamples = 0; /* - * Circular writes. + * Writes and reads (used to be circular). */ - AudioMixBufReset(&mb); - - RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 2 /* Offset */, &aFrames32, sizeof(aFrames32), &cFramesWritten)); - RTTESTI_CHECK(cFramesWritten == 2 /* Frames */); - RTTESTI_CHECK(AudioMixBufUsed(&mb) == 2); + AudioMixBufDrop(&mb); cFramesWrittenAbs = AudioMixBufUsed(&mb); uint32_t cToWrite = AudioMixBufSize(&mb) - cFramesWrittenAbs - 1; /* -1 as padding plus -2 frames for above. */ for (uint32_t i = 0; i < cToWrite; i++) { - RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&mb, &aFrames16, sizeof(aFrames16), &cFramesWritten)); + AudioMixBufWrite(&mb, &WriteState, &aFrames16[0], sizeof(aFrames16), 0 /*offDstFrame*/, 1, &cFramesWritten); RTTESTI_CHECK(cFramesWritten == 1); + AudioMixBufCommit(&mb, cFramesWritten); } RTTESTI_CHECK(!AudioMixBufIsEmpty(&mb)); RTTESTI_CHECK(AudioMixBufFree(&mb) == 1); RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, 1U)); RTTESTI_CHECK(AudioMixBufUsed(&mb) == cToWrite + cFramesWrittenAbs /* + last absolute write */); - RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&mb, &aFrames16, sizeof(aFrames16), &cFramesWritten)); + AudioMixBufWrite(&mb, &WriteState, &aFrames16[0], sizeof(aFrames16), 0 /*offDstFrame*/, 1, &cFramesWritten); RTTESTI_CHECK(cFramesWritten == 1); + AudioMixBufCommit(&mb, cFramesWritten); RTTESTI_CHECK(AudioMixBufFree(&mb) == 0); RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, 0U)); RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize); - /* Circular reads. */ + /* Reads. */ + RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0); + uint32_t cbRead; + uint16_t aFrames16Buf[RT_ELEMENTS(aFrames16)]; uint32_t cToRead = AudioMixBufSize(&mb) - cFramesWrittenAbs - 1; for (uint32_t i = 0; i < cToRead; i++) { - RTTESTI_CHECK_RC_OK(AudioMixBufAcquireReadBlock(&mb, &aFrames16, sizeof(aFrames16), &cFramesRead)); + AudioMixBufPeek(&mb, 0 /*offSrcFrame*/, 1, &cFramesRead, &PeekState, aFrames16Buf, sizeof(aFrames16Buf), &cbRead); RTTESTI_CHECK(cFramesRead == 1); - AudioMixBufReleaseReadBlock(&mb, cFramesRead); - AudioMixBufFinish(&mb, cFramesRead); + RTTESTI_CHECK(cbRead == sizeof(aFrames16Buf)); + AudioMixBufAdvance(&mb, cFramesRead); + RTTESTI_CHECK(AudioMixBufReadPos(&mb) == i + 1); } RTTESTI_CHECK(!AudioMixBufIsEmpty(&mb)); RTTESTI_CHECK(AudioMixBufFree(&mb) == AudioMixBufSize(&mb) - cFramesWrittenAbs - 1); RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, cBufSize - cFramesWrittenAbs - 1)); RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize - cToRead); - RTTESTI_CHECK_RC_OK(AudioMixBufAcquireReadBlock(&mb, &aFrames16, sizeof(aFrames16), &cFramesRead)); + AudioMixBufPeek(&mb, 0 /*offSrcFrame*/, 1, &cFramesRead, &PeekState, aFrames16Buf, sizeof(aFrames16Buf), &cbRead); RTTESTI_CHECK(cFramesRead == 1); - AudioMixBufReleaseReadBlock(&mb, cFramesRead); - AudioMixBufFinish(&mb, cFramesRead); + RTTESTI_CHECK(cbRead == sizeof(aFrames16Buf)); + AudioMixBufAdvance(&mb, cFramesRead); RTTESTI_CHECK(AudioMixBufFree(&mb) == cBufSize - cFramesWrittenAbs); RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, cBufSize - cFramesWrittenAbs)); RTTESTI_CHECK(AudioMixBufUsed(&mb) == cFramesWrittenAbs); + RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0); - AudioMixBufDestroy(&mb); - - return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; + AudioMixBufTerm(&mb); } -static int tstParentChild(RTTEST hTest) +/** @name Eight test samples represented in all basic formats. + * @{ */ +static uint8_t const g_au8TestSamples[8] = { 0x1, 0x11, 0x32, 0x7f, 0x80, 0x81, 0xbe, 0xff }; +static int8_t const g_ai8TestSamples[8] = { -127, -111, -78, -1, 0, 1, 62, 127 }; +static uint16_t const g_au16TestSamples[8] = { 0x100, 0x1100, 0x3200, 0x7f00, 0x8000, 0x8100, 0xbe00, 0xff00 }; +static int16_t const g_ai16TestSamples[8] = { -32512, -28416, -19968, -256, 0, 256, 15872, 32512 }; +static uint32_t const g_au32TestSamples[8] = { 0x1000000, 0x11000000, 0x32000000, 0x7f000000, 0x80000000, 0x81000000, 0xbe000000, 0xff000000 }; +static int32_t const g_ai32TestSamples[8] = { -2130706432, -1862270976, -1308622848, -16777216, 0, 16777216, 1040187392, 2130706432 }; +static int64_t const g_ai64TestSamples[8] = { -2130706432, -1862270976, -1308622848, -16777216, 0, 16777216, 1040187392, 2130706432 }; +static struct { void const *apv[2]; uint32_t cb; } g_aTestSamples[] = { - uint32_t cParentBufSize = RTRandU32Ex(_1K /* Min */, _16K /* Max */); /* Enough room for random sizes */ - - /* 44100Hz, 2 Channels, S16 */ - PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR( - 2, /* Bytes */ - true, /* Signed */ - 2, /* Channels */ - 44100, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ - false /* Swap Endian */ - ); - - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p)); - - PDMAUDIOMIXBUF parent; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cParentBufSize)); - - /* 22050Hz, 2 Channels, S16 */ - PDMAUDIOPCMPROPS cfg_c1 = PDMAUDIOPCMPROPS_INITIALIZOR(/* Upmixing to parent */ - 2, /* Bytes */ - true, /* Signed */ - 2, /* Channels */ - 22050, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ - false /* Swap Endian */ - ); - - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c1)); - - uint32_t cFrames = 16; - uint32_t cChildBufSize = RTRandU32Ex(cFrames /* Min */, 64 /* Max */); - - PDMAUDIOMIXBUF child1; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child1, "Child1", &cfg_c1, cChildBufSize)); - RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child1, &parent)); - - /* 48000Hz, 2 Channels, S16 */ - PDMAUDIOPCMPROPS cfg_c2 = PDMAUDIOPCMPROPS_INITIALIZOR(/* Downmixing to parent */ - 2, /* Bytes */ - true, /* Signed */ - 2, /* Channels */ - 48000, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ - false /* Swap Endian */ - ); - - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c2)); - - PDMAUDIOMIXBUF child2; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child2, "Child2", &cfg_c2, cChildBufSize)); - RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child2, &parent)); - - /* - * Writing + mixing from child/children -> parent, sequential. - */ - uint32_t cbBuf = _1K; - char pvBuf[_1K]; - int16_t aFrames16[32] = { 0xAA, 0xBB }; - uint32_t cFramesRead, cFramesWritten, cFramesMixed; - - uint32_t cFramesChild1 = cFrames; - uint32_t cFramesChild2 = cFrames; - - uint32_t t = RTRandU32() % 32; - - RTTestPrintf(hTest, RTTESTLVL_DEBUG, - "cParentBufSize=%RU32, cChildBufSize=%RU32, %RU32 frames -> %RU32 iterations total\n", - cParentBufSize, cChildBufSize, cFrames, t); - - /* - * Using AudioMixBufWriteAt for writing to children. - */ - RTTestSubF(hTest, "2 Children -> Parent (AudioMixBufWriteAt)"); - - uint32_t cChildrenSamplesMixedTotal = 0; + /* 0/0: */ { { NULL, NULL }, 0 }, + /* 1/8: */ { { g_au8TestSamples, g_ai8TestSamples }, sizeof( g_au8TestSamples) }, + /* 2/16: */ { { g_au16TestSamples, g_ai16TestSamples }, sizeof(g_au16TestSamples) }, + /* 3/24: */ { { NULL, NULL }, 0 }, + /* 4/32: */ { { g_au32TestSamples, g_ai32TestSamples }, sizeof(g_au32TestSamples) }, + /* 5: */ { { NULL, NULL }, 0 }, + /* 6: */ { { NULL, NULL }, 0 }, + /* 7: */ { { NULL, NULL }, 0 }, + /* 8:64 */ { { NULL, g_ai64TestSamples }, sizeof(g_ai64TestSamples) }, /* raw */ +}; +/** @} */ + +/** Fills a buffer with samples from an g_aTestSamples entry. */ +static uint32_t tstFillBuf(PCPDMAUDIOPCMPROPS pCfg, void const *pvTestSamples, uint32_t iTestSample, + uint8_t *pbBuf, uint32_t cFrames) +{ + uint8_t const cTestSamples = RT_ELEMENTS(g_au8TestSamples); - for (uint32_t i = 0; i < t; i++) + cFrames *= PDMAudioPropsChannels(pCfg); + switch (PDMAudioPropsSampleSize(pCfg)) { - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "i=%RU32\n", i); - - uint32_t cChild1Writes = RTRandU32() % 8; - - for (uint32_t c1 = 0; c1 < cChild1Writes; c1++) + case 1: { - /* Child 1. */ - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufWriteAt(&child1, 0, &aFrames16, sizeof(aFrames16), &cFramesWritten)); - RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesChild1, ("Child1: Expected %RU32 written frames, got %RU32\n", cFramesChild1, cFramesWritten)); - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufMixToParent(&child1, cFramesWritten, &cFramesMixed)); - - cChildrenSamplesMixedTotal += cFramesMixed; - - RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesMixed, ("Child1: Expected %RU32 mixed frames, got %RU32\n", cFramesWritten, cFramesMixed)); - RTTESTI_CHECK_MSG_BREAK(AudioMixBufUsed(&child1) == 0, ("Child1: Expected %RU32 used frames, got %RU32\n", 0, AudioMixBufUsed(&child1))); + uint8_t const * const pau8TestSamples = (uint8_t const *)pvTestSamples; + uint8_t *pu8Dst = (uint8_t *)pbBuf; + while (cFrames-- > 0) + { + *pu8Dst++ = pau8TestSamples[iTestSample]; + iTestSample = (iTestSample + 1) % cTestSamples; + } + break; } - uint32_t cChild2Writes = RTRandU32() % 8; - - for (uint32_t c2 = 0; c2 < cChild2Writes; c2++) + case 2: { - /* Child 2. */ - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufWriteAt(&child2, 0, &aFrames16, sizeof(aFrames16), &cFramesWritten)); - RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesChild2, ("Child2: Expected %RU32 written frames, got %RU32\n", cFramesChild2, cFramesWritten)); - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufMixToParent(&child2, cFramesWritten, &cFramesMixed)); - - cChildrenSamplesMixedTotal += cFramesMixed; - - RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesMixed, ("Child2: Expected %RU32 mixed frames, got %RU32\n", cFramesWritten, cFramesMixed)); - RTTESTI_CHECK_MSG_BREAK(AudioMixBufUsed(&child2) == 0, ("Child2: Expected %RU32 used frames, got %RU32\n", 0, AudioMixBufUsed(&child2))); + uint16_t const * const pau16TestSamples = (uint16_t const *)pvTestSamples; + uint16_t *pu16Dst = (uint16_t *)pbBuf; + while (cFrames-- > 0) + { + *pu16Dst++ = pau16TestSamples[iTestSample]; + iTestSample = (iTestSample + 1) % cTestSamples; + } + break; } - /* - * Read out all frames from the parent buffer and also mark the just-read frames as finished - * so that both connected children buffers can keep track of their stuff. - */ - uint32_t cParentSamples = AudioMixBufUsed(&parent); - while (cParentSamples) + case 4: { - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, pvBuf, cbBuf, &cFramesRead)); - if (!cFramesRead) - break; - - AudioMixBufReleaseReadBlock(&parent, cFramesRead); - AudioMixBufFinish(&parent, cFramesRead); + uint32_t const * const pau32TestSamples = (uint32_t const *)pvTestSamples; + uint32_t *pu32Dst = (uint32_t *)pbBuf; + while (cFrames-- > 0) + { + *pu32Dst++ = pau32TestSamples[iTestSample]; + iTestSample = (iTestSample + 1) % cTestSamples; + } + break; + } - RTTESTI_CHECK(cParentSamples >= cFramesRead); - cParentSamples -= cFramesRead; + case 8: + { + uint64_t const * const pau64TestSamples = (uint64_t const *)pvTestSamples; + uint64_t *pu64Dst = (uint64_t *)pbBuf; + while (cFrames-- > 0) + { + *pu64Dst++ = pau64TestSamples[iTestSample]; + iTestSample = (iTestSample + 1) % cTestSamples; + } + break; } - RTTESTI_CHECK(cParentSamples == 0); + default: + AssertFailedBreak(); } - - RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0); - RTTESTI_CHECK(AudioMixBufLive(&child1) == 0); - RTTESTI_CHECK(AudioMixBufLive(&child2) == 0); - - AudioMixBufDestroy(&parent); - AudioMixBufDestroy(&child1); - AudioMixBufDestroy(&child2); - - return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; + return iTestSample; } -/* Test 8-bit sample conversion (8-bit -> internal -> 8-bit). */ -static int tstConversion8(RTTEST hTest) -{ - unsigned i; - uint32_t cBufSize = 256; - - RTTestSubF(hTest, "Sample conversion (U8)"); - /* 44100Hz, 1 Channel, U8 */ - PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR( - 1, /* Bytes */ - false, /* Signed */ - 1, /* Channels */ - 44100, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(1 /* Bytes */, 1 /* Channels */), /* Shift */ - false /* Swap Endian */ - ); +static void tstConversion(RTTEST hTest, uint8_t cSrcBits, bool fSrcSigned, uint8_t cSrcChs, + uint8_t cDstBits, bool fDstSigned, uint8_t cDstChs) +{ + RTTestSubF(hTest, "Conv %uch %c%u to %uch %c%u", cSrcChs, fSrcSigned ? 'S' : 'U', cSrcBits, + cDstChs, fDstSigned ? 'S' : 'U', cDstBits); - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p)); + PDMAUDIOPCMPROPS CfgSrc, CfgDst; + PDMAudioPropsInitEx(&CfgSrc, cSrcBits / 8, fSrcSigned, cSrcChs, 44100, g_fLittleEndian, cSrcBits == 64 /*fRaw*/); + PDMAudioPropsInitEx(&CfgDst, cDstBits / 8, fDstSigned, cDstChs, 44100, g_fLittleEndian, cDstBits == 64 /*fRaw*/); + + void const * const pvSrcTestSamples = g_aTestSamples[cSrcBits / 8].apv[fSrcSigned]; + void const * const pvDstTestSamples = g_aTestSamples[cDstBits / 8].apv[fDstSigned]; + uint32_t const cMixBufFrames = RTRandU32Ex(128, 16384); + uint32_t const cIterations = RTRandU32Ex(256, 1536); + uint32_t const cbSrcBuf = PDMAudioPropsFramesToBytes(&CfgSrc, cMixBufFrames + 64); + uint8_t * const pbSrcBuf = (uint8_t *)RTMemAllocZ(cbSrcBuf); + uint32_t const cbDstBuf = PDMAudioPropsFramesToBytes(&CfgDst, cMixBufFrames + 64); + uint8_t * const pbDstBuf = (uint8_t *)RTMemAllocZ(cbDstBuf); + uint8_t * const pbDstExpect = (uint8_t *)RTMemAllocZ(cbDstBuf); + RTTESTI_CHECK_RETV(pbSrcBuf); + RTTESTI_CHECK_RETV(pbDstBuf); + RTTESTI_CHECK_RETV(pbDstExpect); + + AUDIOMIXBUF MixBuf; + RTTESTI_CHECK_RC_RETV(AudioMixBufInit(&MixBuf, "FormatOutputConversion", &CfgSrc, cMixBufFrames), VINF_SUCCESS); + AUDIOMIXBUFWRITESTATE WriteState; + RTTESTI_CHECK_RC_RETV(AudioMixBufInitWriteState(&MixBuf, &WriteState, &CfgSrc), VINF_SUCCESS); + AUDIOMIXBUFWRITESTATE WriteStateIgnZero = WriteState; RT_NOREF(WriteStateIgnZero); + AUDIOMIXBUFPEEKSTATE PeekState; + RTTESTI_CHECK_RC_RETV(AudioMixBufInitPeekState(&MixBuf, &PeekState, &CfgDst), VINF_SUCCESS); + + uint32_t iSrcTestSample = 0; + uint32_t iDstTestSample = 0; + //RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "cIterations=%u\n", cIterations); + for (uint32_t iIteration = 0; iIteration < cIterations; iIteration++) + { + /* Write some frames to the buffer. */ + uint32_t const cSrcFramesToWrite = iIteration < 16 ? iIteration + 1 + : AudioMixBufFree(&MixBuf) ? RTRandU32Ex(1, AudioMixBufFree(&MixBuf)) : 0; + if (cSrcFramesToWrite > 0) + { + uint32_t const cbSrcToWrite = PDMAudioPropsFramesToBytes(&CfgSrc, cSrcFramesToWrite); + uint32_t cFrames = RTRandU32(); + switch (RTRandU32Ex(0, 3)) + { + default: + iSrcTestSample = tstFillBuf(&CfgSrc, pvSrcTestSamples, iSrcTestSample, pbSrcBuf, cSrcFramesToWrite); + AudioMixBufWrite(&MixBuf, &WriteState, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames); + RTTESTI_CHECK(cFrames == cSrcFramesToWrite); + break; + + case 1: /* zero & blend */ + AudioMixBufSilence(&MixBuf, &WriteStateIgnZero, 0 /*offFrame*/, cSrcFramesToWrite); + iSrcTestSample = tstFillBuf(&CfgSrc, pvSrcTestSamples, iSrcTestSample, pbSrcBuf, cSrcFramesToWrite); + AudioMixBufBlend(&MixBuf, &WriteState, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames); + RTTESTI_CHECK(cFrames == cSrcFramesToWrite); + break; + + case 2: /* blend same equal data twice */ + { + AUDIOMIXBUFWRITESTATE WriteStateSame = WriteState; + iSrcTestSample = tstFillBuf(&CfgSrc, pvSrcTestSamples, iSrcTestSample, pbSrcBuf, cSrcFramesToWrite); + AudioMixBufWrite(&MixBuf, &WriteState, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames); + RTTESTI_CHECK(cFrames == cSrcFramesToWrite); + AudioMixBufBlend(&MixBuf, &WriteStateSame, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames); + RTTESTI_CHECK(cFrames == cSrcFramesToWrite); + break; + } + case 3: /* write & blend with zero */ + { + AUDIOMIXBUFWRITESTATE WriteStateSame = WriteState; + iSrcTestSample = tstFillBuf(&CfgSrc, pvSrcTestSamples, iSrcTestSample, pbSrcBuf, cSrcFramesToWrite); + AudioMixBufWrite(&MixBuf, &WriteState, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames); + RTTESTI_CHECK(cFrames == cSrcFramesToWrite); + PDMAudioPropsClearBuffer(&CfgSrc, pbSrcBuf, cbSrcToWrite, cSrcFramesToWrite); + AudioMixBufBlend(&MixBuf, &WriteStateSame, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames); + RTTESTI_CHECK(cFrames == cSrcFramesToWrite); + break; + } + } + AudioMixBufCommit(&MixBuf, cSrcFramesToWrite); + } - PDMAUDIOMIXBUF parent; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cBufSize)); + /* Read some frames back. */ + uint32_t const cUsed = AudioMixBufUsed(&MixBuf); + uint32_t const cDstFramesToRead = iIteration < 16 ? iIteration + 1 : iIteration + 5 >= cIterations ? cUsed + : cUsed ? RTRandU32Ex(1, cUsed) : 0; + if (cDstFramesToRead > 0) + { + uint32_t const cbDstToRead = PDMAudioPropsFramesToBytes(&CfgDst, cDstFramesToRead); + uint32_t cbRead = RTRandU32(); + uint32_t cFrames = RTRandU32(); + RTRandBytes(pbDstBuf, cbDstToRead); + AudioMixBufPeek(&MixBuf, 0 /*offSrcFrame*/, (iIteration & 3) != 2 ? cDstFramesToRead : cUsed, &cFrames, + &PeekState, pbDstBuf, (iIteration & 3) != 3 ? cbDstToRead : cbDstBuf, &cbRead); + RTTESTI_CHECK(cFrames == cDstFramesToRead); + RTTESTI_CHECK(cbRead == cbDstToRead); + AudioMixBufAdvance(&MixBuf, cFrames); + + /* Verify if we can. */ + if (PDMAudioPropsChannels(&CfgSrc) == PDMAudioPropsChannels(&CfgDst)) + { + iDstTestSample = tstFillBuf(&CfgDst, pvDstTestSamples, iDstTestSample, pbDstExpect, cFrames); + if (memcmp(pbDstExpect, pbDstBuf, cbRead) == 0) + { /* likely */ } + else + { + RTTestFailed(hTest, + "mismatch: %.*Rhxs\n" + "expected: %.*Rhxs\n" + "iIteration=%u cDstFramesToRead=%u cbRead=%#x\n", + RT_MIN(cbRead, 48), pbDstBuf, + RT_MIN(cbRead, 48), pbDstExpect, + iIteration, cDstFramesToRead, cbRead); + break; + } + } + } + } - /* Child uses half the sample rate; that ensures the mixing engine can't - * take shortcuts and performs conversion. Because conversion to double - * the sample rate effectively inserts one additional sample between every - * two source frames, N source frames will be converted to N * 2 - 1 - * frames. However, the last source sample will be saved for later - * interpolation and not immediately output. - */ - - /* 22050Hz, 1 Channel, U8 */ - PDMAUDIOPCMPROPS cfg_c = PDMAUDIOPCMPROPS_INITIALIZOR( /* Upmixing to parent */ - 1, /* Bytes */ - false, /* Signed */ - 1, /* Channels */ - 22050, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(1 /* Bytes */, 1 /* Channels */), /* Shift */ - false /* Swap Endian */ - ); + AudioMixBufTerm(&MixBuf); + RTMemFree(pbSrcBuf); + RTMemFree(pbDstBuf); + RTMemFree(pbDstExpect); +} - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c)); - PDMAUDIOMIXBUF child; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg_c, cBufSize)); - RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent)); +#if 0 /** @todo rewrite to non-parent/child setup */ +static void tstDownsampling(RTTEST hTest, uint32_t uFromHz, uint32_t uToHz) +{ + RTTestSubF(hTest, "Downsampling %u to %u Hz (S16)", uFromHz, uToHz); - /* 8-bit unsigned frames. Often used with SB16 device. */ - uint8_t aFrames8U[16] = { 0xAA, 0xBB, 0, 1, 43, 125, 126, 127, - 128, 129, 130, 131, 132, UINT8_MAX - 1, UINT8_MAX, 0 }; + struct { int16_t l, r; } + aSrcFrames[4096], + aDstFrames[4096]; + + /* Parent (destination) buffer is xxxHz 2ch S16 */ + uint32_t const cFramesParent = RTRandU32Ex(16, RT_ELEMENTS(aDstFrames)); + PDMAUDIOPCMPROPS const CfgDst = PDMAUDIOPCMPROPS_INITIALIZER(2 /*cbSample*/, true /*fSigned*/, 2 /*ch*/, uToHz, false /*fSwap*/); + RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&CfgDst)); + AUDIOMIXBUF Parent; + RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInit(&Parent, "ParentDownsampling", &CfgDst, cFramesParent)); + + /* Child (source) buffer is yyykHz 2ch S16 */ + PDMAUDIOPCMPROPS const CfgSrc = PDMAUDIOPCMPROPS_INITIALIZER(2 /*cbSample*/, true /*fSigned*/, 2 /*ch*/, uFromHz, false /*fSwap*/); + RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&CfgSrc)); + uint32_t const cFramesChild = RTRandU32Ex(32, RT_ELEMENTS(aSrcFrames)); + AUDIOMIXBUF Child; + RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInit(&Child, "ChildDownsampling", &CfgSrc, cFramesChild)); + RTTESTI_CHECK_RC_OK_RETV(AudioMixBufLinkTo(&Child, &Parent)); /* - * Writing + mixing from child -> parent, sequential. + * Test parameters. */ - uint32_t cbBuf = 256; - char achBuf[256]; - uint32_t cFramesRead, cFramesWritten, cFramesMixed; - - uint32_t cFramesChild = 16; - uint32_t cFramesParent = cFramesChild * 2 - 2; - uint32_t cFramesTotalRead = 0; - - /**** 8-bit unsigned samples ****/ - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Conversion test %uHz %uch 8-bit\n", cfg_c.uHz, cfg_c.cChannels); - RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames8U, sizeof(aFrames8U), &cFramesWritten)); - RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten)); - RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed)); - uint32_t cFrames = AudioMixBufUsed(&parent); - RTTESTI_CHECK_MSG(AudioMixBufLive(&child) == cFrames, ("Child: Expected %RU32 mixed frames, got %RU32\n", AudioMixBufLive(&child), cFrames)); + uint32_t const cMaxSrcFrames = RT_MIN(cFramesParent * uFromHz / uToHz - 1, cFramesChild); + uint32_t const cIterations = RTRandU32Ex(4, 128); + RTTestErrContext(hTest, "cFramesParent=%RU32 cFramesChild=%RU32 cMaxSrcFrames=%RU32 cIterations=%RU32", + cFramesParent, cFramesChild, cMaxSrcFrames, cIterations); + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "cFramesParent=%RU32 cFramesChild=%RU32 cMaxSrcFrames=%RU32 cIterations=%RU32\n", + cFramesParent, cFramesChild, cMaxSrcFrames, cIterations); - RTTESTI_CHECK(AudioMixBufUsed(&parent) == AudioMixBufLive(&child)); - - for (;;) + /* + * We generate a simple "A" sine wave as input. + */ + uint32_t iSrcFrame = 0; + uint32_t iDstFrame = 0; + double rdFixed = 2.0 * M_PI * 440.0 /* A */ / PDMAudioPropsHz(&CfgSrc); /* Fixed sin() input. */ + for (uint32_t i = 0; i < cIterations; i++) { - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead)); - if (!cFramesRead) - break; - cFramesTotalRead += cFramesRead; - AudioMixBufReleaseReadBlock(&parent, cFramesRead); - AudioMixBufFinish(&parent, cFramesRead); - } + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "i=%RU32\n", i); - RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead)); + /* + * Generate source frames and write them. + */ + uint32_t const cSrcFrames = i < cIterations / 2 + ? RTRandU32Ex(2, cMaxSrcFrames) & ~(uint32_t)1 + : RTRandU32Ex(1, cMaxSrcFrames - 1) | 1; + for (uint32_t j = 0; j < cSrcFrames; j++, iSrcFrame++) + aSrcFrames[j].r = aSrcFrames[j].l = 32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame); + + uint32_t cSrcFramesWritten = UINT32_MAX / 2; + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufWriteAt(&Child, 0, &aSrcFrames, cSrcFrames * sizeof(aSrcFrames[0]), + &cSrcFramesWritten)); + RTTESTI_CHECK_MSG_BREAK(cSrcFrames == cSrcFramesWritten, + ("cSrcFrames=%RU32 vs cSrcFramesWritten=%RU32\n", cSrcFrames, cSrcFramesWritten)); - /* Check that the frames came out unharmed. Every other sample is interpolated and we ignore it. */ - /* NB: This also checks that the default volume setting is 0dB attenuation. */ - uint8_t *pSrc8 = &aFrames8U[0]; - uint8_t *pDst8 = (uint8_t *)achBuf; + /* + * Mix them. + */ + uint32_t cSrcFramesMixed = UINT32_MAX / 2; + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufMixToParent(&Child, cSrcFramesWritten, &cSrcFramesMixed)); + RTTESTI_CHECK_MSG(AudioMixBufUsed(&Child) == 0, ("%RU32\n", AudioMixBufUsed(&Child))); + RTTESTI_CHECK_MSG_BREAK(cSrcFramesWritten == cSrcFramesMixed, + ("cSrcFramesWritten=%RU32 cSrcFramesMixed=%RU32\n", cSrcFramesWritten, cSrcFramesMixed)); + RTTESTI_CHECK_MSG_BREAK(AudioMixBufUsed(&Child) == 0, ("%RU32\n", AudioMixBufUsed(&Child))); - for (i = 0; i < cFramesChild - 1; ++i) - { - RTTESTI_CHECK_MSG(*pSrc8 == *pDst8, ("index %u: Dst=%d, Src=%d\n", i, *pDst8, *pSrc8)); - pSrc8 += 1; - pDst8 += 2; + /* + * Read out the parent buffer. + */ + uint32_t cDstFrames = AudioMixBufUsed(&Parent); + while (cDstFrames > 0) + { + uint32_t cFramesRead = UINT32_MAX / 2; + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&Parent, aDstFrames, sizeof(aDstFrames), &cFramesRead)); + RTTESTI_CHECK_MSG(cFramesRead > 0 && cFramesRead <= cDstFrames, + ("cFramesRead=%RU32 cDstFrames=%RU32\n", cFramesRead, cDstFrames)); + + AudioMixBufReleaseReadBlock(&Parent, cFramesRead); + AudioMixBufFinish(&Parent, cFramesRead); + + iDstFrame += cFramesRead; + cDstFrames -= cFramesRead; + RTTESTI_CHECK(AudioMixBufUsed(&Parent) == cDstFrames); + } } - RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0); - RTTESTI_CHECK(AudioMixBufLive(&child) == 0); - - AudioMixBufDestroy(&parent); - AudioMixBufDestroy(&child); + RTTESTI_CHECK(AudioMixBufUsed(&Parent) == 0); + RTTESTI_CHECK(AudioMixBufLive(&Child) == 0); + uint32_t const cDstMinExpect = (uint64_t)iSrcFrame * uToHz / uFromHz; + uint32_t const cDstMaxExpect = ((uint64_t)iSrcFrame * uToHz + uFromHz - 1) / uFromHz; + RTTESTI_CHECK_MSG(iDstFrame == cDstMinExpect || iDstFrame == cDstMaxExpect, + ("iSrcFrame=%#x -> %#x,%#x; iDstFrame=%#x\n", iSrcFrame, cDstMinExpect, cDstMaxExpect, iDstFrame)); - return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; + AudioMixBufDestroy(&Parent); + AudioMixBufDestroy(&Child); } +#endif -/* Test 16-bit sample conversion (16-bit -> internal -> 16-bit). */ -static int tstConversion16(RTTEST hTest) -{ - unsigned i; - uint32_t cBufSize = 256; - - RTTestSubF(hTest, "Sample conversion (S16)"); - - /* 44100Hz, 1 Channel, S16 */ - PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR( - 2, /* Bytes */ - true, /* Signed */ - 1, /* Channels */ - 44100, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 1 /* Channels */), /* Shift */ - false /* Swap Endian */ - ); - - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p)); - - PDMAUDIOMIXBUF parent; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cBufSize)); - /* 22050Hz, 1 Channel, S16 */ - PDMAUDIOPCMPROPS cfg_c = PDMAUDIOPCMPROPS_INITIALIZOR( /* Upmixing to parent */ - 2, /* Bytes */ - true, /* Signed */ - 1, /* Channels */ - 22050, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 1 /* Channels */), /* Shift */ - false /* Swap Endian */ - ); - - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c)); - - PDMAUDIOMIXBUF child; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg_c, cBufSize)); - RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent)); +static void tstNewPeek(RTTEST hTest, uint32_t uFromHz, uint32_t uToHz) +{ + RTTestSubF(hTest, "New peek %u to %u Hz (S16)", uFromHz, uToHz); - /* 16-bit signed. More or less exclusively used as output, and usually as input, too. */ - int16_t aFrames16S[16] = { 0xAA, 0xBB, INT16_MIN, INT16_MIN + 1, INT16_MIN / 2, -3, -2, -1, - 0, 1, 2, 3, INT16_MAX / 2, INT16_MAX - 1, INT16_MAX, 0 }; + struct { int16_t l, r; } + aSrcFrames[4096], + aDstFrames[4096]; + + /* Mix buffer is uFromHz 2ch S16 */ + uint32_t const cFrames = RTRandU32Ex(16, RT_ELEMENTS(aSrcFrames)); + PDMAUDIOPCMPROPS const CfgSrc = PDMAUDIOPCMPROPS_INITIALIZER(2 /*cbSample*/, true /*fSigned*/, 2 /*ch*/, uFromHz, false /*fSwap*/); + RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&CfgSrc)); + AUDIOMIXBUF MixBuf; + RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInit(&MixBuf, "NewPeekMixBuf", &CfgSrc, cFrames)); + + /* Write state (source). */ + AUDIOMIXBUFWRITESTATE WriteState; + RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInitWriteState(&MixBuf, &WriteState, &CfgSrc)); + + /* Peek state (destination) is uToHz 2ch S16 */ + PDMAUDIOPCMPROPS const CfgDst = PDMAUDIOPCMPROPS_INITIALIZER(2 /*cbSample*/, true /*fSigned*/, 2 /*ch*/, uToHz, false /*fSwap*/); + RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&CfgDst)); + AUDIOMIXBUFPEEKSTATE PeekState; + RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInitPeekState(&MixBuf, &PeekState, &CfgDst)); /* - * Writing + mixing from child -> parent, sequential. + * Test parameters. */ - uint32_t cbBuf = 256; - char achBuf[256]; - uint32_t cFramesRead, cFramesWritten, cFramesMixed; - - uint32_t cFramesChild = 16; - uint32_t cFramesParent = cFramesChild * 2 - 2; - uint32_t cFramesTotalRead = 0; + uint32_t const cMaxSrcFrames = RT_MIN(cFrames * uFromHz / uToHz - 1, cFrames); + uint32_t const cIterations = RTRandU32Ex(64, 1024); + RTTestErrContext(hTest, "cFrames=%RU32 cMaxSrcFrames=%RU32 cIterations=%RU32", cFrames, cMaxSrcFrames, cIterations); + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "cFrames=%RU32 cMaxSrcFrames=%RU32 cIterations=%RU32\n", + cFrames, cMaxSrcFrames, cIterations); - /**** 16-bit signed samples ****/ - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Conversion test %uHz %uch 16-bit\n", cfg_c.uHz, cfg_c.cChannels); - RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten)); - RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten)); - RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed)); - uint32_t cFrames = AudioMixBufUsed(&parent); - RTTESTI_CHECK_MSG(AudioMixBufLive(&child) == cFrames, ("Child: Expected %RU32 mixed frames, got %RU32\n", AudioMixBufLive(&child), cFrames)); + /* + * We generate a simple "A" sine wave as input. + */ + uint32_t iSrcFrame = 0; + uint32_t iDstFrame = 0; + double rdFixed = 2.0 * M_PI * 440.0 /* A */ / PDMAudioPropsHz(&CfgSrc); /* Fixed sin() input. */ + for (uint32_t i = 0; i < cIterations; i++) + { + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "i=%RU32\n", i); - RTTESTI_CHECK(AudioMixBufUsed(&parent) == AudioMixBufLive(&child)); + /* + * Generate source frames and write them. + */ + uint32_t const cSrcFrames = i < cIterations / 2 + ? RTRandU32Ex(2, cMaxSrcFrames) & ~(uint32_t)1 + : RTRandU32Ex(1, cMaxSrcFrames - 1) | 1; + for (uint32_t j = 0; j < cSrcFrames; j++, iSrcFrame++) + aSrcFrames[j].r = aSrcFrames[j].l = 32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame); + + uint32_t cSrcFramesWritten = UINT32_MAX / 2; + AudioMixBufWrite(&MixBuf, &WriteState, &aSrcFrames[0], cSrcFrames * sizeof(aSrcFrames[0]), + 0 /*offDstFrame*/, cSrcFrames, &cSrcFramesWritten); + RTTESTI_CHECK_MSG_BREAK(cSrcFrames == cSrcFramesWritten, + ("cSrcFrames=%RU32 vs cSrcFramesWritten=%RU32 cLiveFrames=%RU32\n", + cSrcFrames, cSrcFramesWritten, AudioMixBufUsed(&MixBuf))); + AudioMixBufCommit(&MixBuf, cSrcFrames); - for (;;) - { - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead)); - if (!cFramesRead) - break; - cFramesTotalRead += cFramesRead; - AudioMixBufReleaseReadBlock(&parent, cFramesRead); - AudioMixBufFinish(&parent, cFramesRead); - } - RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead)); + /* + * Read out all the frames using the peek function. + */ + uint32_t offSrcFrame = 0; + while (offSrcFrame < cSrcFramesWritten) + { + uint32_t cSrcFramesToRead = cSrcFramesWritten - offSrcFrame; + uint32_t cTmp = (uint64_t)cSrcFramesToRead * uToHz / uFromHz; + if (cTmp + 32 >= RT_ELEMENTS(aDstFrames)) + cSrcFramesToRead = ((uint64_t)RT_ELEMENTS(aDstFrames) - 32) * uFromHz / uToHz; /* kludge */ + + uint32_t cSrcFramesPeeked = UINT32_MAX / 4; + uint32_t cbDstPeeked = UINT32_MAX / 2; + RTRandBytes(aDstFrames, sizeof(aDstFrames)); + AudioMixBufPeek(&MixBuf, offSrcFrame, cSrcFramesToRead, &cSrcFramesPeeked, + &PeekState, aDstFrames, sizeof(aDstFrames), &cbDstPeeked); + uint32_t cDstFramesPeeked = PDMAudioPropsBytesToFrames(&CfgDst, cbDstPeeked); + RTTESTI_CHECK(cbDstPeeked > 0 || cSrcFramesPeeked > 0); + + if (uFromHz == uToHz) + { + for (uint32_t iDst = 0; iDst < cDstFramesPeeked; iDst++) + if (memcmp(&aDstFrames[iDst], &aSrcFrames[offSrcFrame + iDst], sizeof(aSrcFrames[0])) != 0) + RTTestFailed(hTest, "Frame #%u differs: %#x / %#x, expected %#x / %#x\n", iDstFrame + iDst, + aDstFrames[iDst].l, aDstFrames[iDst].r, + aSrcFrames[iDst + offSrcFrame].l, aSrcFrames[iDst + offSrcFrame].r); + } - /* Check that the frames came out unharmed. Every other sample is interpolated and we ignore it. */ - /* NB: This also checks that the default volume setting is 0dB attenuation. */ - int16_t *pSrc16 = &aFrames16S[0]; - int16_t *pDst16 = (int16_t *)achBuf; + offSrcFrame += cSrcFramesPeeked; + iDstFrame += cDstFramesPeeked; + } - for (i = 0; i < cFramesChild - 1; ++i) - { - RTTESTI_CHECK_MSG(*pSrc16 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16)); - pSrc16 += 1; - pDst16 += 2; + /* + * Then advance. + */ + AudioMixBufAdvance(&MixBuf, cSrcFrames); + RTTESTI_CHECK(AudioMixBufUsed(&MixBuf) == 0); } - RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0); - RTTESTI_CHECK(AudioMixBufLive(&child) == 0); - - AudioMixBufDestroy(&parent); - AudioMixBufDestroy(&child); + /** @todo this is a bit lax... */ + uint32_t const cDstMinExpect = ((uint64_t)iSrcFrame * uToHz - uFromHz - 1) / uFromHz; + uint32_t const cDstMaxExpect = ((uint64_t)iSrcFrame * uToHz + uFromHz - 1) / uFromHz; + RTTESTI_CHECK_MSG(iDstFrame >= cDstMinExpect && iDstFrame <= cDstMaxExpect, + ("iSrcFrame=%#x -> %#x..%#x; iDstFrame=%#x (delta %d)\n", + iSrcFrame, cDstMinExpect, cDstMaxExpect, iDstFrame, (cDstMinExpect + cDstMaxExpect) / 2 - iDstFrame)); - return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; + AudioMixBufTerm(&MixBuf); } /* Test volume control. */ -static int tstVolume(RTTEST hTest) +static void tstVolume(RTTEST hTest) { - unsigned i; - uint32_t cBufSize = 256; - - RTTestSubF(hTest, "Volume control"); + RTTestSub(hTest, "Volume control (44.1kHz S16 2ch)"); + uint32_t const cBufSize = 256; - /* Same for parent/child. */ - /* 44100Hz, 2 Channels, S16 */ - PDMAUDIOPCMPROPS cfg = PDMAUDIOPCMPROPS_INITIALIZOR( + /* + * Configure a mixbuf where we read and write 44.1kHz S16 2ch. + */ + PDMAUDIOPCMPROPS const Cfg = PDMAUDIOPCMPROPS_INITIALIZER( 2, /* Bytes */ true, /* Signed */ 2, /* Channels */ 44100, /* Hz */ - PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ false /* Swap Endian */ ); + AUDIOMIXBUF MixBuf; + RTTESTI_CHECK_RC_RETV(AudioMixBufInit(&MixBuf, "Volume", &Cfg, cBufSize), VINF_SUCCESS); - RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg)); + AUDIOMIXBUFWRITESTATE WriteState; + RTTESTI_CHECK_RC_RETV(AudioMixBufInitWriteState(&MixBuf, &WriteState, &Cfg), VINF_SUCCESS); - PDMAUDIOVOLUME vol = { false, 0, 0 }; /* Not muted. */ - PDMAUDIOMIXBUF parent; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg, cBufSize)); - - PDMAUDIOMIXBUF child; - RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg, cBufSize)); - RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent)); - - /* A few 16-bit signed samples. */ - int16_t aFrames16S[16] = { INT16_MIN, INT16_MIN + 1, -128, -64, -4, -1, 0, 1, - 2, 255, 256, INT16_MAX / 2, INT16_MAX - 2, INT16_MAX - 1, INT16_MAX, 0 }; + AUDIOMIXBUFPEEKSTATE PeekState; + RTTESTI_CHECK_RC_RETV(AudioMixBufInitPeekState(&MixBuf, &PeekState, &Cfg), VINF_SUCCESS); /* - * Writing + mixing from child -> parent. + * A few 16-bit signed test samples. */ - uint32_t cbBuf = 256; - char achBuf[256]; - uint32_t cFramesRead, cFramesWritten, cFramesMixed; - - uint32_t cFramesChild = 8; - uint32_t cFramesParent = cFramesChild; - uint32_t cFramesTotalRead; - int16_t *pSrc16; - int16_t *pDst16; - - /**** Volume control test ****/ - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Volume control test %uHz %uch \n", cfg.uHz, cfg.cChannels); - - /* 1) Full volume/0dB attenuation (255). */ - vol.uLeft = vol.uRight = 255; - AudioMixBufSetVolume(&child, &vol); + static int16_t const s_aFrames16S[16] = + { + INT16_MIN, INT16_MIN + 1, -128, -64, -4, -1, 0, 1, + 2, 255, 256, INT16_MAX / 2, INT16_MAX - 2, INT16_MAX - 1, INT16_MAX, 0, + }; - RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten)); - RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten)); - RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed)); + /* + * 1) Full volume/0dB attenuation (255). + */ + PDMAUDIOVOLUME Vol = PDMAUDIOVOLUME_INITIALIZER_MAX; + AudioMixBufSetVolume(&MixBuf, &Vol); - cFramesTotalRead = 0; - for (;;) - { - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead)); - if (!cFramesRead) - break; - cFramesTotalRead += cFramesRead; - AudioMixBufReleaseReadBlock(&parent, cFramesRead); - AudioMixBufFinish(&parent, cFramesRead); - } - RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead)); + /* Write all the test frames to the mixer buffer: */ + uint32_t cFramesWritten; + AudioMixBufWrite(&MixBuf, &WriteState, &s_aFrames16S[0], sizeof(s_aFrames16S), 0 /*offDstFrame*/, cBufSize, &cFramesWritten); + RTTESTI_CHECK(cFramesWritten == RT_ELEMENTS(s_aFrames16S) / 2); + AudioMixBufCommit(&MixBuf, cFramesWritten); + + /* Read them back. We should get them back just like we wrote them. */ + uint16_t au16Buf[cBufSize * 2]; + uint32_t cFramesPeeked; + uint32_t cbPeeked; + AudioMixBufPeek(&MixBuf, 0 /*offSrcFrame*/, cFramesWritten, &cFramesPeeked, &PeekState, au16Buf, sizeof(au16Buf), &cbPeeked); + RTTESTI_CHECK(cFramesPeeked == cFramesWritten); + RTTESTI_CHECK(cbPeeked == PDMAudioPropsFramesToBytes(&Cfg, cFramesPeeked)); + AudioMixBufAdvance(&MixBuf, cFramesPeeked); /* Check that at 0dB the frames came out unharmed. */ - pSrc16 = &aFrames16S[0]; - pDst16 = (int16_t *)achBuf; + if (memcmp(au16Buf, s_aFrames16S, sizeof(s_aFrames16S)) != 0) + RTTestFailed(hTest, + "0dB test failed\n" + "mismatch: %.*Rhxs\n" + "expected: %.*Rhxs\n", + sizeof(s_aFrames16S), au16Buf, sizeof(s_aFrames16S), s_aFrames16S); - for (i = 0; i < cFramesParent * 2 /* stereo */; ++i) - { - RTTESTI_CHECK_MSG(*pSrc16 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16)); - ++pSrc16; - ++pDst16; - } - AudioMixBufReset(&child); - - /* 2) Half volume/-6dB attenuation (16 steps down). */ - vol.uLeft = vol.uRight = 255 - 16; - AudioMixBufSetVolume(&child, &vol); - - RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten)); - RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten)); - RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed)); + /* + * 2) Half volume/-6dB attenuation (16 steps down). + */ + PDMAudioVolumeInitFromStereo(&Vol, false, 255 - 16, 255 - 16); + AudioMixBufSetVolume(&MixBuf, &Vol); - cFramesTotalRead = 0; - for (;;) - { - RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead)); - if (!cFramesRead) - break; - cFramesTotalRead += cFramesRead; - AudioMixBufReleaseReadBlock(&parent, cFramesRead); - AudioMixBufFinish(&parent, cFramesRead); - } - RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead)); + /* Write all the test frames to the mixer buffer: */ + AudioMixBufWrite(&MixBuf, &WriteState, &s_aFrames16S[0], sizeof(s_aFrames16S), 0 /*offDstFrame*/, cBufSize, &cFramesWritten); + RTTESTI_CHECK(cFramesWritten == RT_ELEMENTS(s_aFrames16S) / 2); + AudioMixBufCommit(&MixBuf, cFramesWritten); + + /* Read them back. We should get them back just like we wrote them. */ + AudioMixBufPeek(&MixBuf, 0 /*offSrcFrame*/, cFramesWritten, &cFramesPeeked, &PeekState, au16Buf, sizeof(au16Buf), &cbPeeked); + RTTESTI_CHECK(cFramesPeeked == cFramesWritten); + RTTESTI_CHECK(cbPeeked == PDMAudioPropsFramesToBytes(&Cfg, cFramesPeeked)); + AudioMixBufAdvance(&MixBuf, cFramesPeeked); /* Check that at -6dB the sample values are halved. */ - pSrc16 = &aFrames16S[0]; - pDst16 = (int16_t *)achBuf; - - for (i = 0; i < cFramesParent * 2 /* stereo */; ++i) - { - /* Watch out! For negative values, x >> 1 is not the same as x / 2. */ - RTTESTI_CHECK_MSG(*pSrc16 >> 1 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16)); - ++pSrc16; - ++pDst16; - } + int16_t ai16Expect[sizeof(s_aFrames16S) / 2]; + memcpy(ai16Expect, s_aFrames16S, sizeof(ai16Expect)); + for (uintptr_t i = 0; i < RT_ELEMENTS(ai16Expect); i++) + ai16Expect[i] >>= 1; /* /= 2 - not the same for signed numbers; */ + if (memcmp(au16Buf, ai16Expect, sizeof(ai16Expect)) != 0) + RTTestFailed(hTest, + "-6dB test failed\n" + "mismatch: %.*Rhxs\n" + "expected: %.*Rhxs\n" + "wrote: %.*Rhxs\n", + sizeof(ai16Expect), au16Buf, sizeof(ai16Expect), ai16Expect, sizeof(s_aFrames16S), s_aFrames16S); - AudioMixBufDestroy(&parent); - AudioMixBufDestroy(&child); - - return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; + AudioMixBufTerm(&MixBuf); } + int main(int argc, char **argv) { RTR3InitExe(argc, &argv, 0); @@ -634,15 +866,38 @@ return rc; RTTestBanner(hTest); - rc = tstSingle(hTest); - if (RT_SUCCESS(rc)) - rc = tstParentChild(hTest); - if (RT_SUCCESS(rc)) - rc = tstConversion8(hTest); - if (RT_SUCCESS(rc)) - rc = tstConversion16(hTest); - if (RT_SUCCESS(rc)) - rc = tstVolume(hTest); + tstBasics(hTest); + tstSimple(hTest); + + /* Run tstConversion for all combinations we have test data. */ + for (unsigned iSrc = 0; iSrc < RT_ELEMENTS(g_aTestSamples); iSrc++) + { + for (unsigned iSrcSigned = 0; iSrcSigned < RT_ELEMENTS(g_aTestSamples[0].apv); iSrcSigned++) + if (g_aTestSamples[iSrc].apv[iSrcSigned]) + for (unsigned cSrcChs = 1; cSrcChs <= 2; cSrcChs++) + for (unsigned iDst = 0; iDst < RT_ELEMENTS(g_aTestSamples); iDst++) + for (unsigned iDstSigned = 0; iDstSigned < RT_ELEMENTS(g_aTestSamples[0].apv); iDstSigned++) + if (g_aTestSamples[iDst].apv[iDstSigned]) + for (unsigned cDstChs = 1; cDstChs <= 2; cDstChs++) + tstConversion(hTest, iSrc * 8, iSrcSigned == 1, cSrcChs, + /*->*/ iDst * 8, iDstSigned == 1, cDstChs); + } + +#if 0 /** @todo rewrite to non-parent/child setup */ + tstDownsampling(hTest, 44100, 22050); + tstDownsampling(hTest, 48000, 44100); + tstDownsampling(hTest, 48000, 22050); + tstDownsampling(hTest, 48000, 11000); +#endif + tstNewPeek(hTest, 48000, 48000); + tstNewPeek(hTest, 48000, 11000); + tstNewPeek(hTest, 48000, 44100); + tstNewPeek(hTest, 44100, 22050); + tstNewPeek(hTest, 44100, 11000); + //tstNewPeek(hTest, 11000, 48000); + //tstNewPeek(hTest, 22050, 44100); + + tstVolume(hTest); /* * Summary diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,246 +0,0 @@ -/* $Id: VBoxMMNotificationClient.cpp $ */ -/** @file - * VBoxMMNotificationClient.cpp - Implementation of the IMMNotificationClient interface - * to detect audio endpoint changes. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#include "VBoxMMNotificationClient.h" - -#include - -#pragma warning(push) -#pragma warning(disable: 4201) -#include -#include -#pragma warning(pop) - -#ifdef LOG_GROUP -# undef LOG_GROUP -#endif -#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO -#include - -VBoxMMNotificationClient::VBoxMMNotificationClient(void) - : m_fRegisteredClient(false) - , m_cRef(1) -{ -} - -VBoxMMNotificationClient::~VBoxMMNotificationClient(void) -{ -} - -/** - * Uninitializes the mulitmedia notification client implementation. - */ -void VBoxMMNotificationClient::Dispose(void) -{ - DetachFromEndpoint(); - - if (m_fRegisteredClient) - { - m_pEnum->UnregisterEndpointNotificationCallback(this); - - m_fRegisteredClient = false; - } -} - -/** - * Initializes the mulitmedia notification client implementation. - * - * @return HRESULT - */ -HRESULT VBoxMMNotificationClient::Initialize(void) -{ - HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), - (void **)&m_pEnum); - if (SUCCEEDED(hr)) - { - hr = m_pEnum->RegisterEndpointNotificationCallback(this); - if (SUCCEEDED(hr)) - { - m_fRegisteredClient = true; - - hr = AttachToDefaultEndpoint(); - } - } - - LogFunc(("Returning %Rhrc\n", hr)); - return hr; -} - -/** - * Registration callback implementation for storing our (required) contexts. - * - * @return IPRT status code. - * @param pDrvIns Driver instance to register the notification client to. - * @param pfnCallback Audio callback to call by the notification client in case of new events. - */ -int VBoxMMNotificationClient::RegisterCallback(PPDMDRVINS pDrvIns, PFNPDMHOSTAUDIOCALLBACK pfnCallback) -{ - this->m_pDrvIns = pDrvIns; - this->m_pfnCallback = pfnCallback; - - return VINF_SUCCESS; -} - -/** - * Unregistration callback implementation for cleaning up our mess when we're done handling - * with notifications. - */ -void VBoxMMNotificationClient::UnregisterCallback(void) -{ - this->m_pDrvIns = NULL; - this->m_pfnCallback = NULL; -} - -/** - * Stub being called when attaching to the default audio endpoint. - * Does nothing at the moment. - */ -HRESULT VBoxMMNotificationClient::AttachToDefaultEndpoint(void) -{ - return S_OK; -} - -/** - * Stub being called when detaching from the default audio endpoint. - * Does nothing at the moment. - */ -void VBoxMMNotificationClient::DetachFromEndpoint(void) -{ - -} - -/** - * Handler implementation which is called when an audio device state - * has been changed. - * - * @return HRESULT - * @param pwstrDeviceId Device ID the state is announced for. - * @param dwNewState New state the device is now in. - */ -STDMETHODIMP VBoxMMNotificationClient::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) -{ - char *pszState = "unknown"; - - switch (dwNewState) - { - case DEVICE_STATE_ACTIVE: - pszState = "active"; - break; - case DEVICE_STATE_DISABLED: - pszState = "disabled"; - break; - case DEVICE_STATE_NOTPRESENT: - pszState = "not present"; - break; - case DEVICE_STATE_UNPLUGGED: - pszState = "unplugged"; - break; - default: - break; - } - - LogRel2(("Audio: Device '%ls' has changed state to '%s'\n", pwstrDeviceId, pszState)); - -#ifdef VBOX_WITH_AUDIO_CALLBACKS - AssertPtr(this->m_pDrvIns); - AssertPtr(this->m_pfnCallback); - - if (this->m_pfnCallback) - /* Ignore rc */ this->m_pfnCallback(this->m_pDrvIns, PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED, NULL, 0); -#endif - - return S_OK; -} - -/** - * Handler implementation which is called when a new audio device has been added. - * - * @return HRESULT - * @param pwstrDeviceId Device ID which has been added. - */ -STDMETHODIMP VBoxMMNotificationClient::OnDeviceAdded(LPCWSTR pwstrDeviceId) -{ - RT_NOREF(pwstrDeviceId); - LogFunc(("%ls\n", pwstrDeviceId)); - return S_OK; -} - -/** - * Handler implementation which is called when an audio device has been removed. - * - * @return HRESULT - * @param pwstrDeviceId Device ID which has been removed. - */ -STDMETHODIMP VBoxMMNotificationClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId) -{ - RT_NOREF(pwstrDeviceId); - LogFunc(("%ls\n", pwstrDeviceId)); - return S_OK; -} - -/** - * Handler implementation which is called when the device audio device has been - * changed. - * - * @return HRESULT - * @param eFlow Flow direction of the new default device. - * @param eRole Role of the new default device. - * @param pwstrDefaultDeviceId ID of the new default device. - */ -STDMETHODIMP VBoxMMNotificationClient::OnDefaultDeviceChanged(EDataFlow eFlow, ERole eRole, LPCWSTR pwstrDefaultDeviceId) -{ - RT_NOREF(eFlow, eRole, pwstrDefaultDeviceId); - - if (eFlow == eRender) - { - - } - - return S_OK; -} - -STDMETHODIMP VBoxMMNotificationClient::QueryInterface(REFIID interfaceID, void **ppvInterface) -{ - const IID IID_IMMNotificationClient = __uuidof(IMMNotificationClient); - - if ( IsEqualIID(interfaceID, IID_IUnknown) - || IsEqualIID(interfaceID, IID_IMMNotificationClient)) - { - *ppvInterface = static_cast(this); - AddRef(); - return S_OK; - } - - *ppvInterface = NULL; - return E_NOINTERFACE; -} - -STDMETHODIMP_(ULONG) VBoxMMNotificationClient::AddRef(void) -{ - return InterlockedIncrement(&m_cRef); -} - -STDMETHODIMP_(ULONG) VBoxMMNotificationClient::Release(void) -{ - long lRef = InterlockedDecrement(&m_cRef); - if (lRef == 0) - delete this; - - return lRef; -} - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/VBoxMMNotificationClient.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/VBoxMMNotificationClient.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Audio/VBoxMMNotificationClient.h 2020-10-16 16:32:56.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Audio/VBoxMMNotificationClient.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,91 +0,0 @@ -/* $Id: VBoxMMNotificationClient.h $ */ -/** @file - * VBoxMMNotificationClient.h - Implementation of the IMMNotificationClient interface - * to detect audio endpoint changes. - */ - -/* - * Copyright (C) 2017-2020 Oracle Corporation - * - * This file is part of VirtualBox Open Source Edition (OSE), as - * available from http://www.virtualbox.org. This file is free software; - * you can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) as published by the Free Software - * Foundation, in version 2 as it comes in the "COPYING" file of the - * VirtualBox OSE distribution. VirtualBox OSE is distributed in the - * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. - */ - -#ifndef VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h -#define VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include -#include - -/* Should fix warning in include\ks.h. */ -#ifndef _WIN64 -# ifdef RT_ARCH_X86 -# define _WIN64 1 -# else -# define _WIN64 0 -# endif -#endif - -#include - -#include "DrvAudio.h" - -class VBoxMMNotificationClient : IMMNotificationClient -{ -public: - - VBoxMMNotificationClient(); - virtual ~VBoxMMNotificationClient(); - - HRESULT Initialize(); - int RegisterCallback(PPDMDRVINS pDrvIns, PFNPDMHOSTAUDIOCALLBACK pfnCallback); - void UnregisterCallback(void); - void Dispose(); - - /** @name IUnknown interface - * @{ */ - IFACEMETHODIMP_(ULONG) Release(); - /** @} */ - -private: - - bool m_fRegisteredClient; - IMMDeviceEnumerator *m_pEnum; - IMMDevice *m_pEndpoint; - - long m_cRef; - - PPDMDRVINS m_pDrvIns; - PFNPDMHOSTAUDIOCALLBACK m_pfnCallback; - - HRESULT AttachToDefaultEndpoint(); - void DetachFromEndpoint(); - - /** @name IMMNotificationClient interface - * @{ */ - IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState); - IFACEMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId); - IFACEMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId); - IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId); - IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) { return S_OK; } - IFACEMETHODIMP OnDeviceQueryRemove() { return S_OK; } - IFACEMETHODIMP OnDeviceQueryRemoveFailed() { return S_OK; } - IFACEMETHODIMP OnDeviceRemovePending() { return S_OK; } - /** @} */ - - /** @name IUnknown interface - * @{ */ - IFACEMETHODIMP QueryInterface(const IID& iid, void** ppUnk); - IFACEMETHODIMP_(ULONG) AddRef(); - /** @} */ -}; -#endif /* !VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h */ - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/build/VBoxDD.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/build/VBoxDD.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/build/VBoxDD.cpp 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/build/VBoxDD.cpp 2022-09-01 13:27:00.000000000 +0000 @@ -308,6 +308,9 @@ rc = pCallbacks->pfnRegister(pCallbacks, &g_DrvHostDSound); if (RT_FAILURE(rc)) return rc; + rc = pCallbacks->pfnRegister(pCallbacks, &g_DrvHostAudioWas); + if (RT_FAILURE(rc)) + return rc; #endif #if defined(RT_OS_DARWIN) rc = pCallbacks->pfnRegister(pCallbacks, &g_DrvHostCoreAudio); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/build/VBoxDD.d virtualbox-6.1.38-dfsg/src/VBox/Devices/build/VBoxDD.d --- virtualbox-6.1.16-dfsg/src/VBox/Devices/build/VBoxDD.d 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/build/VBoxDD.d 2022-09-01 13:27:00.000000000 +0000 @@ -24,6 +24,15 @@ probe ahci__req__submit(void *pvReq, int iTxDir, uint64_t offStart, uint64_t cbXfer); probe ahci__req__completed(void *pvReq, int rcReq, uint64_t offStart, uint64_t cbXfer); + + probe hda__stream__setup(uint32_t idxStream, int32_t rc, uint32_t uHz, uint64_t cTicksPeriod, uint32_t cbPeriod); + probe hda__stream__reset(uint32_t idxStream); + probe hda__stream__dma__out(uint32_t idxStream, uint32_t cb, uint64_t off); + probe hda__stream__dma__in(uint32_t idxStream, uint32_t cb, uint64_t off); + probe hda__stream__dma__flowerror(uint32_t idxStream, uint32_t cbFree, uint32_t cbPeriod, int32_t fOverflow); + + probe audio__mixer__sink__aio__out(uint32_t idxStream, uint32_t cb, uint64_t off); + probe audio__mixer__sink__aio__in(uint32_t idxStream, uint32_t cb, uint64_t off); }; #pragma D attributes Evolving/Evolving/Common provider vboxdd provider diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/build/VBoxDD.h virtualbox-6.1.38-dfsg/src/VBox/Devices/build/VBoxDD.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/build/VBoxDD.h 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/build/VBoxDD.h 2022-09-01 13:27:01.000000000 +0000 @@ -138,8 +138,10 @@ extern const PDMDRVREG g_DrvHostValidationKitAudio; #endif extern const PDMDRVREG g_DrvHostNullAudio; +extern DECL_HIDDEN_DATA(struct PDMIHOSTAUDIO) const g_DrvHostAudioNull; #if defined(RT_OS_WINDOWS) extern const PDMDRVREG g_DrvHostDSound; +extern const PDMDRVREG g_DrvHostAudioWas; #endif #if defined(RT_OS_DARWIN) extern const PDMDRVREG g_DrvHostCoreAudio; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Bus/DevPciIch9.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Bus/DevPciIch9.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Bus/DevPciIch9.cpp 2020-10-16 16:32:57.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Bus/DevPciIch9.cpp 2022-09-01 13:23:48.000000000 +0000 @@ -775,7 +775,10 @@ VBOXSTRICTRC rcStrict = VINF_PDM_PCI_DO_DEFAULT; if (pPciDev->Int.s.pfnConfigWrite) rcStrict = pPciDev->Int.s.pfnConfigWrite(pPciDev->Int.s.CTX_SUFF(pDevIns), pPciDev, iRegister, cb, u32Value); - if (rcStrict == VINF_PDM_PCI_DO_DEFAULT) + if ( rcStrict == VINF_PDM_PCI_DO_DEFAULT + || ( rcStrict == VINF_SUCCESS + && ( iRegister == VBOX_PCI_SECONDARY_BUS + || iRegister == VBOX_PCI_SUBORDINATE_BUS))) rcStrict = devpciR3CommonConfigWriteWorker(pDevIns, PDMINS_2_DATA_CC(pDevIns, PDEVPCIBUSCC), pPciDev, iRegister, cb, u32Value); AssertRCSuccess(VBOXSTRICTRC_VAL(rcStrict)); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Config.kmk virtualbox-6.1.38-dfsg/src/VBox/Devices/Config.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Config.kmk 2020-10-16 16:32:57.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Config.kmk 2022-09-01 13:23:48.000000000 +0000 @@ -103,14 +103,6 @@ # Enable backend callback support. VBOX_AUDIO_DEFS += VBOX_WITH_AUDIO_CALLBACKS -# Disable one-time audio stream initialization at device creation (VM startup) instead -# of creating the stream when needed at some later point in time. -# -# This was the default behavior ever since. -# -# We now go with the new behavior by default now. -#VBOX_AUDIO_DEFS += VBOX_WITH_AUDIO_SB16_ONETIME_INIT - ifdef VBOX_WITH_HP_HDA VBOX_AUDIO_DEFS += VBOX_WITH_HP_HDA endif @@ -121,15 +113,6 @@ VBOX_AUDIO_DEFS += VBOX_WITH_NVIDIA_HDA endif -# -# Enables asynchronous audio data handling -# to speed up the actual DMA data routines and keeping up -# audio processing out of EMT as much as possible. -# -# Disabled for AC'97 for now. -#VBOX_AUDIO_DEFS += VBOX_WITH_AUDIO_AC97_ASYNC_IO -VBOX_AUDIO_DEFS += VBOX_WITH_AUDIO_HDA_ASYNC_IO - # Not yet enabled: Callbacks for the device emulation to let the backends # tell the emulation when and how to process data. if 0 diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/GenFw/Elf64Convert.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/GenFw/Elf64Convert.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/GenFw/Elf64Convert.c 2020-10-16 16:33:02.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/GenFw/Elf64Convert.c 2022-09-01 13:23:52.000000000 +0000 @@ -541,7 +541,7 @@ // if the section address is aligned we must align PE/COFF mCoffOffset = (UINT32) ((mCoffOffset + shdr->sh_addralign - 1) & ~(shdr->sh_addralign - 1)); } else { - Error (NULL, 0, 3000, "Invalid", "Section address not aligned to its own alignment."); + Error (NULL, 0, 3000, "Invalid", "Section address not aligned to its own alignment. sec#%i addr=%#llx align=%#llx", i, shdr->sh_addr, shdr->sh_addralign); } } @@ -592,7 +592,7 @@ // if the section address is aligned we must align PE/COFF mCoffOffset = (UINT32) ((mCoffOffset + shdr->sh_addralign - 1) & ~(shdr->sh_addralign - 1)); } else { - Error (NULL, 0, 3000, "Invalid", "Section address not aligned to its own alignment."); + Error (NULL, 0, 3000, "Invalid", "Section address not aligned to its own alignment. sec#%i addr=%#llx align=%#llx", i, shdr->sh_addr, shdr->sh_addralign); } } @@ -644,7 +644,7 @@ // if the section address is aligned we must align PE/COFF mCoffOffset = (UINT32) ((mCoffOffset + shdr->sh_addralign - 1) & ~(shdr->sh_addralign - 1)); } else { - Error (NULL, 0, 3000, "Invalid", "Section address not aligned to its own alignment."); + Error (NULL, 0, 3000, "Invalid", "Section address not aligned to its own alignment. sec#%i addr=%#llx align=%#llx", i, shdr->sh_addr, shdr->sh_addralign); } } if (shdr->sh_size != 0) { diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VfrCompile/Pccts/antlr/antlr.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VfrCompile/Pccts/antlr/antlr.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VfrCompile/Pccts/antlr/antlr.c 2020-10-16 16:33:05.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VfrCompile/Pccts/antlr/antlr.c 2022-09-01 13:23:55.000000000 +0000 @@ -1349,7 +1349,7 @@ zzBLOCK(zztasp1); zzMake0; { - char *t=NULL; TCnode *e; int go=1,tok,totok; TermEntry *p, *term, *toterm; + char *t=NULL; TCnode *e; int go=1,tok,totok; TermEntry *p = NULL, *term, *toterm; char *akaString=NULL; int save_file; int save_line; char *totext=NULL; zzmatch(118); zzCONSUME; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VfrCompile/Pccts/h/err.h virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VfrCompile/Pccts/h/err.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VfrCompile/Pccts/h/err.h 2020-10-16 16:33:07.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VfrCompile/Pccts/h/err.h 2022-09-01 13:23:57.000000000 +0000 @@ -185,6 +185,7 @@ #endif assert(k <= sizeof(f)/sizeof(f[0])); /* MR20 G. Hobbelt */ text[0] = '\0'; + memset(f, '\0', sizeof(f)); for (i=1; i<=k; i++) /* collect all lookahead sets */ { f[i-1] = va_arg(ap, SetWordType *); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VolInfo/VolInfo.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VolInfo/VolInfo.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VolInfo/VolInfo.c 2020-10-16 16:33:07.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/BaseTools/Source/C/VolInfo/VolInfo.c 2022-09-01 13:23:57.000000000 +0000 @@ -40,7 +40,7 @@ /* #ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +//#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #endif */ // diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgIa32.dsc virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgIa32.dsc --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgIa32.dsc 2020-10-16 16:34:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgIa32.dsc 2022-09-01 13:25:33.000000000 +0000 @@ -873,7 +873,6 @@ PcdLib|MdePkg/Library/DxePcdLib/DxePcdLib.inf } - VBoxPkg/VBoxFsDxe/VBoxIso9660.inf VBoxPkg/VBoxFsDxe/VBoxHfs.inf VBoxPkg/VBoxSysTables/VBoxSysTables.inf VBoxPkg/VBoxAppleSim/VBoxAppleSim.inf @@ -924,6 +923,9 @@ } !endif OvmfPkg/VirtioNetDxe/VirtioNet.inf +!ifdef $(VBOX) + VBoxPkg/E1kNetDxe/E1kNet.inf +!endif # # Usb Support diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgIa32.fdf virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgIa32.fdf --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgIa32.fdf 2020-10-16 16:34:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgIa32.fdf 2022-09-01 13:25:33.000000000 +0000 @@ -278,7 +278,6 @@ !ifdef $(VBOX) INF VBoxPkg/VBoxVgaMiniPortDxe/VBoxVgaMiniPortDxe.inf INF VBoxPkg/VBoxVgaDxe/VBoxVgaDxe.inf -INF VBoxPkg/VBoxFsDxe/VBoxIso9660.inf INF VBoxPkg/VBoxFsDxe/VBoxHfs.inf INF VBoxPkg/VBoxSysTables/VBoxSysTables.inf INF VBoxPkg/VBoxAppleSim/VBoxAppleSim.inf @@ -317,6 +316,9 @@ # !include NetworkPkg/Network.fdf.inc INF OvmfPkg/VirtioNetDxe/VirtioNet.inf +!ifdef $(VBOX) + INF VBoxPkg/E1kNetDxe/E1kNet.inf +!endif # # Usb Support diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgX64.dsc virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgX64.dsc --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgX64.dsc 2020-10-16 16:34:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgX64.dsc 2022-09-01 13:25:33.000000000 +0000 @@ -879,7 +879,6 @@ PcdLib|MdePkg/Library/DxePcdLib/DxePcdLib.inf } - VBoxPkg/VBoxFsDxe/VBoxIso9660.inf VBoxPkg/VBoxFsDxe/VBoxHfs.inf VBoxPkg/VBoxSysTables/VBoxSysTables.inf VBoxPkg/VBoxAppleSim/VBoxAppleSim.inf @@ -928,6 +927,9 @@ } !endif OvmfPkg/VirtioNetDxe/VirtioNet.inf +!ifdef $(VBOX) + VBoxPkg/E1kNetDxe/E1kNet.inf +!endif # # Usb Support diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgX64.fdf virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgX64.fdf --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgX64.fdf 2020-10-16 16:34:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/OvmfPkg/OvmfPkgX64.fdf 2022-09-01 13:25:33.000000000 +0000 @@ -279,7 +279,6 @@ !ifdef $(VBOX) INF VBoxPkg/VBoxVgaMiniPortDxe/VBoxVgaMiniPortDxe.inf INF VBoxPkg/VBoxVgaDxe/VBoxVgaDxe.inf -INF VBoxPkg/VBoxFsDxe/VBoxIso9660.inf INF VBoxPkg/VBoxFsDxe/VBoxHfs.inf INF VBoxPkg/VBoxSysTables/VBoxSysTables.inf INF VBoxPkg/VBoxAppleSim/VBoxAppleSim.inf @@ -323,6 +322,9 @@ !endif !include NetworkPkg/Network.fdf.inc INF OvmfPkg/VirtioNetDxe/VirtioNet.inf +!ifdef $(VBOX) + INF VBoxPkg/E1kNetDxe/E1kNet.inf +!endif # # Usb Support diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/.scm-settings virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/.scm-settings --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/.scm-settings 2020-10-16 16:32:57.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/.scm-settings 2022-09-01 13:23:48.000000000 +0000 @@ -130,7 +130,7 @@ # /VBoxPkg/VBoxFsDxe/test/*: --external-copyright --no-strip-trailing-blanks --no-fix-todos --strip-no-trailing-lines /VBoxPkg/VBoxVgaMiniPortDxe/VBoxVgaFont*.h: --external-copyright - +/VBoxPkg/E1kNetDxe/*.*: --external-copyright #/src/VBox/Devices/EFI/Firmware2/VBoxPkg/VBoxVgaDxe/Edid.c: --no-convert-tabs #/src/VBox/Devices/EFI/Firmware2/VBoxPkg/VBoxFsDxe/*.*: --no-convert-tabs diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/ComponentName.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/ComponentName.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/ComponentName.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/ComponentName.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,172 @@ +/** @file + + Component Name code for the virtio-net driver. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2011, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "E1kNet.h" + +STATIC +EFI_UNICODE_STRING_TABLE mE1kNetDriverNameTable[] = { + { "eng;en", L"E1000 network interface card Driver" }, + { NULL, NULL } +}; + +STATIC +EFI_UNICODE_STRING_TABLE mE1kNetControllerNameTable[] = { + { "eng;en", L"E1000 network interface card Driver" }, + { NULL, NULL } +}; + +/** + Retrieves a Unicode string that is the user-readable name of the EFI Driver. + + @param This A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance. + @param Language A pointer to a three-character ISO 639-2 language + identifier. This is the language of the driver name that + that the caller is requesting, and it must match one of + the languages specified in SupportedLanguages. The number + of languages supported by a driver is up to the driver + writer. + @param DriverName A pointer to the Unicode string to return. This Unicode + string is the name of the driver specified by This in the + language specified by Language. + + @retval EFI_SUCCESS The Unicode string for the Driver specified by + This and the language specified by Language was + returned in DriverName. + @retval EFI_INVALID_PARAMETER Language is NULL. + @retval EFI_INVALID_PARAMETER DriverName is NULL. + @retval EFI_UNSUPPORTED The driver specified by This does not support + the language specified by Language. + +**/ + +STATIC +EFI_STATUS +EFIAPI +E1kNetGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ) +{ + return (Language == NULL || DriverName == NULL) ? + EFI_INVALID_PARAMETER : + LookupUnicodeString2 ( + Language, + This->SupportedLanguages, + mE1kNetDriverNameTable, + DriverName, + (BOOLEAN) (This == &gE1kNetComponentName) // Iso639Language + ); +} + + +/** + Retrieves a Unicode string that is the user readable name of the controller + that is being managed by an EFI Driver. + + @param This A pointer to the EFI_COMPONENT_NAME_PROTOCOL + instance. + @param ControllerHandle The handle of a controller that the driver specified + by This is managing. This handle specifies the + controller whose name is to be returned. + @param ChildHandle The handle of the child controller to retrieve the + name of. This is an optional parameter that may be + NULL. It will be NULL for device drivers. It will + also be NULL for a bus drivers that wish to retrieve + the name of the bus controller. It will not be NULL + for a bus driver that wishes to retrieve the name of + a child controller. + @param Language A pointer to a three character ISO 639-2 language + identifier. This is the language of the controller + name that the caller is requesting, and it must + match one of the languages specified in + SupportedLanguages. The number of languages + supported by a driver is up to the driver writer. + @param ControllerName A pointer to the Unicode string to return. This + Unicode string is the name of the controller + specified by ControllerHandle and ChildHandle in the + language specified by Language, from the point of + view of the driver specified by This. + + @retval EFI_SUCCESS The Unicode string for the user-readable name + in the language specified by Language for the + driver specified by This was returned in + DriverName. + @retval EFI_INVALID_PARAMETER ControllerHandle is NULL. + @retval EFI_INVALID_PARAMETER ChildHandle is not NULL and it is not a valid + EFI_HANDLE. + @retval EFI_INVALID_PARAMETER Language is NULL. + @retval EFI_INVALID_PARAMETER ControllerName is NULL. + @retval EFI_UNSUPPORTED The driver specified by This is not currently + managing the controller specified by + ControllerHandle and ChildHandle. + @retval EFI_UNSUPPORTED The driver specified by This does not support + the language specified by Language. + +**/ + +STATIC +EFI_STATUS +EFIAPI +E1kNetGetControllerName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_HANDLE ChildHandle, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ) +{ + EFI_STATUS Status; + + if (ControllerHandle == NULL || Language == NULL || ControllerName == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // confirm that the device is managed by this driver, using the PciIo + // Protocol + // + Status = EfiTestManagedDevice ( + ControllerHandle, + gE1kNetDriverBinding.DriverBindingHandle, + &gEfiPciIoProtocolGuid + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // we don't give different names to the bus (= parent) handle and the + // child (= MAC) handle + // + return LookupUnicodeString2 ( + Language, + This->SupportedLanguages, + mE1kNetControllerNameTable, + ControllerName, + (BOOLEAN) (This == &gE1kNetComponentName) // Iso639Language + ); +} + +EFI_COMPONENT_NAME_PROTOCOL gE1kNetComponentName = { + &E1kNetGetDriverName, + &E1kNetGetControllerName, + "eng" // SupportedLanguages, ISO 639-2 language codes +}; + +EFI_COMPONENT_NAME2_PROTOCOL gE1kNetComponentName2 = { + (EFI_COMPONENT_NAME2_GET_DRIVER_NAME) &E1kNetGetDriverName, + (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &E1kNetGetControllerName, + "en" // SupportedLanguages, RFC 4646 language codes +}; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/DriverBinding.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/DriverBinding.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/DriverBinding.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/DriverBinding.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,666 @@ +/** @file + + Driver Binding code and its private helpers for the virtio-net driver. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2014, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include + +#include "E1kNet.h" + +#define RECEIVE_FILTERS_NO_MCAST ((UINT32) ( \ + EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | \ + EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST | \ + EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS \ + )) + +STATIC +EFI_STATUS +E1kNetEepromRead ( + IN E1K_NET_DEV *Dev, + IN UINT8 Offset, + OUT UINT16 *Data + ) +{ + EFI_STATUS Status; + UINT32 RegEerd = 0; + + Status = E1kNetRegWrite32(Dev, E1K_REG_EERD, ((UINT32)Offset << 8) | E1K_REG_EERD_START); + if (EFI_ERROR (Status)) + return Status; + + // Wait for the read to complete + while ( !EFI_ERROR (Status) + && !(RegEerd & E1K_REG_EERD_DONE)) { + gBS->Stall(1); + Status = E1kNetRegRead32(Dev, E1K_REG_EERD, &RegEerd); + } + + if (!EFI_ERROR(Status)) + *Data = E1K_REG_EERD_DATA_GET(RegEerd); + + return Status; +} + +STATIC +EFI_STATUS +E1kNetMacAddrRead ( + IN E1K_NET_DEV *Dev + ) +{ + EFI_STATUS Status = EFI_SUCCESS; /* bird build fix*/ + UINT8 i; + + for (i = 0; i < 3; i++) + { + UINT16 MacAddr; + Status = E1kNetEepromRead (Dev, i, &MacAddr); + if (EFI_ERROR (Status)) + return Status; + + Dev->Snm.CurrentAddress.Addr[i * 2] = MacAddr & 0xff; + Dev->Snm.CurrentAddress.Addr[i * 2 + 1] = (MacAddr >> 8) & 0xff; + } + + return Status; +} + +/** + Set up the Simple Network Protocol fields, the Simple Network Mode fields, + and the Exit Boot Services Event of the virtio-net driver instance. + + This function may only be called by E1kNetDriverBindingStart(). + + @param[in,out] Dev The E1K_NET_DEV driver instance being created for the + e1000 device. + + @return Status codes from the CreateEvent(). + @retval EFI_SUCCESS Configuration successful. +*/ +STATIC +EFI_STATUS +EFIAPI +E1kNetSnpPopulate ( + IN OUT E1K_NET_DEV *Dev + ) +{ + UINT32 RegSts; + EFI_STATUS Status; + + // + // We set up a function here that is asynchronously callable by an + // external application to check if there are any packets available for + // reception. The least urgent task priority level we can specify for such a + // "software interrupt" is TPL_CALLBACK. + // + // TPL_CALLBACK is also the maximum TPL an SNP implementation is allowed to + // run at (see 6.1 Event, Timer, and Task Priority Services in the UEFI + // Specification 2.3.1+errC). + // + // Since we raise our TPL to TPL_CALLBACK in every single function that + // accesses the device, and the external application also queues its interest + // for received packets at the same TPL_CALLBACK, in effect the + // E1kNetIsPacketAvailable() function will never interrupt any + // device-accessing driver function, it will be scheduled in isolation. + // + // TPL_CALLBACK (which basically this entire driver runs at) is allowed + // for "[l]ong term operations (such as file system operations and disk + // I/O)". Because none of our functions block, we'd satisfy an even stronger + // requirement. + // + Status = gBS->CreateEvent (EVT_NOTIFY_WAIT, TPL_CALLBACK, + &E1kNetIsPacketAvailable, Dev, &Dev->Snp.WaitForPacket); + if (EFI_ERROR (Status)) { + return Status; + } + + Dev->Snp.Revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION; + Dev->Snp.Start = &E1kNetStart; + Dev->Snp.Stop = &E1kNetStop; + Dev->Snp.Initialize = &E1kNetInitialize; + Dev->Snp.Reset = &E1kNetReset; + Dev->Snp.Shutdown = &E1kNetShutdown; + Dev->Snp.ReceiveFilters = &E1kNetReceiveFilters; + Dev->Snp.StationAddress = &E1kNetStationAddress; + Dev->Snp.Statistics = &E1kNetStatistics; + Dev->Snp.MCastIpToMac = &E1kNetMcastIpToMac; + Dev->Snp.NvData = &E1kNetNvData; + Dev->Snp.GetStatus = &E1kNetGetStatus; + Dev->Snp.Transmit = &E1kNetTransmit; + Dev->Snp.Receive = &E1kNetReceive; + Dev->Snp.Mode = &Dev->Snm; + + Dev->Snm.State = EfiSimpleNetworkStopped; + Dev->Snm.HwAddressSize = sizeof (E1K_NET_MAC); + Dev->Snm.MediaHeaderSize = sizeof (E1K_NET_MAC) + // dst MAC + sizeof (E1K_NET_MAC) + // src MAC + 2; // Ethertype + Dev->Snm.MaxPacketSize = 1500; + Dev->Snm.NvRamSize = 0; + Dev->Snm.NvRamAccessSize = 0; + Dev->Snm.ReceiveFilterMask = RECEIVE_FILTERS_NO_MCAST; + Dev->Snm.ReceiveFilterSetting = RECEIVE_FILTERS_NO_MCAST; + Dev->Snm.MaxMCastFilterCount = 0; + Dev->Snm.MCastFilterCount = 0; + Dev->Snm.IfType = 1; // ethernet + Dev->Snm.MacAddressChangeable = FALSE; + Dev->Snm.MultipleTxSupported = TRUE; + + ASSERT (sizeof (E1K_NET_MAC) <= sizeof (EFI_MAC_ADDRESS)); + + Dev->Snm.MediaPresentSupported = TRUE; + Status = E1kNetRegRead32(Dev, E1K_REG_STATUS, &RegSts); + if (EFI_ERROR (Status)) { + goto CloseWaitForPacket; + } + + Dev->Snm.MediaPresent = (BOOLEAN)((RegSts & E1K_REG_STATUS_LU) != 0); + + Status = E1kNetMacAddrRead(Dev); + CopyMem (&Dev->Snm.PermanentAddress, &Dev->Snm.CurrentAddress, + sizeof (E1K_NET_MAC)); + SetMem (&Dev->Snm.BroadcastAddress, sizeof (E1K_NET_MAC), 0xFF); + + // + // E1kNetExitBoot() is queued by ExitBootServices(); its purpose is to + // cancel any pending requests. The TPL_CALLBACK reasoning is + // identical to the one above. There's one difference: this kind of + // event is "globally visible", which means it can be signalled as soon as + // we create it. We haven't raised our TPL here, hence E1kNetExitBoot() + // could be entered immediately. E1kNetExitBoot() checks Dev->Snm.State, + // so we're safe. + // + Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK, + &E1kNetExitBoot, Dev, &Dev->ExitBoot); + if (EFI_ERROR (Status)) { + goto CloseWaitForPacket; + } + + return EFI_SUCCESS; + +CloseWaitForPacket: + gBS->CloseEvent (Dev->Snp.WaitForPacket); + return Status; +} + + +/** + Release any resources allocated by E1kNetSnpPopulate(). + + This function may only be called by E1kNetDriverBindingStart(), when + rolling back a partial, failed driver instance creation, and by + E1kNetDriverBindingStop(), when disconnecting a virtio-net device from the + driver. + + @param[in,out] Dev The E1K_NET_DEV driver instance being destroyed. +*/ +STATIC +VOID +EFIAPI +E1kNetSnpEvacuate ( + IN OUT E1K_NET_DEV *Dev + ) +{ + // + // This function runs either at TPL_CALLBACK already (from + // E1kNetDriverBindingStop()), or it is part of a teardown following + // a partial, failed construction in E1kNetDriverBindingStart(), when + // WaitForPacket was never accessible to the world. + // + gBS->CloseEvent (Dev->ExitBoot); + gBS->CloseEvent (Dev->Snp.WaitForPacket); +} + + +/** + Tests to see if this driver supports a given controller. If a child device is + provided, it further tests to see if this driver supports creating a handle + for the specified child device. + + This function checks to see if the driver specified by This supports the + device specified by ControllerHandle. Drivers will typically use the device + path attached to ControllerHandle and/or the services from the bus I/O + abstraction attached to ControllerHandle to determine if the driver supports + ControllerHandle. This function may be called many times during platform + initialization. In order to reduce boot times, the tests performed by this + function must be very small, and take as little time as possible to execute. + This function must not change the state of any hardware devices, and this + function must be aware that the device specified by ControllerHandle may + already be managed by the same driver or a different driver. This function + must match its calls to AllocatePages() with FreePages(), AllocatePool() with + FreePool(), and OpenProtocol() with CloseProtocol(). Because ControllerHandle + may have been previously started by the same driver, if a protocol is already + in the opened state, then it must not be closed with CloseProtocol(). This is + required to guarantee the state of ControllerHandle is not modified by this + function. + + @param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL + instance. + @param[in] ControllerHandle The handle of the controller to test. This + handle must support a protocol interface + that supplies an I/O abstraction to the + driver. + @param[in] RemainingDevicePath A pointer to the remaining portion of a + device path. This parameter is ignored by + device drivers, and is optional for bus + drivers. For bus drivers, if this parameter + is not NULL, then the bus driver must + determine if the bus controller specified by + ControllerHandle and the child controller + specified by RemainingDevicePath are both + supported by this bus driver. + + @retval EFI_SUCCESS The device specified by ControllerHandle and + RemainingDevicePath is supported by the + driver specified by This. + @retval EFI_ALREADY_STARTED The device specified by ControllerHandle and + RemainingDevicePath is already being managed + by the driver specified by This. + @retval EFI_ACCESS_DENIED The device specified by ControllerHandle and + RemainingDevicePath is already being managed + by a different driver or an application that + requires exclusive access. Currently not + implemented. + @retval EFI_UNSUPPORTED The device specified by ControllerHandle and + RemainingDevicePath is not supported by the + driver specified by This. +**/ + +STATIC +EFI_STATUS +EFIAPI +E1kNetDriverBindingSupported ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL + ) +{ + EFI_STATUS Status; + EFI_PCI_IO_PROTOCOL *PciIo; + PCI_TYPE00 Pci; + + Status = gBS->OpenProtocol ( + ControllerHandle, + &gEfiPciIoProtocolGuid, + (VOID **)&PciIo, + This->DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = PciIo->Pci.Read ( + PciIo, + EfiPciIoWidthUint32, + 0, + sizeof (Pci) / sizeof (UINT32), + &Pci + ); + if (EFI_ERROR (Status)) { + goto Done; + } + + if (Pci.Hdr.VendorId == INTEL_PCI_VENDOR_ID && + (Pci.Hdr.DeviceId == INTEL_82540EM_PCI_DEVICE_ID || + Pci.Hdr.DeviceId == INTEL_82543GC_PCI_DEVICE_ID || + Pci.Hdr.DeviceId == INTEL_82545EM_PCI_DEVICE_ID)) { + Status = EFI_SUCCESS; + } else { + Status = EFI_UNSUPPORTED; + } + +Done: + gBS->CloseProtocol ( + ControllerHandle, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + ControllerHandle + ); + return Status; +} + + +/** + Starts a device controller or a bus controller. + + The Start() function is designed to be invoked from the EFI boot service + ConnectController(). As a result, much of the error checking on the + parameters to Start() has been moved into this common boot service. It is + legal to call Start() from other locations, but the following calling + restrictions must be followed, or the system behavior will not be + deterministic. + 1. ControllerHandle must be a valid EFI_HANDLE. + 2. If RemainingDevicePath is not NULL, then it must be a pointer to a + naturally aligned EFI_DEVICE_PATH_PROTOCOL. + 3. Prior to calling Start(), the Supported() function for the driver + specified by This must have been called with the same calling parameters, + and Supported() must have returned EFI_SUCCESS. + + @param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL + instance. + @param[in] ControllerHandle The handle of the controller to start. This + handle must support a protocol interface + that supplies an I/O abstraction to the + driver. + @param[in] RemainingDevicePath A pointer to the remaining portion of a + device path. This parameter is ignored by + device drivers, and is optional for bus + drivers. For a bus driver, if this parameter + is NULL, then handles for all the children + of Controller are created by this driver. + If this parameter is not NULL and the first + Device Path Node is not the End of Device + Path Node, then only the handle for the + child device specified by the first Device + Path Node of RemainingDevicePath is created + by this driver. If the first Device Path + Node of RemainingDevicePath is the End of + Device Path Node, no child handle is created + by this driver. + + @retval EFI_SUCCESS The device was started. + @retval EFI_DEVICE_ERROR The device could not be started due to a + device error.Currently not implemented. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + @retval Others The driver failed to start the device. + +**/ +STATIC +EFI_STATUS +EFIAPI +E1kNetDriverBindingStart ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL + ) +{ + EFI_STATUS Status; + E1K_NET_DEV *Dev; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + MAC_ADDR_DEVICE_PATH MacNode; + + DEBUG((DEBUG_INFO, "E1kNetControllerStart:\n")); + + Dev = AllocateZeroPool (sizeof (*Dev)); + if (Dev == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Dev->Signature = E1K_NET_DEV_SIGNATURE; + + Status = gBS->OpenProtocol ( + ControllerHandle, + &gEfiPciIoProtocolGuid, + (VOID **)&Dev->PciIo, + This->DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + if (EFI_ERROR (Status)) { + goto FreePool; + } + + Status = Dev->PciIo->Attributes ( + Dev->PciIo, + EfiPciIoAttributeOperationGet, + 0, + &Dev->OriginalPciAttributes + ); + if (EFI_ERROR (Status)) { + goto CloseProtocol; + } + + // + // Enable I/O Space & Bus-Mastering + // + Status = Dev->PciIo->Attributes ( + Dev->PciIo, + EfiPciIoAttributeOperationEnable, + (EFI_PCI_IO_ATTRIBUTE_IO | + EFI_PCI_IO_ATTRIBUTE_BUS_MASTER), + NULL + ); + if (EFI_ERROR (Status)) { + goto CloseProtocol; + } + + // + // Signal device supports 64-bit DMA addresses + // + Status = Dev->PciIo->Attributes ( + Dev->PciIo, + EfiPciIoAttributeOperationEnable, + EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE, + NULL + ); + if (EFI_ERROR (Status)) { + // + // Warn user that device will only be using 32-bit DMA addresses. + // + // Note that this does not prevent the device/driver from working + // and therefore we only warn and continue as usual. + // + DEBUG (( + DEBUG_WARN, + "%a: failed to enable 64-bit DMA addresses\n", + __FUNCTION__ + )); + } + + DEBUG((DEBUG_INFO, "E1kNetControllerStart: Resetting NIC\n")); + Status = E1kNetDevReset (Dev); + if (EFI_ERROR (Status)) { + goto RestoreAttributes; + } + + // + // now we can run a basic one-shot e1000 initialization required to + // retrieve the MAC address + // + DEBUG((DEBUG_INFO, "E1kNetControllerStart: Populating SNP interface\n")); + Status = E1kNetSnpPopulate (Dev); + if (EFI_ERROR (Status)) { + goto UninitDev; + } + + // + // get the device path of the e1000 device -- one-shot open + // + Status = gBS->OpenProtocol (ControllerHandle, &gEfiDevicePathProtocolGuid, + (VOID **)&DevicePath, This->DriverBindingHandle, + ControllerHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR (Status)) { + goto Evacuate; + } + + // + // create another device path that has the MAC address appended + // + MacNode.Header.Type = MESSAGING_DEVICE_PATH; + MacNode.Header.SubType = MSG_MAC_ADDR_DP; + SetDevicePathNodeLength (&MacNode, sizeof MacNode); + CopyMem (&MacNode.MacAddress, &Dev->Snm.CurrentAddress, + sizeof (EFI_MAC_ADDRESS)); + MacNode.IfType = Dev->Snm.IfType; + + Dev->MacDevicePath = AppendDevicePathNode (DevicePath, &MacNode.Header); + if (Dev->MacDevicePath == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Evacuate; + } + + // + // create a child handle with the Simple Network Protocol and the new + // device path installed on it + // + Status = gBS->InstallMultipleProtocolInterfaces (&Dev->MacHandle, + &gEfiSimpleNetworkProtocolGuid, &Dev->Snp, + &gEfiDevicePathProtocolGuid, Dev->MacDevicePath, + NULL); + if (EFI_ERROR (Status)) { + goto FreeMacDevicePath; + } + + DEBUG((DEBUG_INFO, "E1kNetControllerStart: returns EFI_SUCCESS\n")); + return EFI_SUCCESS; + +FreeMacDevicePath: + FreePool (Dev->MacDevicePath); + +Evacuate: + E1kNetSnpEvacuate (Dev); + +UninitDev: + E1kNetDevReset (Dev); + +RestoreAttributes: + Dev->PciIo->Attributes ( + Dev->PciIo, + EfiPciIoAttributeOperationSet, + Dev->OriginalPciAttributes, + NULL + ); + +CloseProtocol: + gBS->CloseProtocol ( + ControllerHandle, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + ControllerHandle + ); + +FreePool: + FreePool (Dev); + + DEBUG((DEBUG_INFO, "E1kNetControllerStart: returns %u\n", Status)); + return Status; +} + +/** + Stops a device controller or a bus controller. + + The Stop() function is designed to be invoked from the EFI boot service + DisconnectController(). As a result, much of the error checking on the + parameters to Stop() has been moved into this common boot service. It is + legal to call Stop() from other locations, but the following calling + restrictions must be followed, or the system behavior will not be + deterministic. + 1. ControllerHandle must be a valid EFI_HANDLE that was used on a previous + call to this same driver's Start() function. + 2. The first NumberOfChildren handles of ChildHandleBuffer must all be a + valid EFI_HANDLE. In addition, all of these handles must have been created + in this driver's Start() function, and the Start() function must have + called OpenProtocol() on ControllerHandle with an Attribute of + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER. + + @param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL + instance. + @param[in] ControllerHandle A handle to the device being stopped. The + handle must support a bus specific I/O + protocol for the driver to use to stop the + device. + @param[in] NumberOfChildren The number of child device handles in + ChildHandleBuffer. + @param[in] ChildHandleBuffer An array of child handles to be freed. May be + NULL if NumberOfChildren is 0. + + @retval EFI_SUCCESS The device was stopped. + @retval EFI_DEVICE_ERROR The device could not be stopped due to a device + error. + +**/ +STATIC +EFI_STATUS +EFIAPI +E1kNetDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ) +{ + if (NumberOfChildren > 0) { + // + // free all resources for whose access we need the child handle, because + // the child handle is going away + // + EFI_STATUS Status; + EFI_SIMPLE_NETWORK_PROTOCOL *Snp; + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + + ASSERT (NumberOfChildren == 1); + + Status = gBS->OpenProtocol (ChildHandleBuffer[0], + &gEfiSimpleNetworkProtocolGuid, (VOID **)&Snp, + This->DriverBindingHandle, ControllerHandle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + ASSERT_EFI_ERROR (Status); + Dev = E1K_NET_FROM_SNP (Snp); + + // + // prevent any interference with WaitForPacket + // + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + + ASSERT (Dev->MacHandle == ChildHandleBuffer[0]); + if (Dev->Snm.State != EfiSimpleNetworkStopped) { + // + // device in use, cannot stop driver instance + // + Status = EFI_DEVICE_ERROR; + } + else { + gBS->UninstallMultipleProtocolInterfaces (Dev->MacHandle, + &gEfiDevicePathProtocolGuid, Dev->MacDevicePath, + &gEfiSimpleNetworkProtocolGuid, &Dev->Snp, + NULL); + FreePool (Dev->MacDevicePath); + E1kNetSnpEvacuate (Dev); + + Dev->PciIo->Attributes ( + Dev->PciIo, + EfiPciIoAttributeOperationSet, + Dev->OriginalPciAttributes, + NULL + ); + + gBS->CloseProtocol ( + ControllerHandle, + &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, + ControllerHandle + ); + + FreePool (Dev); + } + + gBS->RestoreTPL (OldTpl); + return Status; + } + + return EFI_SUCCESS; +} + + +EFI_DRIVER_BINDING_PROTOCOL gE1kNetDriverBinding = { + &E1kNetDriverBindingSupported, + &E1kNetDriverBindingStart, + &E1kNetDriverBindingStop, + 0x10, + NULL, + NULL +}; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kHwIo.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kHwIo.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kHwIo.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kHwIo.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,182 @@ +/** @file + + This file implements the hardware register access functions of the e1000 driver. + + Copyright (c) 2021, Oracle and/or its affiliates.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include + +#include "E1kNet.h" + +EFI_STATUS +EFIAPI +E1kNetRegWrite32 ( + IN E1K_NET_DEV *Dev, + IN UINT32 Addr, + IN UINT32 Data + ) +{ + EFI_STATUS Status; + + Status = Dev->PciIo->Io.Write ( + Dev->PciIo, + EfiPciIoWidthUint32, + PCI_BAR_IDX2, + 0, // IOADDR + 1, + &Addr + ); + if (!EFI_ERROR (Status)) + { + Status = Dev->PciIo->Io.Write ( + Dev->PciIo, + EfiPciIoWidthUint32, + PCI_BAR_IDX2, + 4, // IODATA + 1, + &Data + ); + } + + return Status; +} + +EFI_STATUS +EFIAPI +E1kNetRegRead32 ( + IN E1K_NET_DEV *Dev, + IN UINT32 Addr, + OUT UINT32 *Data + ) +{ + EFI_STATUS Status; + + Status = Dev->PciIo->Io.Write ( + Dev->PciIo, + EfiPciIoWidthUint32, + PCI_BAR_IDX2, + 0, // IOADDR + 1, + &Addr + ); + if (!EFI_ERROR (Status)) + { + return Dev->PciIo->Io.Read ( + Dev->PciIo, + EfiPciIoWidthUint32, + PCI_BAR_IDX2, + 4, // IODATA + 1, + Data + ); + } + + return Status; +} + +EFI_STATUS +EFIAPI +E1kNetRegSet32 ( + IN E1K_NET_DEV *Dev, + IN UINT32 Addr, + IN UINT32 Set) +{ + UINT32 Reg; + EFI_STATUS Status; + + Status = E1kNetRegRead32 (Dev, Addr, &Reg); + if (EFI_ERROR (Status)) { + return Status; + } + + Reg |= Set; + Status = E1kNetRegWrite32 (Dev, Addr, Reg); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +EFIAPI +E1kNetRegClear32 ( + IN E1K_NET_DEV *Dev, + IN UINT32 Addr, + IN UINT32 Clear) +{ + UINT32 Reg; + EFI_STATUS Status; + + Status = E1kNetRegRead32 (Dev, Addr, &Reg); + if (EFI_ERROR (Status)) { + return Status; + } + + Reg &= ~Clear; + Status = E1kNetRegWrite32 (Dev, Addr, Reg); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +EFIAPI +E1kNetDevReset ( + IN E1K_NET_DEV *Dev + ) +{ + EFI_STATUS Status; + + // + // Reset hardware + // + Status = E1kNetRegSet32 (Dev, E1K_REG_CTRL, E1K_REG_CTRL_RST); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Wait for the reset to complete + // + for (;;) + { + UINT32 Ctrl; + + Status = E1kNetRegRead32 (Dev, E1K_REG_CTRL, &Ctrl); + if (EFI_ERROR (Status)) { + return Status; + } + + /// @todo Timeout? + if (!(Ctrl & E1K_REG_CTRL_RST)) + break; + } + + // + // Reset the PHY. + // + Status = E1kNetRegSet32 (Dev, E1K_REG_CTRL, E1K_REG_CTRL_PHY_RST); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Wait for the specified amount of 3us and de-assert the PHY reset signal. + // + gBS->Stall(3); + Status = E1kNetRegClear32 (Dev, E1K_REG_CTRL, E1K_REG_CTRL_PHY_RST); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kNet.h virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kNet.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kNet.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kNet.h 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,350 @@ +/** @file + + Internal definitions for the virtio-net driver, which produces Simple Network + Protocol instances for virtio-net devices. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (c) 2017, AMD Inc, All rights reserved. + Copyright (C) 2013, Red Hat, Inc.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef _E1K_NET_DXE_H_ +#define _E1K_NET_DXE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "E1kNetHw.h" + +#define E1K_NET_DEV_SIGNATURE SIGNATURE_32 ('E','1','K','N') + +// +// maximum number of pending packets, separately for each direction +// +#define E1K_NET_MAX_PENDING 64 + +// +// State diagram: +// +// | ^ +// | | +// BindingStart BindingStop +// +SnpPopulate | +// ++GetFeatures | +// | | +// v | +// +---------+ virtio-net device is reset, no resources are +// | stopped | allocated for traffic, but MAC address has +// +---------+ been retrieved +// | ^ +// | | +// SNP.Start SNP.Stop +// | | +// v | +// +---------+ +// | started | functionally identical to stopped +// +---------+ +// | ^ +// | | +// SNP.Initialize SNP.Shutdown +// | | +// v | +// +-------------+ Virtio-net setup complete, including DRIVER_OK +// | initialized | bit. The receive queue is populated with +// +-------------+ requests; McastIpToMac, GetStatus, Transmit, +// Receive are callable. +// + +typedef struct { + // + // Parts of this structure are initialized / torn down in various functions + // at various call depths. The table to the right should make it easier to + // track them. + // + // field init function + // ------------------ ------------------------------ + UINT32 Signature; // VirtioNetDriverBindingStart + EFI_PCI_IO_PROTOCOL *PciIo; // VirtioNetDriverBindingStart + UINT64 OriginalPciAttributes; // VirtioNetDriverBindingStart + EFI_SIMPLE_NETWORK_PROTOCOL Snp; // VirtioNetSnpPopulate + EFI_SIMPLE_NETWORK_MODE Snm; // VirtioNetSnpPopulate + EFI_EVENT ExitBoot; // VirtioNetSnpPopulate + EFI_DEVICE_PATH_PROTOCOL *MacDevicePath; // VirtioNetDriverBindingStart + EFI_HANDLE MacHandle; // VirtioNetDriverBindingStart + + E1K_RX_DESC *RxRing; // VirtioNetInitRing + UINT8 *RxBuf; // E1kNetInitRx + UINT32 RdhLastSeen; // E1kNetInitRx + UINTN RxBufNrPages; // E1kNetInitRx + EFI_PHYSICAL_ADDRESS RxBufDeviceBase; // E1kNetInitRx + EFI_PHYSICAL_ADDRESS RxDeviceBase; // E1kNetInitRx + VOID *RxMap; // E1kNetInitRx + + UINT16 TxMaxPending; // E1kNetInitTx + UINT16 TxCurPending; // E1kNetInitTx + E1K_TX_DESC *TxRing; // E1kNetInitTx + VOID *TxRingMap; // E1kNetInitTx + UINT16 TxLastUsed; // E1kNetInitTx + UINT32 TdhLastSeen; // E1kNetInitTx + ORDERED_COLLECTION *TxBufCollection; // E1kNetInitTx +} E1K_NET_DEV; + + +// +// In order to avoid duplication of interface documentation, please find all +// leading comments near the respective function / variable definitions (not +// the declarations here), which is where your code editor of choice takes you +// anyway when jumping to a function. +// + +// +// utility macros +// +#define E1K_NET_FROM_SNP(SnpPointer) \ + CR (SnpPointer, E1K_NET_DEV, Snp, E1K_NET_DEV_SIGNATURE) + +// +// component naming +// +extern EFI_COMPONENT_NAME_PROTOCOL gE1kNetComponentName; +extern EFI_COMPONENT_NAME2_PROTOCOL gE1kNetComponentName2; + +// +// driver binding +// +extern EFI_DRIVER_BINDING_PROTOCOL gE1kNetDriverBinding; + +// +// member functions implementing the Simple Network Protocol +// +EFI_STATUS +EFIAPI +E1kNetStart ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This + ); + +EFI_STATUS +EFIAPI +E1kNetStop ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This + ); + +EFI_STATUS +EFIAPI +E1kNetInitialize ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN UINTN ExtraRxBufferSize OPTIONAL, + IN UINTN ExtraTxBufferSize OPTIONAL + ); + +EFI_STATUS +EFIAPI +E1kNetReset ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN ExtendedVerification + ); + +EFI_STATUS +EFIAPI +E1kNetShutdown ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This + ); + +EFI_STATUS +EFIAPI +E1kNetReceiveFilters ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN UINT32 Enable, + IN UINT32 Disable, + IN BOOLEAN ResetMCastFilter, + IN UINTN MCastFilterCnt OPTIONAL, + IN EFI_MAC_ADDRESS *MCastFilter OPTIONAL + ); + +EFI_STATUS +EFIAPI +E1kNetStationAddress ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN Reset, + IN EFI_MAC_ADDRESS *New OPTIONAL + ); + +EFI_STATUS +EFIAPI +E1kNetStatistics ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN Reset, + IN OUT UINTN *StatisticsSize OPTIONAL, + OUT EFI_NETWORK_STATISTICS *StatisticsTable OPTIONAL + ); + +EFI_STATUS +EFIAPI +E1kNetMcastIpToMac ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN IPv6, + IN EFI_IP_ADDRESS *Ip, + OUT EFI_MAC_ADDRESS *Mac + ); + +EFI_STATUS +EFIAPI +E1kNetNvData ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN ReadWrite, + IN UINTN Offset, + IN UINTN BufferSize, + IN OUT VOID *Buffer + ); + +EFI_STATUS +EFIAPI +E1kNetGetStatus ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + OUT UINT32 *InterruptStatus OPTIONAL, + OUT VOID **TxBuf OPTIONAL + ); + +EFI_STATUS +EFIAPI +E1kNetTransmit ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN UINTN HeaderSize, + IN UINTN BufferSize, + IN /* +OUT! */ VOID *Buffer, + IN EFI_MAC_ADDRESS *SrcAddr OPTIONAL, + IN EFI_MAC_ADDRESS *DestAddr OPTIONAL, + IN UINT16 *Protocol OPTIONAL + ); + +EFI_STATUS +EFIAPI +E1kNetReceive ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + OUT UINTN *HeaderSize OPTIONAL, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer, + OUT EFI_MAC_ADDRESS *SrcAddr OPTIONAL, + OUT EFI_MAC_ADDRESS *DestAddr OPTIONAL, + OUT UINT16 *Protocol OPTIONAL + ); + +// +// utility functions shared by various SNP member functions +// +VOID +EFIAPI +E1kNetShutdownRx ( + IN OUT E1K_NET_DEV *Dev + ); + +VOID +EFIAPI +E1kNetShutdownTx ( + IN OUT E1K_NET_DEV *Dev + ); + +// +// utility functions to map caller-supplied Tx buffer system physical address +// to a device address and vice versa +// +EFI_STATUS +EFIAPI +E1kNetMapTxBuf ( + IN E1K_NET_DEV *Dev, + IN VOID *Buffer, + IN UINTN NumberOfBytes, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress + ); + +EFI_STATUS +EFIAPI +E1kNetUnmapTxBuf ( + IN E1K_NET_DEV *Dev, + OUT VOID **Buffer, + IN EFI_PHYSICAL_ADDRESS DeviceAddress + ); + +INTN +EFIAPI +E1kNetTxBufMapInfoCompare ( + IN CONST VOID *UserStruct1, + IN CONST VOID *UserStruct2 + ); + +INTN +EFIAPI +E1kNetTxBufDeviceAddressCompare ( + IN CONST VOID *StandaloneKey, + IN CONST VOID *UserStruct + ); + + +// +// event callbacks +// +VOID +EFIAPI +E1kNetIsPacketAvailable ( + IN EFI_EVENT Event, + IN VOID *Context + ); + +VOID +EFIAPI +E1kNetExitBoot ( + IN EFI_EVENT Event, + IN VOID *Context + ); + +// +// Hardware I/O functions. +// +EFI_STATUS +EFIAPI +E1kNetRegWrite32 ( + IN E1K_NET_DEV *Dev, + IN UINT32 Addr, + IN UINT32 Data + ); + +EFI_STATUS +EFIAPI +E1kNetRegRead32 ( + IN E1K_NET_DEV *Dev, + IN UINT32 Addr, + OUT UINT32 *Data + ); + +EFI_STATUS +EFIAPI +E1kNetRegSet32 ( + IN E1K_NET_DEV *Dev, + IN UINT32 Addr, + IN UINT32 Set + ); + +EFI_STATUS +EFIAPI +E1kNetRegClear32 ( + IN E1K_NET_DEV *Dev, + IN UINT32 Addr, + IN UINT32 Clear + ); + +EFI_STATUS +EFIAPI +E1kNetDevReset ( + IN E1K_NET_DEV *Dev + ); + +#endif // _E1K_NET_DXE_H_ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kNetHw.h virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kNetHw.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kNetHw.h 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/E1kNetHw.h 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,105 @@ +/** @file + + E1000 hardware interface definitions. + + Copyright (c) 2021, Oracle and/or its affiliates.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef _E1K_NET_HW_H_ +#define _E1K_NET_HW_H_ + +#define INTEL_PCI_VENDOR_ID 0x8086 +#define INTEL_82540EM_PCI_DEVICE_ID 0x100e +#define INTEL_82543GC_PCI_DEVICE_ID 0x1004 +#define INTEL_82545EM_PCI_DEVICE_ID 0x100f + +// +// Receive descriptor. +// +typedef struct { + UINT32 AddrBufferLow; + UINT32 AddrBufferHigh; + UINT16 BufferLength; + UINT16 Checksum; + UINT8 Status; + UINT8 Errors; + UINT16 Special; +} E1K_RX_DESC; + +#define E1K_RX_STATUS_DONE BIT0 +#define E1K_RX_STATUS_EOP BIT1 + +#define E1K_RX_ERROR_CE BIT0 +#define E1K_RX_ERROR_SEQ BIT2 +#define E1K_RX_ERROR_CXE BIT4 +#define E1K_RX_ERROR_RXE BIT7 + +// +// Transmit descriptor. +// +typedef struct { + UINT32 AddrBufferLow; + UINT32 AddrBufferHigh; + UINT16 BufferLength; + UINT8 ChecksumOffset; + UINT8 Command; + UINT8 Status; + UINT8 ChecksumStart; + UINT16 Special; +} E1K_TX_DESC; + +#define E1K_TX_CMD_EOP BIT0 +#define E1K_TX_CMD_FCS BIT1 +#define E1K_TX_CMD_RS BIT3 + +#define E1K_REG_CTRL 0x00000000 +# define E1K_REG_CTRL_ASDE BIT5 +# define E1K_REG_CTRL_SLU BIT6 +# define E1K_REG_CTRL_RST BIT26 +# define E1K_REG_CTRL_PHY_RST BIT31 +#define E1K_REG_STATUS 0x00000008 +# define E1K_REG_STATUS_LU BIT1 +#define E1K_REG_EECD 0x00000010 +#define E1K_REG_EERD 0x00000014 +# define E1K_REG_EERD_START BIT0 +# define E1K_REG_EERD_DONE BIT4 +# define E1K_REG_EERD_DATA_GET(x) (((x) >> 16) & 0xffff) +#define E1K_REG_ICR 0x000000c0 +#define E1K_REG_ITR 0x000000c4 +#define E1K_REG_ICS 0x000000c8 +#define E1K_REG_IMS 0x000000d0 +#define E1K_REG_IMC 0x000000d8 +#define E1K_REG_RCTL 0x00000100 +# define E1K_REG_RCTL_EN BIT1 +# define E1K_REG_RCTL_MPE BIT4 +# define E1K_REG_RCTL_BSIZE_MASK 0x00030000 +#define E1K_REG_RDBAL 0x00002800 +#define E1K_REG_RDBAH 0x00002804 +#define E1K_REG_RDLEN 0x00002808 +#define E1K_REG_RDH 0x00002810 +#define E1K_REG_RDT 0x00002818 +#define E1K_REG_RDTR 0x00002820 +#define E1K_REG_TCTL 0x00000400 +# define E1K_REG_TCTL_EN BIT1 +# define E1K_REG_TCTL_PSP BIT3 +#define E1K_REG_TIPG 0x00000410 +#define E1K_REG_TDBAL 0x00003800 +#define E1K_REG_TDBAH 0x00003804 +#define E1K_REG_TDLEN 0x00003808 +#define E1K_REG_TDH 0x00003810 +#define E1K_REG_TDT 0x00003818 +#define E1K_REG_RAL 0x00005400 +#define E1K_REG_RAH 0x00005404 +# define E1K_REG_RAH_AV BIT31 + +// +// MAC address. +// +typedef struct +{ + UINT8 Mac[6]; +} E1K_NET_MAC; + +#endif // _E1K_NET_HW_H_ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/EntryPoint.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/EntryPoint.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/EntryPoint.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/EntryPoint.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,45 @@ +/** @file + + This file implements the entry point of the e1000 driver. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2012, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "E1kNet.h" + +/** + This is the declaration of an EFI image entry point. This entry point is the + same for UEFI Applications, UEFI OS Loaders, and UEFI Drivers including both + device drivers and bus drivers. + + @param ImageHandle The firmware allocated handle for the UEFI + image. + @param SystemTable A pointer to the EFI System Table. + + @retval EFI_SUCCESS The operation completed successfully. + @retval Others An unexpected error occurred. +**/ + +EFI_STATUS +EFIAPI +E1kNetEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + return EfiLibInstallDriverBindingComponentName2 ( + ImageHandle, + SystemTable, + &gE1kNetDriverBinding, + ImageHandle, + &gE1kNetComponentName, + &gE1kNetComponentName2 + ); +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/Events.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/Events.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/Events.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/Events.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,82 @@ +/** @file + + Implements + - the SNM.WaitForPacket EVT_NOTIFY_WAIT event, + - the EVT_SIGNAL_EXIT_BOOT_SERVICES event + for the e1000 driver. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2012, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include + +#include "E1kNet.h" + +/** + Invoke a notification event + + @param Event Event whose notification function is being + invoked. + @param Context The pointer to the notification function's + context, which is implementation-dependent. + +**/ + +VOID +EFIAPI +E1kNetIsPacketAvailable ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + // + // This callback has been enqueued by an external application and is + // running at TPL_CALLBACK already. + // + // The WaitForPacket logic is similar to that of WaitForKey. The former has + // almost no documentation in either the UEFI-2.3.1+errC spec or the + // DWG-2.3.1, but WaitForKey does have some. + // + E1K_NET_DEV *Dev; + UINT32 RdhCur; + + Dev = Context; + if (Dev->Snm.State != EfiSimpleNetworkInitialized) { + return; + } + + E1kNetRegRead32(Dev, E1K_REG_RDH, &RdhCur); + + if (Dev->RdhLastSeen != RdhCur) { + gBS->SignalEvent (Dev->Snp.WaitForPacket); + } +} + +VOID +EFIAPI +E1kNetExitBoot ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + // + // This callback has been enqueued by ExitBootServices() and is running at + // TPL_CALLBACK already. + // + // Shut down pending transfers according to DWG-2.3.1, "25.5.1 Exit Boot + // Services Event". + // + E1K_NET_DEV *Dev; + + DEBUG ((DEBUG_VERBOSE, "%a: Context=0x%p\n", __FUNCTION__, Context)); + Dev = Context; + if (Dev->Snm.State == EfiSimpleNetworkInitialized) { + E1kNetDevReset (Dev); + } +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpGetStatus.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpGetStatus.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpGetStatus.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpGetStatus.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,160 @@ +/** @file + + Implementation of the SNP.GetStatus() function and its private helpers if + any. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2014, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include + +#include "E1kNet.h" + +/** + Reads the current interrupt status and recycled transmit buffer status from + a network interface. + + @param This The protocol instance pointer. + @param InterruptStatus A pointer to the bit mask of the currently active + interrupts If this is NULL, the interrupt status will + not be read from the device. If this is not NULL, the + interrupt status will be read from the device. When + the interrupt status is read, it will also be + cleared. Clearing the transmit interrupt does not + empty the recycled transmit buffer array. + @param TxBuf Recycled transmit buffer address. The network + interface will not transmit if its internal recycled + transmit buffer array is full. Reading the transmit + buffer does not clear the transmit interrupt. If this + is NULL, then the transmit buffer status will not be + read. If there are no transmit buffers to recycle and + TxBuf is not NULL, * TxBuf will be set to NULL. + + @retval EFI_SUCCESS The status of the network interface was + retrieved. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetGetStatus ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + OUT UINT32 *InterruptStatus OPTIONAL, + OUT VOID **TxBuf OPTIONAL + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + UINT32 TdhCur; + UINT32 RdhCur; + EFI_PHYSICAL_ADDRESS DeviceAddress; + + if (This == NULL) { + return EFI_INVALID_PARAMETER; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + switch (Dev->Snm.State) { + case EfiSimpleNetworkStopped: + Status = EFI_NOT_STARTED; + goto Exit; + case EfiSimpleNetworkStarted: + Status = EFI_DEVICE_ERROR; + goto Exit; + default: + break; + } + + // + // update link status + // + if (Dev->Snm.MediaPresentSupported) { + UINT32 RegSts; + + Status = E1kNetRegRead32(Dev, E1K_REG_STATUS, &RegSts); + if (EFI_ERROR (Status)) { + goto Exit; + } + + Dev->Snm.MediaPresent = (BOOLEAN)((RegSts & E1K_REG_STATUS_LU) != 0); + } + + E1kNetRegRead32(Dev, E1K_REG_TDH, &TdhCur); + E1kNetRegRead32(Dev, E1K_REG_RDH, &RdhCur); + + + if (InterruptStatus != NULL) { + // + // report the receive interrupt if there is data available for reception, + // report the transmit interrupt if we have transmitted at least one buffer + // + *InterruptStatus = 0; + if (Dev->RdhLastSeen != RdhCur) { + *InterruptStatus |= EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; + } + if (Dev->TdhLastSeen != TdhCur) { + ASSERT (Dev->TxCurPending > 0); + *InterruptStatus |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; + } + } + + if (TxBuf != NULL) { + if (Dev->TdhLastSeen == TdhCur) { + *TxBuf = NULL; + } + else { + ASSERT (Dev->TxCurPending > 0); + ASSERT (Dev->TxCurPending <= Dev->TxMaxPending); + + // + // get the device address that has been enqueued for the caller's + // transmit buffer + // + DeviceAddress = Dev->TxRing[Dev->TdhLastSeen].AddrBufferLow; + DeviceAddress |= LShiftU64(Dev->TxRing[Dev->TdhLastSeen].AddrBufferHigh, 32); + + Dev->TdhLastSeen = (Dev->TdhLastSeen + 1) % E1K_NET_MAX_PENDING; + Dev->TxCurPending--; + + // + // Unmap the device address and perform the reverse mapping to find the + // caller buffer address. + // + Status = E1kNetUnmapTxBuf ( + Dev, + TxBuf, + DeviceAddress + ); + if (EFI_ERROR (Status)) { + // + // E1kNetUnmapTxBuf should never fail, if we have reached here + // that means our internal state has been corrupted + // + ASSERT (FALSE); + Status = EFI_DEVICE_ERROR; + goto Exit; + } + } + } + + Status = EFI_SUCCESS; + +Exit: + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpInitialize.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpInitialize.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpInitialize.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpInitialize.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,321 @@ +/** @file + + Implementation of the SNP.Initialize() function and its private helpers if + any. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (c) 2017, AMD Inc, All rights reserved. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include + +#include "E1kNet.h" + +/** + Set up static scaffolding for the E1kNetTransmit() and + E1kNetGetStatus() SNP methods. + + This function may only be called by E1kNetInitialize(). + + @param[in,out] Dev The E1K_NET_DEV driver instance about to enter the + EfiSimpleNetworkInitialized state. + + @retval EFI_OUT_OF_RESOURCES Failed to allocate the stack to track the heads + of free descriptor chains or failed to init + TxBufCollection. + @return Status codes from VIRTIO_DEVICE_PROTOCOL. + AllocateSharedPages() or + VirtioMapAllBytesInSharedBuffer() + @retval EFI_SUCCESS TX setup successful. +*/ + +STATIC +EFI_STATUS +EFIAPI +E1kNetInitTx ( + IN OUT E1K_NET_DEV *Dev + ) +{ + UINTN TxRingSize; + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS DeviceAddress; + VOID *TxRingBuffer; + + Dev->TxMaxPending = E1K_NET_MAX_PENDING; + Dev->TxCurPending = 0; + Dev->TxBufCollection = OrderedCollectionInit ( + E1kNetTxBufMapInfoCompare, + E1kNetTxBufDeviceAddressCompare + ); + if (Dev->TxBufCollection == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Exit; + } + + // + // Allocate TxRing header and map with BusMasterCommonBuffer so that it + // can be accessed equally by both processor and device. + // + TxRingSize = Dev->TxMaxPending * sizeof (*Dev->TxRing); + Status = Dev->PciIo->AllocateBuffer ( + Dev->PciIo, + AllocateAnyPages, + EfiBootServicesData, + EFI_SIZE_TO_PAGES (TxRingSize), + &TxRingBuffer, + EFI_PCI_ATTRIBUTE_MEMORY_CACHED + ); + if (EFI_ERROR (Status)) { + goto UninitTxBufCollection; + } + + ZeroMem (TxRingBuffer, TxRingSize); + + Status = Dev->PciIo->Map ( + Dev->PciIo, + EfiPciIoOperationBusMasterCommonBuffer, + TxRingBuffer, + &TxRingSize, + &DeviceAddress, + &Dev->TxRingMap + ); + if (EFI_ERROR (Status)) { + goto FreeTxRingBuffer; + } + + Dev->TxRing = TxRingBuffer; + Dev->TdhLastSeen = 0; + Dev->TxLastUsed = 0; + + // Program the transmit engine. + MemoryFence (); + E1kNetRegWrite32(Dev, E1K_REG_TDBAL, (UINT32)DeviceAddress); + E1kNetRegWrite32(Dev, E1K_REG_TDBAH, (UINT32)(RShiftU64 (DeviceAddress, 32))); + E1kNetRegWrite32(Dev, E1K_REG_TDLEN, (UINT32)TxRingSize); + E1kNetRegWrite32(Dev, E1K_REG_TDH, 0); + E1kNetRegWrite32(Dev, E1K_REG_TDT, 0); + E1kNetRegWrite32(Dev, E1K_REG_TCTL, E1K_REG_TCTL_EN | E1K_REG_TCTL_PSP); + + return EFI_SUCCESS; + +FreeTxRingBuffer: + Dev->PciIo->FreeBuffer ( + Dev->PciIo, + EFI_SIZE_TO_PAGES (TxRingSize), + TxRingBuffer + ); + +UninitTxBufCollection: + OrderedCollectionUninit (Dev->TxBufCollection); + +Exit: + return Status; +} + + +/** + Set up static scaffolding for the E1kNetReceive() SNP method and enable + live device operation. + + This function may only be called as E1kNetInitialize()'s final step. + + @param[in,out] Dev The E1K_NET_DEV driver instance about to enter the + EfiSimpleNetworkInitialized state. + + @return Status codes from VIRTIO_CFG_WRITE() or + VIRTIO_DEVICE_PROTOCOL.AllocateSharedPages or + VirtioMapAllBytesInSharedBuffer(). + @retval EFI_SUCCESS RX setup successful. The device is live and may + already be writing to the receive area. +*/ + +STATIC +EFI_STATUS +EFIAPI +E1kNetInitRx ( + IN OUT E1K_NET_DEV *Dev + ) +{ + EFI_STATUS Status; + UINTN RxBufSize; + UINTN PktIdx; + UINTN NumBytes; + EFI_PHYSICAL_ADDRESS RxBufDeviceAddress; + VOID *RxBuffer; + + // + // For each incoming packet we must supply two buffers: + // - the recipient for the RX descriptor, plus + // - the recipient for the network data (which consists of Ethernet header + // and Ethernet payload) which is a 2KB buffer. + // + RxBufSize = sizeof(*Dev->RxRing) + 2048; + + // + // The RxBuf is shared between guest and hypervisor, use + // AllocateSharedPages() to allocate this memory region and map it with + // BusMasterCommonBuffer so that it can be accessed by both guest and + // hypervisor. + // + NumBytes = E1K_NET_MAX_PENDING * RxBufSize; + Dev->RxBufNrPages = EFI_SIZE_TO_PAGES (NumBytes); + Status = Dev->PciIo->AllocateBuffer ( + Dev->PciIo, + AllocateAnyPages, + EfiBootServicesData, + Dev->RxBufNrPages, + &RxBuffer, + EFI_PCI_ATTRIBUTE_MEMORY_CACHED + ); + if (EFI_ERROR (Status)) { + return Status; + } + + ZeroMem (RxBuffer, NumBytes); + + Status = Dev->PciIo->Map ( + Dev->PciIo, + EfiPciIoOperationBusMasterCommonBuffer, + RxBuffer, + &NumBytes, + &Dev->RxDeviceBase, + &Dev->RxMap + ); + if (EFI_ERROR (Status)) { + goto FreeSharedBuffer; + } + + Dev->RxRing = RxBuffer; + Dev->RxBuf = (UINT8 *)RxBuffer + sizeof(*Dev->RxRing) * E1K_NET_MAX_PENDING; + Dev->RdhLastSeen = 0; + + // Set up the RX descriptors. + Dev->RxBufDeviceBase = Dev->RxDeviceBase + sizeof(*Dev->RxRing) * E1K_NET_MAX_PENDING; + RxBufDeviceAddress = Dev->RxBufDeviceBase; + for (PktIdx = 0; PktIdx < E1K_NET_MAX_PENDING; ++PktIdx) { + Dev->RxRing[PktIdx].AddrBufferLow = (UINT32)RxBufDeviceAddress; + Dev->RxRing[PktIdx].AddrBufferHigh = (UINT32)RShiftU64(RxBufDeviceAddress, 32); + Dev->RxRing[PktIdx].BufferLength = 2048; + + RxBufDeviceAddress += Dev->RxRing[PktIdx].BufferLength; + } + + // Program the receive engine. + MemoryFence (); + E1kNetRegWrite32(Dev, E1K_REG_RDBAL, (UINT32)Dev->RxDeviceBase); + E1kNetRegWrite32(Dev, E1K_REG_RDBAH, (UINT32)(RShiftU64 (Dev->RxDeviceBase, 32))); + E1kNetRegWrite32(Dev, E1K_REG_RDLEN, sizeof(*Dev->RxRing) * E1K_NET_MAX_PENDING); + E1kNetRegWrite32(Dev, E1K_REG_RDH, 0); + E1kNetRegWrite32(Dev, E1K_REG_RDT, E1K_NET_MAX_PENDING - 1); + E1kNetRegClear32(Dev, E1K_REG_RCTL, E1K_REG_RCTL_BSIZE_MASK); + E1kNetRegSet32(Dev, E1K_REG_RCTL, E1K_REG_RCTL_EN | E1K_REG_RCTL_MPE); + + return EFI_SUCCESS; + +FreeSharedBuffer: + Dev->PciIo->FreeBuffer ( + Dev->PciIo, + Dev->RxBufNrPages, + RxBuffer + ); + return Status; +} + +/** + Resets a network adapter and allocates the transmit and receive buffers + required by the network interface; optionally, also requests allocation of + additional transmit and receive buffers. + + @param This The protocol instance pointer. + @param ExtraRxBufferSize The size, in bytes, of the extra receive buffer + space that the driver should allocate for the + network interface. Some network interfaces will not + be able to use the extra buffer, and the caller + will not know if it is actually being used. + @param ExtraTxBufferSize The size, in bytes, of the extra transmit buffer + space that the driver should allocate for the + network interface. Some network interfaces will not + be able to use the extra buffer, and the caller + will not know if it is actually being used. + + @retval EFI_SUCCESS The network interface was initialized. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_OUT_OF_RESOURCES There was not enough memory for the transmit + and receive buffers. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetInitialize ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN UINTN ExtraRxBufferSize OPTIONAL, + IN UINTN ExtraTxBufferSize OPTIONAL + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + + DEBUG((DEBUG_INFO, "E1kNetInitialize:\n")); + + if (This == NULL) { + return EFI_INVALID_PARAMETER; + } + if (ExtraRxBufferSize > 0 || ExtraTxBufferSize > 0) { + return EFI_UNSUPPORTED; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + if (Dev->Snm.State != EfiSimpleNetworkStarted) { + Status = EFI_NOT_STARTED; + goto InitFailed; + } + + // Program the first Receive Address Low/High register. + E1kNetRegSet32(Dev, E1K_REG_CTRL, E1K_REG_CTRL_ASDE | E1K_REG_CTRL_SLU); + E1kNetRegWrite32(Dev, E1K_REG_RAL, *(UINT32 *)&Dev->Snm.CurrentAddress.Addr[0]); + E1kNetRegWrite32(Dev, E1K_REG_RAH, (*(UINT32 *)&Dev->Snm.CurrentAddress.Addr[4]) | E1K_REG_RAH_AV); + + Status = E1kNetInitTx (Dev); + if (EFI_ERROR (Status)) { + goto AbortDevice; + } + + // + // start receiving + // + Status = E1kNetInitRx (Dev); + if (EFI_ERROR (Status)) { + goto ReleaseTxAux; + } + + Dev->Snm.State = EfiSimpleNetworkInitialized; + gBS->RestoreTPL (OldTpl); + return EFI_SUCCESS; + +ReleaseTxAux: + E1kNetShutdownTx (Dev); + +AbortDevice: + E1kNetDevReset(Dev); + +InitFailed: + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpMcastIpToMac.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpMcastIpToMac.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpMcastIpToMac.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpMcastIpToMac.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,103 @@ +/** @file + + Implementation of the SNP.McastIpToMac() function and its private helpers if + any. + + Copyright (c) 2021, Oracle and/or its affiliates.
+ Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "E1kNet.h" + +/** + Converts a multicast IP address to a multicast HW MAC address. + + @param This The protocol instance pointer. + @param IPv6 Set to TRUE if the multicast IP address is IPv6 [RFC 2460]. Set + to FALSE if the multicast IP address is IPv4 [RFC 791]. + @param IP The multicast IP address that is to be converted to a multicast + HW MAC address. + @param MAC The multicast HW MAC address that is to be generated from IP. + + @retval EFI_SUCCESS The multicast IP address was mapped to the + multicast HW MAC address. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_BUFFER_TOO_SMALL The Statistics buffer was too small. The + current buffer size needed to hold the + statistics is returned in StatisticsSize. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetMcastIpToMac ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN IPv6, + IN EFI_IP_ADDRESS *Ip, + OUT EFI_MAC_ADDRESS *Mac + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + + // + // http://en.wikipedia.org/wiki/Multicast_address + // + if (This == NULL || Ip == NULL || Mac == NULL || + ( IPv6 && (Ip->v6.Addr[0] ) != 0xFF) || // invalid IPv6 mcast addr + (!IPv6 && (Ip->v4.Addr[0] & 0xF0) != 0xE0) // invalid IPv4 mcast addr + ) { + return EFI_INVALID_PARAMETER; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + switch (Dev->Snm.State) { + case EfiSimpleNetworkStopped: + Status = EFI_NOT_STARTED; + goto Exit; + case EfiSimpleNetworkStarted: + Status = EFI_DEVICE_ERROR; + goto Exit; + default: + break; + } + + // + // http://en.wikipedia.org/wiki/IP_multicast#Layer_2_delivery + // + if (IPv6) { + Mac->Addr[0] = 0x33; + Mac->Addr[1] = 0x33; + Mac->Addr[2] = Ip->v6.Addr[12]; + Mac->Addr[3] = Ip->v6.Addr[13]; + Mac->Addr[4] = Ip->v6.Addr[14]; + Mac->Addr[5] = Ip->v6.Addr[15]; + } + else { + Mac->Addr[0] = 0x01; + Mac->Addr[1] = 0x00; + Mac->Addr[2] = 0x5E; + Mac->Addr[3] = Ip->v4.Addr[1] & 0x7F; + Mac->Addr[4] = Ip->v4.Addr[2]; + Mac->Addr[5] = Ip->v4.Addr[3]; + } + Status = EFI_SUCCESS; + +Exit: + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpReceive.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpReceive.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpReceive.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpReceive.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,159 @@ +/** @file + + Implementation of the SNP.Receive() function and its private helpers if any. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2013, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include + +#include "E1kNet.h" + +/** + Receives a packet from a network interface. + + @param This The protocol instance pointer. + @param HeaderSize The size, in bytes, of the media header received on the + network interface. If this parameter is NULL, then the + media header size will not be returned. + @param BufferSize On entry, the size, in bytes, of Buffer. On exit, the + size, in bytes, of the packet that was received on the + network interface. + @param Buffer A pointer to the data buffer to receive both the media + header and the data. + @param SrcAddr The source HW MAC address. If this parameter is NULL, the + HW MAC source address will not be extracted from the media + header. + @param DestAddr The destination HW MAC address. If this parameter is NULL, + the HW MAC destination address will not be extracted from + the media header. + @param Protocol The media header type. If this parameter is NULL, then the + protocol will not be extracted from the media header. See + RFC 1700 section "Ether Types" for examples. + + @retval EFI_SUCCESS The received data was stored in Buffer, and + BufferSize has been updated to the number of + bytes received. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_NOT_READY The network interface is too busy to accept + this transmit request. + @retval EFI_BUFFER_TOO_SMALL The BufferSize parameter is too small. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetReceive ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + OUT UINTN *HeaderSize OPTIONAL, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer, + OUT EFI_MAC_ADDRESS *SrcAddr OPTIONAL, + OUT EFI_MAC_ADDRESS *DestAddr OPTIONAL, + OUT UINT16 *Protocol OPTIONAL + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + UINT32 RdhCur; + UINT32 RxLen; + UINTN OrigBufferSize; + EFI_PHYSICAL_ADDRESS BufferAddress; + UINT8 *RxPtr; + UINTN RxBufOffset; + + DEBUG((DEBUG_INFO, "E1kNetReceive: HeaderSize=%p BufferSize=%u Buffer=%p\n", + HeaderSize, *BufferSize, Buffer)); + + if (This == NULL || BufferSize == NULL || Buffer == NULL) { + return EFI_INVALID_PARAMETER; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + switch (Dev->Snm.State) { + case EfiSimpleNetworkStopped: + Status = EFI_NOT_STARTED; + goto Exit; + case EfiSimpleNetworkStarted: + Status = EFI_DEVICE_ERROR; + goto Exit; + default: + break; + } + + E1kNetRegRead32(Dev, E1K_REG_RDH, &RdhCur); + + if (Dev->RdhLastSeen == RdhCur) { + Status = EFI_NOT_READY; + goto Exit; + } + + RxLen = Dev->RxRing[Dev->RdhLastSeen].BufferLength; + // + // the host must not have filled in more data than requested + // + ASSERT (RxLen <= 2048); + + OrigBufferSize = *BufferSize; + *BufferSize = RxLen; + + if (OrigBufferSize < RxLen) { + Status = EFI_BUFFER_TOO_SMALL; + goto Exit; // keep the packet + } + + if (RxLen < Dev->Snm.MediaHeaderSize) { + Status = EFI_DEVICE_ERROR; + goto RecycleDesc; // drop useless short packet + } + + if (HeaderSize != NULL) { + *HeaderSize = Dev->Snm.MediaHeaderSize; + } + + BufferAddress = Dev->RxRing[Dev->RdhLastSeen].AddrBufferLow; + BufferAddress |= LShiftU64(Dev->RxRing[Dev->RdhLastSeen].AddrBufferHigh, 32); + RxBufOffset = (UINTN)(BufferAddress - Dev->RxBufDeviceBase); + RxPtr = Dev->RxBuf + RxBufOffset; + CopyMem (Buffer, RxPtr, RxLen); + + if (DestAddr != NULL) { + CopyMem (DestAddr, RxPtr, sizeof (E1K_NET_MAC)); + } + RxPtr += sizeof (E1K_NET_MAC); + + if (SrcAddr != NULL) { + CopyMem (SrcAddr, RxPtr, sizeof (E1K_NET_MAC)); + } + RxPtr += sizeof (E1K_NET_MAC); + + if (Protocol != NULL) { + *Protocol = (UINT16) ((RxPtr[0] << 8) | RxPtr[1]); + } + RxPtr += sizeof (UINT16); + + Status = EFI_SUCCESS; + +RecycleDesc: + Dev->RdhLastSeen = (Dev->RdhLastSeen + 1) % E1K_NET_MAX_PENDING; + E1kNetRegWrite32(Dev, E1K_REG_RDT, Dev->RdhLastSeen); + +Exit: + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpReceiveFilters.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpReceiveFilters.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpReceiveFilters.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpReceiveFilters.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,100 @@ +/** @file + + Implementation of the SNP.ReceiveFilters() function and its private helpers + if any. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "E1kNet.h" + +/** + Manages the multicast receive filters of a network interface. + + @param This The protocol instance pointer. + @param Enable A bit mask of receive filters to enable on the + network interface. + @param Disable A bit mask of receive filters to disable on the + network interface. + @param ResetMCastFilter Set to TRUE to reset the contents of the multicast + receive filters on the network interface to their + default values. + @param McastFilterCnt Number of multicast HW MAC addresses in the new + MCastFilter list. This value must be less than or + equal to the MCastFilterCnt field of + EFI_SIMPLE_NETWORK_MODE. This field is optional if + ResetMCastFilter is TRUE. + @param MCastFilter A pointer to a list of new multicast receive filter + HW MAC addresses. This list will replace any + existing multicast HW MAC address list. This field + is optional if ResetMCastFilter is TRUE. + + @retval EFI_SUCCESS The multicast receive filter list was updated. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetReceiveFilters ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN UINT32 Enable, + IN UINT32 Disable, + IN BOOLEAN ResetMCastFilter, + IN UINTN MCastFilterCnt OPTIONAL, + IN EFI_MAC_ADDRESS *MCastFilter OPTIONAL + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + + if (This == NULL) { + return EFI_INVALID_PARAMETER; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + switch (Dev->Snm.State) { + case EfiSimpleNetworkStopped: + Status = EFI_NOT_STARTED; + goto Exit; + case EfiSimpleNetworkStarted: + Status = EFI_DEVICE_ERROR; + goto Exit; + default: + break; + } + + // + // MNP apparently fails to initialize on top of us if we simply return + // EFI_UNSUPPORTED in this function. + // + // Hence we openly refuse multicast functionality, and fake the rest by + // selecting a no stricter filter setting than whatever is requested. The + // UEFI-2.3.1+errC spec allows this. In practice we don't change our current + // (default) filter. Additionally, receiving software is responsible for + // discarding any packets getting through the filter. + // + Status = ( + ((Enable | Disable) & ~Dev->Snm.ReceiveFilterMask) != 0 || + (!ResetMCastFilter && MCastFilterCnt > Dev->Snm.MaxMCastFilterCount) + ) ? EFI_INVALID_PARAMETER : EFI_SUCCESS; + +Exit: + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpSharedHelpers.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpSharedHelpers.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpSharedHelpers.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpSharedHelpers.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,281 @@ +/** @file + + Helper functions used by at least two Simple Network Protocol methods. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "E1kNet.h" + +// +// The user structure for the ordered collection that will track the mapping +// info of the packets queued in TxRing +// +typedef struct { + VOID *Buffer; + EFI_PHYSICAL_ADDRESS DeviceAddress; // lookup key for reverse mapping + VOID *BufMap; +} TX_BUF_MAP_INFO; + +/** + Release RX and TX resources on the boundary of the + EfiSimpleNetworkInitialized state. + + These functions contribute to rolling back a partial, failed initialization + of the e1000 SNP driver instance, or to shutting down a fully + initialized, running instance. + + They are only callable by the E1kNetInitialize() and the + E1kNetShutdown() SNP methods. See the state diagram in "E1kNet.h". + + @param[in,out] Dev The E1K_NET_DEV driver instance being shut down, or whose + partial, failed initialization is being rolled back. +*/ + +VOID +EFIAPI +E1kNetShutdownRx ( + IN OUT E1K_NET_DEV *Dev + ) +{ + Dev->PciIo->Unmap (Dev->PciIo, Dev->RxMap); + Dev->PciIo->FreeBuffer ( + Dev->PciIo, + Dev->RxBufNrPages, + Dev->RxRing + ); +} + + +VOID +EFIAPI +E1kNetShutdownTx ( + IN OUT E1K_NET_DEV *Dev + ) +{ + ORDERED_COLLECTION_ENTRY *Entry, *Entry2; + TX_BUF_MAP_INFO *TxBufMapInfo; + VOID *UserStruct; + + Dev->PciIo->Unmap (Dev->PciIo, Dev->TxRingMap); + Dev->PciIo->FreeBuffer ( + Dev->PciIo, + EFI_SIZE_TO_PAGES (Dev->TxMaxPending * sizeof (*Dev->TxRing)), + Dev->TxRing + ); + + for (Entry = OrderedCollectionMin (Dev->TxBufCollection); + Entry != NULL; + Entry = Entry2) { + Entry2 = OrderedCollectionNext (Entry); + OrderedCollectionDelete (Dev->TxBufCollection, Entry, &UserStruct); + TxBufMapInfo = UserStruct; + Dev->PciIo->Unmap (Dev->PciIo, TxBufMapInfo->BufMap); + FreePool (TxBufMapInfo); + } + OrderedCollectionUninit (Dev->TxBufCollection); +} + +/** + Map Caller-supplied TxBuf buffer to the device-mapped address + + @param[in] Dev The E1K_NET_DEV driver instance which wants to + map the Tx packet. + @param[in] Buffer The system physical address of TxBuf + @param[in] NumberOfBytes Number of bytes to map + @param[out] DeviceAddress The resulting device address for the bus + master access. + + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to + a lack of resources. + @return Status codes from + VirtioMapAllBytesInSharedBuffer() + @retval EFI_SUCCESS Caller-supplied buffer is successfully mapped. +*/ +EFI_STATUS +EFIAPI +E1kNetMapTxBuf ( + IN E1K_NET_DEV *Dev, + IN VOID *Buffer, + IN UINTN NumberOfBytes, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress + ) +{ + EFI_STATUS Status; + TX_BUF_MAP_INFO *TxBufMapInfo; + EFI_PHYSICAL_ADDRESS Address; + VOID *Mapping; + + TxBufMapInfo = AllocatePool (sizeof (*TxBufMapInfo)); + if (TxBufMapInfo == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = Dev->PciIo->Map ( + Dev->PciIo, + EfiPciIoOperationBusMasterRead, + Buffer, + &NumberOfBytes, + &Address, + &Mapping + ); + if (EFI_ERROR (Status)) { + goto FreeTxBufMapInfo; + } + + TxBufMapInfo->Buffer = Buffer; + TxBufMapInfo->DeviceAddress = Address; + TxBufMapInfo->BufMap = Mapping; + + Status = OrderedCollectionInsert ( + Dev->TxBufCollection, + NULL, + TxBufMapInfo + ); + switch (Status) { + case EFI_OUT_OF_RESOURCES: + goto UnmapTxBuf; + case EFI_ALREADY_STARTED: + // + // This should never happen: it implies + // + // - an identity-mapping VIRTIO_DEVICE_PROTOCOL.MapSharedBuffer() + // implementation -- which is fine, + // + // - and an SNP client that queues multiple instances of the exact same + // buffer address with SNP.Transmit() -- which is undefined behavior, + // based on the TxBuf language in UEFI-2.7, + // EFI_SIMPLE_NETWORK.GetStatus(). + // + ASSERT (FALSE); + Status = EFI_INVALID_PARAMETER; + goto UnmapTxBuf; + default: + ASSERT_EFI_ERROR (Status); + break; + } + + *DeviceAddress = Address; + return EFI_SUCCESS; + +UnmapTxBuf: + Dev->PciIo->Unmap (Dev->PciIo, Mapping); + +FreeTxBufMapInfo: + FreePool (TxBufMapInfo); + return Status; +} + +/** + Unmap (aka reverse mapping) device mapped TxBuf buffer to the system + physical address + + @param[in] Dev The E1K_NET_DEV driver instance which wants to + reverse- and unmap the Tx packet. + @param[out] Buffer The system physical address of TxBuf + @param[in] DeviceAddress The device address for the TxBuf + + @retval EFI_INVALID_PARAMETER The DeviceAddress is not mapped + @retval EFI_SUCCESS The TxBuf at DeviceAddress has been unmapped, + and Buffer has been set to TxBuf's system + physical address. + +*/ +EFI_STATUS +EFIAPI +E1kNetUnmapTxBuf ( + IN E1K_NET_DEV *Dev, + OUT VOID **Buffer, + IN EFI_PHYSICAL_ADDRESS DeviceAddress + ) +{ + ORDERED_COLLECTION_ENTRY *Entry; + TX_BUF_MAP_INFO *TxBufMapInfo; + VOID *UserStruct; + + Entry = OrderedCollectionFind (Dev->TxBufCollection, &DeviceAddress); + if (Entry == NULL) { + return EFI_INVALID_PARAMETER; + } + + OrderedCollectionDelete (Dev->TxBufCollection, Entry, &UserStruct); + + TxBufMapInfo = UserStruct; + + *Buffer = TxBufMapInfo->Buffer; + Dev->PciIo->Unmap (Dev->PciIo, TxBufMapInfo->BufMap); + FreePool (TxBufMapInfo); + + return EFI_SUCCESS; +} + +/** + Comparator function for two TX_BUF_MAP_INFO objects. + + @param[in] UserStruct1 Pointer to the first TX_BUF_MAP_INFO object. + + @param[in] UserStruct2 Pointer to the second TX_BUF_MAP_INFO object. + + @retval <0 If UserStruct1 compares less than UserStruct2. + + @retval 0 If UserStruct1 compares equal to UserStruct2. + + @retval >0 If UserStruct1 compares greater than UserStruct2. +*/ +INTN +EFIAPI +E1kNetTxBufMapInfoCompare ( + IN CONST VOID *UserStruct1, + IN CONST VOID *UserStruct2 + ) +{ + CONST TX_BUF_MAP_INFO *MapInfo1; + CONST TX_BUF_MAP_INFO *MapInfo2; + + MapInfo1 = UserStruct1; + MapInfo2 = UserStruct2; + + return MapInfo1->DeviceAddress < MapInfo2->DeviceAddress ? -1 : + MapInfo1->DeviceAddress > MapInfo2->DeviceAddress ? 1 : + 0; +} + +/** + Compare a standalone DeviceAddress against a TX_BUF_MAP_INFO object + containing an embedded DeviceAddress. + + @param[in] StandaloneKey Pointer to DeviceAddress, which has type + EFI_PHYSICAL_ADDRESS. + + @param[in] UserStruct Pointer to the TX_BUF_MAP_INFO object with the + embedded DeviceAddress. + + @retval <0 If StandaloneKey compares less than UserStruct's key. + + @retval 0 If StandaloneKey compares equal to UserStruct's key. + + @retval >0 If StandaloneKey compares greater than UserStruct's key. +**/ +INTN +EFIAPI +E1kNetTxBufDeviceAddressCompare ( + IN CONST VOID *StandaloneKey, + IN CONST VOID *UserStruct + ) +{ + CONST EFI_PHYSICAL_ADDRESS *DeviceAddress; + CONST TX_BUF_MAP_INFO *MapInfo; + + DeviceAddress = StandaloneKey; + MapInfo = UserStruct; + + return *DeviceAddress < MapInfo->DeviceAddress ? -1 : + *DeviceAddress > MapInfo->DeviceAddress ? 1 : + 0; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpShutdown.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpShutdown.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpShutdown.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpShutdown.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,74 @@ +/** @file + + Implementation of the SNP.Shutdown() function and its private helpers if any. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (c) 2017, AMD Inc, All rights reserved. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "E1kNet.h" + +/** + Resets a network adapter and leaves it in a state that is safe for another + driver to initialize. + + @param This Protocol instance pointer. + + @retval EFI_SUCCESS The network interface was shutdown. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetShutdown ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + + DEBUG((DEBUG_INFO, "E1kNetShutdown:\n")); + + if (This == NULL) { + return EFI_INVALID_PARAMETER; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + switch (Dev->Snm.State) { + case EfiSimpleNetworkStopped: + Status = EFI_NOT_STARTED; + goto Exit; + case EfiSimpleNetworkStarted: + Status = EFI_DEVICE_ERROR; + goto Exit; + default: + break; + } + + E1kNetDevReset(Dev); + E1kNetShutdownRx (Dev); + E1kNetShutdownTx (Dev); + + Dev->Snm.State = EfiSimpleNetworkStarted; + Status = EFI_SUCCESS; + +Exit: + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpStart.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpStart.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpStart.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpStart.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,59 @@ +/** @file + + Implementation of the SNP.Start() function and its private helpers if any. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "E1kNet.h" + +/** + Changes the state of a network interface from "stopped" to "started". + + @param This Protocol instance pointer. + + @retval EFI_SUCCESS The network interface was started. + @retval EFI_ALREADY_STARTED The network interface is already in the started + state. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. +**/ + +EFI_STATUS +EFIAPI +E1kNetStart ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + + if (This == NULL) { + return EFI_INVALID_PARAMETER; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + if (Dev->Snm.State != EfiSimpleNetworkStopped) { + Status = EFI_ALREADY_STARTED; + } + else { + Dev->Snm.State = EfiSimpleNetworkStarted; + Status = EFI_SUCCESS; + } + + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpStop.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpStop.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpStop.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpStop.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,60 @@ +/** @file + + Implementation of the SNP.Stop() function and its private helpers if any. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "E1kNet.h" + +/** + Changes the state of a network interface from "started" to "stopped". + + @param This Protocol instance pointer. + + @retval EFI_SUCCESS The network interface was stopped. + @retval EFI_ALREADY_STARTED The network interface is already in the stopped + state. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetStop ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + + if (This == NULL) { + return EFI_INVALID_PARAMETER; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + if (Dev->Snm.State != EfiSimpleNetworkStarted) { + Status = EFI_NOT_STARTED; + } + else { + Dev->Snm.State = EfiSimpleNetworkStopped; + Status = EFI_SUCCESS; + } + + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpTransmit.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpTransmit.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpTransmit.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpTransmit.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,170 @@ +/** @file + + Implementation of the SNP.Transmit() function and its private helpers if any. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2013, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include + +#include "E1kNet.h" + +/** + Places a packet in the transmit queue of a network interface. + + @param This The protocol instance pointer. + @param HeaderSize The size, in bytes, of the media header to be filled in by + the Transmit() function. If HeaderSize is non-zero, then + it must be equal to This->Mode->MediaHeaderSize and the + DestAddr and Protocol parameters must not be NULL. + @param BufferSize The size, in bytes, of the entire packet (media header and + data) to be transmitted through the network interface. + @param Buffer A pointer to the packet (media header followed by data) to + be transmitted. This parameter cannot be NULL. If + HeaderSize is zero, then the media header in Buffer must + already be filled in by the caller. If HeaderSize is + non-zero, then the media header will be filled in by the + Transmit() function. + @param SrcAddr The source HW MAC address. If HeaderSize is zero, then + this parameter is ignored. If HeaderSize is non-zero and + SrcAddr is NULL, then This->Mode->CurrentAddress is used + for the source HW MAC address. + @param DestAddr The destination HW MAC address. If HeaderSize is zero, + then this parameter is ignored. + @param Protocol The type of header to build. If HeaderSize is zero, then + this parameter is ignored. See RFC 1700, section "Ether + Types", for examples. + + @retval EFI_SUCCESS The packet was placed on the transmit queue. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_NOT_READY The network interface is too busy to accept + this transmit request. + @retval EFI_BUFFER_TOO_SMALL The BufferSize parameter is too small. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetTransmit ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN UINTN HeaderSize, + IN UINTN BufferSize, + IN /* +OUT! */ VOID *Buffer, + IN EFI_MAC_ADDRESS *SrcAddr OPTIONAL, + IN EFI_MAC_ADDRESS *DestAddr OPTIONAL, + IN UINT16 *Protocol OPTIONAL + ) +{ + E1K_NET_DEV *Dev; + EFI_TPL OldTpl; + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS DeviceAddress; + + DEBUG((DEBUG_INFO, "E1kNetTransmit: HeaderSize=%u BufferSize=%u Buffer=%p\n", + HeaderSize, BufferSize, Buffer)); + + if (This == NULL || BufferSize == 0 || Buffer == NULL) { + return EFI_INVALID_PARAMETER; + } + + Dev = E1K_NET_FROM_SNP (This); + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + switch (Dev->Snm.State) { + case EfiSimpleNetworkStopped: + Status = EFI_NOT_STARTED; + goto Exit; + case EfiSimpleNetworkStarted: + Status = EFI_DEVICE_ERROR; + goto Exit; + default: + break; + } + + if (BufferSize < Dev->Snm.MediaHeaderSize) { + Status = EFI_BUFFER_TOO_SMALL; + goto Exit; + } + if (BufferSize > Dev->Snm.MediaHeaderSize + Dev->Snm.MaxPacketSize) { + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + // + // check if we have room for transmission + // + ASSERT (Dev->TxCurPending <= Dev->TxMaxPending); + if (Dev->TxCurPending == Dev->TxMaxPending) { + Status = EFI_NOT_READY; + goto Exit; + } + + // + // the caller may want us to fill in the media header: + // dst MAC, src MAC, Ethertype + // + if (HeaderSize != 0) { + UINT8 *Ptr; + + if (HeaderSize != Dev->Snm.MediaHeaderSize || + DestAddr == NULL || Protocol == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + Ptr = Buffer; + ASSERT (sizeof (E1K_NET_MAC) <= sizeof (EFI_MAC_ADDRESS)); + + CopyMem (Ptr, DestAddr, sizeof (E1K_NET_MAC)); + Ptr += sizeof (E1K_NET_MAC); + + CopyMem (Ptr, + (SrcAddr == NULL) ? &Dev->Snm.CurrentAddress : SrcAddr, + sizeof (E1K_NET_MAC)); + Ptr += sizeof (E1K_NET_MAC); + + *Ptr++ = (UINT8) (*Protocol >> 8); + *Ptr++ = (UINT8) *Protocol; + + ASSERT ((UINTN) (Ptr - (UINT8 *) Buffer) == Dev->Snm.MediaHeaderSize); + } + + // + // Map the transmit buffer system physical address to device address. + // + Status = E1kNetMapTxBuf ( + Dev, + Buffer, + BufferSize, + &DeviceAddress + ); + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Exit; + } + + Dev->TxCurPending++; + Dev->TxRing[Dev->TxLastUsed].AddrBufferLow = (UINT32)DeviceAddress; + Dev->TxRing[Dev->TxLastUsed].AddrBufferHigh = (UINT32)RShiftU64(DeviceAddress, 32); + Dev->TxRing[Dev->TxLastUsed].BufferLength = (UINT16)BufferSize; + Dev->TxRing[Dev->TxLastUsed].Status = 0; + Dev->TxRing[Dev->TxLastUsed].Command = E1K_TX_CMD_EOP | E1K_TX_CMD_FCS | E1K_TX_CMD_RS; + + Dev->TxLastUsed = (Dev->TxLastUsed + 1) % E1K_NET_MAX_PENDING; + E1kNetRegWrite32(Dev, E1K_REG_TDT, Dev->TxLastUsed); + +Exit: + gBS->RestoreTPL (OldTpl); + return Status; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpUnsupported.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpUnsupported.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpUnsupported.c 1970-01-01 00:00:00.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/E1kNetDxe/SnpUnsupported.c 2022-09-01 13:26:02.000000000 +0000 @@ -0,0 +1,155 @@ +/** @file + + Empty implementation of the SNP methods that dependent protocols don't + absolutely need and the UEFI-2.3.1+errC specification allows us not to + support. + + Copyright (c) 2021, Oracle and/or its affiliates. + Copyright (C) 2013, Red Hat, Inc. + Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "E1kNet.h" + +/** + Resets a network adapter and re-initializes it with the parameters that were + provided in the previous call to Initialize(). + + @param This The protocol instance pointer. + @param ExtendedVerification Indicates that the driver may perform a more + exhaustive verification operation of the device + during reset. + + @retval EFI_SUCCESS The network interface was reset. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetReset ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN ExtendedVerification + ) +{ + return EFI_UNSUPPORTED; +} + + +/** + Modifies or resets the current station address, if supported. + + @param This The protocol instance pointer. + @param Reset Flag used to reset the station address to the network + interfaces permanent address. + @param New The new station address to be used for the network interface. + + @retval EFI_SUCCESS The network interfaces station address was + updated. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetStationAddress ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN Reset, + IN EFI_MAC_ADDRESS *New OPTIONAL + ) +{ + return EFI_UNSUPPORTED; +} + + +/** + Resets or collects the statistics on a network interface. + + @param This Protocol instance pointer. + @param Reset Set to TRUE to reset the statistics for the network + interface. + @param StatisticsSize On input the size, in bytes, of StatisticsTable. On + output the size, in bytes, of the resulting table of + statistics. + @param StatisticsTable A pointer to the EFI_NETWORK_STATISTICS structure + that contains the statistics. + + @retval EFI_SUCCESS The statistics were collected from the network + interface. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_BUFFER_TOO_SMALL The Statistics buffer was too small. The + current buffer size needed to hold the + statistics is returned in StatisticsSize. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetStatistics ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN Reset, + IN OUT UINTN *StatisticsSize OPTIONAL, + OUT EFI_NETWORK_STATISTICS *StatisticsTable OPTIONAL + ) +{ + return EFI_UNSUPPORTED; +} + + +/** + Performs read and write operations on the NVRAM device attached to a network + interface. + + @param This The protocol instance pointer. + @param ReadWrite TRUE for read operations, FALSE for write operations. + @param Offset Byte offset in the NVRAM device at which to start the read + or write operation. This must be a multiple of + NvRamAccessSize and less than NvRamSize. + @param BufferSize The number of bytes to read or write from the NVRAM + device. This must also be a multiple of NvramAccessSize. + @param Buffer A pointer to the data buffer. + + @retval EFI_SUCCESS The NVRAM access was performed. + @retval EFI_NOT_STARTED The network interface has not been started. + @retval EFI_INVALID_PARAMETER One or more of the parameters has an + unsupported value. + @retval EFI_DEVICE_ERROR The command could not be sent to the network + interface. + @retval EFI_UNSUPPORTED This function is not supported by the network + interface. + +**/ + +EFI_STATUS +EFIAPI +E1kNetNvData ( + IN EFI_SIMPLE_NETWORK_PROTOCOL *This, + IN BOOLEAN ReadWrite, + IN UINTN Offset, + IN UINTN BufferSize, + IN OUT VOID *Buffer + ) +{ + return EFI_UNSUPPORTED; +} diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c 2020-10-16 16:35:19.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c 2022-09-01 13:26:03.000000000 +0000 @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2010-2020 Oracle Corporation + * Copyright (C) 2010-2022 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -58,11 +58,6 @@ #include "fsw_efi.h" -#ifdef VBOX -# include -# include -#endif - #define DEBUG_LEVEL 0 #ifndef FSTYPE @@ -141,7 +136,7 @@ IN OUT UINTN *BufferSize, OUT VOID *Buffer); -#if defined(VBOX) && defined(FSTYPE_HFS) +#if defined(VBOX) && (FSTYPE == hfs) extern fsw_status_t fsw_hfs_get_blessed_file(void *vol, struct fsw_string *path); #endif @@ -265,128 +260,6 @@ return Status; } -#if defined(VBOX) && !defined(FSTYPE_HFS) -/** - Find UDF volume identifiers in a Volume Recognition Sequence. - - @param[in] BlockIo BlockIo interface. - @param[in] DiskIo DiskIo interface. - - @retval EFI_SUCCESS UDF volume identifiers were found. - @retval EFI_NOT_FOUND UDF volume identifiers were not found. - @retval other Failed to perform disk I/O. - -**/ -EFI_STATUS -FindUdfVolumeIdentifiers ( - IN EFI_BLOCK_IO_PROTOCOL *BlockIo, - IN EFI_DISK_IO_PROTOCOL *DiskIo - ) -{ - EFI_STATUS Status; - UINT64 Offset; - UINT64 EndDiskOffset; - CDROM_VOLUME_DESCRIPTOR VolDescriptor; - CDROM_VOLUME_DESCRIPTOR TerminatingVolDescriptor; - - ZeroMem ((VOID *)&TerminatingVolDescriptor, sizeof (CDROM_VOLUME_DESCRIPTOR)); - - // - // Start Volume Recognition Sequence - // - EndDiskOffset = MultU64x32 (BlockIo->Media->LastBlock, - BlockIo->Media->BlockSize); - - for (Offset = UDF_VRS_START_OFFSET; Offset < EndDiskOffset; - Offset += UDF_LOGICAL_SECTOR_SIZE) { - // - // Check if block device has a Volume Structure Descriptor and an Extended - // Area. - // - Status = DiskIo->ReadDisk ( - DiskIo, - BlockIo->Media->MediaId, - Offset, - sizeof (CDROM_VOLUME_DESCRIPTOR), - (VOID *)&VolDescriptor - ); - if (EFI_ERROR (Status)) { - return Status; - } - - if (CompareMem ((VOID *)VolDescriptor.Unknown.Id, - (VOID *)UDF_BEA_IDENTIFIER, - sizeof (VolDescriptor.Unknown.Id)) == 0) { - break; - } - - if ((CompareMem ((VOID *)VolDescriptor.Unknown.Id, - (VOID *)CDVOL_ID, - sizeof (VolDescriptor.Unknown.Id)) != 0) || - (CompareMem ((VOID *)&VolDescriptor, - (VOID *)&TerminatingVolDescriptor, - sizeof (CDROM_VOLUME_DESCRIPTOR)) == 0)) { - return EFI_NOT_FOUND; - } - } - - // - // Look for "NSR0{2,3}" identifiers in the Extended Area. - // - Offset += UDF_LOGICAL_SECTOR_SIZE; - if (Offset >= EndDiskOffset) { - return EFI_NOT_FOUND; - } - - Status = DiskIo->ReadDisk ( - DiskIo, - BlockIo->Media->MediaId, - Offset, - sizeof (CDROM_VOLUME_DESCRIPTOR), - (VOID *)&VolDescriptor - ); - if (EFI_ERROR (Status)) { - return Status; - } - - if ((CompareMem ((VOID *)VolDescriptor.Unknown.Id, - (VOID *)UDF_NSR2_IDENTIFIER, - sizeof (VolDescriptor.Unknown.Id)) != 0) && - (CompareMem ((VOID *)VolDescriptor.Unknown.Id, - (VOID *)UDF_NSR3_IDENTIFIER, - sizeof (VolDescriptor.Unknown.Id)) != 0)) { - return EFI_NOT_FOUND; - } - - // - // Look for "TEA01" identifier in the Extended Area - // - Offset += UDF_LOGICAL_SECTOR_SIZE; - if (Offset >= EndDiskOffset) { - return EFI_NOT_FOUND; - } - - Status = DiskIo->ReadDisk ( - DiskIo, - BlockIo->Media->MediaId, - Offset, - sizeof (CDROM_VOLUME_DESCRIPTOR), - (VOID *)&VolDescriptor - ); - if (EFI_ERROR (Status)) { - return Status; - } - - if (CompareMem ((VOID *)VolDescriptor.Unknown.Id, - (VOID *)UDF_TEA_IDENTIFIER, - sizeof (VolDescriptor.Unknown.Id)) != 0) { - return EFI_NOT_FOUND; - } - - return EFI_SUCCESS; -} -#endif - static EFI_STATUS fsw_efi_ReMount(IN FSW_VOLUME_DATA *pVolume, IN EFI_HANDLE ControllerHandle, EFI_DISK_IO *pDiskIo, @@ -403,20 +276,6 @@ Status = fsw_efi_map_status(fsw_mount(pVolume, &fsw_efi_host_table, &FSW_FSTYPE_TABLE_NAME(FSTYPE), &pVolume->vol), pVolume); -#if defined(VBOX) && !defined(FSTYPE_HFS) - /* - * Don't give the iso9660 filesystem driver a chance to claim a volume which supports UDF - * or we loose booting capability from UDF volumes. - */ - if (!EFI_ERROR(Status)) - { - Status = FindUdfVolumeIdentifiers(pBlockIo, pDiskIo); - if (!EFI_ERROR(Status)) - Status = EFI_UNSUPPORTED; - else - Status = EFI_SUCCESS; - } -#endif if (!EFI_ERROR(Status)) { // register the SimpleFileSystem protocol @@ -1127,9 +986,10 @@ *BufferSize = RequiredSize; Status = EFI_SUCCESS; -#if defined(VBOX) && defined(FSTYPE_HFS) +#ifdef VBOX } else if (CompareGuid(InformationType, &gVBoxFsBlessedFileInfoGuid)) { +# if FSTYPE == hfs struct fsw_string StrBlessedFile; fsw_status_t rc = fsw_hfs_get_blessed_file(Volume->vol, &StrBlessedFile); @@ -1150,6 +1010,7 @@ Status = EFI_SUCCESS; } else +# endif Status = EFI_UNSUPPORTED; #endif diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/VBoxHfs.inf virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/VBoxHfs.inf --- virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/VBoxHfs.inf 2020-10-16 16:35:19.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/VBoxHfs.inf 2022-09-01 13:26:03.000000000 +0000 @@ -78,8 +78,8 @@ gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultPlatformLang [BuildOptions.common] - GCC:*_*_*_CC_FLAGS = -DFSTYPE=hfs -DFSTYPE_HFS=1 -DEFI_LOG_ENABLED=1 + GCC:*_*_*_CC_FLAGS = -DFSTYPE=hfs -DEFI_LOG_ENABLED=1 # -DFSW_DEBUG_LEVEL=3 - INTEL:*_*_*_CC_FLAGS = -DFSTYPE=hfs -DFSTYPE_HFS=1 -DEFI_LOG_ENABLED=1 - MSFT:*_*_*_CC_FLAGS = -DFSTYPE=hfs -DFSTYPE_HFS=1 -DEFI_LOG_ENABLED=1 + INTEL:*_*_*_CC_FLAGS = -DFSTYPE=hfs -DEFI_LOG_ENABLED=1 + MSFT:*_*_*_CC_FLAGS = -DFSTYPE=hfs -DEFI_LOG_ENABLED=1 Binary files /tmp/tmp09q2yl6v/sV07raLuHN/virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/FirmwareBin/VBoxEFI32.fd and /tmp/tmp09q2yl6v/7fbhyDgHOT/virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/FirmwareBin/VBoxEFI32.fd differ Binary files /tmp/tmp09q2yl6v/sV07raLuHN/virtualbox-6.1.16-dfsg/src/VBox/Devices/EFI/FirmwareBin/VBoxEFI64.fd and /tmp/tmp09q2yl6v/7fbhyDgHOT/virtualbox-6.1.38-dfsg/src/VBox/Devices/EFI/FirmwareBin/VBoxEFI64.fd differ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative286.asm virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative286.asm --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative286.asm 2020-10-16 16:35:21.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative286.asm 2022-09-01 13:26:05.000000000 +0000 @@ -8036,7 +8036,7 @@ section _DATA progbits vstart=0x4780 align=1 ; size=0x3727 class=DATA group=DGROUP ; disGetNextSymbol 0xc4780 LB 0x3727 -> off=0x0 cb=000000000000002f uValue=00000000000c0000 '_msg_vga_init' _msg_vga_init: ; 0xc4780 LB 0x2f - db 'Oracle VM VirtualBox Version 6.1.15 VGA BIOS', 00dh, 00ah, 000h + db 'Oracle VM VirtualBox Version 6.1.38 VGA BIOS', 00dh, 00ah, 000h ; disGetNextSymbol 0xc47af LB 0x36f8 -> off=0x0 cb=0000000000000080 uValue=00000000000c002f 'vga_modes' vga_modes: ; 0xc47af LB 0x80 db 000h, 000h, 000h, 004h, 000h, 0b8h, 0ffh, 002h, 001h, 000h, 000h, 004h, 000h, 0b8h, 0ffh, 002h @@ -8955,7 +8955,7 @@ db 'Oracle VM VirtualBox VBE Adapter', 000h ; disGetNextSymbol 0xc7e2f LB 0x78 -> off=0x0 cb=0000000000000024 uValue=00000000000c36af '_vbebios_product_revision' _vbebios_product_revision: ; 0xc7e2f LB 0x24 - db 'Oracle VM VirtualBox Version 6.1.15', 000h + db 'Oracle VM VirtualBox Version 6.1.38', 000h ; disGetNextSymbol 0xc7e53 LB 0x54 -> off=0x0 cb=000000000000002b uValue=00000000000c36d3 '_vbebios_info_string' _vbebios_info_string: ; 0xc7e53 LB 0x2b db 'VirtualBox VBE Display Adapter enabled', 00dh, 00ah, 00dh, 00ah, 000h @@ -8971,13 +8971,14 @@ section CONST2 progbits vstart=0x7ea8 align=1 ; size=0x0 class=DATA group=DGROUP ; Padding 0x158 bytes at 0xc7ea8 - db 000h, 000h, 000h, 000h, 001h, 000h, 000h, 000h, 000h, 000h, 000h, 044h, 03ah, 05ch, 052h, 065h - db 070h, 06fh, 073h, 069h, 074h, 06fh, 072h, 079h, 05ch, 062h, 072h, 061h, 06eh, 063h, 068h, 065h - db 073h, 05ch, 056h, 042h, 06fh, 078h, 02dh, 036h, 02eh, 031h, 05ch, 06fh, 075h, 074h, 05ch, 077h - db 069h, 06eh, 02eh, 061h, 06dh, 064h, 036h, 034h, 05ch, 072h, 065h, 06ch, 065h, 061h, 073h, 065h - db 05ch, 06fh, 062h, 06ah, 05ch, 056h, 042h, 06fh, 078h, 056h, 067h, 061h, 042h, 069h, 06fh, 073h - db 032h, 038h, 036h, 05ch, 056h, 042h, 06fh, 078h, 056h, 067h, 061h, 042h, 069h, 06fh, 073h, 032h - db 038h, 036h, 02eh, 073h, 079h, 06dh, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h + db 000h, 000h, 000h, 000h, 001h, 000h, 000h, 000h, 000h, 000h, 000h, 02fh, 068h, 06fh, 06dh, 065h + db 02fh, 067h, 061h, 06ch, 069h, 074h, 073h, 079h, 06eh, 02fh, 063h, 06fh, 06dh, 070h, 069h, 06ch + db 065h, 02dh, 063h, 061h, 063h, 068h, 065h, 02fh, 076h, 062h, 06fh, 078h, 02fh, 062h, 072h, 061h + db 06eh, 063h, 068h, 065h, 073h, 02fh, 056h, 042h, 06fh, 078h, 02dh, 036h, 02eh, 031h, 02fh, 06fh + db 075h, 074h, 02fh, 06ch, 069h, 06eh, 075h, 078h, 02eh, 061h, 06dh, 064h, 036h, 034h, 02fh, 072h + db 065h, 06ch, 065h, 061h, 073h, 065h, 02fh, 06fh, 062h, 06ah, 02fh, 056h, 042h, 06fh, 078h, 056h + db 067h, 061h, 042h, 069h, 06fh, 073h, 032h, 038h, 036h, 02fh, 056h, 042h, 06fh, 078h, 056h, 067h + db 061h, 042h, 069h, 06fh, 073h, 032h, 038h, 036h, 02eh, 073h, 079h, 06dh, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h @@ -8991,5 +8992,4 @@ db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h - db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h - db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 013h + db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 010h diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative286.md5sum virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative286.md5sum --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative286.md5sum 2020-10-16 16:35:21.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative286.md5sum 2022-09-01 13:26:05.000000000 +0000 @@ -1 +1 @@ -f84c87966a5c8c5ad526bdf6c9e8d900 *VBoxVgaBios286.rom +5185614d1cb50785b9e746181fb9058a *VBoxVgaBios286.rom diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative386.asm virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative386.asm --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative386.asm 2020-10-16 16:35:21.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative386.asm 2022-09-01 13:26:05.000000000 +0000 @@ -7507,7 +7507,7 @@ section _DATA progbits vstart=0x4780 align=1 ; size=0x3727 class=DATA group=DGROUP ; disGetNextSymbol 0xc4780 LB 0x3727 -> off=0x0 cb=000000000000002f uValue=00000000000c0000 '_msg_vga_init' _msg_vga_init: ; 0xc4780 LB 0x2f - db 'Oracle VM VirtualBox Version 6.1.15 VGA BIOS', 00dh, 00ah, 000h + db 'Oracle VM VirtualBox Version 6.1.38 VGA BIOS', 00dh, 00ah, 000h ; disGetNextSymbol 0xc47af LB 0x36f8 -> off=0x0 cb=0000000000000080 uValue=00000000000c002f 'vga_modes' vga_modes: ; 0xc47af LB 0x80 db 000h, 000h, 000h, 004h, 000h, 0b8h, 0ffh, 002h, 001h, 000h, 000h, 004h, 000h, 0b8h, 0ffh, 002h @@ -8426,7 +8426,7 @@ db 'Oracle VM VirtualBox VBE Adapter', 000h ; disGetNextSymbol 0xc7e2f LB 0x78 -> off=0x0 cb=0000000000000024 uValue=00000000000c36af '_vbebios_product_revision' _vbebios_product_revision: ; 0xc7e2f LB 0x24 - db 'Oracle VM VirtualBox Version 6.1.15', 000h + db 'Oracle VM VirtualBox Version 6.1.38', 000h ; disGetNextSymbol 0xc7e53 LB 0x54 -> off=0x0 cb=000000000000002b uValue=00000000000c36d3 '_vbebios_info_string' _vbebios_info_string: ; 0xc7e53 LB 0x2b db 'VirtualBox VBE Display Adapter enabled', 00dh, 00ah, 00dh, 00ah, 000h @@ -8442,13 +8442,14 @@ section CONST2 progbits vstart=0x7ea8 align=1 ; size=0x0 class=DATA group=DGROUP ; Padding 0x158 bytes at 0xc7ea8 - db 000h, 000h, 000h, 000h, 001h, 000h, 000h, 000h, 000h, 000h, 000h, 044h, 03ah, 05ch, 052h, 065h - db 070h, 06fh, 073h, 069h, 074h, 06fh, 072h, 079h, 05ch, 062h, 072h, 061h, 06eh, 063h, 068h, 065h - db 073h, 05ch, 056h, 042h, 06fh, 078h, 02dh, 036h, 02eh, 031h, 05ch, 06fh, 075h, 074h, 05ch, 077h - db 069h, 06eh, 02eh, 061h, 06dh, 064h, 036h, 034h, 05ch, 072h, 065h, 06ch, 065h, 061h, 073h, 065h - db 05ch, 06fh, 062h, 06ah, 05ch, 056h, 042h, 06fh, 078h, 056h, 067h, 061h, 042h, 069h, 06fh, 073h - db 033h, 038h, 036h, 05ch, 056h, 042h, 06fh, 078h, 056h, 067h, 061h, 042h, 069h, 06fh, 073h, 033h - db 038h, 036h, 02eh, 073h, 079h, 06dh, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h + db 000h, 000h, 000h, 000h, 001h, 000h, 000h, 000h, 000h, 000h, 000h, 02fh, 068h, 06fh, 06dh, 065h + db 02fh, 067h, 061h, 06ch, 069h, 074h, 073h, 079h, 06eh, 02fh, 063h, 06fh, 06dh, 070h, 069h, 06ch + db 065h, 02dh, 063h, 061h, 063h, 068h, 065h, 02fh, 076h, 062h, 06fh, 078h, 02fh, 062h, 072h, 061h + db 06eh, 063h, 068h, 065h, 073h, 02fh, 056h, 042h, 06fh, 078h, 02dh, 036h, 02eh, 031h, 02fh, 06fh + db 075h, 074h, 02fh, 06ch, 069h, 06eh, 075h, 078h, 02eh, 061h, 06dh, 064h, 036h, 034h, 02fh, 072h + db 065h, 06ch, 065h, 061h, 073h, 065h, 02fh, 06fh, 062h, 06ah, 02fh, 056h, 042h, 06fh, 078h, 056h + db 067h, 061h, 042h, 069h, 06fh, 073h, 033h, 038h, 036h, 02fh, 056h, 042h, 06fh, 078h, 056h, 067h + db 061h, 042h, 069h, 06fh, 073h, 033h, 038h, 036h, 02eh, 073h, 079h, 06dh, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h @@ -8462,5 +8463,4 @@ db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h - db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h - db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 09bh + db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 098h diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative386.md5sum virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative386.md5sum --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative386.md5sum 2020-10-16 16:35:21.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative386.md5sum 2022-09-01 13:26:05.000000000 +0000 @@ -1 +1 @@ -66a245dd53a61efd87f446519bfe2ac4 *VBoxVgaBios386.rom +3f0ac62ea5424bbc83c73349dad36dea *VBoxVgaBios386.rom diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative8086.asm virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative8086.asm --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative8086.asm 2020-10-16 16:35:21.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative8086.asm 2022-09-01 13:26:05.000000000 +0000 @@ -8196,7 +8196,7 @@ section _DATA progbits vstart=0x4780 align=1 ; size=0x3727 class=DATA group=DGROUP ; disGetNextSymbol 0xc4780 LB 0x3727 -> off=0x0 cb=000000000000002f uValue=00000000000c0000 '_msg_vga_init' _msg_vga_init: ; 0xc4780 LB 0x2f - db 'Oracle VM VirtualBox Version 6.1.15 VGA BIOS', 00dh, 00ah, 000h + db 'Oracle VM VirtualBox Version 6.1.38 VGA BIOS', 00dh, 00ah, 000h ; disGetNextSymbol 0xc47af LB 0x36f8 -> off=0x0 cb=0000000000000080 uValue=00000000000c002f 'vga_modes' vga_modes: ; 0xc47af LB 0x80 db 000h, 000h, 000h, 004h, 000h, 0b8h, 0ffh, 002h, 001h, 000h, 000h, 004h, 000h, 0b8h, 0ffh, 002h @@ -9115,7 +9115,7 @@ db 'Oracle VM VirtualBox VBE Adapter', 000h ; disGetNextSymbol 0xc7e2f LB 0x78 -> off=0x0 cb=0000000000000024 uValue=00000000000c36af '_vbebios_product_revision' _vbebios_product_revision: ; 0xc7e2f LB 0x24 - db 'Oracle VM VirtualBox Version 6.1.15', 000h + db 'Oracle VM VirtualBox Version 6.1.38', 000h ; disGetNextSymbol 0xc7e53 LB 0x54 -> off=0x0 cb=000000000000002b uValue=00000000000c36d3 '_vbebios_info_string' _vbebios_info_string: ; 0xc7e53 LB 0x2b db 'VirtualBox VBE Display Adapter enabled', 00dh, 00ah, 00dh, 00ah, 000h @@ -9131,13 +9131,14 @@ section CONST2 progbits vstart=0x7ea8 align=1 ; size=0x0 class=DATA group=DGROUP ; Padding 0x158 bytes at 0xc7ea8 - db 000h, 000h, 000h, 000h, 001h, 000h, 000h, 000h, 000h, 000h, 000h, 044h, 03ah, 05ch, 052h, 065h - db 070h, 06fh, 073h, 069h, 074h, 06fh, 072h, 079h, 05ch, 062h, 072h, 061h, 06eh, 063h, 068h, 065h - db 073h, 05ch, 056h, 042h, 06fh, 078h, 02dh, 036h, 02eh, 031h, 05ch, 06fh, 075h, 074h, 05ch, 077h - db 069h, 06eh, 02eh, 061h, 06dh, 064h, 036h, 034h, 05ch, 072h, 065h, 06ch, 065h, 061h, 073h, 065h - db 05ch, 06fh, 062h, 06ah, 05ch, 056h, 042h, 06fh, 078h, 056h, 067h, 061h, 042h, 069h, 06fh, 073h - db 038h, 030h, 038h, 036h, 05ch, 056h, 042h, 06fh, 078h, 056h, 067h, 061h, 042h, 069h, 06fh, 073h - db 038h, 030h, 038h, 036h, 02eh, 073h, 079h, 06dh, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h + db 000h, 000h, 000h, 000h, 001h, 000h, 000h, 000h, 000h, 000h, 000h, 02fh, 068h, 06fh, 06dh, 065h + db 02fh, 067h, 061h, 06ch, 069h, 074h, 073h, 079h, 06eh, 02fh, 063h, 06fh, 06dh, 070h, 069h, 06ch + db 065h, 02dh, 063h, 061h, 063h, 068h, 065h, 02fh, 076h, 062h, 06fh, 078h, 02fh, 062h, 072h, 061h + db 06eh, 063h, 068h, 065h, 073h, 02fh, 056h, 042h, 06fh, 078h, 02dh, 036h, 02eh, 031h, 02fh, 06fh + db 075h, 074h, 02fh, 06ch, 069h, 06eh, 075h, 078h, 02eh, 061h, 06dh, 064h, 036h, 034h, 02fh, 072h + db 065h, 06ch, 065h, 061h, 073h, 065h, 02fh, 06fh, 062h, 06ah, 02fh, 056h, 042h, 06fh, 078h, 056h + db 067h, 061h, 042h, 069h, 06fh, 073h, 038h, 030h, 038h, 036h, 02fh, 056h, 042h, 06fh, 078h, 056h + db 067h, 061h, 042h, 069h, 06fh, 073h, 038h, 030h, 038h, 036h, 02eh, 073h, 079h, 06dh, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h @@ -9151,5 +9152,4 @@ db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h - db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h - db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 088h + db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 085h diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative8086.md5sum virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative8086.md5sum --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative8086.md5sum 2020-10-16 16:35:21.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/BIOS/VBoxVgaBiosAlternative8086.md5sum 2022-09-01 13:26:05.000000000 +0000 @@ -1 +1 @@ -f4da2084ed564d6db6b857373560d4a3 *VBoxVgaBios8086.rom +c315e962c1d0b9f5f4a9ad662005f71d *VBoxVgaBios8086.rom diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA.cpp 2020-10-16 16:35:22.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA.cpp 2022-09-01 13:26:06.000000000 +0000 @@ -390,6 +390,44 @@ /** + * Mark a page in VGA A0000-AFFFF range as remapped. + * + * @param pThis VGA instance data. + * @param offVGAMem The offset within VGA memory. + */ +DECLINLINE(void) vgaMarkRemapped(PVGASTATE pThis, RTGCPHYS offVGAMem) +{ + AssertMsg(offVGAMem < _64K, ("offVGAMem = %p > 64K\n", offVGAMem)); + ASMBitSet(&pThis->bmPageMapBitmap, offVGAMem >> PAGE_SHIFT); + pThis->fRemappedVGA = true; +} + +/** + * Checks if a page in VGA A0000-AFFFF range is remapped. + * + * @returns true if remapped. + * @returns false if not remapped (accesses will trap). + * @param pThis VGA instance data. + * @param offVGAMem The offset within VGA memory. + */ +DECLINLINE(bool) vgaIsRemapped(PVGASTATE pThis, RTGCPHYS offVGAMem) +{ + AssertMsg(offVGAMem < _64K, ("offVGAMem = %p > 64K\n", offVGAMem)); + return ASMBitTest(&pThis->bmPageMapBitmap, offVGAMem >> PAGE_SHIFT); +} + +/** + * Reset page remap tracking bits. + * + * @param pThis VGA instance data. + */ +DECLINLINE(void) vgaResetRemapped(PVGASTATE pThis) +{ + pThis->fRemappedVGA = false; + ASMBitClearRange(&pThis->bmPageMapBitmap, 0, _64K >> PAGE_SHIFT); +} + +/** * Set a VRAM page dirty. * * @param pThis VGA instance data. @@ -398,10 +436,12 @@ DECLINLINE(void) vgaR3MarkDirty(PVGASTATE pThis, RTGCPHYS offVRAM) { AssertMsg(offVRAM < pThis->vram_size, ("offVRAM = %p, pThis->vram_size = %p\n", offVRAM, pThis->vram_size)); - ASMBitSet(&pThis->au32DirtyBitmap[0], offVRAM >> PAGE_SHIFT); + ASMBitSet(&pThis->bmDirtyBitmap[0], offVRAM >> PAGE_SHIFT); pThis->fHasDirtyBits = true; } +#ifdef IN_RING3 + /** * Tests if a VRAM page is dirty. * @@ -410,13 +450,13 @@ * @param pThis VGA instance data. * @param offVRAM The VRAM offset of the page to check. */ -DECLINLINE(bool) vgaIsDirty(PVGASTATE pThis, RTGCPHYS offVRAM) +DECLINLINE(bool) vgaR3IsDirty(PVGASTATE pThis, RTGCPHYS offVRAM) { AssertMsg(offVRAM < pThis->vram_size, ("offVRAM = %p, pThis->vram_size = %p\n", offVRAM, pThis->vram_size)); - return ASMBitTest(&pThis->au32DirtyBitmap[0], offVRAM >> PAGE_SHIFT); + return ASMBitTest(&pThis->bmDirtyBitmap[0], offVRAM >> PAGE_SHIFT); } -#ifdef IN_RING3 + /** * Reset dirty flags in a give range. * @@ -429,8 +469,73 @@ Assert(offVRAMStart < pThis->vram_size); Assert(offVRAMEnd <= pThis->vram_size); Assert(offVRAMStart < offVRAMEnd); - ASMBitClearRange(&pThis->au32DirtyBitmap[0], offVRAMStart >> PAGE_SHIFT, offVRAMEnd >> PAGE_SHIFT); + ASMBitClearRange(&pThis->bmDirtyBitmap[0], offVRAMStart >> PAGE_SHIFT, offVRAMEnd >> PAGE_SHIFT); +} + + +/** + * Queries the VRAM dirty bits and resets the monitoring. + */ +static void vgaR3UpdateDirtyBitsAndResetMonitoring(PPDMDEVINS pDevIns, PVGASTATE pThis) +{ + size_t const cbBitmap = RT_ALIGN_Z(RT_MIN(pThis->vram_size, VGA_VRAM_MAX), PAGE_SIZE * 64) / PAGE_SIZE / 8; + + /* + * If we don't have any dirty bits from MMIO accesses, we can just query + * straight into the dirty buffer. + */ + if (!pThis->fHasDirtyBits) + { + int rc = PDMDevHlpMmio2QueryAndResetDirtyBitmap(pDevIns, pThis->hMmio2VRam, pThis->bmDirtyBitmap, cbBitmap); + AssertRC(rc); + } + /* + * Otherwise we'll have to query and merge the two. + */ + else + { + uint64_t bmDirtyPages[VGA_VRAM_MAX / PAGE_SIZE / 64]; /* (256 MB VRAM -> 8KB bitmap) */ + int rc = PDMDevHlpMmio2QueryAndResetDirtyBitmap(pDevIns, pThis->hMmio2VRam, bmDirtyPages, cbBitmap); + if (RT_SUCCESS(rc)) + { + /** @todo could use ORPS or VORPS here, I think. */ + uint64_t *pbmDst = pThis->bmDirtyBitmap; + size_t const cTodo = cbBitmap / sizeof(uint64_t); + + /* Do 64 bytes at a time first. */ + size_t const cTodoFirst = cTodo & ~(size_t)7; + size_t idx; + for (idx = 0; idx < cTodoFirst; idx += 8) + { + pbmDst[idx + 0] |= bmDirtyPages[idx + 0]; + pbmDst[idx + 1] |= bmDirtyPages[idx + 1]; + pbmDst[idx + 2] |= bmDirtyPages[idx + 2]; + pbmDst[idx + 3] |= bmDirtyPages[idx + 3]; + pbmDst[idx + 4] |= bmDirtyPages[idx + 4]; + pbmDst[idx + 5] |= bmDirtyPages[idx + 5]; + pbmDst[idx + 6] |= bmDirtyPages[idx + 6]; + pbmDst[idx + 7] |= bmDirtyPages[idx + 7]; + } + + /* Then do a mopup of anything remaining. */ + switch (cTodo - idx) + { + case 7: pbmDst[idx + 6] |= bmDirtyPages[idx + 6]; RT_FALL_THRU(); + case 6: pbmDst[idx + 5] |= bmDirtyPages[idx + 5]; RT_FALL_THRU(); + case 5: pbmDst[idx + 4] |= bmDirtyPages[idx + 4]; RT_FALL_THRU(); + case 4: pbmDst[idx + 3] |= bmDirtyPages[idx + 3]; RT_FALL_THRU(); + case 3: pbmDst[idx + 2] |= bmDirtyPages[idx + 2]; RT_FALL_THRU(); + case 2: pbmDst[idx + 1] |= bmDirtyPages[idx + 1]; RT_FALL_THRU(); + case 1: pbmDst[idx] |= bmDirtyPages[idx]; break; + case 0: break; + default: AssertFailedBreak(); + } + + pThis->fHasDirtyBits = false; + } + } } + #endif /* IN_RING3 */ /* Update the values needed for calculating Vertical Retrace and @@ -722,7 +827,8 @@ if (pThis->fRemappedVGA) { IOMMmioResetRegion(PDMDevHlpGetVM(pDevIns), pDevIns, pThis->hMmioLegacy); - pThis->fRemappedVGA = false; + STAM_COUNTER_INC(&pThis->StatMapReset); + vgaResetRemapped(pThis); } } #endif @@ -761,7 +867,8 @@ if (pThis->fRemappedVGA) { IOMMmioResetRegion(PDMDevHlpGetVM(pDevIns), pDevIns, pThis->hMmioLegacy); - pThis->fRemappedVGA = false; + STAM_COUNTER_INC(&pThis->StatMapReset); + vgaResetRemapped(pThis); } } #endif @@ -913,21 +1020,6 @@ return aligned_pitch; } -# ifdef SOME_UNUSED_FUNCTION -/* Calculate line width in pixels based on bit depth and pitch. */ -static uint32_t calc_line_width(uint16_t bpp, uint32_t pitch) -{ - uint32_t width; - - if (bpp <= 4) - width = pitch << 1; - else - width = pitch / ((bpp + 7) >> 3); - - return width; -} -# endif - static void recalculate_data(PVGASTATE pThis) { uint16_t cBPP = pThis->vbe_regs[VBE_DISPI_INDEX_BPP]; @@ -938,7 +1030,8 @@ uint32_t cbLinePitch = calc_line_pitch(cBPP, cVirtWidth); if (!cbLinePitch) cbLinePitch = calc_line_pitch(cBPP, cX); - Assert(cbLinePitch != 0); + if (!cbLinePitch) + return; uint32_t cVirtHeight = pThis->vram_size / cbLinePitch; uint16_t offX = pThis->vbe_regs[VBE_DISPI_INDEX_X_OFFSET]; uint16_t offY = pThis->vbe_regs[VBE_DISPI_INDEX_Y_OFFSET]; @@ -1032,7 +1125,8 @@ if (pThis->fRemappedVGA) { IOMMmioResetRegion(PDMDevHlpGetVM(pDevIns), pDevIns, pThis->hMmioLegacy); - pThis->fRemappedVGA = false; + STAM_COUNTER_INC(&pThis->StatMapReset); + vgaResetRemapped(pThis); } # endif break; @@ -1141,7 +1235,8 @@ if (pThis->fRemappedVGA) { IOMMmioResetRegion(PDMDevHlpGetVM(pDevIns), pDevIns, pThis->hMmioLegacy); - pThis->fRemappedVGA = false; + STAM_COUNTER_INC(&pThis->StatMapReset); + vgaResetRemapped(pThis); } break; } @@ -1236,7 +1331,7 @@ #ifndef IN_RC /* If all planes are accessible, then map the page to the frame buffer and make it writable. */ if ( (pThis->sr[2] & 3) == 3 - && !vgaIsDirty(pThis, addr) + && !vgaIsRemapped(pThis, GCPhys - 0xa0000) && pThis->GCPhysVRAM) { /** @todo only allow read access (doesn't work now) */ @@ -1245,7 +1340,7 @@ pThis->hMmio2VRam, addr, X86_PTE_RW | X86_PTE_P); /* Set as dirty as write accesses won't be noticed now. */ vgaR3MarkDirty(pThis, addr); - pThis->fRemappedVGA = true; + vgaMarkRemapped(pThis, GCPhys - 0xa0000); } #endif /* !IN_RC */ VERIFY_VRAM_READ_OFF_RETURN(pThis, addr, *prc); @@ -1345,13 +1440,13 @@ #ifndef IN_RC /* If all planes are accessible, then map the page to the frame buffer and make it writable. */ if ( (pThis->sr[2] & 3) == 3 - && !vgaIsDirty(pThis, addr) + && !vgaIsRemapped(pThis, GCPhys - 0xa0000) && pThis->GCPhysVRAM) { STAM_COUNTER_INC(&pThis->StatMapPage); IOMMmioMapMmio2Page(PDMDevHlpGetVM(pDevIns), pDevIns, pThis->hMmioLegacy, GCPhys - 0xa0000, pThis->hMmio2VRam, addr, X86_PTE_RW | X86_PTE_P); - pThis->fRemappedVGA = true; + vgaMarkRemapped(pThis, GCPhys - 0xa0000); } #endif /* !IN_RC */ @@ -2299,10 +2394,10 @@ * irrespective of alignment. Not guaranteed for high res modes, i.e. * anything wider than 2050 pixels @32bpp. Need to check all pages * between the first and last one. */ - bool fUpdate = fFullUpdate | vgaIsDirty(pThis, offPage0) | vgaIsDirty(pThis, offPage1); + bool fUpdate = fFullUpdate | vgaR3IsDirty(pThis, offPage0) | vgaR3IsDirty(pThis, offPage1); if (offPage1 - offPage0 > PAGE_SIZE) /* if wide line, can use another page */ - fUpdate |= vgaIsDirty(pThis, offPage0 + PAGE_SIZE); + fUpdate |= vgaR3IsDirty(pThis, offPage0 + PAGE_SIZE); /* explicit invalidation for the hardware cursor */ fUpdate |= (pThis->invalidated_y_table[y >> 5] >> (y & 0x1f)) & 1; if (fUpdate) @@ -2498,10 +2593,10 @@ * irrespective of alignment. Not guaranteed for high res modes, i.e. * anything wider than 2050 pixels @32bpp. Need to check all pages * between the first and last one. */ - bool update = full_update | vgaIsDirty(pThis, page0) | vgaIsDirty(pThis, page1); + bool update = full_update | vgaR3IsDirty(pThis, page0) | vgaR3IsDirty(pThis, page1); if (page1 - page0 > PAGE_SIZE) { /* if wide line, can use another page */ - update |= vgaIsDirty(pThis, page0 + PAGE_SIZE); + update |= vgaR3IsDirty(pThis, page0 + PAGE_SIZE); } /* explicit invalidation for the hardware cursor */ update |= (pThis->invalidated_y_table[y >> 5] >> (y & 0x1f)) & 1; @@ -2583,8 +2678,7 @@ page_min = (pThis->start_addr * 4) & ~PAGE_OFFSET_MASK; /* round up page_max by one page, as otherwise this can be -PAGE_SIZE, * which causes assertion trouble in vgaR3ResetDirty. */ - page_max = ( pThis->start_addr * 4 + pThis->line_offset * pThis->last_scr_height - - 1 + PAGE_SIZE) & ~PAGE_OFFSET_MASK; + page_max = (pThis->start_addr * 4 + pThis->line_offset * pThis->last_scr_height - 1 + PAGE_SIZE) & ~PAGE_OFFSET_MASK; vgaR3ResetDirty(pThis, page_min, page_max + PAGE_SIZE); } if (pDrv->pbData == pThisCC->pbVRam) /* Do not clear the VRAM itself. */ @@ -2619,7 +2713,7 @@ #endif /** - * Worker for vgaR3PortUpdateDisplay(), vboxR3UpdateDisplayAllInternal() and + * Worker for vgaR3PortUpdateDisplay(), vgaR3UpdateDisplayAllInternal() and * vgaR3PortTakeScreenshot(). */ static int vgaR3UpdateDisplay(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATER3 pThisCC, bool fUpdateAll, @@ -3716,104 +3810,6 @@ } -/** - * Handle LFB access. - * - * @returns Strict VBox status code. - * @param pVM VM handle. - * @param pDevIns The device instance. - * @param pThis The shared VGA instance data. - * @param GCPhys The access physical address. - * @param GCPtr The access virtual address (only GC). - */ -static VBOXSTRICTRC vgaLFBAccess(PVMCC pVM, PPDMDEVINS pDevIns, PVGASTATE pThis, RTGCPHYS GCPhys, RTGCPTR GCPtr) -{ - VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_EM_RAW_EMULATE_INSTR); - if (rc == VINF_SUCCESS) - { - /* - * Set page dirty bit. - */ - vgaR3MarkDirty(pThis, GCPhys - pThis->GCPhysVRAM); - pThis->fLFBUpdated = true; - - /* - * Turn of the write handler for this particular page and make it R/W. - * Then return telling the caller to restart the guest instruction. - * ASSUME: the guest always maps video memory RW. - */ - rc = PGMHandlerPhysicalPageTempOff(pVM, pThis->GCPhysVRAM, GCPhys); - if (RT_SUCCESS(rc)) - { -#ifndef IN_RING3 - rc = PGMShwMakePageWritable(PDMDevHlpGetVMCPU(pDevIns), GCPtr, - PGM_MK_PG_IS_MMIO2 | PGM_MK_PG_IS_WRITE_FAULT); - PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); - AssertMsgReturn( rc == VINF_SUCCESS - /* In the SMP case the page table might be removed while we wait for the PGM lock in the trap handler. */ - || rc == VERR_PAGE_TABLE_NOT_PRESENT - || rc == VERR_PAGE_NOT_PRESENT, - ("PGMShwModifyPage -> GCPtr=%RGv rc=%d\n", GCPtr, VBOXSTRICTRC_VAL(rc)), - rc); -#else /* IN_RING3 - We don't have any virtual page address of the access here. */ - PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); - Assert(GCPtr == 0); - RT_NOREF1(GCPtr); -#endif - return VINF_SUCCESS; - } - - PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); - AssertMsgFailed(("PGMHandlerPhysicalPageTempOff -> rc=%d\n", VBOXSTRICTRC_VAL(rc))); - } - return rc; -} - - -#ifndef IN_RING3 -/** - * @callback_method_impl{FNPGMRCPHYSHANDLER, \#PF Handler for VBE LFB access.} - */ -PDMBOTHCBDECL(VBOXSTRICTRC) vgaLbfAccessPfHandler(PVMCC pVM, PVMCPUCC pVCpu, RTGCUINT uErrorCode, PCPUMCTXCORE pRegFrame, - RTGCPTR pvFault, RTGCPHYS GCPhysFault, void *pvUser) -{ - PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; - PVGASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVGASTATE); - //PVGASTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVGASTATECC); - Assert(GCPhysFault >= pThis->GCPhysVRAM); - AssertMsg(uErrorCode & X86_TRAP_PF_RW, ("uErrorCode=%#x\n", uErrorCode)); - RT_NOREF3(pVCpu, pRegFrame, uErrorCode); - - return vgaLFBAccess(pVM, pDevIns, pThis, GCPhysFault, pvFault); -} -#endif /* !IN_RING3 */ - - -/** - * @callback_method_impl{FNPGMPHYSHANDLER, - * VBE LFB write access handler for the dirty tracking.} - */ -PGM_ALL_CB_DECL(VBOXSTRICTRC) vgaLFBAccessHandler(PVMCC pVM, PVMCPUCC pVCpu, RTGCPHYS GCPhys, void *pvPhys, - void *pvBuf, size_t cbBuf, PGMACCESSTYPE enmAccessType, - PGMACCESSORIGIN enmOrigin, void *pvUser) -{ - PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; - PVGASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVGASTATE); - //PVGASTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVGASTATECC); - Assert(GCPhys >= pThis->GCPhysVRAM); - RT_NOREF(pVCpu, pvPhys, pvBuf, cbBuf, enmAccessType, enmOrigin); - - VBOXSTRICTRC rc = vgaLFBAccess(pVM, pDevIns, pThis, GCPhys, 0); - if (rc == VINF_SUCCESS) - rc = VINF_PGM_HANDLER_DO_DEFAULT; -#ifdef IN_RING3 - else - AssertMsg(rc < VINF_SUCCESS, ("rc=%Rrc\n", VBOXSTRICTRC_VAL(rc))); -#endif - return rc; -} - - /* -=-=-=-=-=- All rings: VGA BIOS I/Os -=-=-=-=-=- */ /** @@ -3960,8 +3956,9 @@ /* * Get bitmap header data */ - PBMPINFO pBmpInfo = (PBMPINFO)(pThisCC->pbLogo + sizeof(LOGOHDR)); - PWINHDR pWinHdr = (PWINHDR)(pThisCC->pbLogo + sizeof(LOGOHDR) + sizeof(BMPINFO)); + PCLOGOHDR pLogoHdr = (PCLOGOHDR)pThisCC->pbLogo; + PBMPINFO pBmpInfo = (PBMPINFO)(pThisCC->pbLogo + sizeof(LOGOHDR)); + PWINHDR pWinHdr = (PWINHDR)(pThisCC->pbLogo + sizeof(LOGOHDR) + sizeof(BMPINFO)); if (pBmpInfo->Type == BMP_ID) { @@ -4023,8 +4020,27 @@ VERR_INVALID_PARAMETER); AssertLogRelMsgReturn(pThisCC->LogoCompression == BMP_COMPRESS_NONE, - ("Unsupported %u compression.\n", pThisCC->LogoCompression), - VERR_INVALID_PARAMETER); + ("Unsupported %u compression.\n", pThisCC->LogoCompression), + VERR_INVALID_PARAMETER); + + AssertLogRelMsgReturn(pLogoHdr->cbLogo > pBmpInfo->Offset, + ("Wrong bitmap data offset %u, cbLogo=%u.\n", pBmpInfo->Offset, pLogoHdr->cbLogo), + VERR_INVALID_PARAMETER); + + uint32_t const cbFileData = pLogoHdr->cbLogo - pBmpInfo->Offset; + uint32_t cbImageData = (uint32_t)pThisCC->cxLogo * pThisCC->cyLogo * pThisCC->cLogoPlanes; + if (pThisCC->cLogoBits == 4) + cbImageData /= 2; + else if (pThisCC->cLogoBits == 24) + cbImageData *= 3; + + AssertLogRelMsgReturn(cbImageData <= cbFileData, + ("Wrong BMP header data %u (cbLogo=%u offBits=%u)\n", cbImageData, pBmpInfo->Offset, pLogoHdr->cbLogo), + VERR_INVALID_PARAMETER); + + AssertLogRelMsgReturn(pLogoHdr->cbLogo == pBmpInfo->FileSize, + ("Wrong bitmap file size %u, cbLogo=%u.\n", pBmpInfo->FileSize, pLogoHdr->cbLogo), + VERR_INVALID_PARAMETER); /* * Read bitmap palette @@ -4720,6 +4736,7 @@ pHlp->pfnPrintf(pHlp, " Linear scanline pitch: 0x%04x\n", pThis->vbe_line_offset); pHlp->pfnPrintf(pHlp, " Linear display start : 0x%04x\n", pThis->vbe_start_addr); pHlp->pfnPrintf(pHlp, " Selected bank: 0x%04x\n", pThis->vbe_regs[VBE_DISPI_INDEX_BANK]); + pHlp->pfnPrintf(pHlp, " DAC: %d-bit\n", pThis->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_8BIT_DAC ? 8 : 6); } } @@ -4867,15 +4884,15 @@ # endif /* VBOX_WITH_HGSMI */ STAM_COUNTER_INC(&pThis->StatUpdateDisp); - if (pThis->fHasDirtyBits && pThis->GCPhysVRAM && pThis->GCPhysVRAM != NIL_RTGCPHYS) - { - PGMHandlerPhysicalReset(PDMDevHlpGetVM(pDevIns), pThis->GCPhysVRAM); - pThis->fHasDirtyBits = false; - } + + if (pThis->GCPhysVRAM != 0 && pThis->GCPhysVRAM != NIL_RTGCPHYS) + vgaR3UpdateDirtyBitsAndResetMonitoring(pDevIns, pThis); + if (pThis->fRemappedVGA) { IOMMmioResetRegion(PDMDevHlpGetVM(pDevIns), pDevIns, pThis->hMmioLegacy); - pThis->fRemappedVGA = false; + STAM_COUNTER_INC(&pThis->StatMapReset); + vgaResetRemapped(pThis); } rc = vgaR3UpdateDisplay(pDevIns, pThis, pThisCC, false /*fUpdateAll*/, false /*fFailOnResize*/, true /*reset_dirty*/, @@ -4888,23 +4905,23 @@ /** * Internal vgaR3PortUpdateDisplayAll worker called under pThis->CritSect. */ -static int vboxR3UpdateDisplayAllInternal(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATECC pThisCC, bool fFailOnResize) +static int vgaR3UpdateDisplayAllInternal(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATECC pThisCC, bool fFailOnResize) { # ifdef VBOX_WITH_VMSVGA if ( !pThis->svga.fEnabled || pThis->svga.fTraces) - { # endif - /* The dirty bits array has been just cleared, reset handlers as well. */ - if (pThis->GCPhysVRAM && pThis->GCPhysVRAM != NIL_RTGCPHYS) - PGMHandlerPhysicalReset(PDMDevHlpGetVM(pDevIns), pThis->GCPhysVRAM); -# ifdef VBOX_WITH_VMSVGA + { + /* Update the dirty bits. */ + if (pThis->GCPhysVRAM != 0 && pThis->GCPhysVRAM != NIL_RTGCPHYS) + vgaR3UpdateDirtyBitsAndResetMonitoring(pDevIns, pThis); } -# endif + if (pThis->fRemappedVGA) { IOMMmioResetRegion(PDMDevHlpGetVM(pDevIns), pDevIns, pThis->hMmioLegacy); - pThis->fRemappedVGA = false; + STAM_COUNTER_INC(&pThis->StatMapReset); + vgaResetRemapped(pThis); } pThis->graphic_mode = -1; /* force full update */ @@ -4933,7 +4950,7 @@ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_SEM_BUSY); AssertRC(rc); - rc = vboxR3UpdateDisplayAllInternal(pDevIns, pThis, pThisCC, fFailOnResize); + rc = vgaR3UpdateDisplayAllInternal(pDevIns, pThis, pThisCC, fFailOnResize); PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); return rc; @@ -5010,7 +5027,7 @@ /* * Get screenshot. This function will fail if a resize is required. - * So there is not need to do a 'vboxR3UpdateDisplayAllInternal' before taking screenshot. + * So there is not need to do a 'vgaR3UpdateDisplayAllInternal' before taking screenshot. */ /* @@ -5584,12 +5601,9 @@ */ int vgaR3RegisterVRAMHandler(PPDMDEVINS pDevIns, PVGASTATE pThis, uint64_t cbFrameBuffer) { - Assert(pThis->GCPhysVRAM); - int rc = PGMHandlerPhysicalRegister(PDMDevHlpGetVM(pDevIns), - pThis->GCPhysVRAM, pThis->GCPhysVRAM + (cbFrameBuffer - 1), - pThis->hLfbAccessHandlerType, pDevIns, pDevIns->pDevInsR0RemoveMe, - pDevIns->pDevInsForRC, "VGA LFB"); - + Assert(pThis->GCPhysVRAM != 0 && pThis->GCPhysVRAM != NIL_RTGCPHYS); + int rc = PDMDevHlpMmio2ControlDirtyPageTracking(pDevIns, pThis->hMmio2VRam, true /*fEnabled*/); + RT_NOREF(cbFrameBuffer); AssertRC(rc); return rc; } @@ -5600,8 +5614,8 @@ */ int vgaR3UnregisterVRAMHandler(PPDMDEVINS pDevIns, PVGASTATE pThis) { - Assert(pThis->GCPhysVRAM); - int rc = PGMHandlerPhysicalDeregister(PDMDevHlpGetVM(pDevIns), pThis->GCPhysVRAM); + Assert(pThis->GCPhysVRAM != 0 && pThis->GCPhysVRAM != NIL_RTGCPHYS); + int rc = PDMDevHlpMmio2ControlDirtyPageTracking(pDevIns, pThis->hMmio2VRam, false /*fEnabled*/); AssertRC(rc); return rc; } @@ -5636,27 +5650,23 @@ if (GCPhysAddress != NIL_RTGCPHYS) { /* - * Mapping the VRAM. + * Make sure the dirty page tracking state is up to date before mapping it. + */ +# ifdef VBOX_WITH_VMSVGA + rc = PDMDevHlpMmio2ControlDirtyPageTracking(pDevIns, pThis->hMmio2VRam, + !pThis->svga.fEnabled ||(pThis->svga.fEnabled && pThis->svga.fVRAMTracking)); +# else + rc = PDMDevHlpMmio2ControlDirtyPageTracking(pDevIns, pThis->hMmio2VRam, true /*fEnabled*/); +# endif + AssertLogRelRC(rc); + + /* + * Map the VRAM. */ rc = PDMDevHlpMmio2Map(pDevIns, pThis->hMmio2VRam, GCPhysAddress); AssertLogRelRC(rc); if (RT_SUCCESS(rc)) { -# ifdef VBOX_WITH_VMSVGA - Assert(!pThis->svga.fEnabled || !pThis->svga.fVRAMTracking); - if ( !pThis->svga.fEnabled - || ( pThis->svga.fEnabled - && pThis->svga.fVRAMTracking - ) - ) -# endif - { - rc = PGMHandlerPhysicalRegister(PDMDevHlpGetVM(pDevIns), GCPhysAddress, GCPhysAddress + (pThis->vram_size - 1), - pThis->hLfbAccessHandlerType, pDevIns, pDevIns->pDevInsR0RemoveMe, - pDevIns->pDevInsForRC, "VGA LFB"); - AssertLogRelRC(rc); - } - pThis->GCPhysVRAM = GCPhysAddress; pThis->vbe_regs[VBE_DISPI_INDEX_FB_BASE_HI] = GCPhysAddress >> 16; @@ -5667,26 +5677,10 @@ { /* * Unmapping of the VRAM in progress (caller will do that). - * Deregister the access handler so PGM doesn't get upset. */ Assert(pThis->GCPhysVRAM); -# ifdef VBOX_WITH_VMSVGA - Assert(!pThis->svga.fEnabled || !pThis->svga.fVRAMTracking); - if ( !pThis->svga.fEnabled - || ( pThis->svga.fEnabled - && pThis->svga.fVRAMTracking - ) - ) -# endif - { - rc = PGMHandlerPhysicalDeregister(PDMDevHlpGetVM(pDevIns), pThis->GCPhysVRAM); - AssertRC(rc); - } -# ifdef VBOX_WITH_VMSVGA - else - rc = VINF_SUCCESS; -# endif pThis->GCPhysVRAM = 0; + rc = VINF_SUCCESS; /* NB: VBE_DISPI_INDEX_FB_BASE_HI is left unchanged here. */ } return rc; @@ -6130,19 +6124,22 @@ /* * Reset the LFB mapping. */ - pThis->fLFBUpdated = false; - if ( ( pDevIns->fRCEnabled - || pDevIns->fR0Enabled) - && pThis->GCPhysVRAM - && pThis->GCPhysVRAM != NIL_RTGCPHYS) - { - int rc = PGMHandlerPhysicalReset(PDMDevHlpGetVM(pDevIns), pThis->GCPhysVRAM); + if ( ( pDevIns->fRCEnabled + || pDevIns->fR0Enabled) + && pThis->GCPhysVRAM != 0 + && pThis->GCPhysVRAM != NIL_RTGCPHYS) + { + /** @todo r=bird: This used to be a PDMDevHlpPGMHandlerPhysicalReset call. + * Not quite sure if it was/is needed. Besides, where do we reset the + * dirty bitmap (bmDirtyBitmap)? */ + int rc = PDMDevHlpMmio2ResetDirtyBitmap(pDevIns, pThis->hMmio2VRam); AssertRC(rc); } if (pThis->fRemappedVGA) { IOMMmioResetRegion(PDMDevHlpGetVM(pDevIns), pDevIns, pThis->hMmioLegacy); - pThis->fRemappedVGA = false; + STAM_COUNTER_INC(&pThis->StatMapReset); + vgaResetRemapped(pThis); } /* @@ -6681,8 +6678,8 @@ * Allocate VRAM and create a PCI region for it. */ rc = PDMDevHlpPCIIORegionCreateMmio2Ex(pDevIns, pThis->pciRegions.iVRAM, pThis->vram_size, - PCI_ADDRESS_SPACE_MEM_PREFETCH, 0 /*fFlags*/, vgaR3PciIORegionVRamMapUnmap, - "VRam", (void **)&pThisCC->pbVRam, &pThis->hMmio2VRam); + PCI_ADDRESS_SPACE_MEM_PREFETCH, PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES, + vgaR3PciIORegionVRamMapUnmap, "VRam", (void **)&pThisCC->pbVRam, &pThis->hMmio2VRam); AssertLogRelRCReturn(rc, PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("Failed to allocate %u bytes of VRAM"), pThis->vram_size)); # ifndef VGA_WITH_PARTIAL_RING0_MAPPING @@ -6690,16 +6687,6 @@ # endif /* - * Register access handler types for tracking dirty VRAM pages. - */ - rc = PGMR3HandlerPhysicalTypeRegister(pVM, PGMPHYSHANDLERKIND_WRITE, - vgaLFBAccessHandler, - g_DeviceVga.pszR0Mod, "vgaLFBAccessHandler", "vgaLbfAccessPfHandler", - g_DeviceVga.pszRCMod, "vgaLFBAccessHandler", "vgaLbfAccessPfHandler", - "VGA LFB", &pThis->hLfbAccessHandlerType); - AssertRCReturn(rc, rc); - - /* * Register I/O ports. */ # define REG_PORT(a_uPort, a_cPorts, a_pfnWrite, a_pfnRead, a_szDesc, a_phIoPort) do { \ @@ -6949,6 +6936,8 @@ * pixelWidth; if (reqSize >= pThis->vram_size) continue; + if (!reqSize) + continue; if ( mode_info_list[i].info.XResolution > maxBiosXRes || mode_info_list[i].info.YResolution > maxBiosYRes) continue; @@ -7012,6 +7001,11 @@ AssertMsgFailed(("Configuration error: Invalid mode data '%s' for '%s'! cBits=%d\n", pszExtraData, szExtraDataKey, cBits)); return VERR_VGA_INVALID_CUSTOM_MODE; } + if (!cx || !cy) + { + AssertMsgFailed(("Configuration error: Invalid mode data '%s' for '%s'! cx=%u, cy=%u\n", pszExtraData, szExtraDataKey, cx, cy)); + return VERR_VGA_INVALID_CUSTOM_MODE; + } cbPitch = calc_line_pitch(cBits, cx); if (cy * cbPitch >= pThis->vram_size) { @@ -7211,10 +7205,10 @@ */ pThisCC->cbLogo = LogoHdr.cbLogo; if (g_cbVgaDefBiosLogo) - pThisCC->cbLogo = g_cbVgaDefBiosLogo; + pThisCC->cbLogo = RT_MAX(pThisCC->cbLogo, g_cbVgaDefBiosLogo); # ifndef VBOX_OSE if (g_cbVgaDefBiosLogoNY) - pThisCC->cbLogo = g_cbVgaDefBiosLogoNY; + pThisCC->cbLogo = RT_MAX(pThisCC->cbLogo, g_cbVgaDefBiosLogoNY); # endif pThisCC->cbLogo += sizeof(LogoHdr); @@ -7304,6 +7298,7 @@ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRZMemoryWrite, STAMTYPE_PROFILE, "RZ/MMIO-Write", STAMUNIT_TICKS_PER_CALL, "Profiling of the VGAGCMemoryWrite() body."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatR3MemoryWrite, STAMTYPE_PROFILE, "R3/MMIO-Write", STAMUNIT_TICKS_PER_CALL, "Profiling of the VGAGCMemoryWrite() body."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatMapPage, STAMTYPE_COUNTER, "MapPageCalls", STAMUNIT_OCCURENCES, "Calls to IOMMmioMapMmio2Page."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatMapReset, STAMTYPE_COUNTER, "MapPageReset", STAMUNIT_OCCURENCES, "Calls to IOMMmioResetRegion."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUpdateDisp, STAMTYPE_COUNTER, "UpdateDisplay", STAMUNIT_OCCURENCES, "Calls to vgaR3PortUpdateDisplay()."); # endif # ifdef VBOX_WITH_HGSMI diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA.h 2020-10-16 16:35:22.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA.h 2022-09-01 13:26:06.000000000 +0000 @@ -349,12 +349,12 @@ /** Current refresh timer interval. */ uint32_t cMilliesRefreshInterval; /** Bitmap tracking dirty pages. */ - uint32_t au32DirtyBitmap[VGA_VRAM_MAX / PAGE_SIZE / 32]; + uint64_t bmDirtyBitmap[VGA_VRAM_MAX / PAGE_SIZE / 64]; + /** Bitmap tracking remapped pages (only needs 16 bits). */ + uint64_t bmPageMapBitmap; /** Flag indicating that there are dirty bits. This is used to optimize the handler resetting. */ bool fHasDirtyBits; - /** LFB was updated flag. */ - bool fLFBUpdated; /** Flag indicating that the VGA memory in the 0xa0000-0xbffff region has been remapped to allow direct access. */ bool fRemappedVGA; /** Whether to render the guest VRAM to the framebuffer memory. False only for some LFB modes. */ @@ -368,9 +368,8 @@ bool fVMSVGAEnabled; bool fVMSVGAPciId; bool fVMSVGAPciBarLayout; - bool Padding4[3]; #else - bool Padding4[3+3]; + bool afPadding4[3]; #endif struct { @@ -382,9 +381,6 @@ #endif } pciRegions; - /** Physical access type for the linear frame buffer dirty page tracking. */ - PGMPHYSHANDLERTYPE hLfbAccessHandlerType; - /** The physical address the VRAM was assigned. */ RTGCPHYS GCPhysVRAM; /** The critical section protect the instance data. */ @@ -513,7 +509,8 @@ STAMPROFILE StatR3MemoryRead; STAMPROFILE StatRZMemoryWrite; STAMPROFILE StatR3MemoryWrite; - STAMCOUNTER StatMapPage; /**< Counts IOMMMIOMapMMIO2Page calls. */ + STAMCOUNTER StatMapPage; /**< Counts IOMMmioMapMmio2Page calls. */ + STAMCOUNTER StatMapReset; /**< Counts IOMMmioResetRegion calls. */ STAMCOUNTER StatUpdateDisp; /**< Counts vgaPortUpdateDisplay calls. */ #ifdef VBOX_WITH_HGSMI STAMCOUNTER StatHgsmiMdaCgaAccesses; @@ -528,6 +525,7 @@ AssertCompileMemberAlignment(VGASTATE, font_offsets, 8); AssertCompileMemberAlignment(VGASTATE, last_ch_attr, 8); AssertCompileMemberAlignment(VGASTATE, u32Marker, 8); +AssertCompile(sizeof(uint64_t)/*bmPageMapBitmap*/ >= (_64K / PAGE_SIZE / 8)); #endif diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp 2020-10-16 16:35:22.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp 2022-09-01 13:26:06.000000000 +0000 @@ -121,7 +121,7 @@ vmsvga3dSurfaceDestroy(pThisCC, sid); RT_ZERO(*pSurface); - pSurface->id = sid; + pSurface->id = SVGA3D_INVALID_ID; /* Keep this value until the surface init completes */ #ifdef VMSVGA3D_OPENGL pSurface->idWeakContextAssociation = SVGA3D_INVALID_ID; pSurface->oglId.buffer = OPENGL_INVALID_ID; @@ -322,6 +322,8 @@ cbMemRemaining -= cbSurface; } + + pSurface->id = sid; return VINF_SUCCESS; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-hlp.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-hlp.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-hlp.cpp 2020-10-16 16:35:22.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-hlp.cpp 2022-09-01 13:26:06.000000000 +0000 @@ -39,6 +39,12 @@ { /** Version token. */ SVGA3dShaderVersion version; + + SVGA3dShaderOpCodeType currentOpcode; + union + { + SVGA3DOpDclArgs *pDclArgs; + } u; } VMSVGA3DSHADERPARSECONTEXT; /** Callback which parses a parameter token. @@ -160,6 +166,15 @@ Log3(("Dest: type %d, r0 %d, shfScale %d, dstMod %d, mask 0x%x, r1 %d, relAddr %d, num %d\n", regType, dest.s.reserved0, dest.s.shfScale, dest.s.dstMod, dest.s.mask, dest.s.reserved1, dest.s.relAddr, dest.s.num)); + if (pCtx->currentOpcode == SVGA3DOP_DCL && regType == SVGA3DREG_SAMPLER) + { + if (pCtx->u.pDclArgs->s2.s1.type == SVGA3DSAMP_UNKNOWN) + { + Log3(("Replacing SVGA3DSAMP_UNKNOWN with SVGA3DSAMP_2D\n")); + pCtx->u.pDclArgs->s2.s1.type = SVGA3DSAMP_2D; + } + } + return vmsvga3dShaderParseRegOffset(pCtx, false, regType, dest.s.num); } @@ -300,9 +315,9 @@ /* Parse the shader code * https://docs.microsoft.com/en-us/windows-hardware/drivers/display/shader-code-format */ -int vmsvga3dShaderParse(SVGA3dShaderType type, uint32_t cbShaderData, uint32_t const* pShaderData) +int vmsvga3dShaderParse(SVGA3dShaderType type, uint32_t cbShaderData, uint32_t* pShaderData) { - uint32_t const* paTokensStart = (uint32_t*)pShaderData; + uint32_t *paTokensStart = (uint32_t*)pShaderData; uint32_t const cTokens = cbShaderData / sizeof(uint32_t); ASSERT_GUEST_RETURN(cTokens * sizeof(uint32_t) == cbShaderData, VERR_INVALID_PARAMETER); @@ -341,8 +356,8 @@ ASSERT_GUEST_RETURN(ctx.version.s.major >= 2 && ctx.version.s.major <= 4, VERR_PARSE_ERROR); /* Scan the tokens. Immediately return an error code on any unexpected data. */ - uint32_t const* paTokensEnd = &paTokensStart[cTokens]; - uint32_t const* pToken = &paTokensStart[1]; /* Skip the version token. */ + uint32_t *paTokensEnd = &paTokensStart[cTokens]; + uint32_t *pToken = &paTokensStart[1]; /* Skip the version token. */ bool bEndTokenFound = false; while (pToken < paTokensEnd) { @@ -366,11 +381,16 @@ break; } + ctx.currentOpcode = (SVGA3dShaderOpCodeType)token.s1.op; + /* If this instrution is in the aOps table. */ if (token.s1.op <= SVGA3DOP_BREAKP) { VMSVGA3DSHADERPARSEOP const* pOp = &aOps[token.s1.op]; + if (ctx.currentOpcode == SVGA3DOP_DCL) + ctx.u.pDclArgs = (SVGA3DOpDclArgs *)&pToken[1]; + /* cInstLen can be greater than pOp->Length. * W10 guest sends a vertex shader MUL instruction with length 4. * So figure out the actual number of valid parameters. diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h 2020-10-16 16:35:22.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h 2022-09-01 13:26:06.000000000 +0000 @@ -1423,7 +1423,7 @@ uint32_t iMipmap); #endif -int vmsvga3dShaderParse(SVGA3dShaderType type, uint32_t cbShaderData, uint32_t const *pShaderData); +int vmsvga3dShaderParse(SVGA3dShaderType type, uint32_t cbShaderData, uint32_t *pShaderData); void vmsvga3dShaderLogRel(char const *pszMsg, SVGA3dShaderType type, uint32_t cbShaderData, uint32_t const *pShaderData); #endif /* !VBOX_INCLUDED_SRC_Graphics_DevVGA_SVGA3d_internal_h */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp 2020-10-16 16:35:22.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp 2022-09-01 13:26:06.000000000 +0000 @@ -1516,8 +1516,13 @@ AssertRCReturn(rc, rc); } - Assert(pSurfaceSrc->enmD3DResType != VMSVGA3D_D3DRESTYPE_VOLUME_TEXTURE); /// @todo - Assert(pSurfaceDest->enmD3DResType != VMSVGA3D_D3DRESTYPE_VOLUME_TEXTURE); /// @todo + AssertReturn(pSurfaceSrc->enmD3DResType != VMSVGA3D_D3DRESTYPE_VOLUME_TEXTURE, VERR_NOT_IMPLEMENTED); /// @todo + AssertReturn(pSurfaceDest->enmD3DResType != VMSVGA3D_D3DRESTYPE_VOLUME_TEXTURE, VERR_NOT_IMPLEMENTED); /// @todo + + /* Surface copy only makes sense between surfaces with identical layout. */ + AssertReturn(pSurfaceSrc->cbBlock == pSurfaceDest->cbBlock, VERR_INVALID_PARAMETER); + AssertReturn(pSurfaceSrc->cxBlock == pSurfaceDest->cxBlock, VERR_INVALID_PARAMETER); + AssertReturn(pSurfaceSrc->cyBlock == pSurfaceDest->cyBlock, VERR_INVALID_PARAMETER); if ( pSurfaceSrc->u.pSurface && pSurfaceDest->u.pSurface) @@ -1728,9 +1733,6 @@ sidDest, dest.face, dest.mipmap, pBox[i].x, pBox[i].y)); Assert(!clipBox.srcz && !clipBox.z); - Assert(pSurfaceSrc->cbBlock == pSurfaceDest->cbBlock); - Assert(pSurfaceSrc->cxBlock == pSurfaceDest->cxBlock); - Assert(pSurfaceSrc->cyBlock == pSurfaceDest->cyBlock); uint32_t cBlocksX = (clipBox.w + pSurfaceSrc->cxBlock - 1) / pSurfaceSrc->cxBlock; uint32_t cBlocksY = (clipBox.h + pSurfaceSrc->cyBlock - 1) / pSurfaceSrc->cyBlock; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp 2020-10-16 16:35:22.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp 2022-09-01 13:26:05.000000000 +0000 @@ -876,8 +876,8 @@ /* Stop walking the array once we go thru all the monitors. */ if (i >= cScreenCount) break; - if ( pScreens[i].xOrigin == -1 - || pScreens[i].yOrigin == -1) + if ( pPosition[i].x == -1 + || pPosition[i].y == -1) continue; if ( pScreens[i].xOrigin == pPosition[i].x && pScreens[i].yOrigin == pPosition[i].y) @@ -1570,7 +1570,7 @@ else if (uFifoPitchLock) pThis->svga.cbScanline = uFifoPitchLock; else - pThis->svga.cbScanline = pThis->svga.uWidth * (RT_ALIGN(pThis->svga.uBpp, 8) / 8); + pThis->svga.cbScanline = (uint32_t)pThis->svga.uWidth * (RT_ALIGN(pThis->svga.uBpp, 8) / 8); if ((uFifoMin / sizeof(uint32_t)) <= SVGA_FIFO_PITCHLOCK) pThis->svga.u32PitchLock = pThis->svga.cbScanline; @@ -1707,7 +1707,7 @@ case SVGA_REG_WIDTH: STAM_REL_COUNTER_INC(&pThis->svga.StatRegWidthWr); - if (pThis->svga.uWidth != u32) + if (u32 <= pThis->svga.u32MaxWidth && u32 != pThis->svga.uWidth) { #if defined(IN_RING3) || defined(IN_RING0) pThis->svga.uWidth = u32; @@ -1723,7 +1723,7 @@ case SVGA_REG_HEIGHT: STAM_REL_COUNTER_INC(&pThis->svga.StatRegHeightWr); - if (pThis->svga.uHeight != u32) + if (u32 <= pThis->svga.u32MaxHeight && u32 != pThis->svga.uHeight) { pThis->svga.uHeight = u32; if (pThis->svga.fEnabled) @@ -1739,7 +1739,7 @@ case SVGA_REG_BITS_PER_PIXEL: /* Current bpp in the guest */ STAM_REL_COUNTER_INC(&pThis->svga.StatRegBitsPerPixelWr); - if (pThis->svga.uBpp != u32) + if (u32 <= 32 && pThis->svga.uBpp != u32) { #if defined(IN_RING3) || defined(IN_RING0) pThis->svga.uBpp = u32; @@ -5854,6 +5854,14 @@ ASMAtomicOrU32(&pThis->svga.u32ActionFlags, VMSVGA_ACTION_CHANGEMODE); + /* VMSVGA is working via VBVA interface, therefore it needs to be + * enabled on saved state restore. See @bugref{10071#c7}. */ + if (pThis->svga.fEnabled) + { + for (uint32_t idScreen = 0; idScreen < pThis->cMonitors; ++idScreen) + pThisCC->pDrv->pfnVBVAEnable(pThisCC->pDrv, idScreen, NULL /*pHostFlags*/); + } + /* Set the active cursor. */ if (pSVGAState->Cursor.fActive) { @@ -6636,6 +6644,10 @@ /* Initialize FIFO 3D capabilities. */ vmsvgaR3InitFifo3DCaps(pThisCC); } + else { + LogRel(("VMSVGA3d: 3D support disabled! (vmsvga3dPowerOn -> %Rrc)\n", rc)); + pThis->svga.f3DEnabled = false; + } } # else /* !VBOX_WITH_VMSVGA3D */ RT_NOREF(pDevIns); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA_VDMA.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA_VDMA.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Graphics/DevVGA_VDMA.cpp 2020-10-16 16:35:22.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Graphics/DevVGA_VDMA.cpp 2022-09-01 13:26:06.000000000 +0000 @@ -68,7 +68,7 @@ *********************************************************************************************************************************/ struct VBOXVDMATHREAD; -typedef DECLCALLBACKPTR(void, PFNVBOXVDMATHREAD_CHANGED)(struct VBOXVDMATHREAD *pThread, int rc, void *pvThreadContext, void *pvChangeContext); +typedef DECLCALLBACKPTR(void, PFNVBOXVDMATHREAD_CHANGED,(struct VBOXVDMATHREAD *pThread, int rc, void *pvThreadContext, void *pvChangeContext)); typedef struct VBOXVDMATHREAD { diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/Devices/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Makefile.kmk 2020-10-16 16:35:40.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Makefile.kmk 2022-09-01 13:26:24.000000000 +0000 @@ -593,65 +593,62 @@ VBoxDD_SOURCES += \ Audio/DevIchAc97.cpp \ Audio/DevSB16.cpp \ - Audio/DevHDA.cpp \ - Audio/DevHDACommon.cpp \ - Audio/HDACodec.cpp \ - Audio/HDAStream.cpp \ - Audio/HDAStreamChannel.cpp \ - Audio/HDAStreamMap.cpp \ - Audio/HDAStreamPeriod.cpp \ + Audio/DevHda.cpp \ + Audio/DevHdaCodec.cpp \ + Audio/DevHdaStream.cpp \ + Audio/AudioHlp.cpp \ Audio/AudioMixBuffer.cpp \ Audio/AudioMixer.cpp \ Audio/DrvAudio.cpp \ - Audio/DrvAudioCommon.cpp \ - Audio/DrvHostNullAudio.cpp + Audio/DrvHostAudioNull.cpp ifdef VBOX_WITH_AUDIO_DEBUG VBoxDD_DEFS += VBOX_WITH_AUDIO_DEBUG VBoxDD_SOURCES += \ - Audio/DrvHostDebugAudio.cpp + Audio/DrvHostAudioDebug.cpp endif ifdef VBOX_WITH_AUDIO_VALIDATIONKIT VBoxDD_DEFS += VBOX_WITH_AUDIO_VALIDATIONKIT VBoxDD_SOURCES += \ - Audio/DrvHostValidationKit.cpp + Audio/DrvHostAudioValidationKit.cpp endif ifeq ($(KBUILD_TARGET),darwin) VBoxDD_SOURCES += \ - Audio/DrvHostCoreAudio.cpp \ - Audio/DrvHostCoreAudio-auth.mm + Audio/DrvHostAudioCoreAudio.cpp \ + Audio/DrvHostAudioCoreAudioAuth.mm endif ifeq ($(KBUILD_TARGET),win) VBoxDD_SOURCES += \ - Audio/DrvHostDSound.cpp + Audio/DrvHostAudioDSound.cpp \ + Audio/DrvHostAudioWasApi.cpp ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT - VBoxDD_SOURCES += \ - Audio/VBoxMMNotificationClient.cpp VBoxDD_DEFS += VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + VBoxDD_SOURCES += \ + Audio/DrvHostAudioDSoundMMNotifClient.cpp endif endif ifdef VBOX_WITH_AUDIO_OSS VBoxDD_DEFS += VBOX_WITH_AUDIO_OSS VBoxDD_SOURCES += \ - Audio/DrvHostOSSAudio.cpp + Audio/DrvHostAudioOss.cpp endif ifdef VBOX_WITH_AUDIO_ALSA VBoxDD_DEFS += VBOX_WITH_AUDIO_ALSA VBoxDD_SOURCES += \ - Audio/DrvHostALSAAudio.cpp \ - Audio/alsa_stubs.c + Audio/DrvHostAudioAlsa.cpp \ + Audio/DrvHostAudioAlsaStubs.cpp endif ifdef VBOX_WITH_AUDIO_PULSE VBoxDD_DEFS += VBOX_WITH_AUDIO_PULSE VBoxDD_SOURCES += \ - Audio/DrvHostPulseAudio.cpp \ - Audio/pulse_stubs.c + Audio/DrvHostAudioPulseAudio.cpp \ + Audio/DrvHostAudioPulseAudioStubs.cpp endif # --- WARNING! SLIRP MESS AHEAD! ;-) --- @@ -936,7 +933,7 @@ PC/DevPit-i8254.cpp \ PC/DevPIC.cpp \ PC/DevRTC.cpp \ - PC/DevDMA.cpp \ + PC/DevDMA.cpp \ PC/DevHPET.cpp \ Storage/DevATA.cpp \ Network/DevPCNet.cpp \ @@ -949,8 +946,7 @@ VBoxDDRC_DEFS += $(VBOX_AUDIO_DEFS) VBoxDDRC_SOURCES += \ - Audio/DevHDA.cpp \ - Audio/DevHDACommon.cpp \ + Audio/DevHda.cpp \ Audio/DevIchAc97.cpp VBoxDDRC_DEFS += \ @@ -1136,6 +1132,11 @@ Network/DrvIntNet.cpp \ Network/DrvDedicatedNic.cpp + ifdef VBOX_WITH_DTRACE_R0 + VBoxDDR0_USES += dtrace + VBoxDDR0_SOURCES += build/VBoxDD.d + endif + VBoxDDR0_SOURCES.win += Parallel/DrvHostParallel.cpp VBoxDDR0_DEFS += \ @@ -1149,9 +1150,9 @@ VBoxDDR0_DEFS += $(VBOX_AUDIO_DEFS) VBoxDDR0_SOURCES += \ - Audio/DevHDA.cpp \ - Audio/DevHDACommon.cpp \ - Audio/DevIchAc97.cpp + Audio/DevHda.cpp \ + Audio/DevHdaStream.cpp \ + Audio/DevIchAc97.cpp ifdef VBOX_WITH_E1000 VBoxDDR0_DEFS += VBOX_WITH_E1000 @@ -1257,9 +1258,10 @@ if1of ($(VBOX_LDR_FMT), pe lx) VBoxDDR0_LIBS = \ - $(PATH_STAGE_LIB)/VMMR0Imp$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/SUPR0$(VBOX_SUFF_LIB) + $(PATH_STAGE_LIB)/VMMR0Imp$(VBOX_SUFF_LIB) endif + VBoxDDR0_LIBS += \ + $(VBOX_LIB_SUPR0) $(call VBOX_SET_VER_INFO_R0,VBoxDDR0,VirtualBox VMM Devices and Drivers$(COMMA) ring-0) # (last!) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DevE1000.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DevE1000.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DevE1000.cpp 2020-10-16 16:35:40.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DevE1000.cpp 2022-09-01 13:26:24.000000000 +0000 @@ -773,6 +773,7 @@ #define E1K_DTYP_LEGACY -1 #define E1K_DTYP_CONTEXT 0 #define E1K_DTYP_DATA 1 +#define E1K_DTYP_INVALID 2 struct E1kTDLegacy { @@ -1065,6 +1066,8 @@ bool fIntRaised; /** EMT: false if the cable is disconnected by the GUI. */ bool fCableConnected; + /** true if the device is attached to a driver. */ + bool fIsAttached; /** EMT: Compute Ethernet CRC for RX packets. */ bool fEthernetCRC; /** All: throttle interrupts. */ @@ -1122,6 +1125,8 @@ #ifdef E1K_WITH_TXD_CACHE /** TX: Fetched TX descriptors. */ E1KTXDESC aTxDescriptors[E1K_TXD_CACHE_SIZE]; + /** TX: Validity of TX descriptors. Set by e1kLocateTxPacket, used by e1kXmitPacket. */ + bool afTxDValid[E1K_TXD_CACHE_SIZE]; /** TX: Actual number of fetched TX descriptors. */ uint8_t nTxDFetched; /** TX: Index in cache of TX descriptor being processed. */ @@ -1379,6 +1384,7 @@ static FNE1KREGWRITE e1kRegWriteMDIC; static FNE1KREGREAD e1kRegReadICR; static FNE1KREGWRITE e1kRegWriteICR; +static FNE1KREGREAD e1kRegReadICS; static FNE1KREGWRITE e1kRegWriteICS; static FNE1KREGWRITE e1kRegWriteIMS; static FNE1KREGWRITE e1kRegWriteIMC; @@ -1434,7 +1440,7 @@ { 0x00038, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteDefault , "VET" , "VLAN EtherType" }, { 0x000c0, 0x00004, 0x0001F6DF, 0x0001F6DF, e1kRegReadICR , e1kRegWriteICR , "ICR" , "Interrupt Cause Read" }, { 0x000c4, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteDefault , "ITR" , "Interrupt Throttling" }, - { 0x000c8, 0x00004, 0x00000000, 0xFFFFFFFF, e1kRegReadUnimplemented, e1kRegWriteICS , "ICS" , "Interrupt Cause Set" }, + { 0x000c8, 0x00004, 0x0001F6DF, 0xFFFFFFFF, e1kRegReadICS , e1kRegWriteICS , "ICS" , "Interrupt Cause Set" }, { 0x000d0, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteIMS , "IMS" , "Interrupt Mask Set/Read" }, { 0x000d8, 0x00004, 0x00000000, 0xFFFFFFFF, e1kRegReadUnimplemented, e1kRegWriteIMC , "IMC" , "Interrupt Mask Clear" }, { 0x00100, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteRCTL , "RCTL" , "Receive Control" }, @@ -1455,9 +1461,9 @@ { 0x02430, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadUnimplemented, e1kRegWriteUnimplemented, "RDFPC" , "Receive Data FIFO Packet Count" }, { 0x02800, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteDefault , "RDBAL" , "Receive Descriptor Base Low" }, { 0x02804, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteDefault , "RDBAH" , "Receive Descriptor Base High" }, - { 0x02808, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteDefault , "RDLEN" , "Receive Descriptor Length" }, - { 0x02810, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteDefault , "RDH" , "Receive Descriptor Head" }, - { 0x02818, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteRDT , "RDT" , "Receive Descriptor Tail" }, + { 0x02808, 0x00004, 0x000FFF80, 0x000FFF80, e1kRegReadDefault , e1kRegWriteDefault , "RDLEN" , "Receive Descriptor Length" }, + { 0x02810, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteDefault , "RDH" , "Receive Descriptor Head" }, + { 0x02818, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteRDT , "RDT" , "Receive Descriptor Tail" }, { 0x02820, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteRDTR , "RDTR" , "Receive Delay Timer" }, { 0x02828, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadUnimplemented, e1kRegWriteUnimplemented, "RXDCTL" , "Receive Descriptor Control" }, { 0x0282c, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteDefault , "RADV" , "Receive Interrupt Absolute Delay Timer" }, @@ -1470,7 +1476,7 @@ { 0x03430, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadUnimplemented, e1kRegWriteUnimplemented, "TDFPC" , "Transmit Data FIFO Packet Count" }, { 0x03800, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteDefault , "TDBAL" , "Transmit Descriptor Base Low" }, { 0x03804, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteDefault , "TDBAH" , "Transmit Descriptor Base High" }, - { 0x03808, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, e1kRegReadDefault , e1kRegWriteDefault , "TDLEN" , "Transmit Descriptor Length" }, + { 0x03808, 0x00004, 0x000FFF80, 0x000FFF80, e1kRegReadDefault , e1kRegWriteDefault , "TDLEN" , "Transmit Descriptor Length" }, { 0x03810, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteDefault , "TDH" , "Transmit Descriptor Head" }, { 0x03818, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteTDT , "TDT" , "Transmit Descriptor Tail" }, { 0x03820, 0x00004, 0x0000FFFF, 0x0000FFFF, e1kRegReadDefault , e1kRegWriteDefault , "TIDV" , "Transmit Interrupt Delay Value" }, @@ -1664,9 +1670,99 @@ #else /* E1K_WITH_TX_CS */ # define e1kCsTxEnter(ps, rc) PDMDevHlpCritSectEnter(pDevIns, &ps->csTx, rc) # define e1kCsTxLeave(ps) PDMDevHlpCritSectLeave(pDevIns, &ps->csTx) +# define e1kCsTxIsOwner(ps) PDMDevHlpCritSectIsOwner(pDevIns, &ps->csTx) #endif /* E1K_WITH_TX_CS */ +#ifdef E1K_WITH_TXD_CACHE +/* + * Transmit Descriptor Register Context + */ +struct E1kTxDContext +{ + uint32_t tdlen; + uint32_t tdh; + uint32_t tdt; + uint8_t nextPacket; +}; +typedef struct E1kTxDContext E1KTXDC, *PE1KTXDC; + +DECLINLINE(bool) e1kUpdateTxDContext(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KTXDC pContext) +{ + Assert(e1kCsTxIsOwner(pThis)); + if (!e1kCsTxIsOwner(pThis)) + { + memset(pContext, 0, sizeof(E1KTXDC)); + return false; + } + pContext->tdlen = TDLEN; + pContext->tdh = TDH; + pContext->tdt = TDT; + uint32_t cTxRingSize = pContext->tdlen / sizeof(E1KTXDESC); +#ifdef DEBUG + if (pContext->tdh >= cTxRingSize) + { + Log(("%s e1kUpdateTxDContext: will return false because TDH too big (%u >= %u)\n", + pThis->szPrf, pContext->tdh, cTxRingSize)); + return VINF_SUCCESS; + } + if (pContext->tdt >= cTxRingSize) + { + Log(("%s e1kUpdateTxDContext: will return false because TDT too big (%u >= %u)\n", + pThis->szPrf, pContext->tdt, cTxRingSize)); + return VINF_SUCCESS; + } +#endif /* DEBUG */ + return pContext->tdh < cTxRingSize && pContext->tdt < cTxRingSize; +} +#endif /* E1K_WITH_TXD_CACHE */ +#ifdef E1K_WITH_RXD_CACHE +/* + * Receive Descriptor Register Context + */ +struct E1kRxDContext +{ + uint32_t rdlen; + uint32_t rdh; + uint32_t rdt; +}; +typedef struct E1kRxDContext E1KRXDC, *PE1KRXDC; + +DECLINLINE(bool) e1kUpdateRxDContext(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KRXDC pContext, const char *pcszCallee) +{ + Assert(e1kCsRxIsOwner(pThis)); + if (!e1kCsRxIsOwner(pThis)) + return false; + pContext->rdlen = RDLEN; + pContext->rdh = RDH; + pContext->rdt = RDT; + uint32_t cRxRingSize = pContext->rdlen / sizeof(E1KRXDESC); + /* + * Note that the checks for RDT are a bit different. Some guests, OS/2 for + * example, intend to use all descriptors in RX ring, so they point RDT + * right beyond the last descriptor in the ring. While this is not + * acceptable for other registers, it works out fine for RDT. + */ +#ifdef DEBUG + if (pContext->rdh >= cRxRingSize) + { + Log(("%s e1kUpdateRxDContext: called from %s, will return false because RDH too big (%u >= %u)\n", + pThis->szPrf, pcszCallee, pContext->rdh, cRxRingSize)); + return VINF_SUCCESS; + } + if (pContext->rdt > cRxRingSize) + { + Log(("%s e1kUpdateRxDContext: called from %s, will return false because RDT too big (%u > %u)\n", + pThis->szPrf, pcszCallee, pContext->rdt, cRxRingSize)); + return VINF_SUCCESS; + } +#else /* !DEBUG */ + RT_NOREF(pcszCallee); +#endif /* !DEBUG */ + return pContext->rdh < cRxRingSize && pContext->rdt <= cRxRingSize; // && (RCTL & RCTL_EN); +} +#endif /* E1K_WITH_RXD_CACHE */ + /** * Wakeup the RX thread. */ @@ -1858,17 +1954,17 @@ * Return the number of RX descriptor that belong to the hardware. * * @returns the number of available descriptors in RX ring. - * @param pThis The device state structure. + * @param pRxdc The receive descriptor register context. * @thread ??? */ -DECLINLINE(uint32_t) e1kGetRxLen(PE1KSTATE pThis) +DECLINLINE(uint32_t) e1kGetRxLen(PE1KRXDC pRxdc) { /** * Make sure RDT won't change during computation. EMT may modify RDT at * any moment. */ - uint32_t rdt = RDT; - return (RDH > rdt ? RDLEN/sizeof(E1KRXDESC) : 0) + rdt - RDH; + uint32_t rdt = pRxdc->rdt; + return (pRxdc->rdh > rdt ? pRxdc->rdlen/sizeof(E1KRXDESC) : 0) + rdt - pRxdc->rdh; } DECLINLINE(unsigned) e1kRxDInCache(PE1KSTATE pThis) @@ -1894,16 +1990,19 @@ * @param pThis The device state structure. * @thread EMT, RX */ -DECLINLINE(unsigned) e1kRxDPrefetch(PPDMDEVINS pDevIns, PE1KSTATE pThis) +DECLINLINE(unsigned) e1kRxDPrefetch(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KRXDC pRxdc) { + E1kLog3(("%s e1kRxDPrefetch: RDH=%x RDT=%x RDLEN=%x " + "iRxDCurrent=%x nRxDFetched=%x\n", + pThis->szPrf, pRxdc->rdh, pRxdc->rdt, pRxdc->rdlen, pThis->iRxDCurrent, pThis->nRxDFetched)); /* We've already loaded pThis->nRxDFetched descriptors past RDH. */ - unsigned nDescsAvailable = e1kGetRxLen(pThis) - e1kRxDInCache(pThis); + unsigned nDescsAvailable = e1kGetRxLen(pRxdc) - e1kRxDInCache(pThis); unsigned nDescsToFetch = RT_MIN(nDescsAvailable, E1K_RXD_CACHE_SIZE - pThis->nRxDFetched); - unsigned nDescsTotal = RDLEN / sizeof(E1KRXDESC); + unsigned nDescsTotal = pRxdc->rdlen / sizeof(E1KRXDESC); Assert(nDescsTotal != 0); if (nDescsTotal == 0) return 0; - unsigned nFirstNotLoaded = (RDH + e1kRxDInCache(pThis)) % nDescsTotal; + unsigned nFirstNotLoaded = (pRxdc->rdh + e1kRxDInCache(pThis)) % nDescsTotal; unsigned nDescsInSingleRead = RT_MIN(nDescsToFetch, nDescsTotal - nFirstNotLoaded); E1kLog3(("%s e1kRxDPrefetch: nDescsAvailable=%u nDescsToFetch=%u " "nDescsTotal=%u nFirstNotLoaded=0x%x nDescsInSingleRead=%u\n", @@ -1924,8 +2023,8 @@ // } E1kLog3(("%s Fetched %u RX descriptors at %08x%08x(0x%x), RDLEN=%08x, RDH=%08x, RDT=%08x\n", pThis->szPrf, nDescsInSingleRead, - RDBAH, RDBAL + RDH * sizeof(E1KRXDESC), - nFirstNotLoaded, RDLEN, RDH, RDT)); + RDBAH, RDBAL + pRxdc->rdh * sizeof(E1KRXDESC), + nFirstNotLoaded, pRxdc->rdlen, pRxdc->rdh, pRxdc->rdt)); if (nDescsToFetch > nDescsInSingleRead) { PDMDevHlpPhysRead(pDevIns, @@ -2164,12 +2263,13 @@ * @param pDevIns The device instance. * @param pThis The device state structure. */ -DECLINLINE(void) e1kAdvanceRDH(PPDMDEVINS pDevIns, PE1KSTATE pThis) +DECLINLINE(void) e1kAdvanceRDH(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KRXDC pRxdc) { Assert(e1kCsRxIsOwner(pThis)); //e1kCsEnter(pThis, RT_SRC_POS); - if (++RDH * sizeof(E1KRXDESC) >= RDLEN) - RDH = 0; + if (++pRxdc->rdh * sizeof(E1KRXDESC) >= pRxdc->rdlen) + pRxdc->rdh = 0; + RDH = pRxdc->rdh; /* Sync the actual register and RXDC */ #ifdef E1K_WITH_RXD_CACHE /* * We need to fetch descriptors now as the guest may advance RDT all the way @@ -2186,15 +2286,15 @@ pThis->iRxDCurrent = pThis->nRxDFetched = 0; E1kLog3(("%s e1kAdvanceRDH: Rx cache is empty, RDH=%x RDT=%x " "iRxDCurrent=%x nRxDFetched=%x\n", - pThis->szPrf, RDH, RDT, pThis->iRxDCurrent, pThis->nRxDFetched)); - e1kRxDPrefetch(pDevIns, pThis); + pThis->szPrf, pRxdc->rdh, pRxdc->rdt, pThis->iRxDCurrent, pThis->nRxDFetched)); + e1kRxDPrefetch(pDevIns, pThis, pRxdc); } #endif /* E1K_WITH_RXD_CACHE */ /* * Compute current receive queue length and fire RXDMT0 interrupt * if we are low on receive buffers */ - uint32_t uRQueueLen = RDH>RDT ? RDLEN/sizeof(E1KRXDESC)-RDH+RDT : RDT-RDH; + uint32_t uRQueueLen = pRxdc->rdh>pRxdc->rdt ? pRxdc->rdlen/sizeof(E1KRXDESC)-pRxdc->rdh+pRxdc->rdt : pRxdc->rdt-pRxdc->rdh; /* * The minimum threshold is controlled by RDMTS bits of RCTL: * 00 = 1/2 of RDLEN @@ -2202,17 +2302,17 @@ * 10 = 1/8 of RDLEN * 11 = reserved */ - uint32_t uMinRQThreshold = RDLEN / sizeof(E1KRXDESC) / (2 << GET_BITS(RCTL, RDMTS)); + uint32_t uMinRQThreshold = pRxdc->rdlen / sizeof(E1KRXDESC) / (2 << GET_BITS(RCTL, RDMTS)); if (uRQueueLen <= uMinRQThreshold) { - E1kLogRel(("E1000: low on RX descriptors, RDH=%x RDT=%x len=%x threshold=%x\n", RDH, RDT, uRQueueLen, uMinRQThreshold)); + E1kLogRel(("E1000: low on RX descriptors, RDH=%x RDT=%x len=%x threshold=%x\n", pRxdc->rdh, pRxdc->rdt, uRQueueLen, uMinRQThreshold)); E1kLog2(("%s Low on RX descriptors, RDH=%x RDT=%x len=%x threshold=%x, raise an interrupt\n", - pThis->szPrf, RDH, RDT, uRQueueLen, uMinRQThreshold)); + pThis->szPrf, pRxdc->rdh, pRxdc->rdt, uRQueueLen, uMinRQThreshold)); E1K_INC_ISTAT_CNT(pThis->uStatIntRXDMT0); e1kRaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, ICR_RXDMT0); } E1kLog2(("%s e1kAdvanceRDH: at exit RDH=%x RDT=%x len=%x\n", - pThis->szPrf, RDH, RDT, uRQueueLen)); + pThis->szPrf, pRxdc->rdh, pRxdc->rdt, uRQueueLen)); //e1kCsLeave(pThis); } #endif /* IN_RING3 */ @@ -2233,7 +2333,7 @@ * @param pThis The device state structure. * @thread RX */ -DECLINLINE(E1KRXDESC *) e1kRxDGet(PPDMDEVINS pDevIns, PE1KSTATE pThis) +DECLINLINE(E1KRXDESC *) e1kRxDGet(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KRXDC pRxdc) { Assert(e1kCsRxIsOwner(pThis)); /* Check the cache first. */ @@ -2241,7 +2341,7 @@ return &pThis->aRxDescriptors[pThis->iRxDCurrent]; /* Cache is empty, reset it and check if we can fetch more. */ pThis->iRxDCurrent = pThis->nRxDFetched = 0; - if (e1kRxDPrefetch(pDevIns, pThis)) + if (e1kRxDPrefetch(pDevIns, pThis, pRxdc)) return &pThis->aRxDescriptors[pThis->iRxDCurrent]; /* Out of Rx descriptors. */ return NULL; @@ -2257,7 +2357,7 @@ * @param pDesc The descriptor being "returned" to the RX ring. * @thread RX */ -DECLINLINE(void) e1kRxDPut(PPDMDEVINS pDevIns, PE1KSTATE pThis, E1KRXDESC* pDesc) +DECLINLINE(void) e1kRxDPut(PPDMDEVINS pDevIns, PE1KSTATE pThis, E1KRXDESC* pDesc, PE1KRXDC pRxdc) { Assert(e1kCsRxIsOwner(pThis)); pThis->iRxDCurrent++; @@ -2266,13 +2366,13 @@ // uint64_t addr = e1kDescAddr(RDBAH, RDBAL, RDH); // uint32_t rdh = RDH; // Assert(pThis->aRxDescAddr[pDesc - pThis->aRxDescriptors] == addr); - PDMDevHlpPCIPhysWrite(pDevIns, e1kDescAddr(RDBAH, RDBAL, RDH), pDesc, sizeof(E1KRXDESC)); + PDMDevHlpPCIPhysWrite(pDevIns, e1kDescAddr(RDBAH, RDBAL, pRxdc->rdh), pDesc, sizeof(E1KRXDESC)); /* * We need to print the descriptor before advancing RDH as it may fetch new * descriptors into the cache. */ e1kPrintRDesc(pThis, pDesc); - e1kAdvanceRDH(pDevIns, pThis); + e1kAdvanceRDH(pDevIns, pThis, pRxdc); } /** @@ -2447,10 +2547,21 @@ #if defined(IN_RING3) /** @todo Remove this extra copying, it's gonna make us run out of kernel / hypervisor stack! */ uint8_t rxPacket[E1K_MAX_RX_PKT_SIZE]; uint8_t *ptr = rxPacket; +# ifdef E1K_WITH_RXD_CACHE + E1KRXDC rxdc; +# endif /* E1K_WITH_RXD_CACHE */ int rc = e1kCsRxEnter(pThis, VERR_SEM_BUSY); if (RT_UNLIKELY(rc != VINF_SUCCESS)) return rc; +# ifdef E1K_WITH_RXD_CACHE + if (RT_UNLIKELY(!e1kUpdateRxDContext(pDevIns, pThis, &rxdc, "e1kHandleRxPacket"))) + { + e1kCsRxLeave(pThis); + E1kLog(("%s e1kHandleRxPacket: failed to update Rx context, returning VINF_SUCCESS\n", pThis->szPrf)); + return VINF_SUCCESS; + } +# endif /* E1K_WITH_RXD_CACHE */ if (cb > 70) /* unqualified guess */ pThis->led.Asserted.s.fReading = pThis->led.Actual.s.fReading = 1; @@ -2532,13 +2643,13 @@ # ifdef E1K_WITH_RXD_CACHE while (cb > 0) { - E1KRXDESC *pDesc = e1kRxDGet(pDevIns, pThis); + E1KRXDESC *pDesc = e1kRxDGet(pDevIns, pThis, &rxdc); if (pDesc == NULL) { E1kLog(("%s Out of receive buffers, dropping the packet " "(cb=%u, in_cache=%u, RDH=%x RDT=%x)\n", - pThis->szPrf, cb, e1kRxDInCache(pThis), RDH, RDT)); + pThis->szPrf, cb, e1kRxDInCache(pThis), rxdc.rdh, rxdc.rdt)); break; } # else /* !E1K_WITH_RXD_CACHE */ @@ -2579,6 +2690,14 @@ rc = e1kCsRxEnter(pThis, VERR_SEM_BUSY); if (RT_UNLIKELY(rc != VINF_SUCCESS)) return rc; +# ifdef E1K_WITH_RXD_CACHE + if (RT_UNLIKELY(!e1kUpdateRxDContext(pDevIns, pThis, &rxdc, "e1kHandleRxPacket"))) + { + e1kCsRxLeave(pThis); + E1kLog(("%s e1kHandleRxPacket: failed to update Rx context, returning VINF_SUCCESS\n", pThis->szPrf)); + return VINF_SUCCESS; + } +# endif /* E1K_WITH_RXD_CACHE */ ptr += u16RxBufferSize; cb -= u16RxBufferSize; } @@ -2591,6 +2710,12 @@ rc = e1kCsRxEnter(pThis, VERR_SEM_BUSY); if (RT_UNLIKELY(rc != VINF_SUCCESS)) return rc; + if (RT_UNLIKELY(!e1kUpdateRxDContext(pDevIns, pThis, &rxdc, "e1kHandleRxPacket"))) + { + e1kCsRxLeave(pThis); + E1kLog(("%s e1kHandleRxPacket: failed to update Rx context, returning VINF_SUCCESS\n", pThis->szPrf)); + return VINF_SUCCESS; + } cb = 0; # else /* !E1K_WITH_RXD_CACHE */ pThis->led.Actual.s.fReading = 0; @@ -2605,7 +2730,7 @@ # ifdef E1K_WITH_RXD_CACHE /* Write back the descriptor. */ pDesc->status.fDD = true; - e1kRxDPut(pDevIns, pThis, pDesc); + e1kRxDPut(pDevIns, pThis, pDesc, &rxdc); # else /* !E1K_WITH_RXD_CACHE */ else { @@ -2770,6 +2895,25 @@ #endif /* unused */ /** + * A helper function to detect the link state to the other side of "the wire". + * + * When deciding to bring up the link we need to take into account both if the + * cable is connected and if our device is actually connected to the outside + * world. If no driver is attached we won't be able to allocate TX buffers, + * which will prevent us from TX descriptor processing, which will result in + * "TX unit hang" in the guest. + * + * @returns true if the device is connected to something. + * + * @param pDevIns The device instance. + */ +DECLINLINE(bool) e1kIsConnected(PPDMDEVINS pDevIns) +{ + PE1KSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PE1KSTATE); + return pThis->fCableConnected && pThis->fIsAttached; +} + +/** * A callback used by PHY to indicate that the link needs to be updated due to * reset of PHY. * @@ -2781,8 +2925,11 @@ PE1KSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PE1KSTATE); /* Make sure we have cable connected and MAC can talk to PHY */ - if (pThis->fCableConnected && (CTRL & CTRL_SLU)) + if (e1kIsConnected(pDevIns) && (CTRL & CTRL_SLU)) e1kArmTimer(pDevIns, pThis, pThis->hLUTimer, E1K_INIT_LINKUP_DELAY_US); + else + Log(("%s PHY link reset callback ignored (cable %sconnected, driver %stached, CTRL_SLU=%u)\n", pThis->szPrf, + pThis->fCableConnected ? "" : "dis", pThis->fIsAttached ? "at" : "de", CTRL & CTRL_SLU ? 1 : 0)); } /** @@ -2828,7 +2975,7 @@ #else /* !E1K_LSC_ON_SLU */ if ( (value & CTRL_SLU) && !(CTRL & CTRL_SLU) - && pThis->fCableConnected + && e1kIsConnected(pDevIns) && !PDMDevHlpTimerIsActive(pDevIns, pThis->hLUTimer)) { /* PXE does not use LSC interrupts, see @bugref{9113}. */ @@ -3120,6 +3267,25 @@ } /** + * Read handler for Interrupt Cause Set register. + * + * VxWorks driver uses this undocumented feature of real H/W to read ICR without acknowledging interrupts. + * + * @returns VBox status code. + * + * @param pThis The device state structure. + * @param offset Register offset in memory-mapped frame. + * @param index Register index in register array. + * @param pu32Value Where to store the value of the register. + * @thread EMT + */ +static int e1kRegReadICS(PPDMDEVINS pDevIns, PE1KSTATE pThis, uint32_t offset, uint32_t index, uint32_t *pu32Value) +{ + RT_NOREF_PV(index); + return e1kRegReadDefault(pDevIns, pThis, offset, ICR_IDX, pu32Value); +} + +/** * Write handler for Interrupt Cause Set register. * * Bits corresponding to 1s in 'value' will be set in ICR register. @@ -3316,6 +3482,13 @@ #endif /* !E1K_WITH_RXD_CACHE */ rc = e1kRegWriteDefault(pDevIns, pThis, offset, index, value); #ifdef E1K_WITH_RXD_CACHE + E1KRXDC rxdc; + if (RT_UNLIKELY(!e1kUpdateRxDContext(pDevIns, pThis, &rxdc, "e1kRegWriteRDT"))) + { + e1kCsRxLeave(pThis); + E1kLog(("%s e1kRegWriteRDT: failed to update Rx context, returning VINF_SUCCESS\n", pThis->szPrf)); + return VINF_SUCCESS; + } /* * We need to fetch descriptors now as RDT may go whole circle * before we attempt to store a received packet. For example, @@ -3336,7 +3509,7 @@ * a later point in e1kRxDGet(). */ if (e1kRxDIsCacheEmpty(pThis) && (RCTL & RCTL_EN)) - e1kRxDPrefetch(pDevIns, pThis); + e1kRxDPrefetch(pDevIns, pThis, &rxdc); #endif /* E1K_WITH_RXD_CACHE */ e1kCsRxLeave(pThis); if (RT_SUCCESS(rc)) @@ -3375,14 +3548,14 @@ return VINF_SUCCESS; } -DECLINLINE(uint32_t) e1kGetTxLen(PE1KSTATE pThis) +DECLINLINE(uint32_t) e1kGetTxLen(PE1KTXDC pTxdc) { /** * Make sure TDT won't change during computation. EMT may modify TDT at * any moment. */ - uint32_t tdt = TDT; - return (TDH>tdt ? TDLEN/sizeof(E1KTXDESC) : 0) + tdt - TDH; + uint32_t tdt = pTxdc->tdt; + return (pTxdc->tdh > tdt ? pTxdc->tdlen/sizeof(E1KTXDESC) : 0) + tdt - pTxdc->tdh; } #ifdef IN_RING3 @@ -3551,7 +3724,7 @@ * and connect+disconnect the cable very quick. Moreover, 82543GC triggers LSC * on reset even if the cable is unplugged (see @bugref{8942}). */ - if (pThis->fCableConnected) + if (e1kIsConnected(pDevIns)) { /* 82543GC does not have an internal PHY */ if (pThis->eChip == E1K_CHIP_82543GC || (CTRL & CTRL_SLU)) @@ -3571,7 +3744,7 @@ * @param pGso The GSO context to setup. * @param pCtx The context descriptor. */ -DECLINLINE(void) e1kSetupGsoCtx(PPDMNETWORKGSO pGso, E1KTXCTX const *pCtx) +DECLINLINE(bool) e1kSetupGsoCtx(PPDMNETWORKGSO pGso, E1KTXCTX const *pCtx) { pGso->u8Type = PDMNETWORKGSOTYPE_INVALID; @@ -3583,33 +3756,33 @@ if (RT_UNLIKELY( pCtx->ip.u8CSS < sizeof(RTNETETHERHDR) )) { E1kLog(("e1kSetupGsoCtx: IPCSS=%#x\n", pCtx->ip.u8CSS)); - return; + return false; } if (RT_UNLIKELY( pCtx->tu.u8CSS < (size_t)pCtx->ip.u8CSS + (pCtx->dw2.fIP ? RTNETIPV4_MIN_LEN : RTNETIPV6_MIN_LEN) )) { E1kLog(("e1kSetupGsoCtx: TUCSS=%#x\n", pCtx->tu.u8CSS)); - return; + return false; } if (RT_UNLIKELY( pCtx->dw2.fTCP ? pCtx->dw3.u8HDRLEN < (size_t)pCtx->tu.u8CSS + RTNETTCP_MIN_LEN : pCtx->dw3.u8HDRLEN != (size_t)pCtx->tu.u8CSS + RTNETUDP_MIN_LEN )) { E1kLog(("e1kSetupGsoCtx: HDRLEN=%#x TCP=%d\n", pCtx->dw3.u8HDRLEN, pCtx->dw2.fTCP)); - return; + return false; } /* The end of the TCP/UDP checksum should stop at the end of the packet or at least after the headers. */ if (RT_UNLIKELY( pCtx->tu.u16CSE > 0 && pCtx->tu.u16CSE <= pCtx->dw3.u8HDRLEN )) { E1kLog(("e1kSetupGsoCtx: TUCSE=%#x HDRLEN=%#x\n", pCtx->tu.u16CSE, pCtx->dw3.u8HDRLEN)); - return; + return false; } /* IPv4 checksum offset. */ if (RT_UNLIKELY( pCtx->dw2.fIP && (size_t)pCtx->ip.u8CSO - pCtx->ip.u8CSS != RT_UOFFSETOF(RTNETIPV4, ip_sum) )) { E1kLog(("e1kSetupGsoCtx: IPCSO=%#x IPCSS=%#x\n", pCtx->ip.u8CSO, pCtx->ip.u8CSS)); - return; + return false; } /* TCP/UDP checksum offsets. */ @@ -3619,7 +3792,7 @@ : RT_UOFFSETOF(RTNETUDP, uh_sum) ) )) { E1kLog(("e1kSetupGsoCtx: TUCSO=%#x TUCSS=%#x TCP=%d\n", pCtx->ip.u8CSO, pCtx->ip.u8CSS, pCtx->dw2.fTCP)); - return; + return false; } /* @@ -3630,7 +3803,7 @@ { E1kLog(("e1kSetupGsoCtx: HDRLEN(=%#x) + PAYLEN(=%#x) = %#x, max is %#x\n", pCtx->dw3.u8HDRLEN, pCtx->dw2.u20PAYLEN, pCtx->dw3.u8HDRLEN + pCtx->dw2.u20PAYLEN, VBOX_MAX_GSO_SIZE)); - return; + return false; } /* @@ -3667,6 +3840,7 @@ Assert(PDMNetGsoIsValid(pGso, sizeof(*pGso), pGso->cbMaxSeg * 5)); E1kLog2(("e1kSetupGsoCtx: mss=%#x hdr=%#x hdrseg=%#x hdr1=%#x hdr2=%#x %s\n", pGso->cbMaxSeg, pGso->cbHdrsTotal, pGso->cbHdrsSeg, pGso->offHdr1, pGso->offHdr2, PDMNetGsoTypeName((PDMNETWORKGSOTYPE)pGso->u8Type) )); + return PDMNetGsoIsValid(pGso, sizeof(*pGso), pGso->cbMaxSeg * 5); } /** @@ -3938,19 +4112,19 @@ * @param pThis The device state structure. * @thread E1000_TX */ -DECLINLINE(unsigned) e1kTxDLoadMore(PPDMDEVINS pDevIns, PE1KSTATE pThis) +DECLINLINE(unsigned) e1kTxDLoadMore(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KTXDC pTxdc) { Assert(pThis->iTxDCurrent == 0); /* We've already loaded pThis->nTxDFetched descriptors past TDH. */ - unsigned nDescsAvailable = e1kGetTxLen(pThis) - pThis->nTxDFetched; + unsigned nDescsAvailable = e1kGetTxLen(pTxdc) - pThis->nTxDFetched; /* The following two lines ensure that pThis->nTxDFetched never overflows. */ AssertCompile(E1K_TXD_CACHE_SIZE < (256 * sizeof(pThis->nTxDFetched))); unsigned nDescsToFetch = RT_MIN(nDescsAvailable, E1K_TXD_CACHE_SIZE - pThis->nTxDFetched); - unsigned nDescsTotal = TDLEN / sizeof(E1KTXDESC); + unsigned nDescsTotal = pTxdc->tdlen / sizeof(E1KTXDESC); Assert(nDescsTotal != 0); if (nDescsTotal == 0) return 0; - unsigned nFirstNotLoaded = (TDH + pThis->nTxDFetched) % nDescsTotal; + unsigned nFirstNotLoaded = (pTxdc->tdh + pThis->nTxDFetched) % nDescsTotal; unsigned nDescsInSingleRead = RT_MIN(nDescsToFetch, nDescsTotal - nFirstNotLoaded); E1kLog3(("%s e1kTxDLoadMore: nDescsAvailable=%u nDescsToFetch=%u nDescsTotal=%u nFirstNotLoaded=0x%x nDescsInSingleRead=%u\n", pThis->szPrf, nDescsAvailable, nDescsToFetch, nDescsTotal, @@ -3963,8 +4137,8 @@ pFirstEmptyDesc, nDescsInSingleRead * sizeof(E1KTXDESC)); E1kLog3(("%s Fetched %u TX descriptors at %08x%08x(0x%x), TDLEN=%08x, TDH=%08x, TDT=%08x\n", pThis->szPrf, nDescsInSingleRead, - TDBAH, TDBAL + TDH * sizeof(E1KTXDESC), - nFirstNotLoaded, TDLEN, TDH, TDT)); + TDBAH, TDBAL + pTxdc->tdh * sizeof(E1KTXDESC), + nFirstNotLoaded, pTxdc->tdlen, pTxdc->tdh, pTxdc->tdt)); if (nDescsToFetch > nDescsInSingleRead) { PDMDevHlpPhysRead(pDevIns, @@ -3988,10 +4162,10 @@ * @param pThis The device state structure. * @thread E1000_TX */ -DECLINLINE(bool) e1kTxDLazyLoad(PPDMDEVINS pDevIns, PE1KSTATE pThis) +DECLINLINE(bool) e1kTxDLazyLoad(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KTXDC pTxdc) { if (pThis->nTxDFetched == 0) - return e1kTxDLoadMore(pDevIns, pThis) != 0; + return e1kTxDLoadMore(pDevIns, pThis, pTxdc) != 0; return true; } #endif /* E1K_WITH_TXD_CACHE */ @@ -4030,6 +4204,11 @@ uint32_t cbFrame = pSg ? (uint32_t)pSg->cbUsed : 0; Assert(!pSg || pSg->cSegs == 1); + if (cbFrame < 14) + { + Log(("%s Ignoring invalid frame (%u bytes)\n", pThis->szPrf, cbFrame)); + return; + } if (cbFrame > 70) /* unqualified guess */ pThis->led.Asserted.s.fWriting = pThis->led.Actual.s.fWriting = 1; @@ -4055,7 +4234,7 @@ #endif /* E1K_INT_STATS */ /* Add VLAN tag */ - if (cbFrame > 12 && pThis->fVTag) + if (cbFrame > 12 && pThis->fVTag && pSg->cbUsed + 4 <= pSg->cbAvailable) { E1kLog3(("%s Inserting VLAN tag %08x\n", pThis->szPrf, RT_BE2H_U16((uint16_t)VET) | (RT_BE2H_U16(pThis->u16VTagTCI) << 16))); @@ -4156,9 +4335,10 @@ * checksum from. * @param cse Offset in packet to stop computing * checksum at. + * @param fUdp Replace 0 checksum with all 1s. * @thread E1000_TX */ -static void e1kInsertChecksum(PE1KSTATE pThis, uint8_t *pPkt, uint16_t u16PktLen, uint8_t cso, uint8_t css, uint16_t cse) +static void e1kInsertChecksum(PE1KSTATE pThis, uint8_t *pPkt, uint16_t u16PktLen, uint8_t cso, uint8_t css, uint16_t cse, bool fUdp = false) { RT_NOREF1(pThis); @@ -4186,6 +4366,8 @@ } uint16_t u16ChkSum = e1kCSum16(pPkt + css, cse - css + 1); + if (fUdp && u16ChkSum == 0) + u16ChkSum = ~u16ChkSum; /* 0 means no checksum computed in case of UDP (see @bugref{9883}) */ E1kLog2(("%s Inserting csum: %04X at %02X, old value: %04X\n", pThis->szPrf, u16ChkSum, cso, *(uint16_t*)(pPkt + cso))); *(uint16_t*)(pPkt + cso) = u16ChkSum; @@ -4911,7 +5093,8 @@ e1kInsertChecksum(pThis, (uint8_t *)pThisCC->CTX_SUFF(pTxSg)->aSegs[0].pvSeg, pThis->u16TxPktLen, pThis->contextNormal.tu.u8CSO, pThis->contextNormal.tu.u8CSS, - pThis->contextNormal.tu.u16CSE); + pThis->contextNormal.tu.u16CSE, + !pThis->contextNormal.dw2.fTCP); e1kTransmitFrame(pDevIns, pThis, pThisCC, fOnWorkerThread); } else @@ -5021,13 +5204,6 @@ e1kPrintTDesc(pThis, pDesc, "vvv"); - if (pDesc->legacy.dw3.fDD) - { - E1kLog(("%s e1kXmitDesc: skipping bad descriptor ^^^\n", pThis->szPrf)); - e1kDescReport(pDevIns, pThis, pDesc, addr); - return VINF_SUCCESS; - } - //#ifdef E1K_USE_TX_TIMERS if (pThis->fTidEnabled) PDMDevHlpTimerStop(pDevIns, pThis->hTIDTimer); @@ -5050,7 +5226,7 @@ STAM_PROFILE_ADV_START(&pThis->CTX_SUFF_Z(StatTransmit), a); if (pDesc->data.cmd.u20DTALEN == 0 || pDesc->data.u64BufAddr == 0) { - E1kLog2(("% Empty data descriptor, skipped.\n", pThis->szPrf)); + E1kLog2(("%s Empty data descriptor, skipped.\n", pThis->szPrf)); if (pDesc->data.cmd.fEOP) { e1kTransmitFrame(pDevIns, pThis, pThisCC, fOnWorkerThread); @@ -5106,7 +5282,8 @@ e1kInsertChecksum(pThis, (uint8_t *)pThisCC->CTX_SUFF(pTxSg)->aSegs[0].pvSeg, pThis->u16TxPktLen, pThis->contextNormal.tu.u8CSO, pThis->contextNormal.tu.u8CSS, - pThis->contextNormal.tu.u16CSE); + pThis->contextNormal.tu.u16CSE, + !pThis->contextNormal.dw2.fTCP); e1kTransmitFrame(pDevIns, pThis, pThisCC, fOnWorkerThread); } else @@ -5131,6 +5308,11 @@ if (pDesc->legacy.cmd.u16Length == 0 || pDesc->legacy.u64BufAddr == 0) { E1kLog(("%s Empty legacy descriptor, skipped.\n", pThis->szPrf)); + if (pDesc->data.cmd.fEOP) + { + e1kTransmitFrame(pDevIns, pThis, pThisCC, fOnWorkerThread); + pThis->u16TxPktLen = 0; + } } else { @@ -5175,10 +5357,15 @@ return rc; } -DECLINLINE(void) e1kUpdateTxContext(PE1KSTATE pThis, E1KTXDESC *pDesc) +DECLINLINE(bool) e1kUpdateTxContext(PE1KSTATE pThis, E1KTXDESC *pDesc) { if (pDesc->context.dw2.fTSE) { + if (!e1kSetupGsoCtx(&pThis->GsoCtx, &pDesc->context)) + { + pThis->contextTSE.dw2.u4DTYP = E1K_DTYP_INVALID; + return false; + } pThis->contextTSE = pDesc->context; uint32_t cbMaxSegmentSize = pThis->contextTSE.dw3.u16MSS + pThis->contextTSE.dw3.u8HDRLEN + 4; /*VTAG*/ if (RT_UNLIKELY(cbMaxSegmentSize > E1K_MAX_TX_PKT_SIZE)) @@ -5206,9 +5393,18 @@ pDesc->context.tu.u8CSS, pDesc->context.tu.u8CSO, pDesc->context.tu.u16CSE)); + return true; /* Consider returning false for invalid descriptors */ } -static bool e1kLocateTxPacket(PE1KSTATE pThis) +enum E1kPacketType +{ + E1K_PACKET_NONE = 0, + E1K_PACKET_LEGACY, + E1K_PACKET_NORMAL, + E1K_PACKET_TSE +}; + +static int e1kLocateTxPacket(PE1KSTATE pThis, PE1KTXDC pTxdc) { LogFlow(("%s e1kLocateTxPacket: ENTER cbTxAlloc=%d\n", pThis->szPrf, pThis->cbTxAlloc)); @@ -5220,30 +5416,64 @@ return true; } + pThis->fGSO = false; + pThis->fVTag = false; + pThis->fIPcsum = false; + pThis->fTCPcsum = false; + pThis->u16TxPktLen = 0; + + enum E1kPacketType packetType = E1K_PACKET_NONE; + enum E1kPacketType expectedPacketType = E1K_PACKET_NONE; + /* + * Valid packets start with 1 or 0 context descriptors, followed by 1 or + * more data descriptors of the same type: legacy, normal or TSE. Note + * that legacy descriptors do not belong to neither normal nor segmentation + * contexts rendering the sequence (context_descriptor, legacy_descriptor) + * invalid, but the context descriptor will still be applied and the legacy + * descriptor will be treated as the beginning of next packet. + */ + bool fInvalidPacket = false; bool fTSE = false; uint32_t cbPacket = 0; + /* Since we process one packet at a time we will only mark current packet's descriptors as valid */ + memset(pThis->afTxDValid, 0, sizeof(pThis->afTxDValid)); for (int i = pThis->iTxDCurrent; i < pThis->nTxDFetched; ++i) { E1KTXDESC *pDesc = &pThis->aTxDescriptors[i]; + switch (e1kGetDescType(pDesc)) { case E1K_DTYP_CONTEXT: + /* There can be only one context per packet. Each context descriptor starts a new packet. */ + if (packetType != E1K_PACKET_NONE) + { + fInvalidPacket = true; + break; + } + packetType = (pDesc->context.dw2.fTSE) ? E1K_PACKET_TSE : E1K_PACKET_NORMAL; if (cbPacket == 0) - e1kUpdateTxContext(pThis, pDesc); + pThis->afTxDValid[i] = e1kUpdateTxContext(pThis, pDesc); else E1kLog(("%s e1kLocateTxPacket: ignoring a context descriptor in the middle of a packet, cbPacket=%d\n", pThis->szPrf, cbPacket)); continue; case E1K_DTYP_LEGACY: + if (packetType != E1K_PACKET_NONE && packetType != E1K_PACKET_LEGACY) + { + fInvalidPacket = true; + break; + } + packetType = E1K_PACKET_LEGACY; /* Skip invalid descriptors. */ if (cbPacket > 0 && (pThis->fGSO || fTSE)) { E1kLog(("%s e1kLocateTxPacket: ignoring a legacy descriptor in the segmentation context, cbPacket=%d\n", pThis->szPrf, cbPacket)); - pDesc->legacy.dw3.fDD = true; /* Make sure it is skipped by processing */ continue; } + pThis->afTxDValid[i] = true; /* Passed all checks, process it */ + /* Skip empty descriptors. */ if (!pDesc->legacy.u64BufAddr || !pDesc->legacy.cmd.u16Length) break; @@ -5251,14 +5481,39 @@ pThis->fGSO = false; break; case E1K_DTYP_DATA: + expectedPacketType = pDesc->data.cmd.fTSE ? E1K_PACKET_TSE : E1K_PACKET_NORMAL; + if (packetType != E1K_PACKET_NONE && packetType != expectedPacketType) + { + fInvalidPacket = true; + break; + } /* Skip invalid descriptors. */ + if (pDesc->data.cmd.fTSE) + { + if (pThis->contextTSE.dw2.u4DTYP == E1K_DTYP_INVALID) + { + E1kLog(("%s e1kLocateTxPacket: ignoring TSE descriptor in invalid segmentation context, cbPacket=%d\n", + pThis->szPrf, cbPacket)); + continue; + } + } + else /* !TSE */ + { + if (pThis->contextNormal.dw2.u4DTYP == E1K_DTYP_INVALID) + { + E1kLog(("%s e1kLocateTxPacket: ignoring non-TSE descriptor in invalid normal context, cbPacket=%d\n", + pThis->szPrf, cbPacket)); + continue; + } + } if (cbPacket > 0 && (bool)pDesc->data.cmd.fTSE != fTSE) { E1kLog(("%s e1kLocateTxPacket: ignoring %sTSE descriptor in the %ssegmentation context, cbPacket=%d\n", pThis->szPrf, pDesc->data.cmd.fTSE ? "" : "non-", fTSE ? "" : "non-", cbPacket)); - pDesc->data.dw3.fDD = true; /* Make sure it is skipped by processing */ continue; } + pThis->afTxDValid[i] = true; /* Passed all checks, process it */ + /* Skip empty descriptors. */ if (!pDesc->data.u64BufAddr || !pDesc->data.cmd.u20DTALEN) break; @@ -5288,6 +5543,17 @@ AssertMsgFailed(("Impossible descriptor type!")); continue; } + if (fInvalidPacket) + { + for (int index = pThis->iTxDCurrent; index < i; ++index) + pThis->afTxDValid[index] = false; /* Make sure all descriptors for this packet are skipped by processing */ + LogFlow(("%s e1kLocateTxPacket: marked %d descriptors as invalid\n", pThis->szPrf, i - pThis->iTxDCurrent)); + LogFlow(("%s e1kLocateTxPacket: RET true cbTxAlloc=%d cbPacket=%d%s%s\n", + pThis->szPrf, pThis->cbTxAlloc, cbPacket, + pThis->fGSO ? " GSO" : "", fTSE ? " TSE" : "")); + pTxdc->nextPacket = i; + return true; + } if (pDesc->legacy.cmd.fEOP) { /* @@ -5312,6 +5578,7 @@ LogFlow(("%s e1kLocateTxPacket: RET true cbTxAlloc=%d cbPacket=%d%s%s\n", pThis->szPrf, pThis->cbTxAlloc, cbPacket, pThis->fGSO ? " GSO" : "", fTSE ? " TSE" : "")); + pTxdc->nextPacket = i + 1; return true; } } @@ -5321,6 +5588,7 @@ /* All descriptors were empty, we need to process them as a dummy packet */ LogFlow(("%s e1kLocateTxPacket: RET true cbTxAlloc=%d, zero packet!\n", pThis->szPrf, pThis->cbTxAlloc)); + pTxdc->nextPacket = pThis->nTxDFetched; return true; } LogFlow(("%s e1kLocateTxPacket: RET false cbTxAlloc=%d cbPacket=%d\n", @@ -5328,7 +5596,7 @@ return false; } -static int e1kXmitPacket(PPDMDEVINS pDevIns, PE1KSTATE pThis, bool fOnWorkerThread) +static int e1kXmitPacket(PPDMDEVINS pDevIns, PE1KSTATE pThis, bool fOnWorkerThread, PE1KTXDC pTxdc) { PE1KSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PE1KSTATECC); int rc = VINF_SUCCESS; @@ -5336,21 +5604,30 @@ LogFlow(("%s e1kXmitPacket: ENTER current=%d fetched=%d\n", pThis->szPrf, pThis->iTxDCurrent, pThis->nTxDFetched)); - while (pThis->iTxDCurrent < pThis->nTxDFetched) + while (pThis->iTxDCurrent < pTxdc->nextPacket && pThis->iTxDCurrent < pThis->nTxDFetched) { E1KTXDESC *pDesc = &pThis->aTxDescriptors[pThis->iTxDCurrent]; E1kLog3(("%s About to process new TX descriptor at %08x%08x, TDLEN=%08x, TDH=%08x, TDT=%08x\n", - pThis->szPrf, TDBAH, TDBAL + TDH * sizeof(E1KTXDESC), TDLEN, TDH, TDT)); - rc = e1kXmitDesc(pDevIns, pThis, pThisCC, pDesc, e1kDescAddr(TDBAH, TDBAL, TDH), fOnWorkerThread); + pThis->szPrf, TDBAH, TDBAL + pTxdc->tdh * sizeof(E1KTXDESC), pTxdc->tdlen, pTxdc->tdh, pTxdc->tdt)); + if (!pThis->afTxDValid[pThis->iTxDCurrent]) + { + e1kPrintTDesc(pThis, pDesc, "vvv"); + E1kLog(("%s e1kXmitDesc: skipping bad descriptor ^^^\n", pThis->szPrf)); + e1kDescReport(pDevIns, pThis, pDesc, e1kDescAddr(TDBAH, TDBAL, pTxdc->tdh)); + rc = VINF_SUCCESS; + } + else + rc = e1kXmitDesc(pDevIns, pThis, pThisCC, pDesc, e1kDescAddr(TDBAH, TDBAL, pTxdc->tdh), fOnWorkerThread); if (RT_FAILURE(rc)) break; - if (++TDH * sizeof(E1KTXDESC) >= TDLEN) - TDH = 0; + if (++pTxdc->tdh * sizeof(E1KTXDESC) >= pTxdc->tdlen) + pTxdc->tdh = 0; + TDH = pTxdc->tdh; /* Sync the actual register and TXDC */ uint32_t uLowThreshold = GET_BITS(TXDCTL, LWTHRESH)*8; - if (uLowThreshold != 0 && e1kGetTxLen(pThis) <= uLowThreshold) + if (uLowThreshold != 0 && e1kGetTxLen(pTxdc) <= uLowThreshold) { E1kLog2(("%s Low on transmit descriptors, raise ICR.TXD_LOW, len=%x thresh=%x\n", - pThis->szPrf, e1kGetTxLen(pThis), GET_BITS(TXDCTL, LWTHRESH)*8)); + pThis->szPrf, e1kGetTxLen(pTxdc), GET_BITS(TXDCTL, LWTHRESH)*8)); e1kRaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, ICR_TXD_LOW); } ++pThis->iTxDCurrent; @@ -5442,10 +5719,10 @@ #else /* E1K_WITH_TXD_CACHE */ -static void e1kDumpTxDCache(PPDMDEVINS pDevIns, PE1KSTATE pThis) +static void e1kDumpTxDCache(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KTXDC pTxdc) { - unsigned i, cDescs = TDLEN / sizeof(E1KTXDESC); - uint32_t tdh = TDH; + unsigned i, cDescs = pTxdc->tdlen / sizeof(E1KTXDESC); + uint32_t tdh = pTxdc->tdh; LogRel(("E1000: -- Transmit Descriptors (%d total) --\n", cDescs)); for (i = 0; i < cDescs; ++i) { @@ -5456,7 +5733,7 @@ LogRel(("E1000: %RGp: %R[e1ktxd]\n", e1kDescAddr(TDBAH, TDBAL, i), &desc)); } LogRel(("E1000: -- Transmit Descriptors in Cache (at %d (TDH %d)/ fetched %d / max %d) --\n", - pThis->iTxDCurrent, TDH, pThis->nTxDFetched, E1K_TXD_CACHE_SIZE)); + pThis->iTxDCurrent, pTxdc->tdh, pThis->nTxDFetched, E1K_TXD_CACHE_SIZE)); if (tdh > pThis->iTxDCurrent) tdh -= pThis->iTxDCurrent; else @@ -5505,8 +5782,10 @@ * Note! Do not process descriptors in locked state */ rc = e1kCsTxEnter(pThis, VERR_SEM_BUSY); - if (RT_LIKELY(rc == VINF_SUCCESS)) + if (RT_LIKELY(rc == VINF_SUCCESS && (TCTL & TCTL_EN))) { + E1KTXDC txdc; + bool fTxContextValid = e1kUpdateTxDContext(pDevIns, pThis, &txdc); STAM_PROFILE_ADV_START(&pThis->CTX_SUFF_Z(StatTransmit), a); /* * fIncomplete is set whenever we try to fetch additional descriptors @@ -5515,10 +5794,12 @@ * stuck in this loop forever. */ bool fIncomplete = false; - while (!pThis->fLocked && e1kTxDLazyLoad(pDevIns, pThis)) + while (fTxContextValid && !pThis->fLocked && e1kTxDLazyLoad(pDevIns, pThis, &txdc)) { - while (e1kLocateTxPacket(pThis)) + while (e1kLocateTxPacket(pThis, &txdc)) { + Log4(("%s e1kXmitPending: Located packet at %d. Next packet at %d\n", + pThis->szPrf, pThis->iTxDCurrent, txdc.nextPacket)); fIncomplete = false; /* Found a complete packet, allocate it. */ rc = e1kXmitAllocBuf(pThis, pThisCC, pThis->fGSO); @@ -5526,7 +5807,7 @@ if (RT_FAILURE(rc)) goto out; /* Copy the packet to allocated buffer and send it. */ - rc = e1kXmitPacket(pDevIns, pThis, fOnWorkerThread); + rc = e1kXmitPacket(pDevIns, pThis, fOnWorkerThread, &txdc); /* If we're out of bandwidth we'll come back later. */ if (RT_FAILURE(rc)) goto out; @@ -5545,11 +5826,11 @@ pThis->szPrf, u8Remain == E1K_TXD_CACHE_SIZE ? " full" : "", pThis->nTxDFetched, pThis->iTxDCurrent, - e1kGetTxLen(pThis))); + e1kGetTxLen(&txdc))); if (!fTxDCacheDumped) { fTxDCacheDumped = true; - e1kDumpTxDCache(pDevIns, pThis); + e1kDumpTxDCache(pDevIns, pThis, &txdc); } pThis->iTxDCurrent = pThis->nTxDFetched = 0; /* @@ -5568,7 +5849,7 @@ Log4(("%s Incomplete packet at %d. Already fetched %d, " "%d more are available\n", pThis->szPrf, pThis->iTxDCurrent, u8Remain, - e1kGetTxLen(pThis) - u8Remain)); + e1kGetTxLen(&txdc) - u8Remain)); /* * A packet was partially fetched. Move incomplete packet to @@ -5579,7 +5860,7 @@ u8Remain * sizeof(E1KTXDESC)); pThis->iTxDCurrent = 0; pThis->nTxDFetched = u8Remain; - e1kTxDLoadMore(pDevIns, pThis); + e1kTxDLoadMore(pDevIns, pThis, &txdc); fIncomplete = true; } else @@ -5662,12 +5943,18 @@ E1kLog2(("%s e1kRegWriteTDT: TDBAL=%08x, TDBAH=%08x, TDLEN=%08x, TDH=%08x, TDT=%08x\n", pThis->szPrf, TDBAL, TDBAH, TDLEN, TDH, TDT)); + /* Compose a temporary TX context, breaking TX CS rule, for debugging purposes. */ + /* If we decide to transmit, the TX critical section will be entered later in e1kXmitPending(). */ + E1KTXDC txdc; + txdc.tdlen = TDLEN; + txdc.tdh = TDH; + txdc.tdt = TDT; /* Ignore TDT writes when the link is down. */ - if (TDH != TDT && (STATUS & STATUS_LU)) + if (txdc.tdh != txdc.tdt && (STATUS & STATUS_LU)) { - Log5(("E1000: TDT write: TDH=%08x, TDT=%08x, %d descriptors to process\n", TDH, TDT, e1kGetTxLen(pThis))); + Log5(("E1000: TDT write: TDH=%08x, TDT=%08x, %d descriptors to process\n", txdc.tdh, txdc.tdt, e1kGetTxLen(&txdc))); E1kLog(("%s e1kRegWriteTDT: %d descriptors to process\n", - pThis->szPrf, e1kGetTxLen(pThis))); + pThis->szPrf, e1kGetTxLen(&txdc))); /* Transmit pending packets if possible, defer it if we cannot do it in the current context. */ @@ -6253,7 +6540,7 @@ { case 0x00: /* IOADDR */ *pu32 = pThis->uSelectedReg; - E1kLog2(("%s e1kIOPortIn: IOADDR(0), selecting register %#010x, val=%#010x\n", pThis->szPrf, pThis->uSelectedReg, *pu32)); + Log9(("%s e1kIOPortIn: IOADDR(0), selecting register %#010x, val=%#010x\n", pThis->szPrf, pThis->uSelectedReg, *pu32)); rc = VINF_SUCCESS; break; @@ -6264,7 +6551,7 @@ rc = e1kRegReadUnaligned(pDevIns, pThis, pThis->uSelectedReg, pu32, cb); if (rc == VINF_IOM_R3_MMIO_READ) rc = VINF_IOM_R3_IOPORT_READ; - E1kLog2(("%s e1kIOPortIn: IODATA(4), reading from selected register %#010x, val=%#010x\n", pThis->szPrf, pThis->uSelectedReg, *pu32)); + Log9(("%s e1kIOPortIn: IODATA(4), reading from selected register %#010x, val=%#010x\n", pThis->szPrf, pThis->uSelectedReg, *pu32)); break; default: @@ -6295,19 +6582,19 @@ STAM_PROFILE_ADV_START(&pThis->CTX_SUFF_Z(StatIOWrite), a); RT_NOREF_PV(pvUser); - E1kLog2(("%s e1kIOPortOut: offPort=%RTiop value=%08x\n", pThis->szPrf, offPort, u32)); + Log9(("%s e1kIOPortOut: offPort=%RTiop value=%08x\n", pThis->szPrf, offPort, u32)); if (RT_LIKELY(cb == 4)) { switch (offPort) { case 0x00: /* IOADDR */ pThis->uSelectedReg = u32; - E1kLog2(("%s e1kIOPortOut: IOADDR(0), selected register %08x\n", pThis->szPrf, pThis->uSelectedReg)); + Log9(("%s e1kIOPortOut: IOADDR(0), selected register %08x\n", pThis->szPrf, pThis->uSelectedReg)); rc = VINF_SUCCESS; break; case 0x04: /* IODATA */ - E1kLog2(("%s e1kIOPortOut: IODATA(4), writing to selected register %#010x, value=%#010x\n", pThis->szPrf, pThis->uSelectedReg, u32)); + Log9(("%s e1kIOPortOut: IODATA(4), writing to selected register %#010x, value=%#010x\n", pThis->szPrf, pThis->uSelectedReg, u32)); if (RT_LIKELY(!(pThis->uSelectedReg & 3))) { rc = e1kRegWriteAlignedU32(pDevIns, pThis, pThis->uSelectedReg, u32); @@ -6437,22 +6724,29 @@ if (RT_UNLIKELY(e1kCsRxEnter(pThis, VERR_SEM_BUSY) != VINF_SUCCESS)) return VERR_NET_NO_BUFFER_SPACE; + E1KRXDC rxdc; + if (RT_UNLIKELY(!e1kUpdateRxDContext(pDevIns, pThis, &rxdc, "e1kCanReceive"))) + { + e1kCsRxLeave(pThis); + E1kLog(("%s e1kCanReceive: failed to update Rx context, returning VERR_NET_NO_BUFFER_SPACE\n", pThis->szPrf)); + return VERR_NET_NO_BUFFER_SPACE; + } - if (RT_UNLIKELY(RDLEN == sizeof(E1KRXDESC))) + if (RT_UNLIKELY(rxdc.rdlen == sizeof(E1KRXDESC))) { E1KRXDESC desc; - PDMDevHlpPhysRead(pDevIns, e1kDescAddr(RDBAH, RDBAL, RDH), &desc, sizeof(desc)); + PDMDevHlpPhysRead(pDevIns, e1kDescAddr(RDBAH, RDBAL, rxdc.rdh), &desc, sizeof(desc)); if (desc.status.fDD) rc = VERR_NET_NO_BUFFER_SPACE; } - else if (e1kRxDIsCacheEmpty(pThis) && RDH == RDT) + else if (e1kRxDIsCacheEmpty(pThis) && rxdc.rdh == rxdc.rdt) { /* Cache is empty, so is the RX ring. */ rc = VERR_NET_NO_BUFFER_SPACE; } E1kLog2(("%s e1kCanReceive: at exit in_cache=%d RDH=%d RDT=%d RDLEN=%d" " u16RxBSize=%d rc=%Rrc\n", pThis->szPrf, - e1kRxDInCache(pThis), RDH, RDT, RDLEN, pThis->u16RxBSize, rc)); + e1kRxDInCache(pThis), rxdc.rdh, rxdc.rdt, rxdc.rdlen, pThis->u16RxBSize, rc)); e1kCsRxLeave(pThis); return rc; @@ -7466,6 +7760,8 @@ * to clean something in this function */ + /* Mark device as detached. */ + pThis->fIsAttached = false; /* * Zero some important members. */ @@ -7517,6 +7813,8 @@ pThisR0->pDrvR0 = PDMIBASER0_QUERY_INTERFACE(PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMIBASER0), PDMINETWORKUP); pThisRC->pDrvRC = PDMIBASERC_QUERY_INTERFACE(PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMIBASERC), PDMINETWORKUP); #endif + /* Mark device as attached. */ + pThis->fIsAttached = true; } } else if ( rc == VERR_PDM_NO_ATTACHED_DRIVER @@ -7737,6 +8035,7 @@ pThis->u64AckedAt = 0; pThis->led.u32Magic = PDMLED_MAGIC; pThis->u32PktNo = 1; + pThis->fIsAttached = false; pThisCC->pDevInsR3 = pDevIns; pThisCC->pShared = pThis; @@ -8002,6 +8301,8 @@ pThisR0->pDrvR0 = PDMIBASER0_QUERY_INTERFACE(PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMIBASER0), PDMINETWORKUP); pThisRC->pDrvRC = PDMIBASERC_QUERY_INTERFACE(PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMIBASERC), PDMINETWORKUP); #endif + /* Mark device as attached. */ + pThis->fIsAttached = true; } else if ( rc == VERR_PDM_NO_ATTACHED_DRIVER || rc == VERR_PDM_CFG_MISSING_DRIVER_NAME) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DevPCNet.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DevPCNet.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DevPCNet.cpp 2020-10-16 16:35:40.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DevPCNet.cpp 2022-09-01 13:26:25.000000000 +0000 @@ -3267,7 +3267,7 @@ | 0x0008 /* Able to do auto-negotiation. */ | 0x0004 /* Link up. */ | 0x0001; /* Extended Capability, i.e. registers 4+ valid. */ - if (!pThis->fLinkUp || pThis->fLinkTempDown || isolate) { + if (!pcnetIsLinkUp(pThis) || isolate) { val &= ~(0x0020 | 0x0004); pThis->cLinkDownReported++; } @@ -3313,7 +3313,7 @@ case 5: /* Link partner ability register. */ - if (pThis->fLinkUp && !pThis->fLinkTempDown && !isolate) + if (pcnetIsLinkUp(pThis) && !isolate) val = 0x8000 /* Next page bit. */ | 0x4000 /* Link partner acked us. */ | 0x0400 /* Can do flow control. */ @@ -3328,7 +3328,7 @@ case 6: /* Auto negotiation expansion register. */ - if (pThis->fLinkUp && !pThis->fLinkTempDown && !isolate) + if (pcnetIsLinkUp(pThis) && !isolate) val = 0x0008 /* Link partner supports npage. */ | 0x0004 /* Enable npage words. */ | 0x0001; /* Can do N-way auto-negotiation. */ @@ -3341,7 +3341,7 @@ case 18: /* Diagnostic Register (FreeBSD pcn/ac101 driver reads this). */ - if (pThis->fLinkUp && !pThis->fLinkTempDown && !isolate) + if (pcnetIsLinkUp(pThis) && !isolate) { val = 0x0100 /* Receive PLL locked. */ | 0x0200; /* Signal detected. */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DevVirtioNet_1_0.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DevVirtioNet_1_0.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DevVirtioNet_1_0.cpp 2020-10-16 16:35:40.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DevVirtioNet_1_0.cpp 2022-09-01 13:26:25.000000000 +0000 @@ -147,7 +147,7 @@ #define TXQIDX(qPairIdx) (RXQIDX(qPairIdx) + 1) #define CTRLQIDX (FEATURE_ENABLED(MQ) ? ((VIRTIONET_MAX_QPAIRS - 1) * 2 + 2) : 2) -#define IS_LINK_UP(pState) (pState->virtioNetConfig.uStatus & VIRTIONET_F_LINK_UP) +#define IS_LINK_UP(pState) !!(pState->virtioNetConfig.uStatus & VIRTIONET_F_LINK_UP) #define IS_LINK_DOWN(pState) !IS_LINK_UP(pState) #define SET_LINK_UP(pState) \ @@ -303,8 +303,6 @@ typedef struct VIRTIONETVIRTQ { - struct VIRTIONETWORKER *pWorker; /**< Pointer to R0 worker struct */ - struct VIRTIONETWORKERR3 *pWorkerR3; /**< Pointer to R3 worker struct */ uint16_t uIdx; /**< Index of this queue */ uint16_t align; char szName[VIRTIO_MAX_VIRTQ_NAME_SIZE]; /**< Virtq name */ @@ -320,7 +318,6 @@ typedef struct VIRTIONETWORKER { SUPSEMEVENT hEvtProcess; /**< handle of associated sleep/wake-up semaphore */ - PVIRTIONETVIRTQ pVirtq; /**< pointer to queue */ uint16_t uIdx; /**< Index of this worker */ bool volatile fSleeping; /**< Flags whether worker thread is sleeping or not */ bool volatile fNotified; /**< Flags whether worker thread notified */ @@ -336,7 +333,6 @@ typedef struct VIRTIONETWORKERR3 { R3PTRTYPE(PPDMTHREAD) pThread; /**< pointer to worker thread's handle */ - PVIRTIONETVIRTQ pVirtq; /**< pointer to queue */ uint16_t uIdx; /**< Index of this worker */ uint16_t pad; } VIRTIONETWORKERR3; @@ -602,6 +598,8 @@ } } + + /** * @callback_method_impl{VIRTIOCORER0,pfnVirtqNotified} */ @@ -611,10 +609,12 @@ PVIRTIONET pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIONET); PVIRTIONETVIRTQ pVirtq = &pThis->aVirtqs[uVirtqNbr]; - PVIRTIONETWORKER pWorker = pVirtq->pWorker; + PVIRTIONETWORKER pWorker = &pThis->aWorkers[uVirtqNbr]; #if defined (IN_RING3) && defined (LOG_ENABLED) + RTLogFlush(NULL); + #endif if (IS_RX_VIRTQ(uVirtqNbr)) @@ -628,7 +628,7 @@ virtioNetWakeupRxBufWaiter(pDevIns); } else - LogRel(("%s \n\n***WARNING: %s notified but no empty bufs added by guest! (skip notifying of leaf device)\n\n", + Log10Func(("%s \n\n***WARNING: %s notified but no empty bufs added by guest! (skip notifying of leaf device)\n\n", pThis->szInst, pVirtq->szName)); } else if (IS_TX_VIRTQ(uVirtqNbr) || IS_CTRL_VIRTQ(uVirtqNbr)) @@ -639,6 +639,7 @@ if (ASMAtomicReadBool(&pWorker->fSleeping)) { Log10Func(("%s %s has available buffers - waking worker.\n", pThis->szInst, pVirtq->szName)); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pWorker->hEvtProcess); AssertRC(rc); } @@ -837,8 +838,11 @@ if (pVirtq->fHasWorker) { - PVIRTIONETWORKER pWorker = pVirtq->pWorker; - PVIRTIONETWORKERR3 pWorkerR3 = pVirtq->pWorkerR3; + PVIRTIONETWORKER pWorker = &pThis->aWorkers[uVirtqNbr]; + PVIRTIONETWORKERR3 pWorkerR3 = &pThisCC->aWorkers[uVirtqNbr]; + + Assert((pWorker->uIdx == pVirtq->uIdx)); + Assert((pWorkerR3->uIdx == pVirtq->uIdx)); if (pWorker->fAssigned) { @@ -1105,6 +1109,8 @@ pHlp->pfnSSMGetU8( pSSM, &pThis->fNoBroadcast); pHlp->pfnSSMGetU32( pSSM, &pThis->cMulticastFilterMacs); + AssertReturn(pThis->cMulticastFilterMacs <= VIRTIONET_MAC_FILTER_LEN, VERR_OUT_OF_RANGE); + pHlp->pfnSSMGetMem( pSSM, pThis->aMacMulticastFilter, pThis->cMulticastFilterMacs * sizeof(RTMAC)); if (pThis->cMulticastFilterMacs < VIRTIONET_MAC_FILTER_LEN) @@ -1112,6 +1118,8 @@ (VIRTIONET_MAC_FILTER_LEN - pThis->cMulticastFilterMacs) * sizeof(RTMAC)); pHlp->pfnSSMGetU32( pSSM, &pThis->cUnicastFilterMacs); + AssertReturn(pThis->cUnicastFilterMacs <= VIRTIONET_MAC_FILTER_LEN, VERR_OUT_OF_RANGE); + pHlp->pfnSSMGetMem( pSSM, pThis->aMacUnicastFilter, pThis->cUnicastFilterMacs * sizeof(RTMAC)); if (pThis->cUnicastFilterMacs < VIRTIONET_MAC_FILTER_LEN) @@ -1133,7 +1141,7 @@ for (int uIdxWorker = 0; uIdxWorker < pThis->cWorkers; uIdxWorker++) { PVIRTIONETWORKER pWorker = &pThis->aWorkers[uIdxWorker]; - PVIRTIONETVIRTQ pVirtq = pWorker->pVirtq; + PVIRTIONETVIRTQ pVirtq = &pThis->aVirtqs[uIdxWorker]; if (pVirtq->fAttachedToVirtioCore) { Log7Func(("%s Waking %s worker.\n", pThis->szInst, pVirtq->szName)); @@ -1423,6 +1431,9 @@ pGso->offHdr1 = sizeof(RTNETETHERHDR); pGso->cbHdrsTotal = pPktHdr->uHdrLen; pGso->cbMaxSeg = pPktHdr->uGsoSize; + /* Mark GSO frames with zero MSS as PDMNETWORKGSOTYPE_INVALID, so they will be ignored by send. */ + if (pPktHdr->uGsoType != VIRTIONET_HDR_GSO_NONE && pPktHdr->uGsoSize == 0) + pGso->u8Type = PDMNETWORKGSOTYPE_INVALID; return pGso; } @@ -2242,6 +2253,9 @@ ((PPDMNETWORKGSO)pSgBuf->pvUser)->cbHdrsSeg = pGso->cbHdrsSeg; Log4Func(("%s adjusted HdrLen to %d.\n", pThis->szInst, pGso->cbHdrsTotal)); + case PDMNETWORKGSOTYPE_INVALID: + LogFunc(("%s ignoring invalid GSO frame\n", pThis->szInst)); + return VERR_INVALID_PARAMETER; } Log2Func(("%s gso type=%x cbHdrsTotal=%u cbHdrsSeg=%u mss=%u off1=0x%x off2=0x%x\n", pThis->szInst, pGso->u8Type, pGso->cbHdrsTotal, pGso->cbHdrsSeg, @@ -2267,13 +2281,6 @@ PVIRTIOCORE pVirtio = &pThis->Virtio; - /* - * Only one thread is allowed to transmit at a time, others should skip - * transmission as the packets will be picked up by the transmitting - * thread. - */ - if (!ASMAtomicCmpXchgU32(&pThis->uIsTransmitting, 1, 0)) - return; if (!pThis->fVirtioReady) { @@ -2288,6 +2295,14 @@ return; } + /* + * Only one thread is allowed to transmit at a time, others should skip + * transmission as the packets will be picked up by the transmitting + * thread. + */ + if (!ASMAtomicCmpXchgU32(&pThis->uIsTransmitting, 1, 0)) + return; + PPDMINETWORKUP pDrv = pThisCC->pDrv; if (pDrv) { @@ -2472,6 +2487,7 @@ */ static void virtioNetR3TempLinkDown(PPDMDEVINS pDevIns, PVIRTIONET pThis, PVIRTIONETCC pThisCC) { + if (IS_LINK_UP(pThis)) { SET_LINK_DOWN(pThis); @@ -2493,8 +2509,7 @@ PPDMDEVINS pDevIns = pThisCC->pDevIns; PVIRTIONET pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIONET); - bool fCachedLinkIsUp = IS_LINK_UP(pThis); - bool fActiveLinkIsUp = (enmState == PDMNETWORKLINKSTATE_UP); + bool fRequestedLinkStateIsUp = (enmState == PDMNETWORKLINKSTATE_UP); if (LogIs7Enabled()) { @@ -2517,7 +2532,8 @@ if (enmState == PDMNETWORKLINKSTATE_DOWN_RESUME) { - if (fCachedLinkIsUp) + + if (IS_LINK_UP(pThis)) { /* * We bother to bring the link down only if it was up previously. The UP link state @@ -2528,16 +2544,16 @@ pThisCC->pDrv->pfnNotifyLinkChanged(pThisCC->pDrv, enmState); } } - else if (fActiveLinkIsUp != fCachedLinkIsUp) + else if (fRequestedLinkStateIsUp != IS_LINK_UP(pThis)) { - if (fCachedLinkIsUp) + if (fRequestedLinkStateIsUp) { Log(("%s Link is up\n", pThis->szInst)); pThis->fCableConnected = true; SET_LINK_UP(pThis); virtioCoreNotifyConfigChanged(&pThis->Virtio); } - else /* cached Link state is down */ + else /* Link requested to be brought down */ { /* The link was brought down explicitly, make sure it won't come up by timer. */ PDMDevHlpTimerStop(pDevIns, pThisCC->hLinkUpTimer); @@ -2588,7 +2604,7 @@ return rc; } -static int virtioNetR3CreateOneWorkerThread(PPDMDEVINS pDevIns, PVIRTIONET pThis, uint16_t uIdxWorker, +static int virtioNetR3CreateOneWorkerThread(PPDMDEVINS pDevIns, PVIRTIONET pThis, PVIRTIONETWORKER pWorker, PVIRTIONETWORKERR3 pWorkerR3, PVIRTIONETVIRTQ pVirtq) { @@ -2596,6 +2612,8 @@ RT_NOREF(pThis); int rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pWorker->hEvtProcess); + LogFunc(("PDMDevHlpSUPSemEventCreate(pDevIns, &pWorker->hEvtProcess=%p)", &pWorker->hEvtProcess)); + LogFunc(("pWorker->hEvtProcess = %x\n", pWorker->hEvtProcess)); if (RT_FAILURE(rc)) return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, @@ -2610,10 +2628,6 @@ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("Error creating thread for Virtual Virtq %s\n"), pVirtq->uIdx); - pWorker->pVirtq = pWorkerR3->pVirtq = pVirtq; - pWorker->uIdx = pWorkerR3->uIdx = uIdxWorker; - pVirtq->pWorker = pWorker; - pVirtq->pWorkerR3 = pWorkerR3; pWorker->fAssigned = true; /* Because worker's state held in fixed-size array w/empty slots */ LogFunc(("%s pThread: %p\n", pVirtq->szName, pWorkerR3->pThread)); @@ -2624,25 +2638,23 @@ static int virtioNetR3CreateWorkerThreads(PPDMDEVINS pDevIns, PVIRTIONET pThis, PVIRTIONETCC pThisCC) { -#define CTRLWIDX 0 /* First worker is for the control queue */ Log10Func(("%s\n", pThis->szInst)); PVIRTIONETVIRTQ pCtlVirtq = &pThis->aVirtqs[CTRLQIDX]; - int rc = virtioNetR3CreateOneWorkerThread(pDevIns, pThis, CTRLQIDX /* uIdxWorker */, - &pThis->aWorkers[CTRLWIDX], &pThisCC->aWorkers[CTRLWIDX], pCtlVirtq); + int rc = virtioNetR3CreateOneWorkerThread(pDevIns, pThis, + &pThis->aWorkers[CTRLQIDX], &pThisCC->aWorkers[CTRLQIDX], pCtlVirtq); AssertRCReturn(rc, rc); pCtlVirtq->fHasWorker = true; - uint16_t uIdxWorker = CTRLWIDX + 1; - for (uint16_t uVirtqPair = pThis->cInitializedVirtqPairs; uVirtqPair < pThis->cVirtqPairs; uVirtqPair++, uIdxWorker++) + for (uint16_t uVirtqPair = pThis->cInitializedVirtqPairs; uVirtqPair < pThis->cVirtqPairs; uVirtqPair++) { PVIRTIONETVIRTQ pTxVirtq = &pThis->aVirtqs[TXQIDX(uVirtqPair)]; PVIRTIONETVIRTQ pRxVirtq = &pThis->aVirtqs[RXQIDX(uVirtqPair)]; - rc = virtioNetR3CreateOneWorkerThread(pDevIns, pThis, uIdxWorker, &pThis->aWorkers[uIdxWorker], - &pThisCC->aWorkers[uIdxWorker], pTxVirtq); + rc = virtioNetR3CreateOneWorkerThread(pDevIns, pThis, &pThis->aWorkers[TXQIDX(uVirtqPair)], + &pThisCC->aWorkers[TXQIDX(uVirtqPair)], pTxVirtq); AssertRCReturn(rc, rc); pTxVirtq->fHasWorker = true; @@ -2664,29 +2676,36 @@ PVIRTIONET pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIONET); PVIRTIONETCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIONETCC); PVIRTIONETWORKER pWorker = (PVIRTIONETWORKER)pThread->pvUser; - PVIRTIONETVIRTQ pVirtq = pWorker->pVirtq; + PVIRTIONETVIRTQ pVirtq = &pThis->aVirtqs[pWorker->uIdx]; + uint16_t uIdx = pWorker->uIdx; + + ASMAtomicWriteBool(&pWorker->fSleeping, false); + + Assert(pWorker->uIdx == pVirtq->uIdx); if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) return VINF_SUCCESS; - LogFunc(("%s worker thread started for %s\n", pThis->szInst, pVirtq->szName)); + LogFunc(("%s worker thread idx=%d started for %s (virtq idx=%d)\n", pThis->szInst, pWorker->uIdx, pVirtq->szName, pVirtq->uIdx)); /** @todo Race w/guest enabling/disabling guest notifications cyclically. See BugRef #8651, Comment #82 */ - virtioCoreVirtqEnableNotify(&pThis->Virtio, pVirtq->uIdx, true /* fEnable */); + virtioCoreVirtqEnableNotify(&pThis->Virtio, uIdx, true /* fEnable */); while ( pThread->enmState != PDMTHREADSTATE_TERMINATING && pThread->enmState != PDMTHREADSTATE_TERMINATED) { - if (IS_VIRTQ_EMPTY(pDevIns, &pThis->Virtio, pVirtq->uIdx)) + if (IS_VIRTQ_EMPTY(pDevIns, &pThis->Virtio, pVirtq->uIdx)) { /* Atomic interlocks avoid missing alarm while going to sleep & notifier waking the awoken */ ASMAtomicWriteBool(&pWorker->fSleeping, true); + bool fNotificationSent = ASMAtomicXchgBool(&pWorker->fNotified, false); if (!fNotificationSent) { Log10Func(("%s %s worker sleeping...\n\n", pThis->szInst, pVirtq->szName)); Assert(ASMAtomicReadBool(&pWorker->fSleeping)); + int rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pWorker->hEvtProcess, RT_INDEFINITE_WAIT); STAM_COUNTER_INC(&pThis->StatTransmitByThread); AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc); @@ -2697,6 +2716,7 @@ ASMAtomicWriteBool(&pWorker->fNotified, false); } ASMAtomicWriteBool(&pWorker->fSleeping, false); + } /* Dispatch to the handler for the queue this worker is set up to drive */ @@ -2759,7 +2779,13 @@ for (unsigned uVirtqNbr = 0; uVirtqNbr < pThis->cVirtVirtqs; uVirtqNbr++) { PVIRTIONETVIRTQ pVirtq = &pThis->aVirtqs[uVirtqNbr]; - pVirtq->uIdx = uVirtqNbr; + PVIRTIONETWORKER pWorker = &pThis->aWorkers[uVirtqNbr]; + + Assert(pWorker->uIdx == uVirtqNbr); + Assert(pVirtq->uIdx == pWorker->uIdx); + + RT_NOREF(pWorker); + (void) virtioCoreR3VirtqAttach(&pThis->Virtio, pVirtq->uIdx, pVirtq->szName); pVirtq->fAttachedToVirtioCore = true; if (IS_VIRTQ_EMPTY(pThisCC->pDevIns, &pThis->Virtio, pVirtq->uIdx)) @@ -3031,6 +3057,15 @@ */ virtioNetR3SetVirtqNames(pThis); pThis->aVirtqs[CTRLQIDX].fCtlVirtq = true; + for (unsigned uVirtqNbr = 0; uVirtqNbr < pThis->cVirtVirtqs; uVirtqNbr++) + { + PVIRTIONETVIRTQ pVirtq = &pThis->aVirtqs[uVirtqNbr]; + PVIRTIONETWORKER pWorker = &pThis->aWorkers[uVirtqNbr]; + PVIRTIONETWORKERR3 pWorkerR3 = &pThisCC->aWorkers[uVirtqNbr]; + pVirtq->uIdx = uVirtqNbr; + pWorker->uIdx = uVirtqNbr; + pWorkerR3->uIdx = uVirtqNbr; + } /* * Create queue workers for life of instance. (I.e. they persist through VirtIO bounces) @@ -3039,6 +3074,7 @@ if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to create worker threads")); + /* * Attach network driver instance */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DevVirtioNet.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DevVirtioNet.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DevVirtioNet.cpp 2020-10-16 16:35:40.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DevVirtioNet.cpp 2022-09-01 13:26:25.000000000 +0000 @@ -338,7 +338,6 @@ vpciCsLeave(pDevIns, &pThis->VPCI); } -#endif /* IN_RING3 */ DECLINLINE(int) vnetCsRxEnter(PVNETSTATE pThis, int rcBusy) { @@ -357,7 +356,25 @@ // PDMCritSectLeave(&pThis->csRx); } -#ifdef IN_RING3 +/** + * A helper function to detect the link state to the other side of "the wire". + * + * When deciding to bring up the link we need to take into account both if the + * cable is connected and if our device is actually connected to the outside + * world. If no driver is attached we won't start the TX thread nor we will + * initialize the TX semaphore, which is a problem for the TX queue handler. + * + * @returns true if the device is connected to something. + * + * @param pDevIns The device instance. + */ +DECLINLINE(bool) vnetR3IsConnected(PPDMDEVINS pDevIns) +{ + PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); + PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); + return pThis->fCableConnected && pThisCC->pDrv; +} + /** * Dump a packet to debug log. * @@ -523,10 +540,13 @@ */ static DECLCALLBACK(int) vnetIoCb_Reset(PPDMDEVINS pDevIns) { +#ifndef IN_RING3 + RT_NOREF(pDevIns); + return VINF_IOM_R3_IOPORT_WRITE; +#else PVNETSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PVNETSTATE); -#ifdef IN_RING3 PVNETSTATECC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVNETSTATECC); -#endif + Log(("%s Reset triggered\n", INSTANCE(pThis))); int rc = vnetCsRxEnter(pThis, VERR_SEM_BUSY); @@ -539,11 +559,11 @@ vnetCsRxLeave(pThis); /// @todo Implement reset - if (pThis->fCableConnected) + if (vnetR3IsConnected(pDevIns)) pThis->config.uStatus = VNET_S_LINK_UP; else pThis->config.uStatus = 0; - Log(("%s vnetIoCb_Reset: Link is %s\n", INSTANCE(pThis), pThis->fCableConnected ? "up" : "down")); + Log(("%s vnetIoCb_Reset: Link is %s\n", INSTANCE(pThis), vnetR3IsConnected(pDevIns) ? "up" : "down")); /* * By default we pass all packets up since the older guests cannot control @@ -555,9 +575,6 @@ memset(pThis->aMacFilter, 0, VNET_MAC_FILTER_LEN * sizeof(RTMAC)); memset(pThis->aVlanFilter, 0, sizeof(pThis->aVlanFilter)); pThis->uIsTransmitting = 0; -#ifndef IN_RING3 - return VINF_IOM_R3_IOPORT_WRITE; -#else if (pThisCC->pDrv) pThisCC->pDrv->pfnSetPromiscuousMode(pThisCC->pDrv, true); return VINF_SUCCESS; @@ -633,9 +650,14 @@ int rc = vnetR3CsEnter(pDevIns, pThis, VERR_SEM_BUSY); AssertRCReturnVoid(rc); - pThis->config.uStatus |= VNET_S_LINK_UP; - vnetR3RaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, VPCI_ISR_CONFIG); - vnetWakeupReceive(pDevIns); + Log(("%s vnetR3LinkUpTimer: connected=%s\n", INSTANCE(pThis), vnetR3IsConnected(pDevIns)?"true":"false")); + /* Do not bring up the link if the device is not connected. */ + if (vnetR3IsConnected(pDevIns)) + { + pThis->config.uStatus |= VNET_S_LINK_UP; + vnetR3RaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, VPCI_ISR_CONFIG); + vnetWakeupReceive(pDevIns); + } vnetR3CsLeave(pDevIns, pThis); @@ -1130,10 +1152,14 @@ { if (fNewUp) { - Log(("%s Link is up\n", INSTANCE(pThis))); pThis->fCableConnected = true; - pThis->config.uStatus |= VNET_S_LINK_UP; - vnetR3RaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, VPCI_ISR_CONFIG); + /* The link state depends both on the cable connected and device attached. */ + if (vnetR3IsConnected(pDevIns)) + { + Log(("%s Link is up\n", INSTANCE(pThis))); + pThis->config.uStatus |= VNET_S_LINK_UP; + vnetR3RaiseInterrupt(pDevIns, pThis, VERR_SEM_BUSY, VPCI_ISR_CONFIG); + } } else { @@ -1203,6 +1229,9 @@ pGso->offHdr1 = sizeof(RTNETETHERHDR); pGso->cbHdrsTotal = pHdr->u16HdrLen; pGso->cbMaxSeg = pHdr->u16GSOSize; + /* Mark GSO frames with zero MSS as PDMNETWORKGSOTYPE_INVALID, so they will be ignored by send. */ + if (pHdr->u8GSOType != VNETHDR_GSO_NONE && pHdr->u16GSOSize == 0) + pGso->u8Type = PDMNETWORKGSOTYPE_INVALID; return pGso; } @@ -1302,6 +1331,9 @@ pGso->cbHdrsTotal = (uint8_t)(pHdr->u16CSumStart + sizeof(RTNETUDP)); pGso->cbHdrsSeg = pHdr->u16CSumStart; break; + case PDMNETWORKGSOTYPE_INVALID: + LogFunc(("%s ignoring invalid GSO frame\n", INSTANCE(pThis))); + return VERR_INVALID_PARAMETER; } /* Update GSO structure embedded into the frame */ ((PPDMNETWORKGSO)pSgBuf->pvUser)->cbHdrsTotal = pGso->cbHdrsTotal; @@ -1330,25 +1362,29 @@ PVQUEUE pQueue, bool fOnWorkerThread) { /* - * Only one thread is allowed to transmit at a time, others should skip - * transmission as the packets will be picked up by the transmitting - * thread. + * Let's deal with the cases we are not going to transmit anything, + * to avoid setting 'uIsTransmitting' on and off. */ - if (!ASMAtomicCmpXchgU32(&pThis->uIsTransmitting, 1, 0)) - return; - if ((pThis->VPCI.uStatus & VPCI_STATUS_DRV_OK) == 0) { Log(("%s Ignoring transmit requests from non-existent driver (status=0x%x).\n", INSTANCE(pThis), pThis->VPCI.uStatus)); return; } - if (!pThis->fCableConnected) + if (!vnetR3IsConnected(pDevIns)) { Log(("%s Ignoring transmit requests while cable is disconnected.\n", INSTANCE(pThis))); return; } + /* + * Only one thread is allowed to transmit at a time, others should skip + * transmission as the packets will be picked up by the transmitting + * thread. + */ + if (!ASMAtomicCmpXchgU32(&pThis->uIsTransmitting, 1, 0)) + return; + PPDMINETWORKUP pDrv = pThisCC->pDrv; if (pDrv) { @@ -1571,7 +1607,12 @@ vnetR3TransmitPendingPackets(pDevIns, pThis, pThisCC, pThisCC->pTxQueue, false /*fOnWorkerThread*/); /// @todo shouldn't it be true instead? Log(("vnetR3TxThread: enable kicking and get to sleep\n")); vringSetNotification(pDevIns, &pThisCC->pTxQueue->VRing, true); - if (vqueueIsEmpty(pDevIns, pThisCC->pTxQueue)) + /* + * Break out of the loop if the device is not connected. Otherwise we will + * end up in a tight loop, not being able to transmit, if there is something + * in TX queue. See @bugref{10096}. + */ + if (vqueueIsEmpty(pDevIns, pThisCC->pTxQueue) || !vnetR3IsConnected(pDevIns)) break; vringSetNotification(pDevIns, &pThisCC->pTxQueue->VRing, false); } @@ -1978,6 +2019,7 @@ pThisCC->pDrv->pfnSetPromiscuousMode(pThisCC->pDrv, true); } } + Log(("%s State has been restored\n", INSTANCE(pThis))); return rc; } @@ -2000,7 +2042,16 @@ * been lost, unless we've been teleported here. */ if (!PDMDevHlpVMTeleportedAndNotFullyResumedYet(pDevIns)) + { + /* + * Since we do not restore the link state, we pretend it is up, so it will be + * lowered by vnetR3TempLinkDown, and the guest will be notified. The actual state + * of the link will be determined later by vnetR3IsConnected in vnetR3LinkUpTimer. + * See @bugref{10096}. + */ + pThis->config.uStatus |= VNET_S_LINK_UP; vnetR3TempLinkDown(pDevIns, pThis, pThisCC); + } return VINF_SUCCESS; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DrvNAT.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DrvNAT.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/DrvNAT.cpp 2020-10-16 16:35:41.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/DrvNAT.cpp 2022-09-01 13:26:25.000000000 +0000 @@ -438,30 +438,34 @@ #endif uint8_t const *pbFrame = (uint8_t const *)pSgBuf->aSegs[0].pvSeg; PCPDMNETWORKGSO pGso = (PCPDMNETWORKGSO)pSgBuf->pvUser; - uint32_t const cSegs = PDMNetGsoCalcSegmentCount(pGso, pSgBuf->cbUsed); Assert(cSegs > 1); - for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + /* Do not attempt to segment frames with invalid GSO parameters. */ + if (PDMNetGsoIsValid(pGso, sizeof(*pGso), pSgBuf->cbUsed)) { - size_t cbSeg; - void *pvSeg; - m = slirp_ext_m_get(pThis->pNATState, pGso->cbHdrsTotal + pGso->cbMaxSeg, &pvSeg, &cbSeg); - if (!m) - break; + uint32_t const cSegs = PDMNetGsoCalcSegmentCount(pGso, pSgBuf->cbUsed); Assert(cSegs > 1); + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + size_t cbSeg; + void *pvSeg; + m = slirp_ext_m_get(pThis->pNATState, pGso->cbHdrsTotal + pGso->cbMaxSeg, &pvSeg, &cbSeg); + if (!m) + break; #if 1 - uint32_t cbPayload, cbHdrs; - uint32_t offPayload = PDMNetGsoCarveSegment(pGso, pbFrame, pSgBuf->cbUsed, - iSeg, cSegs, (uint8_t *)pvSeg, &cbHdrs, &cbPayload); - memcpy((uint8_t *)pvSeg + cbHdrs, pbFrame + offPayload, cbPayload); + uint32_t cbPayload, cbHdrs; + uint32_t offPayload = PDMNetGsoCarveSegment(pGso, pbFrame, pSgBuf->cbUsed, + iSeg, cSegs, (uint8_t *)pvSeg, &cbHdrs, &cbPayload); + memcpy((uint8_t *)pvSeg + cbHdrs, pbFrame + offPayload, cbPayload); - slirp_input(pThis->pNATState, m, cbPayload + cbHdrs); + slirp_input(pThis->pNATState, m, cbPayload + cbHdrs); #else - uint32_t cbSegFrame; - void *pvSegFrame = PDMNetGsoCarveSegmentQD(pGso, (uint8_t *)pbFrame, pSgBuf->cbUsed, abHdrScratch, - iSeg, cSegs, &cbSegFrame); - memcpy((uint8_t *)pvSeg, pvSegFrame, cbSegFrame); + uint32_t cbSegFrame; + void *pvSegFrame = PDMNetGsoCarveSegmentQD(pGso, (uint8_t *)pbFrame, pSgBuf->cbUsed, abHdrScratch, + iSeg, cSegs, &cbSegFrame); + memcpy((uint8_t *)pvSeg, pvSegFrame, cbSegFrame); - slirp_input(pThis->pNATState, m, cbSegFrame); + slirp_input(pThis->pNATState, m, cbSegFrame); #endif + } } } } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/lwip-new/vbox/VBoxLwipCore.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/lwip-new/vbox/VBoxLwipCore.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/lwip-new/vbox/VBoxLwipCore.h 2020-10-16 16:35:44.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/lwip-new/vbox/VBoxLwipCore.h 2022-09-01 13:26:29.000000000 +0000 @@ -23,7 +23,7 @@ #endif -typedef DECLCALLBACKPTR(void, PFNRT1)(void *); +typedef DECLCALLBACKPTR(void, PFNRT1,(void *)); /** * initiliazes LWIP core, and do callback on tcp/ip thread diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/hostres.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/hostres.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/hostres.c 2020-10-16 16:35:45.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/hostres.c 2022-09-01 13:26:29.000000000 +0000 @@ -512,7 +512,7 @@ && qclass != Class_ANY) { LogErr(("NAT: hostres: unsupported qclass %d\n", qclass)); - return refuse(res, RCode_NXDomain); + return refuse(res, RCode_NoError); } if ( qtype != Type_A @@ -521,7 +521,7 @@ && qtype != Type_ANY) { LogErr(("NAT: hostres: unsupported qtype %d\n", qtype)); - return refuse(res, RCode_NXDomain); + return refuse(res, RCode_NoError); } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/ip_icmpwin.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/ip_icmpwin.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/ip_icmpwin.c 2020-10-16 16:35:45.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/ip_icmpwin.c 2022-09-01 13:26:29.000000000 +0000 @@ -45,7 +45,10 @@ TAILQ_ENTRY(pong) queue_entry; - struct ip reqiph; + union { + struct ip ip; + uint8_t au[60]; + } reqiph; struct icmp_echo reqicmph; size_t bufsize; @@ -141,11 +144,13 @@ IP_OPTION_INFORMATION opts; void *reqdata; int status; + size_t hdrsize; ttl = ip->ip_ttl; AssertReturnVoid(ttl > 0); - reqsize = ip->ip_len - hlen - sizeof(struct icmp_echo); + hdrsize = hlen + sizeof(struct icmp_echo); + reqsize = ip->ip_len - hdrsize; bufsize = sizeof(ICMP_ECHO_REPLY); if (reqsize < sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo)) @@ -171,14 +176,13 @@ if (m->m_next == NULL) { /* already in single contiguous buffer */ - reqdata = mtod(m, char *) + sizeof(struct ip) + sizeof(struct icmp_echo); + reqdata = mtod(m, char *) + hdrsize; } else { /* use reply buffer as temporary storage */ reqdata = pong->buf; - m_copydata(m, sizeof(struct ip) + sizeof(struct icmp_echo), - (int)reqsize, reqdata); + m_copydata(m, (int)hdrsize, (int)reqsize, reqdata); } dst = ip->ip_dst.s_addr; @@ -397,7 +401,7 @@ ip->ip_ttl = reply->Options.Ttl; ip->ip_p = IPPROTO_ICMP; ip->ip_src.s_addr = reply->Address; - ip->ip_dst = pong->reqiph.ip_src; + ip->ip_dst = pong->reqiph.ip.ip_src; icmp->icmp_type = ICMP_ECHOREPLY; icmp->icmp_code = 0; @@ -475,10 +479,12 @@ struct ip *ip; struct icmp_echo *icmp; size_t reqsize; + size_t reqhlen; Log2(("NAT: ping error type %d/code %d\n", type, code)); - reqsize = sizeof(pong->reqiph) + sizeof(pong->reqicmph); + reqhlen = pong->reqiph.ip.ip_hl << 2; + reqsize = reqhlen + sizeof(pong->reqicmph); m = icmpwin_get_mbuf(pData, reqsize); if (m == NULL) @@ -493,7 +499,7 @@ ip->ip_ttl = IPDEFTTL; ip->ip_p = IPPROTO_ICMP; ip->ip_src.s_addr = 0; /* NB */ - ip->ip_dst = pong->reqiph.ip_src; + ip->ip_dst = pong->reqiph.ip.ip_src; icmp->icmp_type = type; icmp->icmp_code = code; @@ -501,7 +507,8 @@ icmp->icmp_echo_id = 0; icmp->icmp_echo_seq = 0; - m_append(pData, m, sizeof(pong->reqiph), (caddr_t)&pong->reqiph); + /* payload: the IP and ICMP headers of the original request */ + m_append(pData, m, (int)reqhlen, (caddr_t)&pong->reqiph); m_append(pData, m, sizeof(pong->reqicmph), (caddr_t)&pong->reqicmph); icmp->icmp_cksum = in_cksum_skip(m, ip->ip_len, sizeof(*ip)); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/misc.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/misc.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/misc.c 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/misc.c 2022-09-01 13:26:30.000000000 +0000 @@ -499,6 +499,9 @@ int size = MCLBYTES; LogFlowFunc(("ENTER: cbMin:%d, ppvBuf:%p, pcbBuf:%p\n", cbMin, ppvBuf, pcbBuf)); + *ppvBuf = NULL; + *pcbBuf = 0; + if (cbMin < MCLBYTES) size = MCLBYTES; else if (cbMin < MJUM9BYTES) @@ -506,13 +509,15 @@ else if (cbMin < MJUM16BYTES) size = MJUM16BYTES; else - AssertMsgFailed(("Unsupported size")); + { + AssertMsgFailed(("Unsupported size %zu", cbMin)); + LogFlowFunc(("LEAVE: NULL (bad size %zu)\n", cbMin)); + return NULL; + } m = m_getjcl(pData, M_NOWAIT, MT_HEADER, M_PKTHDR, size); if (m == NULL) { - *ppvBuf = NULL; - *pcbBuf = 0; LogFlowFunc(("LEAVE: NULL\n")); return NULL; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/sbuf.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/sbuf.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/sbuf.c 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/sbuf.c 2022-09-01 13:26:30.000000000 +0000 @@ -80,7 +80,7 @@ { sb->sb_wptr = sb->sb_rptr = - sb->sb_data = (char *)RTMemRealloc(sb->sb_data, size); + sb->sb_data = (char *)RTMemReallocZ(sb->sb_data, sb->sb_datalen, size); sb->sb_cc = 0; if (sb->sb_wptr) sb->sb_datalen = size; @@ -90,7 +90,7 @@ } else { - sb->sb_wptr = sb->sb_rptr = sb->sb_data = (char *)RTMemAlloc(size); + sb->sb_wptr = sb->sb_rptr = sb->sb_data = (char *)RTMemAllocZ(size); sb->sb_cc = 0; if (sb->sb_wptr) sb->sb_datalen = size; @@ -146,7 +146,7 @@ if (m->m_next) { - buf = RTMemAlloc(mlen); + buf = RTMemAllocZ(mlen); if (buf == NULL) { ret = 0; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/slirp.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/slirp.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/slirp.c 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/slirp.c 2022-09-01 13:26:30.000000000 +0000 @@ -1253,10 +1253,13 @@ struct arphdr { unsigned short ar_hrd; /* format of hardware address */ +#define ARPHRD_ETHER 1 /* ethernet hardware format */ unsigned short ar_pro; /* format of protocol address */ unsigned char ar_hln; /* length of hardware address */ unsigned char ar_pln; /* length of protocol address */ unsigned short ar_op; /* ARP opcode (command) */ +#define ARPOP_REQUEST 1 /* ARP request */ +#define ARPOP_REPLY 2 /* ARP reply */ /* * Ethernet looks like this : This bit is variable sized however... @@ -1322,12 +1325,23 @@ { struct ethhdr *pEtherHeader; struct arphdr *pARPHeader; + int ar_op; uint32_t ip4TargetAddress; - int ar_op; + /* drivers never return runt packets, so this should never happen */ + if (RT_UNLIKELY((size_t)m->m_len + < sizeof(struct ethhdr) + sizeof(struct arphdr))) + goto done; + pEtherHeader = mtod(m, struct ethhdr *); pARPHeader = (struct arphdr *)&pEtherHeader[1]; + if (RT_UNLIKELY( pARPHeader->ar_hrd != RT_H2N_U16_C(ARPHRD_ETHER) + || pARPHeader->ar_pro != RT_H2N_U16_C(ETH_P_IP) + || pARPHeader->ar_hln != ETH_ALEN + || pARPHeader->ar_pln != sizeof(RTNETADDRIPV4))) + goto done; + ar_op = RT_N2H_U16(pARPHeader->ar_op); ip4TargetAddress = *(uint32_t*)pARPHeader->ar_tip; @@ -1364,6 +1378,7 @@ break; } + done: m_freem(pData, m); } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/slirp_config.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/slirp_config.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/slirp_config.h 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/slirp_config.h 2022-09-01 13:26:30.000000000 +0000 @@ -210,3 +210,6 @@ /* Define if you have */ #undef HAVE_SYS_TYPES32_H +#ifdef RT_OS_SOLARIS +# define HAVE_SYS_TYPES32_H +#endif diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/slirp.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/slirp.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/slirp.h 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/slirp.h 2022-09-01 13:26:30.000000000 +0000 @@ -389,9 +389,6 @@ # define ETH_ALEN 6 # define ETH_HLEN 14 -# define ARPOP_REQUEST 1 /* ARP request */ -# define ARPOP_REPLY 2 /* ARP reply */ - struct ethhdr { unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/socket.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/socket.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/socket.h 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/socket.h 2022-09-01 13:26:30.000000000 +0000 @@ -59,6 +59,8 @@ struct tcpiphdr *so_ti; /* Pointer to the original ti within * so_mconn, for non-blocking connections */ uint8_t *so_ohdr; /* unmolested IP header of the datagram in so_m */ + caddr_t so_optp; /* tcp options in so_m */ + int so_optlen; /* length of options in so_m */ int so_urgc; struct in_addr so_faddr; /* foreign host table entry */ struct in_addr so_laddr; /* local host table entry */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/tcp_input.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/tcp_input.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/tcp_input.c 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/tcp_input.c 2022-09-01 13:26:30.000000000 +0000 @@ -323,10 +323,16 @@ { so = inso; Log4(("NAT: tcp_input: %R[natsock]\n", so)); + /* Re-set a few variables */ tp = sototcpcb(so); + m = so->so_m; - so->so_m = 0; + optp = so->so_optp; /* points into m if set */ + optlen = so->so_optlen; + so->so_m = NULL; + so->so_optp = 0; + so->so_optlen = 0; if (RT_LIKELY(so->so_ohdr != NULL)) { @@ -825,6 +831,8 @@ so->so_m = m; so->so_ti = ti; so->so_ohdr = RTMemDup(ohdr, ohdrlen); + so->so_optp = optp; + so->so_optlen = optlen; tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; TCP_STATE_SWITCH_TO(tp, TCPS_SYN_RECEIVED); } @@ -2014,7 +2022,8 @@ struct socket *so = tp->t_socket; int mss; - LogFlowFunc(("ENTER: tcp_mss: tp = %R[tcpcb793], offer = %d\n", tp, offer)); + LogFlowFunc(("ENTER: tcp_mss: offer=%u, t_maxseg=%u; tp=%R[natsock]\n", + offer, (unsigned int)tp->t_maxseg, so)); mss = min(if_mtu, if_mru) - sizeof(struct tcpiphdr); if (offer) @@ -2028,7 +2037,6 @@ sbreserve(pData, &so->so_snd, tcp_sndspace+((tcp_sndspace%mss)?(mss-(tcp_sndspace%mss)):0)); sbreserve(pData, &so->so_rcv, tcp_rcvspace+((tcp_rcvspace%mss)?(mss-(tcp_rcvspace%mss)):0)); - Log2((" returning mss = %d\n", mss)); - + LogFlowFunc(("LEAVE: mss=%d\n", mss)); return mss; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/tftp.c virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/tftp.c --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/tftp.c 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/tftp.c 2022-09-01 13:26:31.000000000 +0000 @@ -64,7 +64,6 @@ typedef struct TFTPSESSION { int fInUse; - unsigned char pszFilename[TFTP_FILENAME_MAX]; struct in_addr IpClientAddress; uint16_t u16ClientPort; int iTimestamp; @@ -74,6 +73,9 @@ TFPTPSESSIONOPTDESC OptionBlkSize; TFPTPSESSIONOPTDESC OptionTSize; TFPTPSESSIONOPTDESC OptionTimeout; + + const char *pcszFilenameHost; + char szFilename[TFTP_FILENAME_MAX]; } TFTPSESSION, *PTFTPSESSION, **PPTFTPSESSION; #pragma pack(1) @@ -133,31 +135,57 @@ /** - * This function evaluate file name. - * @param pu8Payload - * @param cbPayload - * @param cbFileName - * @return VINF_SUCCESS - - * VERR_INVALID_PARAMETER - + * This function resolves file name relative to tftp prefix. + * @param pData + * @param pTftpSession */ -DECLINLINE(int) tftpSecurityFilenameCheck(PNATState pData, PCTFTPSESSION pcTftpSession) +DECLINLINE(int) tftpSecurityFilenameCheck(PNATState pData, PTFTPSESSION pTftpSession) { - int rc = VINF_SUCCESS; - AssertPtrReturn(pcTftpSession, VERR_INVALID_PARAMETER); + int rc = VERR_FILE_NOT_FOUND; /* guilty until proved innocent */ - /* only allow exported prefixes */ - if (!tftp_prefix) - rc = VERR_INTERNAL_ERROR; - else - { - char *pszFullPathAbs = RTPathAbsExDup(tftp_prefix, (const char *)pcTftpSession->pszFilename, RTPATH_STR_F_STYLE_HOST); + char *s; + const char *dotdot; + char *pszPathHostAbs; + int cbLen; - if ( !pszFullPathAbs - || !RTPathStartsWith(pszFullPathAbs, tftp_prefix)) - rc = VERR_FILE_NOT_FOUND; + AssertPtrReturn(pTftpSession, VERR_INVALID_PARAMETER); + AssertReturn(pTftpSession->pcszFilenameHost == NULL, VERR_INVALID_PARAMETER); - RTStrFree(pszFullPathAbs); - } + /* prefix must be set to an absolute pathname. assert? */ + if (tftp_prefix == NULL || RTPathSkipRootSpec(tftp_prefix) == tftp_prefix) + goto done; + + /* replace backslashes with forward slashes */ + s = pTftpSession->szFilename; + while ((s = strchr(s, '\\')) != NULL) + *s++ = '/'; + + /* deny dot-dot by itself or at the beginning */ + if ( pTftpSession->szFilename[0] == '.' + && pTftpSession->szFilename[1] == '.' + && ( pTftpSession->szFilename[2] == '\0' + || pTftpSession->szFilename[2] == '/')) + goto done; + + /* deny dot-dot in the middle */ + if (RTStrStr(pTftpSession->szFilename, "/../") != NULL) + goto done; + + /* deny dot-dot at the end (there's no RTStrEndsWith) */ + dotdot = RTStrStr(pTftpSession->szFilename, "/.."); + if (dotdot != NULL && dotdot[3] == '\0') + goto done; + + cbLen = RTStrAPrintf(&pszPathHostAbs, "%s/%s", + tftp_prefix, pTftpSession->szFilename); + if (cbLen == -1) + goto done; + + LogRel2(("NAT: TFTP: %s\n", pszPathHostAbs)); + pTftpSession->pcszFilenameHost = pszPathHostAbs; + rc = VINF_SUCCESS; + + done: LogFlowFuncLeaveRC(rc); return rc; } @@ -252,6 +280,12 @@ DECLINLINE(void) tftpSessionTerminate(PTFTPSESSION pTftpSession) { + if (pTftpSession->pcszFilenameHost != NULL) + { + RTStrFree((char *)pTftpSession->pcszFilenameHost); + pTftpSession->pcszFilenameHost = NULL; + } + pTftpSession->fInUse = 0; } @@ -288,9 +322,9 @@ else break; - if (RTStrNLen((char *)pTftpSession->pszFilename, TFTP_FILENAME_MAX) == 0) + if (RTStrNLen(pTftpSession->szFilename, TFTP_FILENAME_MAX) == 0) { - rc = RTStrCopy((char *)pTftpSession->pszFilename, TFTP_FILENAME_MAX, pszTftpRRQRaw); + rc = RTStrCopy(pTftpSession->szFilename, TFTP_FILENAME_MAX, pszTftpRRQRaw); if (RT_FAILURE(rc)) { LogFlowFuncLeaveRC(rc); @@ -357,6 +391,8 @@ PTFTPSESSION pTftpSession = NULL; int rc = VINF_SUCCESS; int idxSession; + const char *pszPrefix; + AssertPtrReturn(pData, VERR_INVALID_PARAMETER); AssertPtrReturn(pcTftpIpHeader, VERR_INVALID_PARAMETER); AssertPtrReturn(ppTftpSession, VERR_INVALID_PARAMETER); @@ -376,13 +412,40 @@ return VERR_NOT_FOUND; found: - memset(pTftpSession, 0, sizeof(*pTftpSession)); + if (pTftpSession->pcszFilenameHost != NULL) + { + RTStrFree((char *)pTftpSession->pcszFilenameHost); +#if 0 + pTftpSession->pcszFilenameHost = NULL; /* will be zeroed out below */ +#endif + } + RT_ZERO(*pTftpSession); + memcpy(&pTftpSession->IpClientAddress, &pcTftpIpHeader->IPv4Hdr.ip_src, sizeof(pTftpSession->IpClientAddress)); pTftpSession->u16ClientPort = pcTftpIpHeader->UdpHdr.uh_sport; rc = tftpSessionOptionParse(pTftpSession, pcTftpIpHeader); AssertRCReturn(rc, VERR_INTERNAL_ERROR); *ppTftpSession = pTftpSession; + LogRel(("NAT: TFTP RRQ %s", pTftpSession->szFilename)); + pszPrefix = " "; + if (pTftpSession->OptionBlkSize.fRequested) + { + LogRel(("%s" "blksize=%RU64", pszPrefix, pTftpSession->OptionBlkSize.u64Value)); + pszPrefix = ", "; + } + if (pTftpSession->OptionTSize.fRequested) + { + LogRel(("%s" "tsize=%RU64", pszPrefix, pTftpSession->OptionTSize.u64Value)); + pszPrefix = ", "; + } + if (pTftpSession->OptionTimeout.fRequested) + { + LogRel(("%s" "timeout=%RU64", pszPrefix, pTftpSession->OptionTimeout.u64Value)); + pszPrefix = ", "; + } + LogRel(("\n")); + tftpSessionUpdate(pData, pTftpSession); return VINF_SUCCESS; @@ -416,33 +479,28 @@ return VERR_NOT_FOUND; } -DECLINLINE(int) pftpSessionOpenFile(PNATState pData, PTFTPSESSION pTftpSession, bool fVerbose, PRTFILE pSessionFile) +DECLINLINE(int) pftpSessionOpenFile(PTFTPSESSION pTftpSession, PRTFILE pSessionFile) { - char szSessionFilename[TFTP_FILENAME_MAX]; - ssize_t cchSessionFilename; int rc; LogFlowFuncEnter(); - cchSessionFilename = RTStrPrintf2(szSessionFilename, TFTP_FILENAME_MAX, "%s/%s", tftp_prefix, pTftpSession->pszFilename); - if (cchSessionFilename > 0) + if (pTftpSession->pcszFilenameHost == NULL) { - LogFunc(("szSessionFilename: %s\n", szSessionFilename)); - if (RTFileExists(szSessionFilename)) - { - rc = RTFileOpen(pSessionFile, szSessionFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); - } - else - rc = VERR_FILE_NOT_FOUND; + rc = VERR_FILE_NOT_FOUND; } else - rc = VERR_FILENAME_TOO_LONG; - if (fVerbose) - LogRel(("NAT TFTP: %s/%s -> %Rrc\n", tftp_prefix, pTftpSession->pszFilename, rc)); + { + rc = RTFileOpen(pSessionFile, pTftpSession->pcszFilenameHost, + RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(rc)) + rc = VERR_FILE_NOT_FOUND; + } + LogFlowFuncLeaveRC(rc); return rc; } -DECLINLINE(int) tftpSessionEvaluateOptions(PNATState pData, PTFTPSESSION pTftpSession) +DECLINLINE(int) tftpSessionEvaluateOptions(PTFTPSESSION pTftpSession) { int rc; RTFILE hSessionFile; @@ -450,7 +508,7 @@ int cOptions; LogFlowFunc(("pTftpSession:%p\n", pTftpSession)); - rc = pftpSessionOpenFile(pData, pTftpSession, true /*fVerbose*/, &hSessionFile); + rc = pftpSessionOpenFile(pTftpSession, &hSessionFile); if (RT_FAILURE(rc)) { LogFlowFuncLeaveRC(rc); @@ -549,7 +607,7 @@ pcbReadData)); u16BlkSize = (uint16_t)pcTftpSession->OptionBlkSize.u64Value; - rc = pftpSessionOpenFile(pData, pcTftpSession, false /*fVerbose*/, &hSessionFile); + rc = pftpSessionOpenFile(pcTftpSession, &hSessionFile); if (RT_FAILURE(rc)) { LogFlowFuncLeaveRC(rc); @@ -619,10 +677,10 @@ PTFTPIPHDR pTftpIpHeader; int rc; - rc = tftpSessionEvaluateOptions(pData, pTftpSession); + rc = tftpSessionEvaluateOptions(pTftpSession); if (RT_FAILURE(rc)) { - tftpSendError(pData, pTftpSession, 2, "Option negotiation failure (file not found or inaccessible?)", pcTftpIpHeaderRecv); + tftpSendError(pData, pTftpSession, TFTP_EACCESS, "Option negotiation failure (file not found or inaccessible?)", pcTftpIpHeaderRecv); LogFlowFuncLeave(); return rc; } @@ -708,7 +766,7 @@ pTftpSession->cTftpAck++; else { - tftpSendError(pData, pTftpSession, 6, "ACK is wrong", pcTftpIpHeaderRecv); + tftpSendError(pData, pTftpSession, TFTP_EEXIST, "ACK is wrong", pcTftpIpHeaderRecv); return -1; } @@ -745,7 +803,7 @@ else { m_freem(pData, m); - tftpSendError(pData, pTftpSession, 1, "File not found", pcTftpIpHeaderRecv); + tftpSendError(pData, pTftpSession, TFTP_ENOENT, "File not found", pcTftpIpHeaderRecv); /* send "file not found" error back */ return -1; } @@ -788,7 +846,7 @@ /* Dont't bother with rest processing in case of invalid access */ if (RT_FAILURE(tftpSecurityFilenameCheck(pData, pTftpSession))) { - tftpSendError(pData, pTftpSession, 2, "Access violation", pTftpIpHeader); + tftpSendError(pData, pTftpSession, TFTP_EACCESS, "Access violation", pTftpIpHeader); LogFlowFuncLeave(); return; } @@ -797,7 +855,7 @@ if (RT_UNLIKELY(!tftpIsSupportedTransferMode(pTftpSession))) { - tftpSendError(pData, pTftpSession, 4, "Unsupported transfer mode", pTftpIpHeader); + tftpSendError(pData, pTftpSession, TFTP_ENOSYS, "Unsupported transfer mode", pTftpIpHeader); LogFlowFuncLeave(); return; } @@ -823,7 +881,7 @@ if (tftpSendData(pData, pTftpSession, RT_N2H_U16(pTftpIpHeader->Core.u16TftpOpCode), pTftpIpHeader)) - LogRel(("NAT TFTP: failure\n")); + LogRel(("NAT: TFTP send failed\n")); } int slirpTftpInit(PNATState pData) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/tftp.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/tftp.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/slirp/tftp.h 2020-10-16 16:35:46.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/slirp/tftp.h 2022-09-01 13:26:31.000000000 +0000 @@ -31,6 +31,19 @@ #define TFTP_ERROR 5 #define TFTP_OACK 6 +/* error codes */ +#define TFTP_EUNDEF 0 /* Not defined, see error message (if any). */ +#define TFTP_ENOENT 1 /* File not found. */ +#define TFTP_EACCESS 2 /* Access violation. */ +#define TFTP_EFBIG 3 /* Disk full or allocation exceeded. */ +#define TFTP_ENOSYS 4 /* Illegal TFTP operation. */ +#define TFTP_ESRCH 5 /* Unknown transfer ID. */ +#define TFTP_EEXIST 6 /* File already exists. */ +#define TFTP_EUSER 7 /* No such user. */ +/* RFC 2347 */ +#define TFTP_EONAK 8 /* Option refused. */ + + #define TFTP_FILENAME_MAX 512 diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/SrvIntNetR0.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/SrvIntNetR0.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Network/SrvIntNetR0.cpp 2020-10-16 16:35:41.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Network/SrvIntNetR0.cpp 2022-09-01 13:26:25.000000000 +0000 @@ -646,10 +646,9 @@ Assert(off + cb > off); /* The optimized case. */ - if (RT_LIKELY( pSG->cSegsUsed == 1 - || pSG->aSegs[0].cb >= off + cb)) + if (RT_LIKELY(pSG->aSegs[0].cb >= off + cb)) { - Assert(pSG->cbTotal == pSG->aSegs[0].cb); + AssertMsg(pSG->cbTotal >= pSG->aSegs[0].cb, ("%#x vs %#x\n", pSG->cbTotal, pSG->aSegs[0].cb)); memcpy(pvBuf, (uint8_t const *)pSG->aSegs[0].pv + off, cb); return true; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative286.asm virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative286.asm --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative286.asm 2020-10-16 16:35:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative286.asm 2022-09-01 13:26:31.000000000 +0000 @@ -1208,7 +1208,7 @@ section CONST2 progbits vstart=0xed8 align=1 ; size=0x3fa class=DATA group=DGROUP ; disGetNextSymbol 0xf0ed8 LB 0x3fa -> off=0x0 cb=0000000000000012 uValue=00000000000f0ed8 'bios_cvs_version_string' bios_cvs_version_string: ; 0xf0ed8 LB 0x12 - db 'VirtualBox 6.1.15', 000h + db 'VirtualBox 6.1.38', 000h ; disGetNextSymbol 0xf0eea LB 0x3e8 -> off=0x0 cb=0000000000000008 uValue=00000000000f0eea '_bios_prefix_string' _bios_prefix_string: ; 0xf0eea LB 0x8 db 'BIOS: ', 000h, 000h @@ -19425,4 +19425,4 @@ cpu_reset: ; 0xffff0 LB 0x10 jmp far 0f000h:0e05bh ; ea 5b e0 00 f0 ; 0xffff0 orgs.asm:2063 ; disGetNextSymbol 0xffff5 LB 0xb -> off=0xb cb=0000000000000000 uValue=0000000000100000 '_dummy_addr_0x100000' - db 030h, 036h, 02fh, 032h, 033h, 02fh, 039h, 039h, 000h, 0fch, 0a2h + db 030h, 036h, 02fh, 032h, 033h, 02fh, 039h, 039h, 000h, 0fch, 09dh diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative286.md5sum virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative286.md5sum --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative286.md5sum 2020-10-16 16:35:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative286.md5sum 2022-09-01 13:26:31.000000000 +0000 @@ -1 +1 @@ -7984c98c55dab095ba8bafbe7b777e64 *VBoxPcBios286.rom +f451343282e8d6a8fab3e49ed83b6d4b *VBoxPcBios286.rom diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative386.asm virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative386.asm --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative386.asm 2020-10-16 16:35:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative386.asm 2022-09-01 13:26:31.000000000 +0000 @@ -1173,7 +1173,7 @@ section CONST2 progbits vstart=0xf12 align=1 ; size=0x3fa class=DATA group=DGROUP ; disGetNextSymbol 0xf0f12 LB 0x3fa -> off=0x0 cb=0000000000000012 uValue=00000000000f0f12 'bios_cvs_version_string' bios_cvs_version_string: ; 0xf0f12 LB 0x12 - db 'VirtualBox 6.1.15', 000h + db 'VirtualBox 6.1.38', 000h ; disGetNextSymbol 0xf0f24 LB 0x3e8 -> off=0x0 cb=0000000000000008 uValue=00000000000f0f24 '_bios_prefix_string' _bios_prefix_string: ; 0xf0f24 LB 0x8 db 'BIOS: ', 000h, 000h @@ -19200,4 +19200,4 @@ cpu_reset: ; 0xffff0 LB 0x10 jmp far 0f000h:0e05bh ; ea 5b e0 00 f0 ; 0xffff0 orgs.asm:2063 ; disGetNextSymbol 0xffff5 LB 0xb -> off=0xb cb=0000000000000000 uValue=0000000000100000 '_dummy_addr_0x100000' - db 030h, 036h, 02fh, 032h, 033h, 02fh, 039h, 039h, 000h, 0fch, 0ech + db 030h, 036h, 02fh, 032h, 033h, 02fh, 039h, 039h, 000h, 0fch, 0e7h diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative386.md5sum virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative386.md5sum --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative386.md5sum 2020-10-16 16:35:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative386.md5sum 2022-09-01 13:26:31.000000000 +0000 @@ -1 +1 @@ -52e8fd60efd11809e0182ddb228a01a8 *VBoxPcBios386.rom +1bc856526ce7497f88fa1e40c730c766 *VBoxPcBios386.rom diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative8086.asm virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative8086.asm --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative8086.asm 2020-10-16 16:35:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative8086.asm 2022-09-01 13:26:31.000000000 +0000 @@ -1208,7 +1208,7 @@ section CONST2 progbits vstart=0xed8 align=1 ; size=0x3fa class=DATA group=DGROUP ; disGetNextSymbol 0xf0ed8 LB 0x3fa -> off=0x0 cb=0000000000000012 uValue=00000000000f0ed8 'bios_cvs_version_string' bios_cvs_version_string: ; 0xf0ed8 LB 0x12 - db 'VirtualBox 6.1.15', 000h + db 'VirtualBox 6.1.38', 000h ; disGetNextSymbol 0xf0eea LB 0x3e8 -> off=0x0 cb=0000000000000008 uValue=00000000000f0eea '_bios_prefix_string' _bios_prefix_string: ; 0xf0eea LB 0x8 db 'BIOS: ', 000h, 000h @@ -19899,4 +19899,4 @@ cpu_reset: ; 0xffff0 LB 0x10 jmp far 0f000h:0e05bh ; ea 5b e0 00 f0 ; 0xffff0 orgs.asm:2063 ; disGetNextSymbol 0xffff5 LB 0xb -> off=0xb cb=0000000000000000 uValue=0000000000100000 '_dummy_addr_0x100000' - db 030h, 036h, 02fh, 032h, 033h, 02fh, 039h, 039h, 000h, 0fbh, 024h + db 030h, 036h, 02fh, 032h, 033h, 02fh, 039h, 039h, 000h, 0fbh, 01fh diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative8086.md5sum virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative8086.md5sum --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative8086.md5sum 2020-10-16 16:35:47.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/BIOS/VBoxBiosAlternative8086.md5sum 2022-09-01 13:26:31.000000000 +0000 @@ -1 +1 @@ -7e0f370b5da5761249656335b4b221a6 *VBoxPcBios8086.rom +8198eb3f832f14e1bbdbb8ea54584711 *VBoxPcBios8086.rom diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevDMA.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevDMA.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevDMA.cpp 2020-10-16 16:35:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevDMA.cpp 2022-09-01 13:26:32.000000000 +0000 @@ -102,6 +102,7 @@ /* State information for a single DMA channel. */ typedef struct { + PPDMDEVINS pDevInsHandler; /**< The device instance the channel is associated with. */ RTR3PTR pvUser; /* User specific context. */ R3PTRTYPE(PFNDMATRANSFERHANDLER) pfnXferHandler; /* Transfer handler for channel. */ uint16_t u16BaseAddr; /* Base address for transfers. */ @@ -590,13 +591,14 @@ opmode = (ch->u8Mode >> 6) & 3; Log3(("DMA address %screment, mode %d\n", IS_MODE_DEC(ch->u8Mode) ? "de" : "in", ch->u8Mode >> 6)); + AssertReturnVoid(ch->pfnXferHandler); /* Addresses and counts are shifted for 16-bit channels. */ start_cnt = ch->u16CurCount << dc->is16bit; /* NB: The device is responsible for examining the DMA mode and not * transferring more than it should if auto-init is not in use. */ - end_cnt = ch->pfnXferHandler(pThis->pDevIns, ch->pvUser, (ctlidx * 4) + chidx, + end_cnt = ch->pfnXferHandler(ch->pDevInsHandler, ch->pvUser, (ctlidx * 4) + chidx, start_cnt, (ch->u16BaseCount + 1) << dc->is16bit); ch->u16CurCount = end_cnt >> dc->is16bit; /* Set the TC (Terminal Count) bit if transfer was completed. */ @@ -623,7 +625,19 @@ DMAState *pThis = PDMDEVINS_2_DATA(pDevIns, PDMASTATE); DMAControl *dc; int chidx, mask; + STAM_PROFILE_START(&pThis->StatRun, a); + + /* We must first lock all the devices then the DMAC or we end up with a + lock order validation when the callback helpers (PDMDMACREG) are being + invoked from I/O port and MMIO callbacks in channel devices. While this + may sound a little brutish, it's actually in line with the bus locking + the original DMAC did back in the days. Besides, we've only got the FDC + and SB16 as potential customers here at present, so hardly a problem. */ + for (unsigned idxCtl = 0; idxCtl < RT_ELEMENTS(pThis->DMAC); idxCtl++) + for (unsigned idxCh = 0; idxCh < RT_ELEMENTS(pThis->DMAC[idxCtl].ChState); idxCh++) + if (pThis->DMAC[idxCtl].ChState[idxCh].pDevInsHandler) + PDMDevHlpCritSectEnter(pDevIns, pThis->DMAC[idxCtl].ChState[idxCh].pDevInsHandler->pCritSectRoR3, VERR_IGNORED); PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); /* Run all controllers and channels. */ @@ -643,7 +657,13 @@ } } + /* Unlock everything (order is mostly irrelevant). */ + for (unsigned idxCtl = 0; idxCtl < RT_ELEMENTS(pThis->DMAC); idxCtl++) + for (unsigned idxCh = 0; idxCh < RT_ELEMENTS(pThis->DMAC[idxCtl].ChState); idxCh++) + if (pThis->DMAC[idxCtl].ChState[idxCh].pDevInsHandler) + PDMDevHlpCritSectLeave(pDevIns, pThis->DMAC[idxCtl].ChState[idxCh].pDevInsHandler->pCritSectRoR3); PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + STAM_PROFILE_STOP(&pThis->StatRun, a); return 0; } @@ -651,7 +671,7 @@ /** * @interface_method_impl{PDMDMAREG,pfnRegister} */ -static DECLCALLBACK(void) dmaR3Register(PPDMDEVINS pDevIns, unsigned uChannel, +static DECLCALLBACK(void) dmaR3Register(PPDMDEVINS pDevIns, unsigned uChannel, PPDMDEVINS pDevInsHandler, PFNDMATRANSFERHANDLER pfnTransferHandler, void *pvUser) { DMAState *pThis = PDMDEVINS_2_DATA(pDevIns, PDMASTATE); @@ -660,6 +680,7 @@ LogFlow(("dmaR3Register: pThis=%p uChannel=%u pfnTransferHandler=%p pvUser=%p\n", pThis, uChannel, pfnTransferHandler, pvUser)); PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + ch->pDevInsHandler = pDevInsHandler; ch->pfnXferHandler = pfnTransferHandler; ch->pvUser = pvUser; PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevHPET.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevHPET.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevHPET.cpp 2020-10-16 16:35:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevHPET.cpp 2022-09-01 13:26:32.000000000 +0000 @@ -1,6 +1,25 @@ /* $Id: DevHPET.cpp $ */ /** @file * HPET virtual device - High Precision Event Timer emulation. + * + * This implementation is based on the (generic) Intel IA-PC HPET specification + * and the Intel ICH9 datasheet. + * + * Typical windows 1809 usage (efi, smp) is to do repated one-shots and + * a variable rate. The reprogramming sequence is as follows (all accesses + * are 32-bit): + * -# counter register read. + * -# timer 0: config register read. + * -# timer 0: write 0x134 to config register. + * -# timer 0: write comparator register. + * -# timer 0: write 0x134 to config register. + * -# timer 0: read comparator register. + * -# counter register read. + * + * Typical linux will configure the timer at Hz but not necessarily enable + * interrupts (HPET_TN_ENABLE not set). It would be nice to emulate this + * mode without using timers. + * */ /* @@ -15,10 +34,6 @@ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ -/* This implementation is based on the (generic) Intel IA-PC HPET specification - * and the Intel ICH9 datasheet. - */ - /********************************************************************************************************************************* * Header Files * @@ -27,7 +42,7 @@ #include #include #include -#include +#include #include #include @@ -131,7 +146,7 @@ #define HPET_TN_PERIODIC RT_BIT_64(3) #define HPET_TN_PERIODIC_CAP RT_BIT_64(4) #define HPET_TN_SIZE_CAP RT_BIT_64(5) -#define HPET_TN_SETVAL RT_BIT_64(6) +#define HPET_TN_SETVAL RT_BIT_64(6) /**< Periodic timers only: Change COMPARATOR as well as ACCUMULATOR. */ #define HPET_TN_32BIT RT_BIT_64(8) #define HPET_TN_INT_ROUTE_MASK UINT64_C(0x3e00) #define HPET_TN_CFG_WRITE_MASK UINT64_C(0x3e46) @@ -203,6 +218,8 @@ *********************************************************************************************************************************/ /** * A HPET timer. + * + * @note To avoid excessive locking, we many of the updates atomically. */ typedef struct HPETTIMER { @@ -228,7 +245,7 @@ /** @name Hidden register state. * @{ */ - /** Last value written to comparator. */ + /** Accumulator / Last value written to comparator. */ uint64_t u64Period; /** @} */ @@ -236,6 +253,7 @@ STAMCOUNTER StatSetTimer; } HPETTIMER; AssertCompileMemberAlignment(HPETTIMER, u64Config, sizeof(uint64_t)); +AssertCompileSizeAlignment(HPETTIMER, 64); /** Pointer to the shared state of an HPET timer. */ typedef HPETTIMER *PHPETTIMER; /** Const pointer to the shared state of an HPET timer. */ @@ -257,8 +275,10 @@ * @{ */ /** Capabilities. */ uint32_t u32Capabilities; - /** HPET_PERIOD - . */ - uint32_t u32Period; + /** Used to be u32Period. We only implement two period values depending on + * fIch9, and since we usually would have to RT_MIN(u32Period,1) we could + * just as well select between HPET_CLK_PERIOD_ICH9 and HPET_CLK_PERIOD_PIIX. */ + uint32_t u32Padding; /** Configuration. */ uint64_t u64HpetConfig; /** Interrupt status register. */ @@ -270,19 +290,22 @@ /** Whether we emulate ICH9 HPET (different frequency & timer count). */ bool fIch9; /** Size alignment padding. */ - uint8_t abPadding0[7]; - - /** Global device lock. */ - PDMCRITSECT CritSect; + uint8_t abPadding0[7+8]; /** The handle of the MMIO region. */ IOMMMIOHANDLE hMmio; + /** Global device lock. */ + PDMCRITSECT CritSect; + STAMCOUNTER StatCounterRead4Byte; STAMCOUNTER StatCounterRead8Byte; STAMCOUNTER StatCounterWriteLow; STAMCOUNTER StatCounterWriteHigh; + STAMCOUNTER StatZeroDeltaHack; } HPET; +AssertCompileMemberAlignment(HPET, aTimers, 64); +AssertCompileMemberAlignment(HPET, CritSect, 64); /** Pointer to the shared HPET device state. */ typedef HPET *PHPET; /** Const pointer to the shared HPET device state. */ @@ -333,13 +356,19 @@ #ifndef VBOX_DEVICE_STRUCT_TESTCASE +DECLINLINE(bool) hpet32bitTimerEx(uint64_t fConfig) +{ + return !(fConfig & HPET_TN_SIZE_CAP) + || (fConfig & HPET_TN_32BIT); +} + DECLINLINE(bool) hpet32bitTimer(PHPETTIMER pHpetTimer) { - uint64_t u64Cfg = pHpetTimer->u64Config; - return ((u64Cfg & HPET_TN_SIZE_CAP) == 0) || ((u64Cfg & HPET_TN_32BIT) != 0); + return hpet32bitTimerEx(ASMAtomicUoReadU64(&pHpetTimer->u64Config)); } + DECLINLINE(uint64_t) hpetInvalidValue(PHPETTIMER pHpetTimer) { return hpet32bitTimer(pHpetTimer) ? UINT32_MAX : UINT64_MAX; @@ -347,20 +376,17 @@ DECLINLINE(uint64_t) hpetTicksToNs(PHPET pThis, uint64_t value) { - return ASMMultU64ByU32DivByU32(value, pThis->u32Period, FS_PER_NS); + return ASMMultU64ByU32DivByU32(value, pThis->fIch9 ? HPET_CLK_PERIOD_ICH9 : HPET_CLK_PERIOD_PIIX, FS_PER_NS); } DECLINLINE(uint64_t) nsToHpetTicks(PCHPET pThis, uint64_t u64Value) { - return ASMMultU64ByU32DivByU32(u64Value, FS_PER_NS, RT_MAX(pThis->u32Period, 1 /* no div/zero */)); + return ASMMultU64ByU32DivByU32(u64Value, FS_PER_NS, pThis->fIch9 ? HPET_CLK_PERIOD_ICH9 : HPET_CLK_PERIOD_PIIX); } -DECLINLINE(uint64_t) hpetGetTicks(PPDMDEVINS pDevIns, PCHPET pThis) +DECLINLINE(uint64_t) hpetGetTicksEx(PCHPET pThis, uint64_t tsNow) { - /* - * We can use any timer to get current time, they all go with the same speed. - */ - return nsToHpetTicks(pThis, PDMDevHlpTimerGet(pDevIns, pThis->aTimers[0].hTimer) + pThis->u64HpetOffset); + return nsToHpetTicks(pThis, tsNow + pThis->u64HpetOffset); } DECLINLINE(uint64_t) hpetUpdateMasked(uint64_t u64NewValue, uint64_t u64OldValue, uint64_t u64Mask) @@ -382,36 +408,37 @@ && !(u64NewValue & u64Mask); } -DECLINLINE(uint64_t) hpetComputeDiff(PHPETTIMER pHpetTimer, uint64_t u64Now) +DECLINLINE(uint64_t) hpetComputeDiff(uint64_t fConfig, uint64_t uCmp, uint64_t uHpetNow) { - - if (hpet32bitTimer(pHpetTimer)) + if (hpet32bitTimerEx(fConfig)) { - uint32_t u32Diff; - - u32Diff = (uint32_t)pHpetTimer->u64Cmp - (uint32_t)u64Now; - u32Diff = (int32_t)u32Diff > 0 ? u32Diff : (uint32_t)0; - return (uint64_t)u32Diff; + uint32_t u32Diff = (uint32_t)uCmp - (uint32_t)uHpetNow; + if ((int32_t)u32Diff > 0) + return u32Diff; } - uint64_t u64Diff; - - u64Diff = pHpetTimer->u64Cmp - u64Now; - u64Diff = (int64_t)u64Diff > 0 ? u64Diff : (uint64_t)0; - return u64Diff; + else + { + uint64_t u64Diff = uCmp - uHpetNow; + if ((int64_t)u64Diff > 0) + return u64Diff; + } + return 0; } -static void hpetAdjustComparator(PHPETTIMER pHpetTimer, uint64_t u64Now) +DECLINLINE(uint64_t) hpetAdjustComparator(PHPETTIMER pHpetTimer, uint64_t fConfig, uint64_t uCmp, + uint64_t uPeriod, uint64_t uHpetNow) { - if ((pHpetTimer->u64Config & HPET_TN_PERIODIC)) + if (fConfig & HPET_TN_PERIODIC) { - uint64_t u64Period = pHpetTimer->u64Period; - if (u64Period) + if (uPeriod) { - uint64_t cPeriods = (u64Now - pHpetTimer->u64Cmp) / u64Period; - pHpetTimer->u64Cmp += (cPeriods + 1) * u64Period; + uint64_t cPeriods = (uHpetNow - uCmp) / uPeriod; + uCmp += (cPeriods + 1) * uPeriod; + ASMAtomicWriteU64(&pHpetTimer->u64Cmp, uCmp); } } + return uCmp; } @@ -421,66 +448,94 @@ * @param pDevIns The device instance. * @param pThis The shared HPET state. * @param pHpetTimer The timer. + * @param fConfig Already read config value. + * @param uPeriod Already read period value. */ -DECLINLINE(void) hpetTimerSetFrequencyHint(PPDMDEVINS pDevIns, PHPET pThis, PHPETTIMER pHpetTimer) +DECLINLINE(void) hpetTimerSetFrequencyHint(PPDMDEVINS pDevIns, PHPET pThis, PHPETTIMER pHpetTimer, + uint64_t fConfig, uint64_t uPeriod) { - if (pHpetTimer->u64Config & HPET_TN_PERIODIC) + if (fConfig & HPET_TN_PERIODIC) { - uint64_t const u64Period = pHpetTimer->u64Period; - uint32_t const u32Freq = pThis->u32Period; - if (u64Period > 0 && u64Period < u32Freq) - PDMDevHlpTimerSetFrequencyHint(pDevIns, pHpetTimer->hTimer, u32Freq / (uint32_t)u64Period); + uint64_t const nsPeriod = hpetTicksToNs(pThis, uPeriod); + if (nsPeriod < RT_NS_100MS) + PDMDevHlpTimerSetFrequencyHint(pDevIns, pHpetTimer->hTimer, RT_NS_1SEC / (uint32_t)nsPeriod); } } -static void hpetProgramTimer(PPDMDEVINS pDevIns, PHPET pThis, PHPETTIMER pHpetTimer) +/** + * Programs an HPET timer, arming hTimer for the next IRQ. + * + * @param pDevIns The device instance. + * @param pThis The HPET instance data. + * @param pHpetTimer The HPET timer to program. The wrap-around indicator is + * updates, and for periodic timer the comparator. + * @param tsNow The current virtual sync clock time. + * @note Caller must both the virtual sync (timer) and HPET locks. + */ +static void hpetProgramTimer(PPDMDEVINS pDevIns, PHPET pThis, PHPETTIMER pHpetTimer, uint64_t const tsNow) { - /* no wrapping on new timers */ - pHpetTimer->u8Wrap = 0; - - uint64_t u64Ticks = hpetGetTicks(pDevIns, pThis); - hpetAdjustComparator(pHpetTimer, u64Ticks); - - uint64_t u64Diff = hpetComputeDiff(pHpetTimer, u64Ticks); + /* + * Calculate the number of HPET ticks to the next timer IRQ, but + * first updating comparator if periodic timer. + */ + uint64_t const fConfig = pHpetTimer->u64Config; + uint64_t const uPeriod = pHpetTimer->u64Period; + uint64_t uCmp = pHpetTimer->u64Cmp; + uint64_t const uHpetNow = hpetGetTicksEx(pThis, tsNow); + uCmp = hpetAdjustComparator(pHpetTimer, fConfig, uCmp, uPeriod, uHpetNow); + uint64_t uHpetDelta = hpetComputeDiff(fConfig, uCmp, uHpetNow); /* * HPET spec says in one-shot 32-bit mode, generate an interrupt when * counter wraps in addition to an interrupt with comparator match. */ - if ( hpet32bitTimer(pHpetTimer) - && !(pHpetTimer->u64Config & HPET_TN_PERIODIC)) + bool fWrap = false; + if ( hpet32bitTimerEx(fConfig) + && !(fConfig & HPET_TN_PERIODIC)) { - uint32_t u32TillWrap = 0xffffffff - (uint32_t)u64Ticks + 1; - if (u32TillWrap < (uint32_t)u64Diff) + uint32_t cHpetTicksTillWrap = UINT32_MAX - (uint32_t)uHpetNow + 1; + if (cHpetTicksTillWrap < (uint32_t)uHpetDelta) { - Log(("wrap on timer %d: till=%u ticks=%lld diff64=%lld\n", - pHpetTimer->idxTimer, u32TillWrap, u64Ticks, u64Diff)); - u64Diff = u32TillWrap; - pHpetTimer->u8Wrap = 1; + Log(("HPET[%u]: wrap: till=%u ticks=%lld diff64=%lld\n", + pHpetTimer->idxTimer, cHpetTicksTillWrap, uHpetNow, uHpetDelta)); + uHpetDelta = cHpetTicksTillWrap; + fWrap = true; } } + pHpetTimer->u8Wrap = fWrap; /* * HACK ALERT! Avoid killing VM with interrupts. */ #if 1 /** @todo HACK, rethink, may have negative impact on the guest */ - if (u64Diff == 0) - u64Diff = 100000; /* 1 millisecond */ + if (uHpetDelta != 0) + { /* likely? */ } + else + { + Log(("HPET[%u]: Applying zero delta hack!\n", pHpetTimer->idxTimer)); + STAM_REL_COUNTER_INC(&pThis->StatZeroDeltaHack); +/** @todo lower this. */ + uHpetDelta = pThis->fIch9 ? 14318 : 100000; /* 1 millisecond */ + } #endif + /* + * Arm the timer. + */ uint64_t u64TickLimit = pThis->fIch9 ? HPET_TICKS_IN_100YR_ICH9 : HPET_TICKS_IN_100YR_PIIX; - if (u64Diff <= u64TickLimit) + if (uHpetDelta <= u64TickLimit) { - Log4(("HPET: next IRQ in %lld ticks (%lld ns)\n", u64Diff, hpetTicksToNs(pThis, u64Diff))); + uint64_t const cTicksDelta = hpetTicksToNs(pThis, uHpetDelta); + uint64_t const tsDeadline = tsNow + cTicksDelta; + Log4(("HPET[%u]: next IRQ in %lld hpet ticks (TM %lld ticks, at %llu)\n", + pHpetTimer->idxTimer, uHpetDelta, cTicksDelta, tsDeadline)); + PDMDevHlpTimerSet(pDevIns, pHpetTimer->hTimer, tsDeadline); + hpetTimerSetFrequencyHint(pDevIns, pThis, pHpetTimer, fConfig, uPeriod); STAM_REL_COUNTER_INC(&pHpetTimer->StatSetTimer); - PDMDevHlpTimerSetNano(pDevIns, pHpetTimer->hTimer, hpetTicksToNs(pThis, u64Diff)); } else - { - LogRelMax(10, ("HPET: Not scheduling an interrupt more than 100 years in the future.\n")); - } - hpetTimerSetFrequencyHint(pDevIns, pThis, pHpetTimer); + LogRelMax(10, ("HPET[%u]: Not scheduling an interrupt more than 100 years in the future.\n", pHpetTimer->idxTimer)); } @@ -490,57 +545,57 @@ /** * Reads a HPET timer register. * - * @param pDevIns The device instance. + * @returns The register value. * @param pThis The HPET instance. * @param iTimerNo The timer index. * @param iTimerReg The index of the timer register to read. - * @param pu32Value Where to return the register value. * - * @remarks ASSUMES the caller holds the HPET lock. + * @note No locking required. */ -static void hpetTimerRegRead32(PPDMDEVINS pDevIns, PCHPET pThis, uint32_t iTimerNo, uint32_t iTimerReg, uint32_t *pu32Value) +static uint32_t hpetTimerRegRead32(PHPET pThis, uint32_t iTimerNo, uint32_t iTimerReg) { - Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); - RT_NOREF(pDevIns); - uint32_t u32Value; if ( iTimerNo < HPET_CAP_GET_TIMERS(pThis->u32Capabilities) && iTimerNo < RT_ELEMENTS(pThis->aTimers) ) { - PCHPETTIMER pHpetTimer = &pThis->aTimers[iTimerNo]; + PHPETTIMER pHpetTimer = &pThis->aTimers[iTimerNo]; switch (iTimerReg) { case HPET_TN_CFG: - u32Value = (uint32_t)pHpetTimer->u64Config; - Log(("read HPET_TN_CFG on %d: %#x\n", iTimerNo, u32Value)); + u32Value = (uint32_t)ASMAtomicReadU64(&pHpetTimer->u64Config); + Log(("HPET[%u]: read32 HPET_TN_CFG: %#x\n", iTimerNo, u32Value)); break; case HPET_TN_CFG + 4: - u32Value = (uint32_t)(pHpetTimer->u64Config >> 32); - Log(("read HPET_TN_CFG+4 on %d: %#x\n", iTimerNo, u32Value)); + u32Value = (uint32_t)(ASMAtomicReadU64(&pHpetTimer->u64Config) >> 32); + Log(("HPET[%u]: read32 HPET_TN_CFG+4: %#x\n", iTimerNo, u32Value)); break; case HPET_TN_CMP: - u32Value = (uint32_t)pHpetTimer->u64Cmp; - Log(("read HPET_TN_CMP on %d: %#x (%#llx)\n", pHpetTimer->idxTimer, u32Value, pHpetTimer->u64Cmp)); + { + uint64_t uCmp = ASMAtomicReadU64(&pHpetTimer->u64Cmp); + u32Value = (uint32_t)uCmp; + Log(("HPET[%u]: read32 HPET_TN_CMP: %#x (%#RX64)\n", pHpetTimer->idxTimer, u32Value, uCmp)); break; + } case HPET_TN_CMP + 4: - u32Value = (uint32_t)(pHpetTimer->u64Cmp >> 32); - Log(("read HPET_TN_CMP+4 on %d: %#x (%#llx)\n", pHpetTimer->idxTimer, u32Value, pHpetTimer->u64Cmp)); + { + uint64_t uCmp = ASMAtomicReadU64(&pHpetTimer->u64Cmp); + u32Value = (uint32_t)(uCmp >> 32); + Log(("HPET[%u]: read32 HPET_TN_CMP+4: %#x (%#RX64)\n", pHpetTimer->idxTimer, u32Value, uCmp)); break; + } case HPET_TN_ROUTE: u32Value = (uint32_t)(pHpetTimer->u64Fsb >> 32); /** @todo Looks wrong, but since it's not supported, who cares. */ - Log(("read HPET_TN_ROUTE on %d: %#x\n", iTimerNo, u32Value)); + Log(("HPET[%u]: read32 HPET_TN_ROUTE: %#x\n", iTimerNo, u32Value)); break; default: - { - LogRelMax(10, ("HPET: Invalid HPET register read %d on %d\n", iTimerReg, pHpetTimer->idxTimer)); + LogRelMax(10, ("HPET[%u]: Invalid HPET register read: %d\n", iTimerNo, iTimerReg)); u32Value = 0; break; - } } } else @@ -548,7 +603,54 @@ LogRelMax(10, ("HPET: Using timer above configured range: %d\n", iTimerNo)); u32Value = 0; } - *pu32Value = u32Value; + return u32Value; +} + + +/** + * Reads a HPET timer register, 64-bit access. + * + * @returns The register value. + * @param pThis The HPET instance. + * @param iTimerNo The timer index. + * @param iTimerReg The index of the timer register to read. + */ +static uint64_t hpetTimerRegRead64(PHPET pThis, uint32_t iTimerNo, uint32_t iTimerReg) +{ + uint64_t u64Value; + if ( iTimerNo < HPET_CAP_GET_TIMERS(pThis->u32Capabilities) + && iTimerNo < RT_ELEMENTS(pThis->aTimers) ) + { + PHPETTIMER pHpetTimer = &pThis->aTimers[iTimerNo]; + switch (iTimerReg) + { + case HPET_TN_CFG: + u64Value = ASMAtomicReadU64(&pHpetTimer->u64Config); + Log(("HPET[%u]: read64 HPET_TN_CFG: %#RX64\n", iTimerNo, u64Value)); + break; + + case HPET_TN_CMP: + u64Value = ASMAtomicReadU64(&pHpetTimer->u64Config); + Log(("HPET[%u]: read64 HPET_TN_CMP: %#RX64\n", iTimerNo, u64Value)); + break; + + case HPET_TN_ROUTE: + u64Value = (uint32_t)(pHpetTimer->u64Fsb >> 32); /** @todo Looks wrong, but since it's not supported, who cares. */ + Log(("HPET[%u]: read64 HPET_TN_ROUTE: %#RX64\n", iTimerNo, u64Value)); + break; + + default: + LogRelMax(10, ("HPET[%u]: Invalid 64-bit HPET register read64: %d\n", iTimerNo, iTimerReg)); + u64Value = 0; + break; + } + } + else + { + LogRelMax(10, ("HPET: Using timer above configured range: %d\n", iTimerNo)); + u64Value = 0; + } + return u64Value; } @@ -563,111 +665,214 @@ * @param iTimerReg The register being written to. * @param u32NewValue The value being written. * - * @remarks The caller should not hold the device lock, unless it also holds - * the TM lock. + * @remarks The caller should not hold any locks. */ static VBOXSTRICTRC hpetTimerRegWrite32(PPDMDEVINS pDevIns, PHPET pThis, uint32_t iTimerNo, uint32_t iTimerReg, uint32_t u32NewValue) { - Assert(!PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect) || PDMDevHlpTimerIsLockOwner(pDevIns, pThis->aTimers[0].hTimer)); + Assert(!PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + Assert(!PDMDevHlpTimerIsLockOwner(pDevIns, pThis->aTimers[0].hTimer)); - if ( iTimerNo >= HPET_CAP_GET_TIMERS(pThis->u32Capabilities) - || iTimerNo >= RT_ELEMENTS(pThis->aTimers) ) /* Parfait - see above. */ + if ( iTimerNo < HPET_CAP_GET_TIMERS(pThis->u32Capabilities) + && iTimerNo < RT_ELEMENTS(pThis->aTimers) ) /* Parfait - see above. */ { - LogRelMax(10, ("HPET: Using timer above configured range: %d\n", iTimerNo)); - return VINF_SUCCESS; - } - PHPETTIMER pHpetTimer = &pThis->aTimers[iTimerNo]; + PHPETTIMER pHpetTimer = &pThis->aTimers[iTimerNo]; - switch (iTimerReg) - { - case HPET_TN_CFG: + switch (iTimerReg) { - DEVHPET_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); - uint64_t u64Mask = HPET_TN_CFG_WRITE_MASK; + case HPET_TN_CFG: + { + /* + * Calculate the writable mask and see if anything actually changed + * before doing any locking. Windows 10 (1809) does two CFG writes + * with the same value (0x134) when reprogramming the HPET#0 timer. + */ + uint64_t fConfig = ASMAtomicUoReadU64(&pHpetTimer->u64Config); + uint64_t const fMask = HPET_TN_CFG_WRITE_MASK + | (fConfig & HPET_TN_PERIODIC_CAP ? HPET_TN_PERIODIC : 0) + | (fConfig & HPET_TN_SIZE_CAP ? HPET_TN_32BIT : 0); + if ((u32NewValue & fMask) == (fConfig & fMask)) + Log(("HPET[%u]: write32 HPET_TN_CFG: %#x - no change (%#RX64)\n", iTimerNo, u32NewValue, fConfig)); + else + { +#ifndef IN_RING3 + /* Return to ring-3 (where LogRel works) to complain about level-triggered interrupts. */ + if ((u32NewValue & HPET_TN_INT_TYPE) == HPET_TIMER_TYPE_LEVEL) + return VINF_IOM_R3_MMIO_WRITE; +#endif + DEVHPET_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); - Log(("write HPET_TN_CFG: %d: %x\n", iTimerNo, u32NewValue)); - if (pHpetTimer->u64Config & HPET_TN_PERIODIC_CAP) - u64Mask |= HPET_TN_PERIODIC; + fConfig = ASMAtomicUoReadU64(&pHpetTimer->u64Config); + uint64_t const fConfigNew = hpetUpdateMasked(u32NewValue, fConfig, fMask); + Log(("HPET[%u]: write HPET_TN_CFG: %#RX64 -> %#RX64\n", iTimerNo, fConfig, fConfigNew)); + + if ((fConfigNew & HPET_TN_32BIT) == (fConfig & HPET_TN_32BIT)) + { /* likely it stays the same */ } + else if (fConfigNew & HPET_TN_32BIT) + { + Log(("HPET[%u]: Changing timer to 32-bit mode.\n", iTimerNo)); + /* Clear the top bits of the comparator and period to be on the safe side. */ + ASMAtomicUoWriteU64(&pHpetTimer->u64Cmp, (uint32_t)pHpetTimer->u64Cmp); + ASMAtomicUoWriteU64(&pHpetTimer->u64Period, (uint32_t)pHpetTimer->u64Period); + } + else + Log(("HPET[%u]: Changing timer to 64-bit mode.\n", iTimerNo)); + ASMAtomicWriteU64(&pHpetTimer->u64Config, fConfigNew); + + DEVHPET_UNLOCK(pDevIns, pThis); + + if (RT_LIKELY((fConfigNew & HPET_TN_INT_TYPE) != HPET_TIMER_TYPE_LEVEL)) + { /* likely */ } + else + { + LogRelMax(10, ("HPET[%u]: Level-triggered config not yet supported\n", iTimerNo)); + ASSERT_GUEST_MSG_FAILED(("Level-triggered config not yet supported")); + } + } + break; + } - if (pHpetTimer->u64Config & HPET_TN_SIZE_CAP) - u64Mask |= HPET_TN_32BIT; - else - u32NewValue &= ~HPET_TN_32BIT; + case HPET_TN_CFG + 4: /* Interrupt capabilities - read only. */ + Log(("HPET[%u]: write32 HPET_TN_CFG + 4 (ignored)\n", iTimerNo)); + break; - if (u32NewValue & HPET_TN_32BIT) + case HPET_TN_CMP: /* lower bits of comparator register */ { - Log(("setting timer %d to 32-bit mode\n", iTimerNo)); - pHpetTimer->u64Cmp = (uint32_t)pHpetTimer->u64Cmp; - pHpetTimer->u64Period = (uint32_t)pHpetTimer->u64Period; + DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); + uint64_t fConfig = ASMAtomicUoReadU64(&pHpetTimer->u64Config); + Log(("HPET[%u]: write32 HPET_TN_CMP: %#x (fCfg=%#RX32)\n", iTimerNo, u32NewValue, (uint32_t)fConfig)); + + if (fConfig & HPET_TN_PERIODIC) + ASMAtomicUoWriteU64(&pHpetTimer->u64Period, RT_MAKE_U64(u32NewValue, RT_HI_U32(pHpetTimer->u64Period))); + + if (!(fConfig & HPET_TN_PERIODIC) || (fConfig & HPET_TN_SETVAL)) + ASMAtomicUoWriteU64(&pHpetTimer->u64Cmp, RT_MAKE_U64(u32NewValue, RT_HI_U32(pHpetTimer->u64Cmp))); + + ASMAtomicAndU64(&pHpetTimer->u64Config, ~HPET_TN_SETVAL); + Log2(("HPET[%u]: after32 HPET_TN_CMP cmp=%#llx per=%#llx\n", iTimerNo, pHpetTimer->u64Cmp, pHpetTimer->u64Period)); + + if (pThis->u64HpetConfig & HPET_CFG_ENABLE) + hpetProgramTimer(pDevIns, pThis, pHpetTimer, PDMDevHlpTimerGet(pDevIns, pHpetTimer->hTimer)); + DEVHPET_UNLOCK_BOTH(pDevIns, pThis); + break; } - if ((u32NewValue & HPET_TN_INT_TYPE) == HPET_TIMER_TYPE_LEVEL) + + /** @todo figure out how exactly it behaves wrt to HPET_TN_SETVAL */ + case HPET_TN_CMP + 4: /* upper bits of comparator register */ { - LogRelMax(10, ("HPET: Level-triggered config not yet supported\n")); - AssertFailed(); - } + DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); + uint64_t fConfig = ASMAtomicUoReadU64(&pHpetTimer->u64Config); - /* We only care about lower 32-bits so far */ - pHpetTimer->u64Config = hpetUpdateMasked(u32NewValue, pHpetTimer->u64Config, u64Mask); - DEVHPET_UNLOCK(pDevIns, pThis); - break; - } + if (!hpet32bitTimerEx(fConfig)) + { + Log(("HPET[%u]: write32 HPET_TN_CMP + 4: %#x (fCfg=%#RX32)\n", iTimerNo, u32NewValue, (uint32_t)fConfig)); + if (fConfig & HPET_TN_PERIODIC) + ASMAtomicUoWriteU64(&pHpetTimer->u64Period, RT_MAKE_U64(RT_LO_U32(pHpetTimer->u64Period), u32NewValue)); - case HPET_TN_CFG + 4: /* Interrupt capabilities - read only. */ - Log(("write HPET_TN_CFG + 4, useless\n")); - break; + if (!(fConfig & HPET_TN_PERIODIC) || (fConfig & HPET_TN_SETVAL)) + ASMAtomicUoWriteU64(&pHpetTimer->u64Cmp, RT_MAKE_U64(RT_LO_U32(pHpetTimer->u64Cmp), u32NewValue)); - case HPET_TN_CMP: /* lower bits of comparator register */ - { - DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); - Log(("write HPET_TN_CMP on %d: %#x\n", iTimerNo, u32NewValue)); + ASMAtomicAndU64(&pHpetTimer->u64Config, ~HPET_TN_SETVAL); + Log2(("HPET[%u]: after32 HPET_TN_CMP+4: cmp=%#llx per=%#llx\n", iTimerNo, pHpetTimer->u64Cmp, pHpetTimer->u64Period)); - if (pHpetTimer->u64Config & HPET_TN_PERIODIC) - pHpetTimer->u64Period = RT_MAKE_U64(u32NewValue, RT_HI_U32(pHpetTimer->u64Period)); - pHpetTimer->u64Cmp = RT_MAKE_U64(u32NewValue, RT_HI_U32(pHpetTimer->u64Cmp)); - pHpetTimer->u64Config &= ~HPET_TN_SETVAL; - Log2(("after HPET_TN_CMP cmp=%#llx per=%#llx\n", pHpetTimer->u64Cmp, pHpetTimer->u64Period)); + if (pThis->u64HpetConfig & HPET_CFG_ENABLE) + hpetProgramTimer(pDevIns, pThis, pHpetTimer, PDMDevHlpTimerGet(pDevIns, pHpetTimer->hTimer)); + } + else + Log(("HPET[%u]: write32 HPET_TN_CMP + 4: %#x - but timer is 32-bit!! (fCfg=%#RX32)\n", iTimerNo, u32NewValue, (uint32_t)fConfig)); + DEVHPET_UNLOCK_BOTH(pDevIns, pThis); + break; + } - if (pThis->u64HpetConfig & HPET_CFG_ENABLE) - hpetProgramTimer(pDevIns, pThis, pHpetTimer); - DEVHPET_UNLOCK_BOTH(pDevIns, pThis); - break; + case HPET_TN_ROUTE: + Log(("HPET[%u]: write32 HPET_TN_ROUTE (ignored)\n", iTimerNo)); + break; + + case HPET_TN_ROUTE + 4: + Log(("HPET[%u]: write32 HPET_TN_ROUTE + 4 (ignored)\n", iTimerNo)); + break; + + default: + LogRelMax(10, ("HPET[%u]: Invalid timer register write: %d\n", iTimerNo, iTimerReg)); + break; } + } + else + LogRelMax(10, ("HPET: Using timer above configured range: %d (reg %#x)\n", iTimerNo, iTimerReg)); + return VINF_SUCCESS; +} + - case HPET_TN_CMP + 4: /* upper bits of comparator register */ +/** + * 32-bit write to a HPET timer register. + * + * @returns Strict VBox status code. + * + * @param pDevIns The device instance. + * @param pThis The shared HPET state. + * @param iTimerNo The timer being written to. + * @param iTimerReg The register being written to. + * @param u64NewValue The value being written. + * + * @remarks The caller should not hold any locks. + */ +static VBOXSTRICTRC hpetTimerRegWrite64(PPDMDEVINS pDevIns, PHPET pThis, uint32_t iTimerNo, + uint32_t iTimerReg, uint64_t u64NewValue) +{ + Assert(!PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); + Assert(!PDMDevHlpTimerIsLockOwner(pDevIns, pThis->aTimers[0].hTimer)); + Assert(!(iTimerReg & 7)); + + if ( iTimerNo < HPET_CAP_GET_TIMERS(pThis->u32Capabilities) + && iTimerNo < RT_ELEMENTS(pThis->aTimers) ) /* Parfait - see above. */ + { + PHPETTIMER pHpetTimer = &pThis->aTimers[iTimerNo]; + + switch (iTimerReg) { - DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); - Log(("write HPET_TN_CMP + 4 on %d: %#x\n", iTimerNo, u32NewValue)); - if (!hpet32bitTimer(pHpetTimer)) + case HPET_TN_CFG: + /* The upper 32 bits are not writable, so join paths with the 32-bit version. */ + return hpetTimerRegWrite32(pDevIns, pThis, iTimerNo, iTimerReg, (uint32_t)u64NewValue); + + case HPET_TN_CMP: { - if (pHpetTimer->u64Config & HPET_TN_PERIODIC) - pHpetTimer->u64Period = RT_MAKE_U64(RT_LO_U32(pHpetTimer->u64Period), u32NewValue); - pHpetTimer->u64Cmp = RT_MAKE_U64(RT_LO_U32(pHpetTimer->u64Cmp), u32NewValue); + DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); + uint64_t fConfig = ASMAtomicUoReadU64(&pHpetTimer->u64Config); + Log(("HPET[%u]: write64 HPET_TN_CMP: %#RX64 (fCfg=%#RX64)\n", iTimerNo, u64NewValue, (uint32_t)fConfig)); + + /** @todo not sure if this is right, but it is consistent with the 32-bit config + * change behaviour and defensive wrt mixups. */ + if (!hpet32bitTimerEx(fConfig)) + { /* likely */ } + else + u64NewValue = (uint32_t)u64NewValue; - Log2(("after HPET_TN_CMP+4 cmp=%llx per=%llx tmr=%d\n", pHpetTimer->u64Cmp, pHpetTimer->u64Period, iTimerNo)); + if (fConfig & HPET_TN_PERIODIC) + ASMAtomicUoWriteU64(&pHpetTimer->u64Period, u64NewValue); - pHpetTimer->u64Config &= ~HPET_TN_SETVAL; + if (!(fConfig & HPET_TN_PERIODIC) || (fConfig & HPET_TN_SETVAL)) + ASMAtomicUoWriteU64(&pHpetTimer->u64Cmp, u64NewValue); + + ASMAtomicAndU64(&pHpetTimer->u64Config, ~HPET_TN_SETVAL); + Log2(("HPET[%u]: after64 HPET_TN_CMP cmp=%#llx per=%#llx\n", iTimerNo, pHpetTimer->u64Cmp, pHpetTimer->u64Period)); if (pThis->u64HpetConfig & HPET_CFG_ENABLE) - hpetProgramTimer(pDevIns, pThis, pHpetTimer); + hpetProgramTimer(pDevIns, pThis, pHpetTimer, PDMDevHlpTimerGet(pDevIns, pHpetTimer->hTimer)); + DEVHPET_UNLOCK_BOTH(pDevIns, pThis); + break; } - DEVHPET_UNLOCK_BOTH(pDevIns, pThis); - break; - } - case HPET_TN_ROUTE: - Log(("write HPET_TN_ROUTE\n")); - break; - - case HPET_TN_ROUTE + 4: - Log(("write HPET_TN_ROUTE + 4\n")); - break; + case HPET_TN_ROUTE: + Log(("HPET[%u]: write64 HPET_TN_ROUTE (ignored)\n", iTimerNo)); + break; - default: - LogRelMax(10, ("HPET: Invalid timer register write: %d\n", iTimerReg)); - break; + default: + LogRelMax(10, ("HPET[%u]: Invalid timer register write: %d\n", iTimerNo, iTimerReg)); + break; + } } - + else + LogRelMax(10, ("HPET: Using timer above configured range: %d (reg %#x)\n", iTimerNo, iTimerReg)); return VINF_SUCCESS; } @@ -701,9 +906,7 @@ break; case HPET_PERIOD: - DEVHPET_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_READ); - u32Value = pThis->u32Period; - DEVHPET_UNLOCK(pDevIns, pThis); + u32Value = pThis->fIch9 ? HPET_CLK_PERIOD_ICH9 : HPET_CLK_PERIOD_PIIX; Log(("read HPET_PERIOD: %#x\n", u32Value)); break; @@ -724,19 +927,28 @@ case HPET_COUNTER: case HPET_COUNTER + 4: { - STAM_REL_COUNTER_INC(&pThis->StatCounterRead4Byte); + /** @todo We don't technically need to sit on the virtualsync lock here to + * read it, but it helps wrt quality... */ DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_READ); uint64_t u64Ticks; if (pThis->u64HpetConfig & HPET_CFG_ENABLE) - u64Ticks = hpetGetTicks(pDevIns, pThis); + { + uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pThis->aTimers[0].hTimer); + PDMDevHlpTimerUnlockClock(pDevIns, pThis->aTimers[0].hTimer); + u64Ticks = hpetGetTicksEx(pThis, tsNow); + } else + { + PDMDevHlpTimerUnlockClock(pDevIns, pThis->aTimers[0].hTimer); u64Ticks = pThis->u64HpetCounter; + } - DEVHPET_UNLOCK_BOTH(pDevIns, pThis); + STAM_REL_COUNTER_INC(&pThis->StatCounterRead4Byte); + DEVHPET_UNLOCK(pDevIns, pThis); /** @todo is it correct? */ - u32Value = (idxReg == HPET_COUNTER) ? (uint32_t)u64Ticks : (uint32_t)(u64Ticks >> 32); + u32Value = idxReg == HPET_COUNTER ? (uint32_t)u64Ticks : (uint32_t)(u64Ticks >> 32); Log(("read HPET_COUNTER: %s part value %x (%#llx)\n", (idxReg == HPET_COUNTER) ? "low" : "high", u32Value, u64Ticks)); break; } @@ -816,33 +1028,37 @@ #endif } - pThis->u64HpetConfig = hpetUpdateMasked(u32NewValue, iOldValue, HPET_CFG_WRITE_MASK); + /* Updating it using an atomic write just to be on the safe side. */ + ASMAtomicWriteU64(&pThis->u64HpetConfig, hpetUpdateMasked(u32NewValue, iOldValue, HPET_CFG_WRITE_MASK)); - uint32_t const cTimers = HPET_CAP_GET_TIMERS(pThis->u32Capabilities); + uint32_t const cTimers = RT_MIN(HPET_CAP_GET_TIMERS(pThis->u32Capabilities), RT_ELEMENTS(pThis->aTimers)); if (hpetBitJustSet(iOldValue, u32NewValue, HPET_CFG_ENABLE)) { -/** @todo Only get the time stamp once when reprogramming? */ - /* Enable main counter and interrupt generation. */ + /* + * Enable main counter and interrupt generation. + */ uint64_t u64TickLimit = pThis->fIch9 ? HPET_TICKS_IN_100YR_ICH9 : HPET_TICKS_IN_100YR_PIIX; if (pThis->u64HpetCounter <= u64TickLimit) - { - pThis->u64HpetOffset = hpetTicksToNs(pThis, pThis->u64HpetCounter) - - PDMDevHlpTimerGet(pDevIns, pThis->aTimers[0].hTimer); - } + pThis->u64HpetOffset = hpetTicksToNs(pThis, pThis->u64HpetCounter); else { LogRelMax(10, ("HPET: Counter set more than 100 years in the future, reducing.\n")); - pThis->u64HpetOffset = 1000000LL * 60 * 60 * 24 * 365 * 100 - - PDMDevHlpTimerGet(pDevIns, pThis->aTimers[0].hTimer); + pThis->u64HpetOffset = 1000000LL * 60 * 60 * 24 * 365 * 100; } + + uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pThis->aTimers[0].hTimer); + pThis->u64HpetOffset -= tsNow; + for (uint32_t i = 0; i < cTimers; i++) if (pThis->aTimers[i].u64Cmp != hpetInvalidValue(&pThis->aTimers[i])) - hpetProgramTimer(pDevIns, pThis, &pThis->aTimers[i]); + hpetProgramTimer(pDevIns, pThis, &pThis->aTimers[i], tsNow); } else if (hpetBitJustCleared(iOldValue, u32NewValue, HPET_CFG_ENABLE)) { - /* Halt main counter and disable interrupt generation. */ - pThis->u64HpetCounter = hpetGetTicks(pDevIns, pThis); + /* + * Halt main counter and disable interrupt generation. + */ + pThis->u64HpetCounter = hpetGetTicksEx(pThis, PDMDevHlpTimerGet(pDevIns, pThis->aTimers[0].hTimer)); for (uint32_t i = 0; i < cTimers; i++) PDMDevHlpTimerStop(pDevIns, pThis->aTimers[i].hTimer); } @@ -853,6 +1069,8 @@ case HPET_CFG + 4: { +/** @todo r=bird: Is the whole upper part of the config register really + * writable? Only 2 bits are writable in the lower part... */ DEVHPET_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); pThis->u64HpetConfig = hpetUpdateMasked((uint64_t)u32NewValue << 32, pThis->u64HpetConfig, @@ -885,6 +1103,7 @@ STAM_REL_COUNTER_INC(&pThis->StatCounterWriteLow); DEVHPET_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); pThis->u64HpetCounter = RT_MAKE_U64(u32NewValue, RT_HI_U32(pThis->u64HpetCounter)); +/** @todo how is this supposed to work if the HPET is enabled? */ Log(("write HPET_COUNTER: %#x -> %llx\n", u32NewValue, pThis->u64HpetCounter)); DEVHPET_UNLOCK(pDevIns, pThis); break; @@ -920,6 +1139,7 @@ HPET *pThis = PDMDEVINS_2_DATA(pDevIns, HPET*); NOREF(pvUser); Assert(cb == 4 || cb == 8); + Assert(!(off & (cb - 1))); LogFlow(("hpetMMIORead (%d): %RGp\n", cb, off)); @@ -931,12 +1151,9 @@ */ if (off >= 0x100 && off < 0x400) { - DEVHPET_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_READ); - hpetTimerRegRead32(pDevIns, pThis, - (uint32_t)(off - 0x100) / 0x20, - (uint32_t)(off - 0x100) % 0x20, - (uint32_t *)pv); - DEVHPET_UNLOCK(pDevIns, pThis); + *(uint32_t *)pv = hpetTimerRegRead32(pThis, + (uint32_t)(off - 0x100) / 0x20, + (uint32_t)(off - 0x100) % 0x20); rc = VINF_SUCCESS; } else @@ -951,36 +1168,47 @@ PRTUINT64U pValue = (PRTUINT64U)pv; if (off == HPET_COUNTER) { + /** @todo We don't technically need to sit on the virtualsync lock here to + * read it, but it helps wrt quality... */ /* When reading HPET counter we must read it in a single read, to avoid unexpected time jumps on 32-bit overflow. */ - STAM_REL_COUNTER_INC(&pThis->StatCounterRead8Byte); DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_READ); + if (pThis->u64HpetConfig & HPET_CFG_ENABLE) - pValue->u = hpetGetTicks(pDevIns, pThis); + { + uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pThis->aTimers[0].hTimer); + PDMDevHlpTimerUnlockClock(pDevIns, pThis->aTimers[0].hTimer); + pValue->u = hpetGetTicksEx(pThis, tsNow); + } else + { + PDMDevHlpTimerUnlockClock(pDevIns, pThis->aTimers[0].hTimer); pValue->u = pThis->u64HpetCounter; - DEVHPET_UNLOCK_BOTH(pDevIns, pThis); + } + + STAM_REL_COUNTER_INC(&pThis->StatCounterRead8Byte); + DEVHPET_UNLOCK(pDevIns, pThis); rc = VINF_SUCCESS; } else { - DEVHPET_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_READ); if (off >= 0x100 && off < 0x400) { uint32_t iTimer = (uint32_t)(off - 0x100) / 0x20; uint32_t iTimerReg = (uint32_t)(off - 0x100) % 0x20; - hpetTimerRegRead32(pDevIns, pThis, iTimer, iTimerReg, &pValue->s.Lo); - hpetTimerRegRead32(pDevIns, pThis, iTimer, iTimerReg + 4, &pValue->s.Hi); + Assert(!(iTimerReg & 7)); + pValue->u = hpetTimerRegRead64(pThis, iTimer, iTimerReg); rc = VINF_SUCCESS; } else { /* for most 8-byte accesses we just split them, happens under lock anyway. */ + DEVHPET_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_READ); rc = hpetConfigRegRead32(pDevIns, pThis, off, &pValue->s.Lo); if (rc == VINF_SUCCESS) rc = hpetConfigRegRead32(pDevIns, pThis, off + 4, &pValue->s.Hi); + DEVHPET_UNLOCK(pDevIns, pThis); } - DEVHPET_UNLOCK(pDevIns, pThis); } } return rc; @@ -997,6 +1225,7 @@ cb, off, cb == 4 ? *(uint32_t *)pv : cb == 8 ? *(uint64_t *)pv : 0xdeadbeef)); NOREF(pvUser); Assert(cb == 4 || cb == 8); + Assert(!(off & (cb - 1))); VBOXSTRICTRC rc; if (cb == 4) @@ -1014,26 +1243,22 @@ /* * 8-byte access. */ - /* Split the access and rely on the locking to prevent trouble. */ - DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); - RTUINT64U uValue; - uValue.u = *(uint64_t const *)pv; if (off >= 0x100 && off < 0x400) - { - uint32_t iTimer = (uint32_t)(off - 0x100) / 0x20; - uint32_t iTimerReg = (uint32_t)(off - 0x100) % 0x20; - /** @todo Consider handling iTimerReg == HPET_TN_CMP specially here */ - rc = hpetTimerRegWrite32(pDevIns, pThis, iTimer, iTimerReg, uValue.s.Lo); - if (RT_LIKELY(rc == VINF_SUCCESS)) - rc = hpetTimerRegWrite32(pDevIns, pThis, iTimer, iTimerReg + 4, uValue.s.Hi); - } + rc = hpetTimerRegWrite64(pDevIns, pThis, + (uint32_t)(off - 0x100) / 0x20, + (uint32_t)(off - 0x100) % 0x20, + *(uint64_t const *)pv); else { + /* Split the access and rely on the locking to prevent trouble. */ + RTUINT64U uValue; + uValue.u = *(uint64_t const *)pv; + DEVHPET_LOCK_BOTH_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); rc = hpetConfigRegWrite32(pDevIns, pThis, off, uValue.s.Lo); if (RT_LIKELY(rc == VINF_SUCCESS)) rc = hpetConfigRegWrite32(pDevIns, pThis, off + 4, uValue.s.Hi); + DEVHPET_UNLOCK_BOTH(pDevIns, pThis); } - DEVHPET_UNLOCK_BOTH(pDevIns, pThis); } return rc; @@ -1049,8 +1274,9 @@ * @returns IRQ number. * @param pThis The shared HPET state. * @param pHpetTimer The HPET timer. + * @param fConfig The HPET timer config value. */ -static uint32_t hpetR3TimerGetIrq(PHPET pThis, PCHPETTIMER pHpetTimer) +DECLINLINE(uint32_t) hpetR3TimerGetIrq(PHPET pThis, PCHPETTIMER pHpetTimer, uint64_t fConfig) { /* * Per spec, in legacy mode the HPET timers are wired as follows: @@ -1060,99 +1286,103 @@ * ISA IRQ delivery logic will take care of correct delivery * to the different ICs. */ - if ( (pHpetTimer->idxTimer <= 1) + if ( pHpetTimer->idxTimer <= 1 && (pThis->u64HpetConfig & HPET_CFG_LEGACY)) - return (pHpetTimer->idxTimer == 0) ? 0 : 8; + return pHpetTimer->idxTimer == 0 ? 0 : 8; - return (pHpetTimer->u64Config & HPET_TN_INT_ROUTE_MASK) >> HPET_TN_INT_ROUTE_SHIFT; + return (fConfig & HPET_TN_INT_ROUTE_MASK) >> HPET_TN_INT_ROUTE_SHIFT; } /** - * Used by hpetR3Timer to update the IRQ status. - * - * @param pDevIns The device instance. - * @param pThis The shared HPET state. - * @param pHpetTimer The HPET timer. - */ -static void hpetR3TimerUpdateIrq(PPDMDEVINS pDevIns, PHPET pThis, PHPETTIMER pHpetTimer) -{ - /** @todo is it correct? */ - if ( !!(pHpetTimer->u64Config & HPET_TN_ENABLE) - && !!(pThis->u64HpetConfig & HPET_CFG_ENABLE)) - { - uint32_t irq = hpetR3TimerGetIrq(pThis, pHpetTimer); - Log4(("HPET: raising IRQ %d\n", irq)); - - /* ISR bits are only set in level-triggered mode. */ - if ((pHpetTimer->u64Config & HPET_TN_INT_TYPE) == HPET_TIMER_TYPE_LEVEL) - pThis->u64Isr |= UINT64_C(1) << pHpetTimer->idxTimer; - - /* We trigger flip/flop in edge-triggered mode and do nothing in - level-triggered mode yet. */ - if ((pHpetTimer->u64Config & HPET_TN_INT_TYPE) == HPET_TIMER_TYPE_EDGE) - { - PHPETCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHPETCC); - AssertReturnVoid(pThisCC); - pThisCC->pHpetHlp->pfnSetIrq(pDevIns, irq, PDM_IRQ_LEVEL_FLIP_FLOP); - STAM_REL_COUNTER_INC(&pHpetTimer->StatSetIrq); - } - else - AssertFailed(); - /** @todo implement IRQs in level-triggered mode */ - } -} - -/** * Device timer callback function. * * @param pDevIns Device instance of the device which registered the timer. * @param pTimer The timer handle. * @param pvUser Pointer to the HPET timer state. + * + * @note Only the virtual sync lock is held when called. */ static DECLCALLBACK(void) hpetR3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { - PHPET pThis = PDMDEVINS_2_DATA(pDevIns, PHPET); - PHPETTIMER pHpetTimer = (HPETTIMER *)pvUser; - uint64_t u64Period = pHpetTimer->u64Period; - uint64_t u64CurTick = hpetGetTicks(pDevIns, pThis); - uint64_t u64Diff; + PHPET pThis = PDMDEVINS_2_DATA(pDevIns, PHPET); + PHPETTIMER pHpetTimer = (HPETTIMER *)pvUser; + + /* + * Read the timer configuration values we need first. + * + * The comparator and period are only written while owning the virtual sync + * lock, so we don't run any risk there. The configuration register is + * written with only the device lock, so must be a bit more careful with it. + */ + uint64_t uCmp = ASMAtomicUoReadU64(&pHpetTimer->u64Cmp); + uint64_t const uPeriod = ASMAtomicUoReadU64(&pHpetTimer->u64Period); + uint64_t const fConfig = ASMAtomicUoReadU64(&pHpetTimer->u64Config); RT_NOREF(pTimer); - if (pHpetTimer->u64Config & HPET_TN_PERIODIC) + if (fConfig & HPET_TN_PERIODIC) { - if (u64Period) + if (uPeriod) { - hpetAdjustComparator(pHpetTimer, u64CurTick); - - u64Diff = hpetComputeDiff(pHpetTimer, u64CurTick); - - uint64_t u64TickLimit = pThis->fIch9 ? HPET_TICKS_IN_100YR_ICH9 : HPET_TICKS_IN_100YR_PIIX; - if (u64Diff <= u64TickLimit) + uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pHpetTimer->hTimer); + uint64_t const uHpetNow = hpetGetTicksEx(pThis, tsNow); + uCmp = hpetAdjustComparator(pHpetTimer, fConfig, uCmp, uPeriod, uHpetNow); + uint64_t const cTicksDiff = hpetComputeDiff(fConfig, uCmp, uHpetNow); + uint64_t const u64TickLimit = pThis->fIch9 ? HPET_TICKS_IN_100YR_ICH9 : HPET_TICKS_IN_100YR_PIIX; + if (cTicksDiff <= u64TickLimit) { - Log4(("HPET: periodic: next in %llu\n", hpetTicksToNs(pThis, u64Diff))); + uint64_t const tsDeadline = tsNow + hpetTicksToNs(pThis, cTicksDiff); + Log4(("HPET[%u]: periodic: next in %llu\n", pHpetTimer->idxTimer, tsDeadline)); + PDMDevHlpTimerSet(pDevIns, pHpetTimer->hTimer, tsDeadline); STAM_REL_COUNTER_INC(&pHpetTimer->StatSetTimer); - PDMDevHlpTimerSetNano(pDevIns, pHpetTimer->hTimer, hpetTicksToNs(pThis, u64Diff)); } else - { - LogRelMax(10, ("HPET: Not scheduling periodic interrupt more than 100 years in the future.\n")); - } + LogRelMax(10, ("HPET[%u]: Not scheduling periodic interrupt more than 100 years in the future.\n", + pHpetTimer->idxTimer)); } } - else if (hpet32bitTimer(pHpetTimer)) + /* For 32-bit non-periodic timers, generate wrap-around interrupts. */ + else if (pHpetTimer->u8Wrap && hpet32bitTimerEx(fConfig)) + { + pHpetTimer->u8Wrap = 0; /* (only modified while owning the virtual sync lock) */ + uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pHpetTimer->hTimer); + uint64_t const uHpetNow = nsToHpetTicks(pThis, tsNow + pThis->u64HpetOffset); + uint64_t const cTicksDiff = hpetComputeDiff(fConfig, uCmp, uHpetNow); + uint64_t const tsDeadline = tsNow + hpetTicksToNs(pThis, cTicksDiff); + Log4(("HPET[%u]: post-wrap deadline: %llu\n", pHpetTimer->idxTimer, tsDeadline)); + PDMDevHlpTimerSet(pDevIns, pHpetTimer->hTimer, tsDeadline); + } + + /* + * IRQ update. + */ + if ( (fConfig & HPET_TN_ENABLE) + && (pThis->u64HpetConfig & HPET_CFG_ENABLE)) { - /* For 32-bit non-periodic timers, generate wrap-around interrupts. */ - if (pHpetTimer->u8Wrap) + AssertCompile(HPET_TN_INT_TYPE == 2); + + /* We trigger flip/flop in edge-triggered mode and do nothing in + level-triggered mode yet. */ + if ((fConfig & HPET_TN_INT_TYPE) == HPET_TIMER_TYPE_EDGE) { - u64Diff = hpetComputeDiff(pHpetTimer, u64CurTick); - PDMDevHlpTimerSetNano(pDevIns, pHpetTimer->hTimer, hpetTicksToNs(pThis, u64Diff)); - pHpetTimer->u8Wrap = 0; + PHPETCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHPETCC); + AssertReturnVoid(pThisCC); + + uint32_t const uIrq = hpetR3TimerGetIrq(pThis, pHpetTimer, fConfig); + Log4(("HPET[%u]: raising IRQ %u\n", pHpetTimer->idxTimer, uIrq)); + + pThisCC->pHpetHlp->pfnSetIrq(pDevIns, uIrq, PDM_IRQ_LEVEL_FLIP_FLOP); + STAM_REL_COUNTER_INC(&pHpetTimer->StatSetIrq); + } + /* ISR bits are only set in level-triggered mode. */ + else + { + Assert((fConfig & HPET_TN_INT_TYPE) == HPET_TIMER_TYPE_LEVEL); + ASMAtomicOrU64(&pThis->u64Isr, RT_BIT_64(pHpetTimer->idxTimer)); + /** @todo implement IRQs in level-triggered mode */ } } - /* Should it really be under lock, does it really matter? */ - hpetR3TimerUpdateIrq(pDevIns, pThis, pHpetTimer); } @@ -1170,21 +1400,54 @@ pHlp->pfnPrintf(pHlp, "HPET status:\n" " config=%016RX64 isr=%016RX64\n" - " offset=%016RX64 counter=%016RX64 frequency=%08x\n" + " offset=%016RX64 counter=%016RX64 frequency=%u fs\n" " legacy-mode=%s timer-count=%u\n", pThis->u64HpetConfig, pThis->u64Isr, - pThis->u64HpetOffset, pThis->u64HpetCounter, pThis->u32Period, + pThis->u64HpetOffset, pThis->u64HpetCounter, pThis->fIch9 ? HPET_CLK_PERIOD_ICH9 : HPET_CLK_PERIOD_PIIX, !!(pThis->u64HpetConfig & HPET_CFG_LEGACY) ? "on " : "off", HPET_CAP_GET_TIMERS(pThis->u32Capabilities)); pHlp->pfnPrintf(pHlp, "Timers:\n"); for (unsigned i = 0; i < RT_ELEMENTS(pThis->aTimers); i++) { - pHlp->pfnPrintf(pHlp, " %d: comparator=%016RX64 period(hidden)=%016RX64 cfg=%016RX64\n", + static const struct + { + const char *psz; + uint32_t cch; + uint32_t fFlags; + } s_aFlags[] = + { + { RT_STR_TUPLE(" lvl"), HPET_TN_INT_TYPE }, + { RT_STR_TUPLE(" en"), HPET_TN_ENABLE }, + { RT_STR_TUPLE(" per"), HPET_TN_PERIODIC }, + { RT_STR_TUPLE(" cap_per"), HPET_TN_PERIODIC_CAP }, + { RT_STR_TUPLE(" cap_64"), HPET_TN_SIZE_CAP }, + { RT_STR_TUPLE(" setval"), HPET_TN_SETVAL }, + { RT_STR_TUPLE(" 32b"), HPET_TN_32BIT }, + }; + char szTmp[64]; + uint64_t fCfg = pThis->aTimers[i].u64Config; + size_t off = 0; + for (unsigned j = 0; j < RT_ELEMENTS(s_aFlags); j++) + if (fCfg & s_aFlags[j].fFlags) + { + memcpy(&szTmp[off], s_aFlags[j].psz, s_aFlags[j].cch); + off += s_aFlags[j].cch; + fCfg &= ~(uint64_t)s_aFlags[j].fFlags; + } + szTmp[off] = '\0'; + Assert(off < sizeof(szTmp)); + + pHlp->pfnPrintf(pHlp, + " %d: comparator=%016RX64 accumulator=%016RX64 (%RU64 ns)\n" + " config=%016RX64 irq=%d%s\n", pThis->aTimers[i].idxTimer, pThis->aTimers[i].u64Cmp, pThis->aTimers[i].u64Period, - pThis->aTimers[i].u64Config); + hpetTicksToNs(pThis, pThis->aTimers[i].u64Period), + pThis->aTimers[i].u64Config, + hpetR3TimerGetIrq(pThis, &pThis->aTimers[i], pThis->aTimers[i].u64Config), + szTmp); } } @@ -1237,7 +1500,7 @@ } pHlp->pfnSSMPutU64(pSSM, pThis->u64HpetOffset); - uint64_t u64CapPer = RT_MAKE_U64(pThis->u32Capabilities, pThis->u32Period); + uint64_t u64CapPer = RT_MAKE_U64(pThis->u32Capabilities, pThis->fIch9 ? HPET_CLK_PERIOD_ICH9 : HPET_CLK_PERIOD_PIIX); pHlp->pfnSSMPutU64(pSSM, u64CapPer); pHlp->pfnSSMPutU64(pSSM, pThis->u64HpetConfig); pHlp->pfnSSMPutU64(pSSM, pThis->u64Isr); @@ -1313,7 +1576,10 @@ RT_LO_U32(u64CapPer), (unsigned)HPET_CAP_GET_TIMERS(RT_LO_U32(u64CapPer)), RT_ELEMENTS(pThis->aTimers)); pThis->u32Capabilities = RT_LO_U32(u64CapPer); - pThis->u32Period = RT_HI_U32(u64CapPer); + uint32_t const uExpectedPeriod = pThis->fIch9 ? HPET_CLK_PERIOD_ICH9 : HPET_CLK_PERIOD_PIIX; + if (RT_HI_U32(u64CapPer) != uExpectedPeriod) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch - Expected period %RU32 fs, loaded %RU32 fs"), + uExpectedPeriod, RT_HI_U32(u64CapPer)); /* * Set the timer frequency hints. @@ -1323,7 +1589,7 @@ { PHPETTIMER pHpetTimer = &pThis->aTimers[iTimer]; if (PDMDevHlpTimerIsActive(pDevIns, pHpetTimer->hTimer)) - hpetTimerSetFrequencyHint(pDevIns, pThis, pHpetTimer); + hpetTimerSetFrequencyHint(pDevIns, pThis, pHpetTimer, pHpetTimer->u64Config, pHpetTimer->u64Period); } PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); return VINF_SUCCESS; @@ -1365,16 +1631,16 @@ PDMDevHlpTimerStop(pDevIns, pHpetTimer->hTimer); /* capable of periodic operations and 64-bits */ + uint64_t fConfig; if (pThis->fIch9) - pHpetTimer->u64Config = (i == 0) - ? (HPET_TN_PERIODIC_CAP | HPET_TN_SIZE_CAP) - : 0; + fConfig = i == 0 ? HPET_TN_PERIODIC_CAP | HPET_TN_SIZE_CAP : 0; else - pHpetTimer->u64Config = HPET_TN_PERIODIC_CAP | HPET_TN_SIZE_CAP; + fConfig = HPET_TN_PERIODIC_CAP | HPET_TN_SIZE_CAP; /* We can do all IRQs */ uint32_t u32RoutingCap = 0xffffffff; - pHpetTimer->u64Config |= ((uint64_t)u32RoutingCap) << HPET_TN_INT_ROUTE_CAP_SHIFT; + fConfig |= ((uint64_t)u32RoutingCap) << HPET_TN_INT_ROUTE_CAP_SHIFT; + ASMAtomicWriteU64(&pHpetTimer->u64Config, fConfig); pHpetTimer->u64Period = 0; pHpetTimer->u8Wrap = 0; pHpetTimer->u64Cmp = hpetInvalidValue(pHpetTimer); @@ -1400,7 +1666,6 @@ AssertCompile(HPET_NUM_TIMERS_ICH9 <= RT_ELEMENTS(pThis->aTimers)); AssertCompile(HPET_NUM_TIMERS_PIIX <= RT_ELEMENTS(pThis->aTimers)); - pThis->u32Period = pThis->fIch9 ? HPET_CLK_PERIOD_ICH9 : HPET_CLK_PERIOD_PIIX; /* * Notify the PIT/RTC devices. @@ -1462,15 +1727,13 @@ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aTimers); i++) { PHPETTIMER pHpetTimer = &pThis->aTimers[i]; - rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, hpetR3Timer, pHpetTimer, TMTIMER_FLAGS_NO_CRIT_SECT, s_apszTimerNames[i], &pThis->aTimers[i].hTimer); AssertRCReturn(rc, rc); - /** @todo r=bird: This is TOTALLY MESSED UP! Why do we need - * DEVHPET_LOCK_BOTH_RETURN() when the timers use the same critsect as - * we do?!? */ - rc = PDMDevHlpTimerSetCritSect(pDevIns, pThis->aTimers[i].hTimer, &pThis->CritSect); - AssertRCReturn(rc, rc); + uint64_t const cTicksPerSec = PDMDevHlpTimerGetFreq(pDevIns, pThis->aTimers[i].hTimer); + if (cTicksPerSec != RT_NS_1SEC) + return PDMDevHlpVMSetError(pDevIns, VERR_INTERNAL_ERROR_2, RT_SRC_POS, + "Unexpected timer resolution %RU64, code assumes nanonsecond resolution!", cTicksPerSec); } /* @@ -1510,13 +1773,16 @@ /* Statistics: */ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCounterRead4Byte, STAMTYPE_COUNTER, - "CounterRead4Byte", STAMUNIT_OCCURENCES, "HPET_COUNTER 32-bit reads"); + "ReadCounter32bit", STAMUNIT_OCCURENCES, "HPET_COUNTER 32-bit reads"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCounterRead8Byte, STAMTYPE_COUNTER, - "CounterRead8Byte", STAMUNIT_OCCURENCES, "HPET_COUNTER 64-bit reads"); + "ReadCounter64bit", STAMUNIT_OCCURENCES, "HPET_COUNTER 64-bit reads"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCounterWriteLow, STAMTYPE_COUNTER, - "CounterWriteLow", STAMUNIT_OCCURENCES, "Low HPET_COUNTER writes"); + "WriteCounterLow", STAMUNIT_OCCURENCES, "Low HPET_COUNTER writes"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCounterWriteHigh, STAMTYPE_COUNTER, - "CounterWriteHigh", STAMUNIT_OCCURENCES, "High HPET_COUNTER writes"); + "WriteCounterHigh", STAMUNIT_OCCURENCES, "High HPET_COUNTER writes"); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatZeroDeltaHack, STAMTYPE_COUNTER, + "ZeroDeltaHacks", STAMUNIT_OCCURENCES, "High HPET_COUNTER writes"); + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aTimers); i++) { PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aTimers[i].StatSetIrq, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevIoApic.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevIoApic.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevIoApic.cpp 2020-10-16 16:35:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevIoApic.cpp 2022-09-01 13:26:32.000000000 +0000 @@ -33,7 +33,9 @@ * Defined Constants And Macros * *********************************************************************************************************************************/ /** The current IO APIC saved state version. */ -#define IOAPIC_SAVED_STATE_VERSION 2 +#define IOAPIC_SAVED_STATE_VERSION 3 +/** The current IO APIC saved state version. */ +#define IOAPIC_SAVED_STATE_VERSION_NO_FLIPFLOP_MAP 2 /** The saved state version used by VirtualBox 5.0 and * earlier. */ #define IOAPIC_SAVED_STATE_VERSION_VBOX_50 1 @@ -208,6 +210,8 @@ uint64_t au64RedirTable[IOAPIC_NUM_INTR_PINS]; /** The IRQ tags and source IDs for each pin (tracing purposes). */ uint32_t au32TagSrc[IOAPIC_NUM_INTR_PINS]; + /** Bitmap keeping the flip-flop-ness of pending interrupts. */ + uint64_t bmFlipFlop[(IOAPIC_NUM_INTR_PINS + 63) / 64]; /** The internal IRR reflecting state of the interrupt lines. */ uint32_t uIrr; @@ -453,21 +457,33 @@ AssertMsgFailed(("APIC: Interrupt discarded u8Vector=%#x (%u) u64Rte=%#RX64\n", u8Vector, u8Vector, u64Rte)); #endif - /* - * For level-triggered interrupts, we set the remote IRR bit to indicate - * the local APIC has accepted the interrupt. - * - * For edge-triggered interrupts, we should not clear the IRR bit as it - * should remain intact to reflect the state of the interrupt line. - * The device will explicitly transition to inactive state via the - * ioapicSetIrq() callback. - */ - if ( u8TriggerMode == IOAPIC_RTE_TRIGGER_MODE_LEVEL - && rc == VINF_SUCCESS) + if (rc == VINF_SUCCESS) { - Assert(u8TriggerMode == IOAPIC_RTE_TRIGGER_MODE_LEVEL); - pThis->au64RedirTable[idxRte] |= IOAPIC_RTE_REMOTE_IRR; - STAM_COUNTER_INC(&pThis->StatLevelIrqSent); + /* + * For level-triggered interrupts, we set the remote IRR bit to indicate + * the local APIC has accepted the interrupt. + * + * For edge-triggered interrupts, we should not clear the IRR bit as it + * should remain intact to reflect the state of the interrupt line. + * The device will explicitly transition to inactive state via the + * ioapicSetIrq() callback. + */ + if (u8TriggerMode == IOAPIC_RTE_TRIGGER_MODE_LEVEL) + { + Assert(u8TriggerMode == IOAPIC_RTE_TRIGGER_MODE_LEVEL); + pThis->au64RedirTable[idxRte] |= IOAPIC_RTE_REMOTE_IRR; + STAM_COUNTER_INC(&pThis->StatLevelIrqSent); + } + /* + * Edge-triggered flip-flops gets cleaned up here as the device code will + * not do any explicit ioapicSetIrq and we won't receive any EOI either. + */ + else if (ASMBitTest(pThis->bmFlipFlop, idxRte)) + { + Log2(("IOAPIC: Clearing IRR for edge flip-flop %#x uTagSrc=%#x\n", idxRte, pThis->au32TagSrc[idxRte])); + pThis->au32TagSrc[idxRte] = 0; + pThis->uIrr &= ~RT_BIT_32(idxRte); + } } } } @@ -645,7 +661,15 @@ for (uint8_t idxRte = 0; idxRte < RT_ELEMENTS(pThis->au64RedirTable); idxRte++) { uint64_t const u64Rte = pThis->au64RedirTable[idxRte]; - if (IOAPIC_RTE_GET_VECTOR(u64Rte) == u8Vector) + /** @todo r=bird: bugref:10073: I've changed it to ignore edge triggered + * entries here since the APIC will only call us for those? Not doing so + * confuses ended up with spurious HPET/RTC IRQs in SMP linux because of it + * sharing the vector with a level-triggered IRQ (like vboxguest) delivered on a + * different CPU. + * + * Maybe we should also/instead filter on the source APIC number? */ + if ( IOAPIC_RTE_GET_VECTOR(u64Rte) == u8Vector + && IOAPIC_RTE_GET_TRIGGER_MODE(u64Rte) != IOAPIC_RTE_TRIGGER_MODE_EDGE) { #ifdef DEBUG_ramshankar /* This assertion may trigger when restoring saved-states created using the old, incorrect I/O APIC code. */ @@ -680,8 +704,8 @@ */ static DECLCALLBACK(void) ioapicSetIrq(PPDMDEVINS pDevIns, int iIrq, int iLevel, uint32_t uTagSrc) { -#define IOAPIC_ASSERT_IRQ(a_idxRte, a_PinMask) do { \ - pThis->au32TagSrc[(a_idxRte)] = !pThis->au32TagSrc[(a_idxRte)] ? uTagSrc : RT_BIT_32(31); \ +#define IOAPIC_ASSERT_IRQ(a_idxRte, a_PinMask, a_fForceTag) do { \ + pThis->au32TagSrc[(a_idxRte)] = (a_fForceTag) || !pThis->au32TagSrc[(a_idxRte)] ? uTagSrc : RT_BIT_32(31); \ pThis->uIrr |= a_PinMask; \ ioapicSignalIntrForRte(pDevIns, pThis, pThisCC, (a_idxRte)); \ } while (0) @@ -712,14 +736,17 @@ if (!fActive) { pThis->uIrr &= ~uPinMask; + pThis->au32TagSrc[idxRte] = 0; IOAPIC_UNLOCK(pDevIns, pThis, pThisCC); return; } - bool const fFlipFlop = ((iLevel & PDM_IRQ_LEVEL_FLIP_FLOP) == PDM_IRQ_LEVEL_FLIP_FLOP); - uint32_t const uPrevIrr = pThis->uIrr & uPinMask; + bool const fFlipFlop = ((iLevel & PDM_IRQ_LEVEL_FLIP_FLOP) == PDM_IRQ_LEVEL_FLIP_FLOP); if (!fFlipFlop) { + ASMBitClear(pThis->bmFlipFlop, idxRte); + + uint32_t const uPrevIrr = pThis->uIrr & uPinMask; if (u8TriggerMode == IOAPIC_RTE_TRIGGER_MODE_EDGE) { /* @@ -727,7 +754,7 @@ * See ICH9 spec. 13.5.7 "REDIR_TBL: Redirection Table (LPC I/F-D31:F0)". */ if (!uPrevIrr) - IOAPIC_ASSERT_IRQ(idxRte, uPinMask); + IOAPIC_ASSERT_IRQ(idxRte, uPinMask, false); else { STAM_COUNTER_INC(&pThis->StatRedundantEdgeIntr); @@ -751,7 +778,7 @@ Log2(("IOAPIC: Redundant level-triggered interrupt %#x (%u)\n", idxRte, idxRte)); } - IOAPIC_ASSERT_IRQ(idxRte, uPinMask); + IOAPIC_ASSERT_IRQ(idxRte, uPinMask, false); } } else @@ -762,7 +789,8 @@ * after a flip-flop request. The de-assert is a NOP wrts to signaling an interrupt * hence just the assert is done. */ - IOAPIC_ASSERT_IRQ(idxRte, uPinMask); + ASMBitSet(pThis->bmFlipFlop, idxRte); + IOAPIC_ASSERT_IRQ(idxRte, uPinMask, true); } IOAPIC_UNLOCK(pDevIns, pThis, pThisCC); @@ -777,7 +805,7 @@ static DECLCALLBACK(void) ioapicSendMsi(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, uint32_t uValue, uint32_t uTagSrc) { PIOAPICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PIOAPICCC); - LogFlow(("IOAPIC: ioapicSendMsi: GCPhys=%#RGp uValue=%#RX32\n", GCPhys, uValue)); + LogFlow(("IOAPIC: ioapicSendMsi: GCPhys=%#RGp uValue=%#RX32 uTagSrc=%#x\n", GCPhys, uValue, uTagSrc)); /* * Parse the message from the physical address. @@ -1117,6 +1145,9 @@ for (uint8_t idxRte = 0; idxRte < RT_ELEMENTS(pThis->au64RedirTable); idxRte++) pHlp->pfnSSMPutU64(pSSM, pThis->au64RedirTable[idxRte]); + for (uint8_t idx = 0; idx < RT_ELEMENTS(pThis->bmFlipFlop); idx++) + pHlp->pfnSSMPutU64(pSSM, pThis->bmFlipFlop[idx]); + return VINF_SUCCESS; } @@ -1135,13 +1166,14 @@ /* Weed out invalid versions. */ if ( uVersion != IOAPIC_SAVED_STATE_VERSION + && uVersion != IOAPIC_SAVED_STATE_VERSION_NO_FLIPFLOP_MAP && uVersion != IOAPIC_SAVED_STATE_VERSION_VBOX_50) { LogRel(("IOAPIC: ioapicR3LoadExec: Invalid/unrecognized saved-state version %u (%#x)\n", uVersion, uVersion)); return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; } - if (uVersion == IOAPIC_SAVED_STATE_VERSION) + if (uVersion >= IOAPIC_SAVED_STATE_VERSION_NO_FLIPFLOP_MAP) pHlp->pfnSSMGetU32(pSSM, &pThis->uIrr); pHlp->pfnSSMGetU8V(pSSM, &pThis->u8Id); @@ -1149,6 +1181,10 @@ for (uint8_t idxRte = 0; idxRte < RT_ELEMENTS(pThis->au64RedirTable); idxRte++) pHlp->pfnSSMGetU64(pSSM, &pThis->au64RedirTable[idxRte]); + if (uVersion > IOAPIC_SAVED_STATE_VERSION_NO_FLIPFLOP_MAP) + for (uint8_t idx = 0; idx < RT_ELEMENTS(pThis->bmFlipFlop); idx++) + pHlp->pfnSSMGetU64(pSSM, &pThis->bmFlipFlop[idx]); + return VINF_SUCCESS; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevPcArch.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevPcArch.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevPcArch.cpp 2020-10-16 16:35:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevPcArch.cpp 2022-09-01 13:26:33.000000000 +0000 @@ -192,69 +192,6 @@ /** - * @callback_method_impl{FNIOMMMIONEWWRITE, Ignores writes to the reserved memory.} - * @note off is an absolute address. - */ -static DECLCALLBACK(VBOXSTRICTRC) -pcarchReservedMemoryWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) -{ - Log2(("pcarchReservedMemoryRead: %#RGp LB %#x %.*Rhxs\n", off, cb, RT_MIN(cb, 16), pv)); - RT_NOREF(pDevIns, pvUser, off, pv, cb); - return VINF_SUCCESS; -} - - -/** - * @callback_method_impl{FNIOMMMIONEWREAD, The reserved memory reads as 0xff.} - * @note off is an absolute address. - */ -static DECLCALLBACK(VBOXSTRICTRC) -pcarchReservedMemoryRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) -{ - Log2(("pcarchReservedMemoryRead: %#RGp LB %#x\n", off, cb)); - RT_NOREF(pDevIns, pvUser, off); - memset(pv, 0xff, cb); - return VINF_SUCCESS; -} - - -/** - * @interface_method_impl{PDMDEVREG,pfnInitComplete, - * Turn RAM pages between 0xa0000 and 0xfffff into reserved memory.} - */ -static DECLCALLBACK(int) pcarchInitComplete(PPDMDEVINS pDevIns) -{ - PVM pVM = PDMDevHlpGetVM(pDevIns); - int iRegion = 0; - RTGCPHYS const GCPhysEnd = 0x100000; - RTGCPHYS GCPhysCur = 0x0a0000; - do - { - if (!PGMPhysIsGCPhysNormal(pVM, GCPhysCur)) - GCPhysCur += X86_PAGE_SIZE; - else - { - RTGCPHYS const GCPhysStart = GCPhysCur; - do - GCPhysCur += X86_PAGE_SIZE; - while (GCPhysCur < GCPhysEnd && PGMPhysIsGCPhysNormal(pVM, GCPhysCur)); - - IOMMMIOHANDLE hMmioRegion; - int rc = PDMDevHlpMmioCreateAndMap(pDevIns, GCPhysStart, GCPhysCur - GCPhysStart, - pcarchReservedMemoryWrite, pcarchReservedMemoryRead, - IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU | IOMMMIO_FLAGS_ABS, - MMR3HeapAPrintf(pVM, MM_TAG_PGM_PHYS /* bad bird*/, "PC Arch Reserved #%u", iRegion), - &hMmioRegion); - AssertLogRelRCReturn(rc, rc); - iRegion++; - } - } while (GCPhysCur < GCPhysEnd); - - return VINF_SUCCESS; -} - - -/** * @interface_method_impl{PDMDEVREG,pfnConstruct} */ static DECLCALLBACK(int) pcarchConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) @@ -324,7 +261,7 @@ /* .pfnAttach = */ NULL, /* .pfnDetach = */ NULL, /* .pfnQueryInterface = */ NULL, - /* .pfnInitComplete = */ pcarchInitComplete, + /* .pfnInitComplete = */ NULL, /* .pfnPowerOff = */ NULL, /* .pfnSoftReset = */ NULL, /* .pfnReserved0 = */ NULL, diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevPcBios.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevPcBios.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevPcBios.cpp 2020-10-16 16:35:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevPcBios.cpp 2022-09-01 13:26:33.000000000 +0000 @@ -544,7 +544,7 @@ p = &aMBR[0x1be + i * 16]; iEndHead = p[5]; iEndSector = p[6] & 63; - if ((p[12] | p[13] | p[14] | p[15]) && iEndSector & iEndHead) + if ((p[12] | p[13] | p[14] | p[15]) && iEndSector && iEndHead) { /* Assumption: partition terminates on a cylinder boundary. */ cLCHSHeads = iEndHead + 1; @@ -623,7 +623,7 @@ || LCHSGeometry.cCylinders == 0 || LCHSGeometry.cCylinders > 1024 || LCHSGeometry.cHeads == 0 - || LCHSGeometry.cHeads > 16 + || LCHSGeometry.cHeads > 255 || LCHSGeometry.cSectors == 0 || LCHSGeometry.cSectors > 63) { diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevPIC.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevPIC.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevPIC.cpp 2020-10-16 16:35:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevPIC.cpp 2022-09-01 13:26:33.000000000 +0000 @@ -499,11 +499,15 @@ pPic->init_state = 1; pPic->init4 = val & 1; - if (val & 0x02) - AssertReleaseMsgFailed(("single mode not supported")); - if (val & 0x08) - if (pThis->cRelLogEntries++ < 64) - LogRel(("pic_write: Level sensitive IRQ setting ignored.\n")); + if (!(val & 0x0a)) + { /* likely */ } + else if (pThis->cRelLogEntries++ < 64) + { + if (val & 0x02) + LogRel(("PIC: Single mode not supported, ignored.\n")); + if (val & 0x08) + LogRel(("PIC: Level sensitive IRQ setting ignored.\n")); + } } else if (val & 0x08) { diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevRTC.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevRTC.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/PC/DevRTC.cpp 2020-10-16 16:35:48.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/PC/DevRTC.cpp 2022-09-01 13:26:33.000000000 +0000 @@ -253,16 +253,19 @@ PDMDevHlpTimerSet(pDevIns, pThis->hPeriodicTimer, pThis->next_periodic_time); #ifdef IN_RING3 - if (RT_UNLIKELY(period != pThis->CurLogPeriod)) -#else - if (RT_UNLIKELY(period != pThis->CurHintPeriod)) -#endif + if (RT_LIKELY(period == pThis->CurLogPeriod)) + { /* likely */ } + else { -#ifdef IN_RING3 if (pThis->cRelLogEntries++ < 64) LogRel(("RTC: period=%#x (%d) %u Hz\n", period, period, _32K / period)); - pThis->CurLogPeriod = period; + pThis->CurLogPeriod = period; + } #endif + if (RT_LIKELY(period == pThis->CurHintPeriod)) + { /* likely */ } + else + { pThis->CurHintPeriod = period; PDMDevHlpTimerSetFrequencyHint(pDevIns, pThis->hPeriodicTimer, _32K / period); } @@ -274,6 +277,8 @@ LogRel(("RTC: Stopped the periodic timer\n")); #endif PDMDevHlpTimerStop(pDevIns, pThis->hPeriodicTimer); + pThis->CurHintPeriod = 0; + pThis->CurLogPeriod = 0; } RT_NOREF(pDevIns); } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Samples/DevPlayground.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Samples/DevPlayground.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Samples/DevPlayground.cpp 2020-10-16 16:36:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Samples/DevPlayground.cpp 2022-09-01 13:26:58.000000000 +0000 @@ -62,6 +62,8 @@ IOMMMIOHANDLE hMmio0; /** The MMIO region \#2 handle. */ IOMMMIOHANDLE hMmio2; + /** Backing storage. */ + uint8_t abBacking[4096]; } VBOXPLAYGROUNDDEVICEFUNCTION; /** Pointer to a PCI function of the playground device. */ typedef VBOXPLAYGROUNDDEVICEFUNCTION *PVBOXPLAYGROUNDDEVICEFUNCTION; @@ -85,24 +87,48 @@ * Device Functions * *********************************************************************************************************************************/ +/** + * @callback_method_impl{FNIOMMMIONEWREAD} + */ static DECLCALLBACK(VBOXSTRICTRC) devPlaygroundMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) { + PVBOXPLAYGROUNDDEVICEFUNCTION pFun = (PVBOXPLAYGROUNDDEVICEFUNCTION)pvUser; NOREF(pDevIns); - NOREF(pvUser); - NOREF(off); - NOREF(pv); - NOREF(cb); + +#ifdef LOG_ENABLED + unsigned const cbLog = cb; + RTGCPHYS offLog = off; +#endif + uint8_t *pbDst = (uint8_t *)pv; + while (cb-- > 0) + { + *pbDst = pFun->abBacking[off % RT_ELEMENTS(pFun->abBacking)]; + pbDst++; + off++; + } + + Log(("DevPlayGr/[%u]: READ off=%RGv cb=%u: %.*Rhxs\n", pFun->iFun, offLog, cbLog, cbLog, pv)); return VINF_SUCCESS; } +/** + * @callback_method_impl{FNIOMMMIONEWWRITE} + */ static DECLCALLBACK(VBOXSTRICTRC) devPlaygroundMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) { + PVBOXPLAYGROUNDDEVICEFUNCTION pFun = (PVBOXPLAYGROUNDDEVICEFUNCTION)pvUser; NOREF(pDevIns); - NOREF(pvUser); - NOREF(off); - NOREF(pv); - NOREF(cb); + Log(("DevPlayGr/[%u]: WRITE off=%RGv cb=%u: %.*Rhxs\n", pFun->iFun, off, cb, cb, pv)); + + uint8_t const *pbSrc = (uint8_t const *)pv; + while (cb-- > 0) + { + pFun->abBacking[off % RT_ELEMENTS(pFun->abBacking)] = *pbSrc; + pbSrc++; + off++; + } + return VINF_SUCCESS; } @@ -142,7 +168,7 @@ return VERR_UNEXPECTED_EXCEPTION; } #else -RT_NOREF(pSSM, pHlp); + pHlp->pfnSSMPutStrZ(pSSM, "playground"); #endif return VINF_SUCCESS; @@ -291,7 +317,7 @@ RTGCPHYS const cbFirst = iPciFun == 0 ? cbFirstBAR : iPciFun * _4K; RTStrPrintf(pFun->szMmio0, sizeof(pFun->szMmio0), "PG-F%d-BAR0", iPciFun); rc = PDMDevHlpMmioCreate(pDevIns, cbFirst, pPciDev, 0 /*iPciRegion*/, - devPlaygroundMMIOWrite, devPlaygroundMMIORead, NULL /*pvUser*/, + devPlaygroundMMIOWrite, devPlaygroundMMIORead, pFun, IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU, pFun->szMmio0, &pFun->hMmio0); AssertLogRelRCReturn(rc, rc); @@ -305,7 +331,7 @@ RTGCPHYS const cbSecond = iPciFun == 0 ? cbSecondBAR : iPciFun * _32K; RTStrPrintf(pFun->szMmio2, sizeof(pFun->szMmio2), "PG-F%d-BAR2", iPciFun); rc = PDMDevHlpMmioCreate(pDevIns, cbSecond, pPciDev, 2 << 16 /*iPciRegion*/, - devPlaygroundMMIOWrite, devPlaygroundMMIORead, NULL /*pvUser*/, + devPlaygroundMMIOWrite, devPlaygroundMMIORead, pFun, IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU, pFun->szMmio2, &pFun->hMmio2); AssertLogRelRCReturn(rc, rc); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Serial/UartCore.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Serial/UartCore.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Serial/UartCore.cpp 2020-10-16 16:36:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Serial/UartCore.cpp 2022-09-01 13:26:58.000000000 +0000 @@ -314,21 +314,26 @@ LogFlowFunc((" uRegIirNew=%#x uRegIir=%#x\n", uRegIirNew, pThis->uRegIir)); - /* Change interrupt only if the interrupt status really changed from the previous value. */ if (uRegIirNew != (pThis->uRegIir & UART_REG_IIR_CHANGED_MASK)) - { LogFlow((" Interrupt source changed from %#x -> %#x (IRQ %d -> %d)\n", pThis->uRegIir, uRegIirNew, pThis->uRegIir == UART_REG_IIR_IP_NO_INT ? 0 : 1, uRegIirNew == UART_REG_IIR_IP_NO_INT ? 0 : 1)); - if (uRegIirNew == UART_REG_IIR_IP_NO_INT) - pThisCC->pfnUartIrqReq(pDevIns, pThis, pThis->iLUN, 0); - else - pThisCC->pfnUartIrqReq(pDevIns, pThis, pThis->iLUN, 1); - } else LogFlow((" No change in interrupt source\n")); + /* + * Set interrupt value accordingly. As this is an ISA device most guests + * configure the IRQ as edge triggered instead of level triggered. + * So this needs to be done everytime, even if the internal interrupt state + * doesn't change in order to avoid the guest losing interrupts (reading one byte at + * a time from the FIFO for instance which doesn't change the interrupt source). + */ + if (uRegIirNew == UART_REG_IIR_IP_NO_INT) + pThisCC->pfnUartIrqReq(pDevIns, pThis, pThis->iLUN, 0); + else + pThisCC->pfnUartIrqReq(pDevIns, pThis, pThis->iLUN, 1); + if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN) uRegIirNew |= UART_REG_IIR_FIFOS_EN; if (pThis->uRegFcr & UART_REG_FCR_64BYTE_FIFO_EN) @@ -858,18 +863,20 @@ #ifdef IN_RING3 if (fNotifyDrv) { + /* Leave the device critical section before calling into the lower driver. */ + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + if ( pThisCC->pDrvSerial && !(pThis->uRegMcr & UART_REG_MCR_LOOP)) { - /* Leave the device critical section before calling into the lower driver. */ - PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); int rc2 = pThisCC->pDrvSerial->pfnDataAvailWrNotify(pThisCC->pDrvSerial); if (RT_FAILURE(rc2)) LogRelMax(10, ("Serial#%d: Failed to send data with %Rrc\n", pDevIns->iInstance, rc2)); - PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_SUCCESS); } else PDMDevHlpTimerSetRelative(pDevIns, pThis->hTimerTxUnconnected, pThis->cSymbolXferTicks, NULL); + + PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_SUCCESS); } #endif @@ -1416,7 +1423,7 @@ rc = uartRegMcrWrite(pDevIns, pThis, pThisCC, uVal); break; case UART_REG_SCR_INDEX: - pThis->uRegScr = u32; + pThis->uRegScr = uVal; break; default: break; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevATA.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevATA.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevATA.cpp 2020-10-16 16:36:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevATA.cpp 2022-09-01 13:26:58.000000000 +0000 @@ -109,10 +109,10 @@ /* MediaEventStatus */ #define ATA_EVENT_STATUS_UNCHANGED 0 /**< medium event status not changed */ -#define ATA_EVENT_STATUS_MEDIA_NEW 1 /**< new medium inserted */ -#define ATA_EVENT_STATUS_MEDIA_REMOVED 2 /**< medium removed */ -#define ATA_EVENT_STATUS_MEDIA_CHANGED 3 /**< medium was removed + new medium was inserted */ -#define ATA_EVENT_STATUS_MEDIA_EJECT_REQUESTED 4 /**< medium eject requested (eject button pressed) */ +#define ATA_EVENT_STATUS_MEDIA_EJECT_REQUESTED 1 /**< medium eject requested (eject button pressed) */ +#define ATA_EVENT_STATUS_MEDIA_NEW 2 /**< new medium inserted */ +#define ATA_EVENT_STATUS_MEDIA_REMOVED 3 /**< medium removed */ +#define ATA_EVENT_STATUS_MEDIA_CHANGED 4 /**< medium was removed + new medium was inserted */ /* Media track type */ #define ATA_MEDIA_TYPE_UNKNOWN 0 /**< unknown CD type */ @@ -1668,6 +1668,7 @@ else { /* CHS */ + AssertMsgReturnVoid(s->PCHSGeometry.cHeads && s->PCHSGeometry.cSectors, ("Device geometry not set!\n")); cyl = iLBA / (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors); r = iLBA % (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors); s->uATARegHCyl = cyl >> 8; @@ -3011,7 +3012,7 @@ scsiH2BE_U16(pbBuf + 0, 6); pbBuf[2] = 0x04; /* media */ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */ - pbBuf[4] = 0x03; /* media removal */ + pbBuf[4] = OldStatus == ATA_EVENT_STATUS_MEDIA_CHANGED ? 0x04 /* media changed */ : 0x03; /* media removed */ pbBuf[5] = 0x00; /* medium absent / door closed */ pbBuf[6] = 0x00; pbBuf[7] = 0x00; @@ -3750,7 +3751,7 @@ break; } atapiR3CmdOK(pCtl, s); - ataSetStatus(pCtl, s, ATA_STAT_SEEK); /* Linux expects this. */ + ataSetStatus(pCtl, s, ATA_STAT_SEEK); /* Linux expects this. Required by ATAPI 2.x when seek completes. */ break; } case SCSI_START_STOP_UNIT: @@ -3787,7 +3788,10 @@ break; } if (RT_SUCCESS(rc)) + { atapiR3CmdOK(pCtl, s); + ataSetStatus(pCtl, s, ATA_STAT_SEEK); /* Needed by NT 3.51/4.0, see @bugref{5869}. */ + } else atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED); break; @@ -4408,6 +4412,8 @@ ataR3StartTransfer(pDevIns, pCtl, s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false); break; case ATA_READ_NATIVE_MAX_ADDRESS_EXT: + if (!pDevR3->pDrvMedia || s->fATAPI) + goto abort_cmd; s->fLBA48 = true; ataR3SetSector(s, s->cTotalSectors - 1); ataR3CmdOK(pCtl, s, 0); @@ -4418,6 +4424,8 @@ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */ break; case ATA_READ_NATIVE_MAX_ADDRESS: + if (!pDevR3->pDrvMedia || s->fATAPI) + goto abort_cmd; ataR3SetSector(s, RT_MIN(s->cTotalSectors, 1 << 28) - 1); ataR3CmdOK(pCtl, s, 0); ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevBusLogic.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevBusLogic.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevBusLogic.cpp 2020-10-16 16:36:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevBusLogic.cpp 2022-09-01 13:26:58.000000000 +0000 @@ -1931,7 +1931,7 @@ if (pThis->iParameter == 1) { /* First pass - set the number of following parameter bytes. */ - pThis->cbCommandParametersLeft = pThis->aCommandBuffer[0]; + pThis->cbCommandParametersLeft = RT_MIN(pThis->aCommandBuffer[0], sizeof(pThis->aCommandBuffer) - 1); Log(("Set HA options: %u bytes follow\n", pThis->cbCommandParametersLeft)); } else @@ -1950,7 +1950,7 @@ if (pThis->iParameter == 12) { /* First pass - set the number of following CDB bytes. */ - pThis->cbCommandParametersLeft = pThis->aCommandBuffer[11]; + pThis->cbCommandParametersLeft = RT_MIN(pThis->aCommandBuffer[11], sizeof(pThis->aCommandBuffer) - 12); Log(("Execute SCSI cmd: %u more bytes follow\n", pThis->cbCommandParametersLeft)); } else @@ -2641,7 +2641,7 @@ AssertMsgFailed(("Invalid operation code %#x\n", uVal)); } } - else + else if (pThis->cbCommandParametersLeft) { #ifndef IN_RING3 /* This command must be executed in R3 as it rehooks the ISA I/O port. */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevFdc.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevFdc.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevFdc.cpp 2020-10-16 16:36:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevFdc.cpp 2022-09-01 13:26:58.000000000 +0000 @@ -1639,13 +1639,20 @@ } pos = fdctrl->data_pos % FD_SECTOR_LEN; if (fdctrl->msr & FD_MSR_NONDMA) { - if (pos == 0) { + if (cur_drv->pDrvMedia == NULL) + { + if (fdctrl->data_dir == FD_DIR_WRITE) + fdctrl_stop_transfer_now(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00); + else + fdctrl_stop_transfer_now(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00); + } else if (pos == 0) { if (fdctrl->data_pos != 0) if (!fdctrl_seek_to_next_sect(fdctrl, cur_drv)) { FLOPPY_DPRINTF("error seeking to next sector %d\n", fd_sector(cur_drv)); return 0; } + rc = blk_read(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1); if (RT_FAILURE(rc)) { @@ -2156,7 +2163,14 @@ pos = fdctrl->data_pos++; pos %= FD_SECTOR_LEN; fdctrl->fifo[pos] = value; - if (pos == FD_SECTOR_LEN - 1 || + + if (cur_drv->pDrvMedia == NULL) + { + if (fdctrl->data_dir == FD_DIR_WRITE) + fdctrl_stop_transfer_now(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00); + else + fdctrl_stop_transfer_now(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00); + } else if (pos == FD_SECTOR_LEN - 1 || fdctrl->data_pos == fdctrl->data_len) { blk_write(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1); } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp 2020-10-16 16:36:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp 2022-09-01 13:26:58.000000000 +0000 @@ -344,6 +344,8 @@ uint32_t cbMemRegns; uint32_t u32Padding3; + /** Critical section protecting the memory regions. */ + RTCRITSECT CritSectMemRegns; /** List of memory regions - PLSILOGICMEMREGN. */ RTLISTANCHORR3 ListMemRegns; @@ -600,7 +602,6 @@ pThis->u16NextHandle = 1; pThis->u32DiagMemAddr = 0; - lsilogicR3ConfigurationPagesFree(pThis, pThisCC); lsilogicR3InitializeConfigurationPages(pDevIns, pThis, pThisCC); /* Mark that we finished performing the reset. */ @@ -609,6 +610,74 @@ } /** + * Allocates the configuration pages based on the device. + * + * @returns nothing. + * @param pThis Pointer to the shared LsiLogic device state. + * @param pThisCC Pointer to the ring-3 LsiLogic device state. + */ +static int lsilogicR3ConfigurationPagesAlloc(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC) +{ + pThisCC->pConfigurationPages = (PMptConfigurationPagesSupported)RTMemAllocZ(sizeof(MptConfigurationPagesSupported)); + if (!pThisCC->pConfigurationPages) + return VERR_NO_MEMORY; + + if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS) + { + PMptConfigurationPagesSas pPages = &pThisCC->pConfigurationPages->u.SasPages; + + pPages->cbManufacturingPage7 = LSILOGICSCSI_MANUFACTURING7_GET_SIZE(pThis->cPorts); + PMptConfigurationPageManufacturing7 pManufacturingPage7 = (PMptConfigurationPageManufacturing7)RTMemAllocZ(pPages->cbManufacturingPage7); + AssertPtrReturn(pManufacturingPage7, VERR_NO_MEMORY); + pPages->pManufacturingPage7 = pManufacturingPage7; + + /* SAS I/O unit page 0 - Port specific information. */ + pPages->cbSASIOUnitPage0 = LSILOGICSCSI_SASIOUNIT0_GET_SIZE(pThis->cPorts); + PMptConfigurationPageSASIOUnit0 pSASPage0 = (PMptConfigurationPageSASIOUnit0)RTMemAllocZ(pPages->cbSASIOUnitPage0); + AssertPtrReturn(pSASPage0, VERR_NO_MEMORY); + pPages->pSASIOUnitPage0 = pSASPage0; + + /* SAS I/O unit page 1 - Port specific settings. */ + pPages->cbSASIOUnitPage1 = LSILOGICSCSI_SASIOUNIT1_GET_SIZE(pThis->cPorts); + PMptConfigurationPageSASIOUnit1 pSASPage1 = (PMptConfigurationPageSASIOUnit1)RTMemAllocZ(pPages->cbSASIOUnitPage1); + AssertPtrReturn(pSASPage1, VERR_NO_MEMORY); + pPages->pSASIOUnitPage1 = pSASPage1; + + pPages->cPHYs = pThis->cPorts; + pPages->paPHYs = (PMptPHY)RTMemAllocZ(pPages->cPHYs * sizeof(MptPHY)); + AssertPtrReturn(pPages->paPHYs, VERR_NO_MEMORY); + + /* Initialize the PHY configuration */ + for (unsigned i = 0; i < pThis->cPorts; i++) + { + /* Settings for present devices. */ + if (pThisCC->paDeviceStates[i].pDrvBase) + { + PMptSASDevice pSASDevice = (PMptSASDevice)RTMemAllocZ(sizeof(MptSASDevice)); + AssertPtrReturn(pSASDevice, VERR_NO_MEMORY); + + /* Link into device list. */ + if (!pPages->cDevices) + { + pPages->pSASDeviceHead = pSASDevice; + pPages->pSASDeviceTail = pSASDevice; + pPages->cDevices = 1; + } + else + { + pSASDevice->pPrev = pPages->pSASDeviceTail; + pPages->pSASDeviceTail->pNext = pSASDevice; + pPages->pSASDeviceTail = pSASDevice; + pPages->cDevices++; + } + } + } + } + + return VINF_SUCCESS; +} + +/** * Frees the configuration pages if allocated. * * @returns nothing. @@ -641,9 +710,16 @@ RTMemFree(pSasPages->pSASIOUnitPage0); if (pSasPages->pSASIOUnitPage1) RTMemFree(pSasPages->pSASIOUnitPage1); + + pSasPages->pSASDeviceHead = NULL; + pSasPages->paPHYs = NULL; + pSasPages->pManufacturingPage7 = NULL; + pSasPages->pSASIOUnitPage0 = NULL; + pSasPages->pSASIOUnitPage1 = NULL; } RTMemFree(pThisCC->pConfigurationPages); + pThisCC->pConfigurationPages = NULL; } } @@ -885,8 +961,9 @@ */ static void lsilogicR3DiagRegDataWrite(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC, uint32_t u32Data) { - PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, pThis->u32DiagMemAddr); + RTCritSectEnter(&pThisCC->CritSectMemRegns); + PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, pThis->u32DiagMemAddr); if (pRegion) { uint32_t offRegion = pThis->u32DiagMemAddr - pRegion->u32AddrStart; @@ -961,6 +1038,7 @@ /* Memory access is always 32bit big. */ pThis->u32DiagMemAddr += sizeof(uint32_t); + RTCritSectLeave(&pThisCC->CritSectMemRegns); } /** @@ -973,8 +1051,9 @@ */ static void lsilogicR3DiagRegDataRead(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC, uint32_t *pu32Data) { - PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, pThis->u32DiagMemAddr); + RTCritSectEnter(&pThisCC->CritSectMemRegns); + PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, pThis->u32DiagMemAddr); if (pRegion) { uint32_t offRegion = pThis->u32DiagMemAddr - pRegion->u32AddrStart; @@ -991,6 +1070,7 @@ /* Memory access is always 32bit big. */ pThis->u32DiagMemAddr += sizeof(uint32_t); + RTCritSectLeave(&pThisCC->CritSectMemRegns); } /** @@ -1119,34 +1199,36 @@ pReply->IOCFacts.u8MaxDevices = pThis->cMaxDevices; pReply->IOCFacts.u8MaxBuses = pThis->cMaxBuses; - /* Check for a valid firmware image in the IOC memory which was downlaoded by tzhe guest earlier. */ - PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, LSILOGIC_FWIMGHDR_LOAD_ADDRESS); + pReply->IOCFacts.u16ProductID = 0xcafe; /* Our own product ID :) */ + pReply->IOCFacts.u32FwImageSize = 0; /* No image needed. */ + pReply->IOCFacts.u32FWVersion = 0; + /* Check for a valid firmware image in the IOC memory which was downloaded by the guest earlier and use that. */ + RTCritSectEnter(&pThisCC->CritSectMemRegns); + PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, LSILOGIC_FWIMGHDR_LOAD_ADDRESS); if (pRegion) { - uint32_t offImgHdr = (LSILOGIC_FWIMGHDR_LOAD_ADDRESS - pRegion->u32AddrStart) / 4; - PFwImageHdr pFwImgHdr = (PFwImageHdr)&pRegion->au32Data[offImgHdr]; - - /* Check for the signature. */ - /** @todo Checksum validation. */ - if ( pFwImgHdr->u32Signature1 == LSILOGIC_FWIMGHDR_SIGNATURE1 - && pFwImgHdr->u32Signature2 == LSILOGIC_FWIMGHDR_SIGNATURE2 - && pFwImgHdr->u32Signature3 == LSILOGIC_FWIMGHDR_SIGNATURE3) + uint32_t offImgHdr = (LSILOGIC_FWIMGHDR_LOAD_ADDRESS - pRegion->u32AddrStart); + if (pRegion->u32AddrEnd - offImgHdr + 1 >= sizeof(FwImageHdr)) /* End address is inclusive. */ { - LogFlowFunc(("IOC Facts: Found valid firmware image header in memory, using version (%#x), size (%d) and product ID (%#x) from there\n", - pFwImgHdr->u32FwVersion, pFwImgHdr->u32ImageSize, pFwImgHdr->u16ProductId)); + PFwImageHdr pFwImgHdr = (PFwImageHdr)&pRegion->au32Data[offImgHdr / 4]; - pReply->IOCFacts.u16ProductID = pFwImgHdr->u16ProductId; - pReply->IOCFacts.u32FwImageSize = pFwImgHdr->u32ImageSize; - pReply->IOCFacts.u32FWVersion = pFwImgHdr->u32FwVersion; + /* Check for the signature. */ + /** @todo Checksum validation. */ + if ( pFwImgHdr->u32Signature1 == LSILOGIC_FWIMGHDR_SIGNATURE1 + && pFwImgHdr->u32Signature2 == LSILOGIC_FWIMGHDR_SIGNATURE2 + && pFwImgHdr->u32Signature3 == LSILOGIC_FWIMGHDR_SIGNATURE3) + { + LogFlowFunc(("IOC Facts: Found valid firmware image header in memory, using version (%#x), size (%d) and product ID (%#x) from there\n", + pFwImgHdr->u32FwVersion, pFwImgHdr->u32ImageSize, pFwImgHdr->u16ProductId)); + + pReply->IOCFacts.u16ProductID = pFwImgHdr->u16ProductId; + pReply->IOCFacts.u32FwImageSize = pFwImgHdr->u32ImageSize; + pReply->IOCFacts.u32FWVersion = pFwImgHdr->u32FwVersion; + } } } - else - { - pReply->IOCFacts.u16ProductID = 0xcafe; /* Our own product ID :) */ - pReply->IOCFacts.u32FwImageSize = 0; /* No image needed. */ - pReply->IOCFacts.u32FWVersion = 0; - } + RTCritSectLeave(&pThisCC->CritSectMemRegns); break; } case MPT_MESSAGE_HDR_FUNCTION_PORT_FACTS: @@ -2329,6 +2411,8 @@ IOCReply.SCSIIOError.u8Function = pGuestReq->SCSIIO.u8Function; IOCReply.SCSIIOError.u8CDBLength = pGuestReq->SCSIIO.u8CDBLength; IOCReply.SCSIIOError.u8SenseBufferLength = pGuestReq->SCSIIO.u8SenseBufferLength; + IOCReply.SCSIIOError.u8Reserved = 0; + IOCReply.SCSIIOError.u8MessageFlags = 0; IOCReply.SCSIIOError.u32MessageContext = pGuestReq->SCSIIO.u32MessageContext; IOCReply.SCSIIOError.u8SCSIStatus = SCSI_STATUS_OK; IOCReply.SCSIIOError.u8SCSIState = MPT_SCSI_IO_ERROR_SCSI_STATE_TERMINATED; @@ -3420,9 +3504,9 @@ LogFlowFunc(("pThis=%#p\n", pThis)); /* Manufacturing Page 7 - Connector settings. */ - pPages->cbManufacturingPage7 = LSILOGICSCSI_MANUFACTURING7_GET_SIZE(pThis->cPorts); - PMptConfigurationPageManufacturing7 pManufacturingPage7 = (PMptConfigurationPageManufacturing7)RTMemAllocZ(pPages->cbManufacturingPage7); + PMptConfigurationPageManufacturing7 pManufacturingPage7 = pPages->pManufacturingPage7; AssertPtr(pManufacturingPage7); + MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(pManufacturingPage7, 0, 7, MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY); @@ -3432,11 +3516,9 @@ else pManufacturingPage7->u.fields.Header.u8PageLength = pPages->cbManufacturingPage7 / 4; pManufacturingPage7->u.fields.u8NumPhys = pThis->cPorts; - pPages->pManufacturingPage7 = pManufacturingPage7; /* SAS I/O unit page 0 - Port specific information. */ - pPages->cbSASIOUnitPage0 = LSILOGICSCSI_SASIOUNIT0_GET_SIZE(pThis->cPorts); - PMptConfigurationPageSASIOUnit0 pSASPage0 = (PMptConfigurationPageSASIOUnit0)RTMemAllocZ(pPages->cbSASIOUnitPage0); + PMptConfigurationPageSASIOUnit0 pSASPage0 = pPages->pSASIOUnitPage0; AssertPtr(pSASPage0); MPT_CONFIG_EXTENDED_PAGE_HEADER_INIT(pSASPage0, pPages->cbSASIOUnitPage0, @@ -3446,8 +3528,7 @@ pPages->pSASIOUnitPage0 = pSASPage0; /* SAS I/O unit page 1 - Port specific settings. */ - pPages->cbSASIOUnitPage1 = LSILOGICSCSI_SASIOUNIT1_GET_SIZE(pThis->cPorts); - PMptConfigurationPageSASIOUnit1 pSASPage1 = (PMptConfigurationPageSASIOUnit1)RTMemAllocZ(pPages->cbSASIOUnitPage1); + PMptConfigurationPageSASIOUnit1 pSASPage1 = pPages->pSASIOUnitPage1; AssertPtr(pSASPage1); MPT_CONFIG_EXTENDED_PAGE_HEADER_INIT(pSASPage1, pPages->cbSASIOUnitPage1, @@ -3456,7 +3537,6 @@ pSASPage1->u.fields.u8NumPhys = pSASPage0->u.fields.u8NumPhys; pSASPage1->u.fields.u16ControlFlags = 0; pSASPage1->u.fields.u16AdditionalControlFlags = 0; - pPages->pSASIOUnitPage1 = pSASPage1; /* SAS I/O unit page 2 - Port specific information. */ pPages->SASIOUnitPage2.u.fields.ExtHeader.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY @@ -3472,11 +3552,11 @@ pPages->SASIOUnitPage3.u.fields.ExtHeader.u8ExtPageType = MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASIOUNIT; pPages->SASIOUnitPage3.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASIOUnit3) / 4; - pPages->cPHYs = pThis->cPorts; - pPages->paPHYs = (PMptPHY)RTMemAllocZ(pPages->cPHYs * sizeof(MptPHY)); + Assert(pPages->cPHYs == pThis->cPorts); AssertPtr(pPages->paPHYs); /* Initialize the PHY configuration */ + PMptSASDevice pSASDevice = pPages->pSASDeviceHead; for (unsigned i = 0; i < pThis->cPorts; i++) { PMptPHY pPHYPages = &pPages->paPHYs[i]; @@ -3525,7 +3605,6 @@ { uint16_t u16DeviceHandle = lsilogicGetHandle(pThis); SASADDRESS SASAddress; - PMptSASDevice pSASDevice = (PMptSASDevice)RTMemAllocZ(sizeof(MptSASDevice)); AssertPtr(pSASDevice); memset(&SASAddress, 0, sizeof(SASADDRESS)); @@ -3583,20 +3662,7 @@ pSASDevice->SASDevicePage2.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASDevice2) / 4; pSASDevice->SASDevicePage2.u.fields.SASAddress = SASAddress; - /* Link into device list. */ - if (!pPages->cDevices) - { - pPages->pSASDeviceHead = pSASDevice; - pPages->pSASDeviceTail = pSASDevice; - pPages->cDevices = 1; - } - else - { - pSASDevice->pPrev = pPages->pSASDeviceTail; - pPages->pSASDeviceTail->pNext = pSASDevice; - pPages->pSASDeviceTail = pSASDevice; - pPages->cDevices++; - } + pSASDevice = pSASDevice->pNext; } } } @@ -3612,16 +3678,11 @@ static void lsilogicR3InitializeConfigurationPages(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC) { /* Initialize the common pages. */ - PMptConfigurationPagesSupported pPages = (PMptConfigurationPagesSupported)RTMemAllocZ(sizeof(MptConfigurationPagesSupported)); - /** @todo r=bird: Missing alloc failure check. Why do we allocate this - * structure? It's fixed size... */ - - pThisCC->pConfigurationPages = pPages; LogFlowFunc(("pThis=%#p\n", pThis)); - /* Clear everything first. */ - memset(pPages, 0, sizeof(MptConfigurationPagesSupported)); + AssertPtrReturnVoid(pThisCC->pConfigurationPages); + PMptConfigurationPagesSupported pPages = pThisCC->pConfigurationPages; /* Manufacturing Page 0. */ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage0, @@ -5149,6 +5210,9 @@ PDMDevHlpCritSectDelete(pDevIns, &pThis->RequestQueueCritSect); PDMDevHlpCritSectDelete(pDevIns, &pThis->ReplyFreeQueueWriteCritSect); + if (RTCritSectIsInitialized(&pThisCC->CritSectMemRegns)) + RTCritSectDelete(&pThisCC->CritSectMemRegns); + RTMemFree(pThisCC->paDeviceStates); pThisCC->paDeviceStates = NULL; @@ -5313,6 +5377,13 @@ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: cannot create critical section for reply free queue write access")); /* + * Critical section protecting the memory regions. + */ + rc = RTCritSectInit(&pThisCC->CritSectMemRegns); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: Failed to initialize critical section protecting the memory regions")); + + /* * Register the PCI device, it's I/O regions. */ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); @@ -5536,6 +5607,11 @@ ? "LsiLogic SPI info." : "LsiLogic SAS info.", lsilogicR3Info); + /* Allocate configuration pages. */ + rc = lsilogicR3ConfigurationPagesAlloc(pThis, pThisCC); + if (RT_FAILURE(rc)) + PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: Failed to allocate memory for configuration pages")); + /* Perform hard reset. */ rc = lsilogicR3HardReset(pDevIns, pThis, pThisCC); AssertRC(rc); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevLsiLogicSCSI.h virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevLsiLogicSCSI.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevLsiLogicSCSI.h 2020-10-16 16:36:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevLsiLogicSCSI.h 2022-09-01 13:26:58.000000000 +0000 @@ -3326,21 +3326,26 @@ (pg)->u.fields.Header.u8PageLength = sizeof(type) / 4 #define MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(pg, type, nr, flags) \ + RT_ZERO(*pg); \ MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags | MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING) #define MPT_CONFIG_PAGE_HEADER_INIT_IO_UNIT(pg, type, nr, flags) \ + RT_ZERO(*pg); \ MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags | MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT) #define MPT_CONFIG_PAGE_HEADER_INIT_IOC(pg, type, nr, flags) \ + RT_ZERO(*pg); \ MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags | MPT_CONFIGURATION_PAGE_TYPE_IOC) #define MPT_CONFIG_PAGE_HEADER_INIT_BIOS(pg, type, nr, flags) \ + RT_ZERO(*pg); \ MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags | MPT_CONFIGURATION_PAGE_TYPE_BIOS) /** * Initializes a extended page header. */ #define MPT_CONFIG_EXTENDED_PAGE_HEADER_INIT(pg, cb, nr, flags, exttype) \ + RT_BZERO(pg, cb); \ (pg)->u.fields.ExtHeader.u8PageType = (flags) | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED; \ (pg)->u.fields.ExtHeader.u8PageNumber = (nr); \ (pg)->u.fields.ExtHeader.u8ExtPageType = (exttype); \ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevVirtioSCSI.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevVirtioSCSI.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DevVirtioSCSI.cpp 2020-10-16 16:36:13.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DevVirtioSCSI.cpp 2022-09-01 13:26:58.000000000 +0000 @@ -1554,9 +1554,11 @@ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) return VINF_SUCCESS; + Log6Func(("[Re]starting %s worker\n", VIRTQNAME(uVirtqNbr))); while (pThread->enmState == PDMTHREADSTATE_RUNNING) { - if (!pWorkerR3->cRedoDescs && IS_VIRTQ_EMPTY(pDevIns, &pThis->Virtio, uVirtqNbr)) + if ( !pWorkerR3->cRedoDescs + && IS_VIRTQ_EMPTY(pDevIns, &pThis->Virtio, uVirtqNbr)) { /* Atomic interlocks avoid missing alarm while going to sleep & notifier waking the awoken */ ASMAtomicWriteBool(&pWorker->fSleeping, true); @@ -1568,15 +1570,26 @@ int rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pWorker->hEvtProcess, RT_INDEFINITE_WAIT); AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc); if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + { + Log6Func(("%s worker thread not running, exiting\n", VIRTQNAME(uVirtqNbr))); return VINF_SUCCESS; + } if (rc == VERR_INTERRUPTED) + { + Log6Func(("%s worker interrupted ... continuing\n", VIRTQNAME(uVirtqNbr))); continue; + } Log6Func(("%s worker woken\n", VIRTQNAME(uVirtqNbr))); ASMAtomicWriteBool(&pWorker->fNotified, false); } ASMAtomicWriteBool(&pWorker->fSleeping, false); } + if (!virtioCoreIsVirtqEnabled(&pThis->Virtio, uVirtqNbr)) + { + LogFunc(("%s queue not enabled, worker aborting...\n", VIRTQNAME(uVirtqNbr))); + break; + } if (!pThis->afVirtqAttached[uVirtqNbr]) { LogFunc(("%s queue not attached, worker aborting...\n", VIRTQNAME(uVirtqNbr))); @@ -2281,7 +2294,8 @@ */ for (uint16_t uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_REQ_VIRTQ_CNT; uVirtqNbr++) { - if (ASMAtomicReadBool(&pThis->aWorkers[uVirtqNbr].fSleeping)) + if ( virtioCoreIsVirtqEnabled(&pThis->Virtio, uVirtqNbr) + && ASMAtomicReadBool(&pThis->aWorkers[uVirtqNbr].fSleeping)) { Log6Func(("waking %s worker.\n", VIRTQNAME(uVirtqNbr))); int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[uVirtqNbr].hEvtProcess); @@ -2464,6 +2478,11 @@ /* * Initialize queues. + * @todo This should ideally be moved to virtioScsiR3StatusChanged() or become a function + * invoked from there only if the driver has enabled the queue. In the current form + * workers are created for all queues whether the guest has enabled them or not, + * which wastes resources. Currently there are only a few request queues so it's + * probably not urgent. */ virtioScsiSetVirtqNames(pThis); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DrvHostBase.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DrvHostBase.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DrvHostBase.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DrvHostBase.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -858,9 +858,11 @@ if (!pThis->fLocked) { if (pThis->pfnDoLock) + { rc = pThis->pfnDoLock(pThis, true); - if (RT_SUCCESS(rc)) - pThis->fLocked = true; + if (RT_SUCCESS(rc)) + pThis->fLocked = true; + } } else LogFlow(("%s-%d: drvHostBaseLock: already locked\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance)); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DrvHostDVD.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DrvHostDVD.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DrvHostDVD.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DrvHostDVD.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -46,7 +46,7 @@ */ typedef struct DRVHOSTDVD { - /** Base drivr data. */ + /** Base driver data. */ DRVHOSTBASE Core; /** The current tracklist of the loaded medium if passthrough is used. */ PTRACKLIST pTrackList; @@ -496,6 +496,25 @@ return rc; } +/** + * Reset a host dvd drive driver instance. + * + * @copydoc FNPDMDRVRESET + */ +static DECLCALLBACK(void) drvHostDvdReset(PPDMDRVINS pDrvIns) +{ + PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD); + + if (pThis->pTrackList) + { + ATAPIPassthroughTrackListDestroy(pThis->pTrackList); + pThis->pTrackList = NULL; + } + + int rc = drvHostBaseDoLockOs(&pThis->Core, false); + RT_NOREF(rc); +} + /** * Block driver registration record. @@ -531,7 +550,7 @@ /* pfnPowerOn */ NULL, /* pfnReset */ - NULL, + drvHostDvdReset, /* pfnSuspend */ NULL, /* pfnResume */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DrvHostFloppy.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DrvHostFloppy.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DrvHostFloppy.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DrvHostFloppy.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -50,7 +50,7 @@ /* * Init instance data. */ - int rc = DRVHostBaseInit(pDrvIns, pCfg, "Path\0ReadOnly\0Interval\0Locked\0BIOSVisible\0", + int rc = DRVHostBaseInit(pDrvIns, pCfg, "Path\0ReadOnly\0Interval\0BIOSVisible\0", PDMMEDIATYPE_FLOPPY_1_44); LogFlow(("drvHostFloppyConstruct: returns %Rrc\n", rc)); return rc; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DrvVD.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DrvVD.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/DrvVD.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/DrvVD.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -4420,6 +4420,7 @@ pThis->cbDataValid = 0; pThis->offDisk = 0; } + pThis->fLocked = false; } /** diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/UsbMsd.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/UsbMsd.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/UsbMsd.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/UsbMsd.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -886,7 +886,8 @@ static void usbMsdReqPrepare(PUSBMSDREQ pReq, PCUSBCBW pCbw) { /* Copy the CBW */ - size_t cbCopy = RT_UOFFSETOF_DYN(USBCBW, CBWCB[pCbw->bCBWCBLength]); + uint8_t bCBWLen = RT_MIN(pCbw->bCBWCBLength, sizeof(pCbw->CBWCB)); + size_t cbCopy = RT_UOFFSETOF_DYN(USBCBW, CBWCB[bCBWLen]); memcpy(&pReq->Cbw, pCbw, cbCopy); memset((uint8_t *)&pReq->Cbw + cbCopy, 0, sizeof(pReq->Cbw) - cbCopy); @@ -1621,7 +1622,7 @@ Log(("usbMsd: CBW: Bad bCBWLun value: %#x\n", pCbw->bCBWLun)); return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW"); } - if (pCbw->bCBWCBLength == 0) + if ((pCbw->bCBWCBLength == 0) || (pCbw->bCBWCBLength > sizeof(pCbw->CBWCB))) { Log(("usbMsd: CBW: Bad bCBWCBLength value: %#x\n", pCbw->bCBWCBLength)); return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW"); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/VBoxSCSI.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/VBoxSCSI.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/VBoxSCSI.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/VBoxSCSI.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -135,7 +135,8 @@ { /* If we're not in the 'command ready' state, there may not even be a buffer yet. */ if ( pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY - && pVBoxSCSI->cbBufLeft > 0) + && pVBoxSCSI->cbBufLeft > 0 + && pVBoxSCSI->pbBuf) { AssertMsg(pVBoxSCSI->pbBuf, ("pBuf is NULL\n")); Assert(!pVBoxSCSI->fBusy); @@ -239,17 +240,20 @@ { Log(("%s: Command ready for processing\n", __FUNCTION__)); pVBoxSCSI->enmState = VBOXSCSISTATE_COMMAND_READY; - pVBoxSCSI->cbBufLeft = pVBoxSCSI->cbBuf; + Assert(!pVBoxSCSI->cbBufLeft); + Assert(!pVBoxSCSI->pbBuf); if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE) { /* This is a write allocate buffer. */ pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf); if (!pVBoxSCSI->pbBuf) return VERR_NO_MEMORY; + pVBoxSCSI->cbBufLeft = pVBoxSCSI->cbBuf; } else { /* This is a read from the device. */ + pVBoxSCSI->cbBufLeft = pVBoxSCSI->cbBuf; ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true); rc = VERR_MORE_DATA; /** @todo Better return value to indicate ready command? */ } @@ -425,11 +429,11 @@ uint32_t cbTransfer = *pcTransfers * cb; if (pVBoxSCSI->cbBufLeft > 0) { - Assert(cbTransfer <= pVBoxSCSI->cbBuf); - if (cbTransfer > pVBoxSCSI->cbBuf) + Assert(cbTransfer <= pVBoxSCSI->cbBufLeft); + if (cbTransfer > pVBoxSCSI->cbBufLeft) { - memset(pbDst + pVBoxSCSI->cbBuf, 0xff, cbTransfer - pVBoxSCSI->cbBuf); - cbTransfer = pVBoxSCSI->cbBuf; /* Ignore excess data (not supposed to happen). */ + memset(pbDst + pVBoxSCSI->cbBufLeft, 0xff, cbTransfer - pVBoxSCSI->cbBufLeft); + cbTransfer = pVBoxSCSI->cbBufLeft; /* Ignore excess data (not supposed to happen). */ } /* Copy the data and adance the buffer position. */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -43,14 +43,14 @@ { /** Medium event status not changed. */ MMCEVENTSTATUSTYPE_UNCHANGED = 0, + /** Medium eject requested (eject button pressed). */ + MMCEVENTSTATUSTYPE_MEDIA_EJECT_REQUESTED, /** New medium inserted. */ MMCEVENTSTATUSTYPE_MEDIA_NEW, /** Medium removed. */ MMCEVENTSTATUSTYPE_MEDIA_REMOVED, /** Medium was removed + new medium was inserted. */ MMCEVENTSTATUSTYPE_MEDIA_CHANGED, - /** Medium eject requested (eject button pressed). */ - MMCEVENTSTATUSTYPE_MEDIA_EJECT_REQUESTED, /** 32bit hack. */ MMCEVENTSTATUSTYPE_32BIT_HACK = 0x7fffffff } MMCEVENTSTATUSTYPE; @@ -859,7 +859,7 @@ scsiH2BE_U16(&aReply[0], 6); aReply[2] = 0x04; /* media */ aReply[3] = 0x5e; /* supported = busy|media|external|power|operational */ - aReply[4] = 0x03; /* media removal */ + aReply[4] = (OldStatus == MMCEVENTSTATUSTYPE_MEDIA_CHANGED) ? 0x04 /* media changed */ : 0x03; /* media removed */ aReply[5] = 0x00; /* medium absent / door closed */ aReply[6] = 0x00; aReply[7] = 0x00; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/testcase/tstDeviceInternal.h virtualbox-6.1.38-dfsg/src/VBox/Devices/testcase/tstDeviceInternal.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/testcase/tstDeviceInternal.h 2020-10-16 16:36:16.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/testcase/tstDeviceInternal.h 2022-09-01 13:27:01.000000000 +0000 @@ -110,6 +110,7 @@ /** Size of the allocation. */ size_t cbAlloc; /** Start of the real allocation. */ + RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abAlloc[RT_FLEXIBLE_ARRAY]; } TSTDEVMMHEAPALLOC; /** Pointer to a MM Heap allocation. */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/testcase/tstDeviceStructSize.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/testcase/tstDeviceStructSize.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/testcase/tstDeviceStructSize.cpp 2020-10-16 16:36:16.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/testcase/tstDeviceStructSize.cpp 2022-09-01 13:27:01.000000000 +0000 @@ -135,7 +135,7 @@ #undef LOG_GROUP #include "../Audio/DevIchAc97.cpp" #undef LOG_GROUP -#include "../Audio/DevHDA.cpp" +#include "../Audio/DevHda.h" /* Check that important preprocessor macros didn't get redefined: */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/DevOHCI.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/DevOHCI.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/DevOHCI.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/DevOHCI.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -135,6 +135,8 @@ /* Macro to query the number of currently configured ports. */ #define OHCI_NDP_CFG(pohci) ((pohci)->RootHub.desc_a & OHCI_RHA_NDP) +/** Macro to convert a EHCI port index (zero based) to a VUSB roothub port ID (one based). */ +#define OHCI_PORT_2_VUSB_PORT(a_uPort) ((a_uPort) + 1) /** Pointer to OHCI device data. */ typedef struct OHCI *POHCI; @@ -179,15 +181,10 @@ { /** The port register. */ uint32_t fReg; -#if HC_ARCH_BITS == 64 - uint32_t Alignment0; /**< Align the pointer correctly. */ -#endif - /** The device attached to the port. */ - R3PTRTYPE(PVUSBIDEVICE) pDev; + /** Flag whether there is a device attached to the port. */ + bool fAttached; + bool afPadding[3]; } OHCIHUBPORT; -#if HC_ARCH_BITS == 64 -AssertCompile(sizeof(OHCIHUBPORT) == 16); /* saved state */ -#endif /** Pointer to an OHCI hub port. */ typedef OHCIHUBPORT *POHCIHUBPORT; @@ -221,8 +218,6 @@ R3PTRTYPE(PPDMIBASE) pIBase; /** Pointer to the connector interface of the VUSB RootHub. */ R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn; - /** Pointer to the device interface of the VUSB RootHub. */ - R3PTRTYPE(PVUSBIDEVICE) pIDev; /** The base interface exposed to the roothub driver. */ PDMIBASE IBase; /** The roothub port interface exposed to the roothub driver. */ @@ -241,24 +236,6 @@ /** Pointer to the OHCI ring-3 root hub data. */ typedef OHCIROOTHUBR3 *POHCIROOTHUBR3; - -/** - * Data used for reattaching devices on a state load. - */ -typedef struct OHCILOAD -{ - /** Timer used once after state load to inform the guest about new devices. - * We do this to be sure the guest get any disconnect / reconnect on the - * same port. */ - TMTIMERHANDLE hTimer; - /** Number of detached devices. */ - unsigned cDevs; - /** Array of devices which were detached. */ - PVUSBIDEVICE apDevs[OHCI_NDP_MAX]; -} OHCILOAD; -/** Pointer to an OHCILOAD structure. */ -typedef OHCILOAD *POHCILOAD; - #ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE typedef struct OHCIPAGECACHE { @@ -282,6 +259,8 @@ /** frame number overflow. */ uint32_t fno : 1; + /** Align roothub structure on a 8-byte boundary. */ + uint32_t u32Alignment0; /** Root hub device, shared data. */ OHCIROOTHUB RootHub; @@ -422,8 +401,6 @@ /** Critical section to synchronize the framer and URB completion handler. */ RTCRITSECT CritSect; - /** Pointer to state load data. */ - R3PTRTYPE(POHCILOAD) pLoad; /** The restored periodic frame rate. */ uint32_t uRestoredPeriodicFrameRate; } OHCIR3; @@ -889,7 +866,7 @@ RT_C_DECLS_BEGIN #ifdef IN_RING3 /* Update host controller state to reflect a device attach */ -static void ohciR3RhPortPower(POHCIROOTHUB pRh, unsigned iPort, bool fPowerUp); +static void ohciR3RhPortPower(POHCIROOTHUBR3 pRh, unsigned iPort, bool fPowerUp); static void ohciR3BusResume(PPDMDEVINS pDevIns, POHCI pOhci, POHCICC pThisCC, bool fHardware); static void ohciR3BusStop(POHCICC pThisCC); #ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE @@ -903,7 +880,6 @@ # if defined(VBOX_STRICT) || defined(LOG_ENABLED) static int ohciR3InDoneQueueFind(POHCICC pThisCC, uint32_t GCPhysTD); # endif -static DECLCALLBACK(void) ohciR3LoadReattachDevices(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser); #endif /* IN_RING3 */ RT_C_DECLS_END @@ -1037,7 +1013,7 @@ PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); for (unsigned iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) - if (!pThis->RootHub.aPorts[iPort].pDev) + if (!pThis->RootHub.aPorts[iPort].fAttached) { cPorts++; ASMBitSet(pAvailable, iPort + 1); @@ -1061,20 +1037,13 @@ } -/** - * A device is being attached to a port in the roothub. - * - * @param pInterface Pointer to this structure. - * @param pDev Pointer to the device being attached. - * @param uPort The port number assigned to the device. - */ -static DECLCALLBACK(int) ohciR3RhAttach(PVUSBIROOTHUBPORT pInterface, PVUSBIDEVICE pDev, unsigned uPort) +/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */ +static DECLCALLBACK(int) ohciR3RhAttach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort, VUSBSPEED enmSpeed) { POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); PPDMDEVINS pDevIns = pThisCC->pDevInsR3; POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); - VUSBSPEED enmSpeed; - LogFlow(("ohciR3RhAttach: pDev=%p uPort=%u\n", pDev, uPort)); + LogFlow(("ohciR3RhAttach: uPort=%u\n", uPort)); PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); /* @@ -1082,8 +1051,7 @@ */ Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis)); uPort--; - Assert(!pThis->RootHub.aPorts[uPort].pDev); - enmSpeed = pDev->pfnGetSpeed(pDev); + Assert(!pThis->RootHub.aPorts[uPort].fAttached); /* Only LS/FS devices should end up here. */ Assert(enmSpeed == VUSB_SPEED_LOW || enmSpeed == VUSB_SPEED_FULL); @@ -1093,8 +1061,8 @@ pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC; if (enmSpeed == VUSB_SPEED_LOW) pThis->RootHub.aPorts[uPort].fReg |= OHCI_PORT_LSDA; - pThis->RootHub.aPorts[uPort].pDev = pDev; - ohciR3RhPortPower(&pThis->RootHub, uPort, 1 /* power on */); + pThis->RootHub.aPorts[uPort].fAttached = true; + ohciR3RhPortPower(&pThisCC->RootHub, uPort, 1 /* power on */); ohciR3RemoteWakeup(pDevIns, pThis, pThisCC); ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); @@ -1108,16 +1076,14 @@ * A device is being detached from a port in the roothub. * * @param pInterface Pointer to this structure. - * @param pDev Pointer to the device being detached. * @param uPort The port number assigned to the device. */ -static DECLCALLBACK(void) ohciR3RhDetach(PVUSBIROOTHUBPORT pInterface, PVUSBIDEVICE pDev, unsigned uPort) +static DECLCALLBACK(void) ohciR3RhDetach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort) { POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); PPDMDEVINS pDevIns = pThisCC->pDevInsR3; POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); - RT_NOREF(pDev); - LogFlow(("ohciR3RhDetach: pDev=%p uPort=%u\n", pDev, uPort)); + LogFlow(("ohciR3RhDetach: uPort=%u\n", uPort)); PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); /* @@ -1125,12 +1091,12 @@ */ Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis)); uPort--; - Assert(pThis->RootHub.aPorts[uPort].pDev == pDev); + Assert(pThis->RootHub.aPorts[uPort].fAttached); /* * Detach it. */ - pThis->RootHub.aPorts[uPort].pDev = NULL; + pThis->RootHub.aPorts[uPort].fAttached = false; if (pThis->RootHub.aPorts[uPort].fReg & OHCI_PORT_PES) pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC | OHCI_PORT_PESC; else @@ -1151,13 +1117,14 @@ * during a root hub reset. * * @param pDev The root hub device. + * @param uPort The port of the device completing the reset. * @param rc The result of the operation. * @param pvUser Pointer to the controller. */ -static DECLCALLBACK(void) ohciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, int rc, void *pvUser) +static DECLCALLBACK(void) ohciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) { LogRel(("OHCI: root hub reset completed with %Rrc\n", rc)); - NOREF(pDev); NOREF(rc); NOREF(pvUser); + RT_NOREF(pDev, uPort, rc, pvUser); } @@ -1199,13 +1166,14 @@ */ for (unsigned iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) { - if (pThis->RootHub.aPorts[iPort].pDev) + if (pThis->RootHub.aPorts[iPort].fAttached) { pThis->RootHub.aPorts[iPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC | OHCI_PORT_PPS; if (fResetOnLinux) { PVM pVM = PDMDevHlpGetVM(pDevIns); - VUSBIDevReset(pThis->RootHub.aPorts[iPort].pDev, fResetOnLinux, ohciR3RhResetDoneOneDev, pThis, pVM); + VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort), fResetOnLinux, + ohciR3RhResetDoneOneDev, pThis, pVM); } } else @@ -1301,7 +1269,7 @@ * device construction, so nothing to worry about there.) */ if (fNewMode == OHCI_USB_RESET) - VUSBIDevReset(pThisCC->RootHub.pIDev, fResetOnLinux, NULL, NULL, NULL); + pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, fResetOnLinux); } @@ -1752,7 +1720,7 @@ DECLINLINE(void) ohciR3DumpTdQueueCore(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull) { uint32_t GCPhys = GCPhysHead; - int cMax = 100; + int cIterations = 128; for (;;) { OHCITD Td; @@ -1779,7 +1747,8 @@ Log4((" -> ")); GCPhys = Td.NextTD & ED_PTR_MASK; Assert(GCPhys != GCPhysHead); - Assert(cMax-- > 0); NOREF(cMax); + if (!--cIterations) + break; } } @@ -1801,7 +1770,7 @@ { RT_NOREF(fFull); uint32_t GCPhys = GCPhysHead; - int cMax = 100; + int cIterations = 100; for (;;) { OHCIITD ITd; @@ -1828,7 +1797,8 @@ Log4((" -> ")); GCPhys = ITd.NextTD & ED_PTR_MASK; Assert(GCPhys != GCPhysHead); - Assert(cMax-- > 0); NOREF(cMax); + if (!--cIterations) + break; } } @@ -1890,14 +1860,14 @@ unsigned i = iStart; while (i < RT_ELEMENTS(pThisCC->aInFlight)) { - if (pThisCC->aInFlight[i].GCPhysTD == 0) + if (pThisCC->aInFlight[i].pUrb == NULL) return i; i++; } i = iStart; while (i-- > 0) { - if (pThisCC->aInFlight[i].GCPhysTD == 0) + if (pThisCC->aInFlight[i].pUrb == NULL) return i; } return -1; @@ -1960,9 +1930,9 @@ const int iLast = i; while (i < RT_ELEMENTS(pThisCC->aInFlight)) { - if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD) + if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb) return i; - if (pThisCC->aInFlight[i].GCPhysTD) + if (pThisCC->aInFlight[i].pUrb) if (cLeft-- <= 1) return -1; i++; @@ -1970,9 +1940,9 @@ i = iLast; while (i-- > 0) { - if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD) + if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb) return i; - if (pThisCC->aInFlight[i].GCPhysTD) + if (pThisCC->aInFlight[i].pUrb) if (cLeft-- <= 1) return -1; } @@ -2220,10 +2190,10 @@ TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr)); AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false); - uint32_t cMax = 256; + uint32_t cIterations = 256; uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK; while ( CurTdAddr != LastTdAddr - && cMax-- > 0) + && cIterations-- > 0) { OHCIITD ITd; ohciR3ReadITd(pDevIns, pThis, CurTdAddr, &ITd); @@ -2239,7 +2209,7 @@ CurTdAddr = ITd.NextTD & ED_PTR_MASK; } - Log(("ohciUnlinkIsocTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cMax=%d)\n", TdAddr, cMax)); + Log(("ohciUnlinkIsocTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cIterations=%d)\n", TdAddr, cIterations)); return false; } @@ -2252,10 +2222,10 @@ TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr)); AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false); - uint32_t cMax = 256; + uint32_t cIterations = 256; uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK; while ( CurTdAddr != LastTdAddr - && cMax-- > 0) + && cIterations-- > 0) { OHCITD Td; ohciR3ReadTd(pDevIns, CurTdAddr, &Td); @@ -2271,7 +2241,7 @@ CurTdAddr = Td.NextTD & ED_PTR_MASK; } - Log(("ohciR3UnlinkGeneralTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cMax=%d)\n", TdAddr, cMax)); + Log(("ohciR3UnlinkGeneralTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cIterations=%d)\n", TdAddr, cIterations)); return false; } @@ -3066,7 +3036,7 @@ /* * Allocate and initialize a new URB. */ - PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL, + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, enmType, enmDir, Buf.cbTotal, 1, NULL); if (!pUrb) return false; /* retry later... */ @@ -3236,7 +3206,7 @@ /* * Allocate and initialize a new URB. */ - PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL, + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, enmType, enmDir, cbTotal, cTds, "ohciR3ServiceTdMultiple"); if (!pUrb) /* retry later... */ @@ -3470,7 +3440,7 @@ /* * Allocate and initialize a new URB. */ - PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL, + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, VUSBXFERTYPE_ISOC, enmDir, cbTotal, 1, NULL); if (!pUrb) /* retry later... */ @@ -3712,7 +3682,10 @@ pThis->bulk_cur = 0; uint32_t EdAddr = pThis->bulk_head; - while (EdAddr) + uint32_t cIterations = 256; + while (EdAddr + && (pThis->ctl & OHCI_CTL_BLE) + && (cIterations-- > 0)) { OHCIED Ed; @@ -3812,7 +3785,9 @@ pThis->fBulkNeedsCleaning = false; uint32_t EdAddr = pThis->bulk_head; - while (EdAddr) + uint32_t cIterations = 256; + while (EdAddr + && (cIterations-- > 0)) { OHCIED Ed; @@ -3859,7 +3834,10 @@ pThis->ctrl_cur = 0; uint32_t EdAddr = pThis->ctrl_head; - while (EdAddr) + uint32_t cIterations = 256; + while ( EdAddr + && (pThis->ctl & OHCI_CTL_CLE) + && (cIterations-- > 0)) { OHCIED Ed; @@ -3935,7 +3913,10 @@ /* * Iterate the endpoint list. */ - while (EdAddr) + unsigned cIterations = 128; + while (EdAddr + && (pThis->ctl & OHCI_CTL_PLE) + && (cIterations-- > 0)) { OHCIED Ed; @@ -4105,7 +4086,10 @@ # endif break; } - while (EdAddr) + + unsigned cIterED = 128; + while ( EdAddr + && (cIterED-- > 0)) { OHCIED Ed; OHCITD Td; @@ -4113,7 +4097,7 @@ ohciR3ReadEd(pDevIns, EdAddr, &Ed); uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; uint32_t TailP = Ed.TailP & ED_PTR_MASK; - unsigned k = 0; + unsigned cIterTD = 0; if ( !(Ed.hwinfo & ED_HWINFO_SKIP) && (TdAddr != TailP)) { @@ -4137,7 +4121,7 @@ if (TdAddr == 0) break; /* Failsafe for temporarily looped lists. */ - if (++k == 128) + if (++cIterTD == 128) break; } while (TdAddr != (Ed.TailP & ED_PTR_MASK)); } @@ -4346,7 +4330,7 @@ */ static void ohciR3BusStart(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) { - VUSBIDevPowerOn(pThisCC->RootHub.pIDev); + pThisCC->RootHub.pIRhConn->pfnPowerOn(pThisCC->RootHub.pIRhConn); pThis->dqic = 0x7; Log(("ohci: Bus started\n")); @@ -4364,7 +4348,7 @@ { int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, 0); AssertRC(rc); - VUSBIDevPowerOff(pThisCC->RootHub.pIDev); + pThisCC->RootHub.pIRhConn->pfnPowerOff(pThisCC->RootHub.pIRhConn); } @@ -4387,7 +4371,7 @@ /* Power a port up or down */ -static void ohciR3RhPortPower(POHCIROOTHUB pRh, unsigned iPort, bool fPowerUp) +static void ohciR3RhPortPower(POHCIROOTHUBR3 pRh, unsigned iPort, bool fPowerUp) { POHCIHUBPORT pPort = &pRh->aPorts[iPort]; bool fOldPPS = !!(pPort->fReg & OHCI_PORT_PPS); @@ -4397,19 +4381,19 @@ if (fPowerUp) { /* power up */ - if (pPort->pDev) + if (pPort->fAttached) pPort->fReg |= OHCI_PORT_CCS; if (pPort->fReg & OHCI_PORT_CCS) pPort->fReg |= OHCI_PORT_PPS; - if (pPort->pDev && !fOldPPS) - VUSBIDevPowerOn(pPort->pDev); + if (pPort->fAttached && !fOldPPS) + VUSBIRhDevPowerOn(pRh->pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort)); } else { /* power down */ pPort->fReg &= ~(OHCI_PORT_PPS | OHCI_PORT_CCS | OHCI_PORT_PSS | OHCI_PORT_PRS); - if (pPort->pDev && fOldPPS) - VUSBIDevPowerOff(pPort->pDev); + if (pPort->fAttached && fOldPPS) + VUSBIRhDevPowerOff(pRh->pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort)); } } @@ -4504,7 +4488,7 @@ /** @todo This should probably do a real reset, but we don't implement * that correctly in the roothub reset callback yet. check it's * comments and argument for more details. */ - VUSBIDevReset(pThisCC->RootHub.pIDev, false /* don't do a real reset */, NULL, NULL, NULL); + pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, false /* don't do a real reset */); break; } } @@ -5151,6 +5135,8 @@ static VBOXSTRICTRC HcRhStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) { #ifdef IN_RING3 + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + /* log */ uint32_t old = pThis->RootHub.status; uint32_t chg; @@ -5172,7 +5158,7 @@ unsigned i; Log2(("ohci: global power up\n")); for (i = 0; i < OHCI_NDP_CFG(pThis); i++) - ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */); + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); } /* ClearGlobalPower */ @@ -5181,7 +5167,7 @@ unsigned i; Log2(("ohci: global power down\n")); for (i = 0; i < OHCI_NDP_CFG(pThis); i++) - ohciR3RhPortPower(&pThis->RootHub, i, false /* power down */); + ohciR3RhPortPower(&pThisCC->RootHub, i, false /* power down */); } if ( val & OHCI_RHS_DRWE ) @@ -5237,27 +5223,15 @@ * Completion callback for the vusb_dev_reset() operation. * @thread EMT. */ -static DECLCALLBACK(void) ohciR3PortResetDone(PVUSBIDEVICE pDev, int rc, void *pvUser) +static DECLCALLBACK(void) ohciR3PortResetDone(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) { - PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; - POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + RT_NOREF(pDev); - /* - * Find the port in question - */ - POHCIHUBPORT pPort = NULL; - unsigned iPort; - for (iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) /* lazy bird */ - if (pThis->RootHub.aPorts[iPort].pDev == pDev) - { - pPort = &pThis->RootHub.aPorts[iPort]; - break; - } - if (!pPort) - { - Assert(pPort); /* sometimes happens because of @bugref{1510} */ - return; - } + Assert(uPort >= 1); + PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + POHCIHUBPORT pPort = &pThis->RootHub.aPorts[uPort - 1]; if (RT_SUCCESS(rc)) { @@ -5271,8 +5245,8 @@ else { /* desperate measures. */ - if ( pPort->pDev - && VUSBIDevGetState(pPort->pDev) == VUSB_DEVICE_STATE_ATTACHED) + if ( pPort->fAttached + && VUSBIRhDevGetState(pThisCC->RootHub.pIRhConn, uPort) == VUSB_DEVICE_STATE_ATTACHED) { /* * Damn, something weird happened during reset. We'll pretend the user did an @@ -5337,6 +5311,7 @@ { #ifdef IN_RING3 const unsigned i = iReg - 21; + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); POHCIHUBPORT p = &pThis->RootHub.aPorts[i]; uint32_t old_state = p->fReg; @@ -5384,7 +5359,8 @@ { PVM pVM = PDMDevHlpGetVM(pDevIns); p->fReg &= ~OHCI_PORT_PRSC; - VUSBIDevReset(p->pDev, false /* don't reset on linux */, ohciR3PortResetDone, pDevIns, pVM); + VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, OHCI_PORT_2_VUSB_PORT(i), false /* don't reset on linux */, + ohciR3PortResetDone, pDevIns, pVM); } else if (p->fReg & OHCI_PORT_PRS) { @@ -5401,15 +5377,15 @@ * sure it isn't gang powered */ if (val & OHCI_PORT_CLRPP) - ohciR3RhPortPower(&pThis->RootHub, i, false /* power down */); + ohciR3RhPortPower(&pThisCC->RootHub, i, false /* power down */); if (val & OHCI_PORT_PPS) - ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */); + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); } /** @todo r=frank: ClearSuspendStatus. Timing? */ if (val & OHCI_PORT_CLRSS) { - ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */); + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); pThis->RootHub.aPorts[i].fReg &= ~OHCI_PORT_PSS; pThis->RootHub.aPorts[i].fReg |= OHCI_PORT_PSSC; ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); @@ -5555,58 +5531,6 @@ #ifdef IN_RING3 /** - * Prepares for state saving. - * All URBs needs to be canceled. - * - * @returns VBox status code. - * @param pDevIns The device instance. - * @param pSSM The handle to save the state to. - */ -static DECLCALLBACK(int) ohciR3SavePrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) -{ - RT_NOREF(pSSM); - POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); - POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); - LogFlow(("ohciR3SavePrep: \n")); - - /* - * Detach all proxied devices. - */ - PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); - /** @todo this won't work well when continuing after saving! */ - for (unsigned i = 0; i < RT_ELEMENTS(pThis->RootHub.aPorts); i++) - { - PVUSBIDEVICE pDev = pThis->RootHub.aPorts[i].pDev; - if (pDev) - { - if (!VUSBIDevIsSavedStateSupported(pDev)) - { - VUSBIRhDetachDevice(pThisCC->RootHub.pIRhConn, pDev); - /* - * Save the device pointers here so we can reattach them afterwards. - * This will work fine even if the save fails since the Done handler is - * called unconditionally if the Prep handler was called. - */ - pThis->RootHub.aPorts[i].pDev = pDev; - } - } - } - PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); - - /* - * Kill old load data which might be hanging around. - */ - if (pThisCC->pLoad) - { - PDMDevHlpTimerDestroy(pDevIns, pThisCC->pLoad->hTimer); - PDMDevHlpMMHeapFree(pDevIns, pThisCC->pLoad); - pThisCC->pLoad = NULL; - } - return VINF_SUCCESS; -} - - -/** * Saves the state of the OHCI device. * * @returns VBox status code. @@ -5628,102 +5552,6 @@ /** - * Done state save operation. - * - * @returns VBox load code. - * @param pDevIns Device instance of the device which registered the data unit. - * @param pSSM SSM operation handle. - */ -static DECLCALLBACK(int) ohciR3SaveDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) -{ - POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); - POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); - LogFlow(("ohciR3SaveDone:\n")); - RT_NOREF(pSSM); - - /* - * NULL the dev pointers. - */ - POHCIROOTHUB pRh = &pThis->RootHub; - OHCIROOTHUB Rh = *pRh; - for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++) - { - if ( pRh->aPorts[i].pDev - && !VUSBIDevIsSavedStateSupported(pRh->aPorts[i].pDev)) - pRh->aPorts[i].pDev = NULL; - } - - /* - * Attach the devices. - */ - for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++) - { - PVUSBIDEVICE pDev = Rh.aPorts[i].pDev; - if ( pDev - && !VUSBIDevIsSavedStateSupported(pDev)) - VUSBIRhAttachDevice(pThisCC->RootHub.pIRhConn, pDev); - } - - return VINF_SUCCESS; -} - - -/** - * Prepare loading the state of the OHCI device. - * This must detach the devices currently attached and save - * the up for reconnect after the state load have been completed - * - * @returns VBox status code. - * @param pDevIns The device instance. - * @param pSSM The handle to the saved state. - */ -static DECLCALLBACK(int) ohciR3LoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) -{ - POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); - POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); - LogFlow(("ohciR3LoadPrep:\n")); - RT_NOREF(pSSM); - - if (!pThisCC->pLoad) - { - /* - * Detach all devices which are present in this session. Save them in the load - * structure so we can reattach them after restoring the guest. - */ - POHCIROOTHUB pRh = &pThis->RootHub; - OHCILOAD Load; - Load.hTimer = NIL_TMTIMERHANDLE; - Load.cDevs = 0; - for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++) - { - PVUSBIDEVICE pDev = pRh->aPorts[i].pDev; - if ( pDev - && !VUSBIDevIsSavedStateSupported(pDev)) - { - Load.apDevs[Load.cDevs++] = pDev; - VUSBIRhDetachDevice(pThisCC->RootHub.pIRhConn, pDev); - Assert(!pRh->aPorts[i].pDev); - } - } - - /* - * Any devices to reattach, if so duplicate the Load struct. - */ - if (Load.cDevs) - { - pThisCC->pLoad = (POHCILOAD)PDMDevHlpMMHeapAlloc(pDevIns, sizeof(Load)); - if (!pThisCC->pLoad) - return VERR_NO_MEMORY; - *pThisCC->pLoad = Load; - } - } - /* else: we ASSUME no device can be attached or detach in the period - * between a state load and the pLoad stuff is processed. */ - return VINF_SUCCESS; -} - - -/** * Loads the state of the OHCI device. * * @returns VBox status code. @@ -5785,62 +5613,6 @@ /** - * Done state load operation. - * - * @returns VBox load code. - * @param pDevIns Device instance of the device which registered the data unit. - * @param pSSM SSM operation handle. - */ -static DECLCALLBACK(int) ohciR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) -{ - POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); - LogFlow(("ohciR3LoadDone:\n")); - RT_NOREF(pSSM); - - /* - * Start a timer if we've got devices to reattach - */ - if (pThisCC->pLoad) - { - int rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ohciR3LoadReattachDevices, NULL /*pvUser*/, - TMTIMER_FLAGS_NO_CRIT_SECT, "OHCI reattach devices on load", &pThisCC->pLoad->hTimer); - if (RT_SUCCESS(rc)) - rc = PDMDevHlpTimerSetMillies(pDevIns, pThisCC->pLoad->hTimer, 250); - return rc; - } - - return VINF_SUCCESS; -} - - -/** - * @callback_method_impl{FNTMTIMERDEV, - * Reattaches devices after a saved state load.} - */ -static DECLCALLBACK(void) ohciR3LoadReattachDevices(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) -{ - POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); - POHCILOAD pLoad = pThisCC->pLoad; - LogFlow(("ohciR3LoadReattachDevices:\n")); - RT_NOREF(pTimer, pvUser); - - /* - * Reattach devices. - */ - for (unsigned i = 0; i < pLoad->cDevs; i++) - VUSBIRhAttachDevice(pThisCC->RootHub.pIRhConn, pLoad->apDevs[i]); - - /* - * Cleanup. - */ - PDMDevHlpTimerDestroy(pDevIns, pLoad->hTimer); - pLoad->hTimer = NIL_TMTIMERHANDLE; - PDMDevHlpMMHeapFree(pDevIns, pLoad); - pThisCC->pLoad = NULL; -} - - -/** * Reset notification. * * @returns VBox status code. @@ -6090,8 +5862,8 @@ */ rc = PDMDevHlpSSMRegisterEx(pDevIns, OHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL, NULL, NULL, NULL, - ohciR3SavePrep, ohciR3SaveExec, ohciR3SaveDone, - ohciR3LoadPrep, ohciR3LoadExec, ohciR3LoadDone); + NULL, ohciR3SaveExec, NULL, + NULL, ohciR3LoadExec, NULL); AssertRCReturn(rc, rc); /* @@ -6107,10 +5879,6 @@ AssertMsgReturn(pThisCC->RootHub.pIRhConn, ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"), VERR_PDM_MISSING_INTERFACE); - pThisCC->RootHub.pIDev = PDMIBASE_QUERY_INTERFACE(pThisCC->RootHub.pIBase, VUSBIDEVICE); - AssertMsgReturn(pThisCC->RootHub.pIDev, - ("Configuration error: The driver doesn't provide the VUSBIDEVICE interface!\n"), - VERR_PDM_MISSING_INTERFACE); /* * Attach status driver (optional). diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/DrvVUSBRootHub.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/DrvVUSBRootHub.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/DrvVUSBRootHub.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/DrvVUSBRootHub.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -219,6 +219,118 @@ #include "VBoxDD.h" +#define VUSB_ROOTHUB_SAVED_STATE_VERSION 1 + + +/** + * Data used for reattaching devices on a state load. + */ +typedef struct VUSBROOTHUBLOAD +{ + /** Timer used once after state load to inform the guest about new devices. + * We do this to be sure the guest get any disconnect / reconnect on the + * same port. */ + TMTIMERHANDLE hTimer; + /** Number of detached devices. */ + unsigned cDevs; + /** Array of devices which were detached. */ + PVUSBDEV apDevs[VUSB_DEVICES_MAX]; +} VUSBROOTHUBLOAD; + + +/** + * Returns the attached VUSB device for the given port or NULL if none is attached. + * + * @returns Pointer to the VUSB device or NULL if not found. + * @param pThis The VUSB roothub device instance. + * @param uPort The port to get the device for. + * @param pszWho Caller of this method. + * + * @note The reference count of the VUSB device structure is retained to prevent it from going away. + */ +static PVUSBDEV vusbR3RhGetVUsbDevByPortRetain(PVUSBROOTHUB pThis, uint32_t uPort, const char *pszWho) +{ + PVUSBDEV pDev = NULL; + + AssertReturn(uPort < RT_ELEMENTS(pThis->apDevByPort), NULL); + + RTCritSectEnter(&pThis->CritSectDevices); + + pDev = pThis->apDevByPort[uPort]; + if (RT_LIKELY(pDev)) + vusbDevRetain(pDev, pszWho); + + RTCritSectLeave(&pThis->CritSectDevices); + + return pDev; +} + + +/** + * Returns the attached VUSB device for the given port or NULL if none is attached. + * + * @returns Pointer to the VUSB device or NULL if not found. + * @param pThis The VUSB roothub device instance. + * @param u8Address The address to get the device for. + * @param pszWho Caller of this method. + * + * @note The reference count of the VUSB device structure is retained to prevent it from going away. + */ +static PVUSBDEV vusbR3RhGetVUsbDevByAddrRetain(PVUSBROOTHUB pThis, uint8_t u8Address, const char *pszWho) +{ + PVUSBDEV pDev = NULL; + + AssertReturn(u8Address < RT_ELEMENTS(pThis->apDevByAddr), NULL); + + RTCritSectEnter(&pThis->CritSectDevices); + + pDev = pThis->apDevByAddr[u8Address]; + if (RT_LIKELY(pDev)) + vusbDevRetain(pDev, pszWho); + + RTCritSectLeave(&pThis->CritSectDevices); + + return pDev; +} + + +/** + * Returns a human readable string fromthe given USB speed enum. + * + * @returns Human readable string. + * @param enmSpeed The speed to stringify. + */ +static const char *vusbGetSpeedString(VUSBSPEED enmSpeed) +{ + const char *pszSpeed = NULL; + + switch (enmSpeed) + { + case VUSB_SPEED_LOW: + pszSpeed = "Low"; + break; + case VUSB_SPEED_FULL: + pszSpeed = "Full"; + break; + case VUSB_SPEED_HIGH: + pszSpeed = "High"; + break; + case VUSB_SPEED_VARIABLE: + pszSpeed = "Variable"; + break; + case VUSB_SPEED_SUPER: + pszSpeed = "Super"; + break; + case VUSB_SPEED_SUPERPLUS: + pszSpeed = "SuperPlus"; + break; + default: + pszSpeed = "Unknown"; + break; + } + return pszSpeed; +} + /** * Attaches a device to a specific hub. @@ -226,17 +338,112 @@ * This function is called by the vusb_add_device() and vusbRhAttachDevice(). * * @returns VBox status code. - * @param pHub The hub to attach it to. + * @param pThis The roothub to attach it to. * @param pDev The device to attach. * @thread EMT */ -static int vusbHubAttach(PVUSBHUB pHub, PVUSBDEV pDev) +static int vusbHubAttach(PVUSBROOTHUB pThis, PVUSBDEV pDev) +{ + LogFlow(("vusbHubAttach: pThis=%p[%s] pDev=%p[%s]\n", pThis, pThis->pszName, pDev, pDev->pUsbIns->pszName)); + + /* + * Assign a port. + */ + int iPort = ASMBitFirstSet(&pThis->Bitmap, sizeof(pThis->Bitmap) * 8); + if (iPort < 0) + { + LogRel(("VUSB: No ports available!\n")); + return VERR_VUSB_NO_PORTS; + } + ASMBitClear(&pThis->Bitmap, iPort); + pThis->cDevices++; + pDev->i16Port = iPort; + + /* Call the device attach helper, so it can initialize its state. */ + int rc = vusbDevAttach(pDev, pThis); + if (RT_SUCCESS(rc)) + { + RTCritSectEnter(&pThis->CritSectDevices); + Assert(!pThis->apDevByPort[iPort]); + pThis->apDevByPort[iPort] = pDev; + RTCritSectLeave(&pThis->CritSectDevices); + + /* + * Call the HCI attach routine and let it have its say before the device is + * linked into the device list of this hub. + */ + VUSBSPEED enmSpeed = pDev->IDevice.pfnGetSpeed(&pDev->IDevice); + rc = pThis->pIRhPort->pfnAttach(pThis->pIRhPort, iPort, enmSpeed); + if (RT_SUCCESS(rc)) + { + LogRel(("VUSB: Attached '%s' to port %d on %s (%sSpeed)\n", pDev->pUsbIns->pszName, + iPort, pThis->pszName, vusbGetSpeedString(pDev->pUsbIns->enmSpeed))); + return VINF_SUCCESS; + } + + /* Remove from the port in case of failure. */ + RTCritSectEnter(&pThis->CritSectDevices); + Assert(!pThis->apDevByPort[iPort]); + pThis->apDevByPort[iPort] = NULL; + RTCritSectLeave(&pThis->CritSectDevices); + + vusbDevDetach(pDev); + } + + ASMBitSet(&pThis->Bitmap, iPort); + pThis->cDevices--; + pDev->i16Port = -1; + LogRel(("VUSB: Failed to attach '%s' to port %d, rc=%Rrc\n", pDev->pUsbIns->pszName, iPort, rc)); + + return rc; +} + + +/** + * Detaches the given device from the given roothub. + * + * @returns VBox status code. + * @param pThis The roothub to detach the device from. + * @param pDev The device to detach. + */ +static int vusbHubDetach(PVUSBROOTHUB pThis, PVUSBDEV pDev) { - LogFlow(("vusbHubAttach: pHub=%p[%s] pDev=%p[%s]\n", pHub, pHub->pszName, pDev, pDev->pUsbIns->pszName)); - return vusbDevAttach(pDev, pHub); + Assert(pDev->i16Port != -1); + + /* + * Detach the device and mark the port as available. + */ + unsigned uPort = pDev->i16Port; + pDev->i16Port = -1; + pThis->pIRhPort->pfnDetach(pThis->pIRhPort, uPort); + ASMBitSet(&pThis->Bitmap, uPort); + pThis->cDevices--; + + /* Check that it's attached and remove it. */ + RTCritSectEnter(&pThis->CritSectDevices); + Assert(pThis->apDevByPort[uPort] == pDev); + pThis->apDevByPort[uPort] = NULL; + + if (pDev->u8Address != VUSB_INVALID_ADDRESS) + { + Assert(pThis->apDevByAddr[pDev->u8Address] == pDev); + pThis->apDevByAddr[pDev->u8Address] = NULL; + + pDev->u8Address = VUSB_INVALID_ADDRESS; + pDev->u8NewAddress = VUSB_INVALID_ADDRESS; + } + RTCritSectLeave(&pThis->CritSectDevices); + + /* Cancel all in-flight URBs from this device. */ + vusbDevCancelAllUrbs(pDev, true); + + /* Free resources. */ + vusbDevDetach(pDev); + return VINF_SUCCESS; } + /* -=-=-=-=-=- PDMUSBHUBREG methods -=-=-=-=-=- */ /** @interface_method_impl{PDMUSBHUBREG,pfnAttachDevice} */ @@ -253,7 +460,7 @@ if (RT_SUCCESS(rc)) { pUsbIns->pvVUsbDev2 = pDev; - rc = vusbHubAttach(&pThis->Hub, pDev); + rc = vusbHubAttach(pThis, pDev); if (RT_SUCCESS(rc)) { *piPort = UINT32_MAX; /// @todo implement piPort @@ -263,7 +470,7 @@ RTMemFree(pDev->paIfStates); pUsbIns->pvVUsbDev2 = NULL; } - vusbDevRelease(pDev); + vusbDevRelease(pDev, "vusbPDMHubAttachDevice"); return rc; } @@ -271,23 +478,20 @@ /** @interface_method_impl{PDMUSBHUBREG,pfnDetachDevice} */ static DECLCALLBACK(int) vusbPDMHubDetachDevice(PPDMDRVINS pDrvIns, PPDMUSBINS pUsbIns, uint32_t iPort) { - RT_NOREF(pDrvIns, iPort); + RT_NOREF(iPort); + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); PVUSBDEV pDev = (PVUSBDEV)pUsbIns->pvVUsbDev2; Assert(pDev); + LogRel(("VUSB: Detached '%s' from port %u on %s\n", pDev->pUsbIns->pszName, pDev->i16Port, pThis->pszName)); + /* * Deal with pending async reset. * (anything but reset) */ vusbDevSetStateCmp(pDev, VUSB_DEVICE_STATE_DEFAULT, VUSB_DEVICE_STATE_RESET); - - /* - * Detach and free resources. - */ - if (pDev->pHub) - vusbDevDetach(pDev); - - vusbDevRelease(pDev); + vusbHubDetach(pThis, pDev); + vusbDevRelease(pDev, "vusbPDMHubDetachDevice"); return VINF_SUCCESS; } @@ -307,34 +511,6 @@ /** - * Finds an device attached to a roothub by it's address. - * - * @returns Pointer to the device. - * @returns NULL if not found. - * @param pRh Pointer to the root hub. - * @param Address The device address. - */ -static PVUSBDEV vusbRhFindDevByAddress(PVUSBROOTHUB pRh, uint8_t Address) -{ - unsigned iHash = vusbHashAddress(Address); - PVUSBDEV pDev = NULL; - - RTCritSectEnter(&pRh->CritSectDevices); - for (PVUSBDEV pCur = pRh->apAddrHash[iHash]; pCur; pCur = pCur->pNextHash) - if (pCur->u8Address == Address) - { - pDev = pCur; - break; - } - - if (pDev) - vusbDevRetain(pDev); - RTCritSectLeave(&pRh->CritSectDevices); - return pDev; -} - - -/** * Callback for freeing an URB. * @param pUrb The URB to free. */ @@ -349,6 +525,10 @@ Assert(pUrb->enmState != VUSBURBSTATE_FREE); +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbRhFreeUrb", true); +#endif + /* * Free the URB description (logging builds only). */ @@ -364,21 +544,21 @@ PVUSBDEV pDev = pUrb->pVUsb->pDev; vusbUrbPoolFree(&pUrb->pVUsb->pDev->UrbPool, pUrb); - vusbDevRelease(pDev); + vusbDevRelease(pDev, "vusbRhFreeUrb"); } else - vusbUrbPoolFree(&pRh->Hub.Dev.UrbPool, pUrb); + vusbUrbPoolFree(&pRh->UrbPool, pUrb); } /** * Worker routine for vusbRhConnNewUrb(). */ -static PVUSBURB vusbRhNewUrb(PVUSBROOTHUB pRh, uint8_t DstAddress, PVUSBDEV pDev, VUSBXFERTYPE enmType, +static PVUSBURB vusbRhNewUrb(PVUSBROOTHUB pRh, uint8_t DstAddress, uint32_t uPort, VUSBXFERTYPE enmType, VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag) { RT_NOREF(pszTag); - PVUSBURBPOOL pUrbPool = &pRh->Hub.Dev.UrbPool; + PVUSBURBPOOL pUrbPool = &pRh->UrbPool; if (RT_UNLIKELY(cbData > (32 * _1M))) { @@ -386,10 +566,11 @@ return NULL; } - if (!pDev) - pDev = vusbRhFindDevByAddress(pRh, DstAddress); + PVUSBDEV pDev; + if (uPort == VUSB_DEVICE_PORT_INVALID) + pDev = vusbR3RhGetVUsbDevByAddrRetain(pRh, DstAddress, "vusbRhNewUrb"); else - vusbDevRetain(pDev); + pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhNewUrb"); if (pDev) pUrbPool = &pDev->UrbPool; @@ -429,6 +610,8 @@ RTStrAPrintf(&pUrb->pszDesc, "URB %p %s%c%04d (%s)", pUrb, pszType, (pUrb->enmDir == VUSBDIRECTION_IN) ? '<' : ((pUrb->enmDir == VUSBDIRECTION_SETUP) ? 's' : '>'), pRh->iSerial, pszTag ? pszTag : ""); + + vusbUrbTrace(pUrb, "vusbRhNewUrb", false); #endif } @@ -630,12 +813,57 @@ } +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnReset} */ +static DECLCALLBACK(int) vusbR3RhReset(PVUSBIROOTHUBCONNECTOR pInterface, bool fResetOnLinux) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + return pRh->pIRhPort->pfnReset(pRh->pIRhPort, fResetOnLinux); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnPowerOn} */ +static DECLCALLBACK(int) vusbR3RhPowerOn(PVUSBIROOTHUBCONNECTOR pInterface) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + LogFlow(("vusR3bRhPowerOn: pRh=%p\n", pRh)); + + Assert( pRh->enmState != VUSB_DEVICE_STATE_DETACHED + && pRh->enmState != VUSB_DEVICE_STATE_RESET); + + if (pRh->enmState == VUSB_DEVICE_STATE_ATTACHED) + pRh->enmState = VUSB_DEVICE_STATE_POWERED; + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnPowerOff} */ +static DECLCALLBACK(int) vusbR3RhPowerOff(PVUSBIROOTHUBCONNECTOR pInterface) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + LogFlow(("vusbR3RhDevPowerOff: pThis=%p\n", pThis)); + + Assert( pThis->enmState != VUSB_DEVICE_STATE_DETACHED + && pThis->enmState != VUSB_DEVICE_STATE_RESET); + + /* + * Cancel all URBs and reap them. + */ + VUSBIRhCancelAllUrbs(&pThis->IRhConnector); + for (uint32_t uPort = 0; uPort < RT_ELEMENTS(pThis->apDevByPort); uPort++) + VUSBIRhReapAsyncUrbs(&pThis->IRhConnector, uPort, 0); + + pThis->enmState = VUSB_DEVICE_STATE_ATTACHED; + return VINF_SUCCESS; +} + + /** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnNewUrb} */ -static DECLCALLBACK(PVUSBURB) vusbRhConnNewUrb(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, PVUSBIDEVICE pDev, VUSBXFERTYPE enmType, +static DECLCALLBACK(PVUSBURB) vusbRhConnNewUrb(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, uint32_t uPort, VUSBXFERTYPE enmType, VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag) { PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); - return vusbRhNewUrb(pRh, DstAddress, (PVUSBDEV)pDev, enmType, enmDir, cbData, cTds, pszTag); + return vusbRhNewUrb(pRh, DstAddress, uPort, enmType, enmDir, cbData, cTds, pszTag); } @@ -726,13 +954,11 @@ } else { - vusbDevRetain(&pRh->Hub.Dev); - pUrb->pVUsb->pDev = &pRh->Hub.Dev; Log(("vusb: pRh=%p: SUBMIT: Address %i not found!!!\n", pRh, pUrb->DstAddress)); pUrb->enmState = VUSBURBSTATE_REAPED; pUrb->enmStatus = VUSBSTATUS_DNR; - vusbUrbCompletionRh(pUrb); + vusbUrbCompletionRhEx(pRh, pUrb); rc = VINF_SUCCESS; } @@ -759,18 +985,23 @@ } /** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnReapAsyncUrbs} */ -static DECLCALLBACK(void) vusbRhReapAsyncUrbs(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, RTMSINTERVAL cMillies) +static DECLCALLBACK(void) vusbRhReapAsyncUrbs(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, RTMSINTERVAL cMillies) { PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); NOREF(pRh); - PVUSBDEV pDev = (PVUSBDEV)pDevice; + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhReapAsyncUrbs"); - if (RTListIsEmpty(&pDev->LstAsyncUrbs)) + if (!pDev) return; - STAM_PROFILE_START(&pRh->StatReapAsyncUrbs, a); - int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhReapAsyncUrbsWorker, 2, pDev, cMillies); - AssertRC(rc); - STAM_PROFILE_STOP(&pRh->StatReapAsyncUrbs, a); + if (!RTListIsEmpty(&pDev->LstAsyncUrbs)) + { + STAM_PROFILE_START(&pRh->StatReapAsyncUrbs, a); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhReapAsyncUrbsWorker, 2, pDev, cMillies); + AssertRC(rc); + STAM_PROFILE_STOP(&pRh->StatReapAsyncUrbs, a); + } + + vusbDevRelease(pDev, "vusbRhReapAsyncUrbs"); } @@ -823,16 +1054,16 @@ /** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnCancelAllUrbs} */ static DECLCALLBACK(void) vusbRhCancelAllUrbs(PVUSBIROOTHUBCONNECTOR pInterface) { - PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); - RTCritSectEnter(&pRh->CritSectDevices); - PVUSBDEV pDev = pRh->pDevices; - while (pDev) + RTCritSectEnter(&pThis->CritSectDevices); + for (unsigned i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) { - vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhCancelAllUrbsWorker, 1, pDev); - pDev = pDev->pNext; + PVUSBDEV pDev = pThis->apDevByPort[i]; + if (pDev) + vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhCancelAllUrbsWorker, 1, pDev); } - RTCritSectLeave(&pRh->CritSectDevices); + RTCritSectLeave(&pThis->CritSectDevices); } /** @@ -870,16 +1101,16 @@ /** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnAbortEp} */ -static DECLCALLBACK(int) vusbRhAbortEp(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, int EndPt, VUSBDIRECTION enmDir) +static DECLCALLBACK(int) vusbRhAbortEp(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, int EndPt, VUSBDIRECTION enmDir) { PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); - if (&pRh->Hub != ((PVUSBDEV)pDevice)->pHub) + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhAbortEp"); + + if (pDev->pHub != pRh) AssertFailedReturn(VERR_INVALID_PARAMETER); - RTCritSectEnter(&pRh->CritSectDevices); - PVUSBDEV pDev = (PVUSBDEV)pDevice; vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhAbortEpWorker, 3, pDev, EndPt, enmDir); - RTCritSectLeave(&pRh->CritSectDevices); + vusbDevRelease(pDev, "vusbRhAbortEp"); /* The reaper thread will take care of completing the URB. */ @@ -887,24 +1118,6 @@ } -/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnAttachDevice} */ -static DECLCALLBACK(int) vusbRhAttachDevice(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice) -{ - PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); - return vusbHubAttach(&pRh->Hub, (PVUSBDEV)pDevice); -} - - -/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDetachDevice} */ -static DECLCALLBACK(int) vusbRhDetachDevice(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice) -{ - PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); - if (&pRh->Hub != ((PVUSBDEV)pDevice)->pHub) - AssertFailedReturn(VERR_INVALID_PARAMETER); - return vusbDevDetach((PVUSBDEV)pDevice); -} - - /** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSetPeriodicFrameProcessing} */ static DECLCALLBACK(int) vusbRhSetFrameProcessing(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uFrameRate) { @@ -979,12 +1192,12 @@ } /** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnUpdateIsocFrameDelta} */ -static DECLCALLBACK(uint32_t) vusbRhUpdateIsocFrameDelta(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, +static DECLCALLBACK(uint32_t) vusbRhUpdateIsocFrameDelta(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, int EndPt, VUSBDIRECTION enmDir, uint16_t uNewFrameID, uint8_t uBits) { PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); AssertReturn(pRh, 0); - PVUSBDEV pDev = (PVUSBDEV)pDevice; + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhUpdateIsocFrameDelta"); AssertPtr(pDev); PVUSBPIPE pPipe = &pDev->aPipes[EndPt]; uint32_t *puLastFrame; int32_t uFrameDelta; @@ -997,214 +1210,280 @@ if (uFrameDelta < 0) uFrameDelta += uMaxVal; + vusbDevRelease(pDev, "vusbRhUpdateIsocFrameDelta"); return (uint16_t)uFrameDelta; } -/* -=-=-=-=-=- VUSB Device methods (for the root hub) -=-=-=-=-=- */ +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevReset} */ +static DECLCALLBACK(int) vusbR3RhDevReset(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, bool fResetOnLinux, + PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevReset"); + AssertPtr(pDev); -/** - * @interface_method_impl{VUSBIDEVICE,pfnReset} - */ -static DECLCALLBACK(int) vusbRhDevReset(PVUSBIDEVICE pInterface, bool fResetOnLinux, - PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM) + int rc = VUSBIDevReset(&pDev->IDevice, fResetOnLinux, pfnDone, pvUser, pVM); + vusbDevRelease(pDev, "vusbR3RhDevReset"); + return rc; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevPowerOn} */ +static DECLCALLBACK(int) vusbR3RhDevPowerOn(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) { - RT_NOREF(pfnDone, pvUser, pVM); - PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice); - Assert(!pfnDone); - return pRh->pIRhPort->pfnReset(pRh->pIRhPort, fResetOnLinux); /** @todo change rc from bool to vbox status everywhere! */ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevPowerOn"); + AssertPtr(pDev); + + int rc = VUSBIDevPowerOn(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevPowerOn"); + return rc; } -/** - * @interface_method_impl{VUSBIDEVICE,pfnPowerOn} - */ -static DECLCALLBACK(int) vusbRhDevPowerOn(PVUSBIDEVICE pInterface) +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevPowerOff} */ +static DECLCALLBACK(int) vusbR3RhDevPowerOff(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) { - PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice); - LogFlow(("vusbRhDevPowerOn: pRh=%p\n", pRh)); + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevPowerOff"); + AssertPtr(pDev); - Assert( pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_DETACHED - && pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_RESET); + int rc = VUSBIDevPowerOff(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevPowerOff"); + return rc; +} - if (pRh->Hub.Dev.enmState == VUSB_DEVICE_STATE_ATTACHED) - pRh->Hub.Dev.enmState = VUSB_DEVICE_STATE_POWERED; - return VINF_SUCCESS; +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevGetState} */ +static DECLCALLBACK(VUSBDEVICESTATE) vusbR3RhDevGetState(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevGetState"); + AssertPtr(pDev); + + VUSBDEVICESTATE enmState = VUSBIDevGetState(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevGetState"); + return enmState; } -/** - * @interface_method_impl{VUSBIDEVICE,pfnPowerOff} - */ -static DECLCALLBACK(int) vusbRhDevPowerOff(PVUSBIDEVICE pInterface) +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevIsSavedStateSupported} */ +static DECLCALLBACK(bool) vusbR3RhDevIsSavedStateSupported(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) { - PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice); - LogFlow(("vusbRhDevPowerOff: pRh=%p\n", pRh)); + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevIsSavedStateSupported"); + AssertPtr(pDev); - Assert( pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_DETACHED - && pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_RESET); + bool fSavedStateSupported = VUSBIDevIsSavedStateSupported(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevIsSavedStateSupported"); + return fSavedStateSupported; +} - /* - * Cancel all URBs and reap them. - */ - VUSBIRhCancelAllUrbs(&pRh->IRhConnector); - RTCritSectEnter(&pRh->CritSectDevices); - PVUSBDEV pDev = pRh->pDevices; - while (pDev) - { - VUSBIRhReapAsyncUrbs(&pRh->IRhConnector, (PVUSBIDEVICE)pDev, 0); - pDev = pDev->pNext; - } - RTCritSectLeave(&pRh->CritSectDevices); - pRh->Hub.Dev.enmState = VUSB_DEVICE_STATE_ATTACHED; - return VINF_SUCCESS; +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevGetSpeed} */ +static DECLCALLBACK(VUSBSPEED) vusbR3RhDevGetSpeed(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevGetSpeed"); + AssertPtr(pDev); + + VUSBSPEED enmSpeed = pDev->IDevice.pfnGetSpeed(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevGetSpeed"); + return enmSpeed; } + /** - * @interface_method_impl{VUSBIDEVICE,pfnGetState} + * @callback_method_impl{FNSSMDRVSAVEPREP, All URBs needs to be canceled.} */ -static DECLCALLBACK(VUSBDEVICESTATE) vusbRhDevGetState(PVUSBIDEVICE pInterface) +static DECLCALLBACK(int) vusbR3RhSavePrep(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) { - PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice); - return pRh->Hub.Dev.enmState; -} + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + LogFlow(("vusbR3RhSavePrep:\n")); + RT_NOREF(pSSM); + /* + * Detach all proxied devices. + */ + RTCritSectEnter(&pThis->CritSectDevices); -static const char *vusbGetSpeedString(VUSBSPEED enmSpeed) -{ - const char *pszSpeed = NULL; + /** @todo we a) can't tell which are proxied, and b) this won't work well when continuing after saving! */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) + { + PVUSBDEV pDev = pThis->apDevByPort[i]; + if (pDev) + { + if (!VUSBIDevIsSavedStateSupported(&pDev->IDevice)) + { + int rc = vusbHubDetach(pThis, pDev); + AssertRC(rc); + + /* + * Save the device pointers here so we can reattach them afterwards. + * This will work fine even if the save fails since the Done handler is + * called unconditionally if the Prep handler was called. + */ + pThis->apDevByPort[i] = pDev; + } + } + } - switch (enmSpeed) + RTCritSectLeave(&pThis->CritSectDevices); + + /* + * Kill old load data which might be hanging around. + */ + if (pThis->pLoad) { - case VUSB_SPEED_LOW: - pszSpeed = "Low"; - break; - case VUSB_SPEED_FULL: - pszSpeed = "Full"; - break; - case VUSB_SPEED_HIGH: - pszSpeed = "High"; - break; - case VUSB_SPEED_VARIABLE: - pszSpeed = "Variable"; - break; - case VUSB_SPEED_SUPER: - pszSpeed = "Super"; - break; - case VUSB_SPEED_SUPERPLUS: - pszSpeed = "SuperPlus"; - break; - default: - pszSpeed = "Unknown"; - break; + PDMDrvHlpTimerDestroy(pDrvIns, pThis->pLoad->hTimer); + pThis->pLoad->hTimer = NIL_TMTIMERHANDLE; + RTMemFree(pThis->pLoad); + pThis->pLoad = NULL; } - return pszSpeed; -} -/* -=-=-=-=-=- VUSB Hub methods -=-=-=-=-=- */ + return VINF_SUCCESS; +} /** - * Attach the device to the hub. - * Port assignments and all such stuff is up to this routine. - * - * @returns VBox status code. - * @param pHub Pointer to the hub. - * @param pDev Pointer to the device. + * @callback_method_impl{FNSSMDRVSAVEDONE} */ -static int vusbRhHubOpAttach(PVUSBHUB pHub, PVUSBDEV pDev) +static DECLCALLBACK(int) vusbR3RhSaveDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) { - PVUSBROOTHUB pRh = (PVUSBROOTHUB)pHub; + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + PVUSBDEV aPortsOld[VUSB_DEVICES_MAX]; + unsigned i; + LogFlow(("vusbR3RhSaveDone:\n")); + RT_NOREF(pSSM); + + /* Save the current data. */ + memcpy(aPortsOld, pThis->apDevByPort, sizeof(aPortsOld)); + AssertCompile(sizeof(aPortsOld) == sizeof(pThis->apDevByPort)); /* - * Assign a port. + * NULL the dev pointers. */ - int iPort = ASMBitFirstSet(&pRh->Bitmap, sizeof(pRh->Bitmap) * 8); - if (iPort < 0) - { - LogRel(("VUSB: No ports available!\n")); - return VERR_VUSB_NO_PORTS; - } - ASMBitClear(&pRh->Bitmap, iPort); - pHub->cDevices++; - pDev->i16Port = iPort; + for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) + if (pThis->apDevByPort[i] && !VUSBIDevIsSavedStateSupported(&pThis->apDevByPort[i]->IDevice)) + pThis->apDevByPort[i] = NULL; /* - * Call the HCI attach routine and let it have its say before the device is - * linked into the device list of this hub. + * Attach the devices. */ - int rc = pRh->pIRhPort->pfnAttach(pRh->pIRhPort, &pDev->IDevice, iPort); - if (RT_SUCCESS(rc)) + for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) { - RTCritSectEnter(&pRh->CritSectDevices); - pDev->pNext = pRh->pDevices; - pRh->pDevices = pDev; - RTCritSectLeave(&pRh->CritSectDevices); - LogRel(("VUSB: Attached '%s' to port %d on %s (%sSpeed)\n", pDev->pUsbIns->pszName, - iPort, pHub->pszName, vusbGetSpeedString(pDev->pUsbIns->enmSpeed))); + PVUSBDEV pDev = aPortsOld[i]; + if (pDev && !VUSBIDevIsSavedStateSupported(&pDev->IDevice)) + vusbHubAttach(pThis, pDev); } - else - { - ASMBitSet(&pRh->Bitmap, iPort); - pHub->cDevices--; - pDev->i16Port = -1; - LogRel(("VUSB: Failed to attach '%s' to port %d, rc=%Rrc\n", pDev->pUsbIns->pszName, iPort, rc)); + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSSMDRVLOADPREP, This must detach the devices + * currently attached and save them for reconnect after the state load has been + * completed.} + */ +static DECLCALLBACK(int) vusbR3RhLoadPrep(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) +{ + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + int rc = VINF_SUCCESS; + LogFlow(("vusbR3RhLoadPrep:\n")); + RT_NOREF(pSSM); + + if (!pThis->pLoad) + { + VUSBROOTHUBLOAD Load; + unsigned i; + + /// @todo This is all bogus. + /* + * Detach all devices which are present in this session. Save them in the load + * structure so we can reattach them after restoring the guest. + */ + Load.hTimer = NIL_TMTIMERHANDLE; + Load.cDevs = 0; + for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) + { + PVUSBDEV pDev = pThis->apDevByPort[i]; + if (pDev && !VUSBIDevIsSavedStateSupported(&pDev->IDevice)) + { + Load.apDevs[Load.cDevs++] = pDev; + vusbHubDetach(pThis, pDev); + Assert(!pThis->apDevByPort[i]); + } + } + + /* + * Any devices to reattach? If so, duplicate the Load struct. + */ + if (Load.cDevs) + { + pThis->pLoad = (PVUSBROOTHUBLOAD)RTMemAllocZ(sizeof(Load)); + if (!pThis->pLoad) + return VERR_NO_MEMORY; + *pThis->pLoad = Load; + } } + /* else: we ASSUME no device can be attached or detached in the time + * between a state load and the pLoad stuff processing. */ return rc; } /** - * Detach the device from the hub. - * - * @returns VBox status code. - * @param pHub Pointer to the hub. - * @param pDev Pointer to the device. + * Reattaches devices after a saved state load. */ -static void vusbRhHubOpDetach(PVUSBHUB pHub, PVUSBDEV pDev) +static DECLCALLBACK(void) vusbR3RhLoadReattachDevices(PPDMDRVINS pDrvIns, PTMTIMER pTimer, void *pvUser) { - PVUSBROOTHUB pRh = (PVUSBROOTHUB)pHub; - Assert(pDev->i16Port != -1); + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + PVUSBROOTHUBLOAD pLoad = pThis->pLoad; + LogFlow(("vusbR3RhLoadReattachDevices:\n")); + RT_NOREF(pTimer, pvUser); /* - * Check that it's attached and unlink it from the linked list. + * Reattach devices. */ - RTCritSectEnter(&pRh->CritSectDevices); - if (pRh->pDevices != pDev) - { - PVUSBDEV pPrev = pRh->pDevices; - while (pPrev && pPrev->pNext != pDev) - pPrev = pPrev->pNext; - Assert(pPrev); - pPrev->pNext = pDev->pNext; - } - else - pRh->pDevices = pDev->pNext; - pDev->pNext = NULL; - RTCritSectLeave(&pRh->CritSectDevices); + for (unsigned i = 0; i < pLoad->cDevs; i++) + vusbHubAttach(pThis, pLoad->apDevs[i]); /* - * Detach the device and mark the port as available. + * Cleanup. */ - unsigned uPort = pDev->i16Port; - pRh->pIRhPort->pfnDetach(pRh->pIRhPort, &pDev->IDevice, uPort); - LogRel(("VUSB: Detached '%s' from port %u on %s\n", pDev->pUsbIns->pszName, uPort, pHub->pszName)); - ASMBitSet(&pRh->Bitmap, uPort); - pHub->cDevices--; + PDMDrvHlpTimerDestroy(pDrvIns, (TMTIMERHANDLE)pTimer); + pLoad->hTimer = NIL_TMTIMERHANDLE; + RTMemFree(pLoad); + pThis->pLoad = NULL; } /** - * The Hub methods implemented by the root hub. + * @callback_method_impl{FNSSMDRVLOADDONE} */ -static const VUSBHUBOPS s_VUsbRhHubOps = +static DECLCALLBACK(int) vusbR3RhLoadDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) { - vusbRhHubOpAttach, - vusbRhHubOpDetach -}; + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + LogFlow(("vusbR3RhLoadDone:\n")); + RT_NOREF(pSSM); + /* + * Start a timer if we've got devices to reattach + */ + if (pThis->pLoad) + { + int rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_VIRTUAL, vusbR3RhLoadReattachDevices, NULL, + TMTIMER_FLAGS_NO_CRIT_SECT, + "VUSB reattach on load", &pThis->pLoad->hTimer); + if (RT_SUCCESS(rc)) + rc = PDMDrvHlpTimerSetMillies(pDrvIns, pThis->pLoad->hTimer, 250); + return rc; + } + + return VINF_SUCCESS; +} /* -=-=-=-=-=- PDM Base interface methods -=-=-=-=-=- */ @@ -1220,7 +1499,6 @@ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBCONNECTOR, &pRh->IRhConnector); - PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIDEVICE, &pRh->Hub.Dev.IDevice); return NULL; } @@ -1241,11 +1519,11 @@ PVUSBROOTHUB pRh = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); - vusbUrbPoolDestroy(&pRh->Hub.Dev.UrbPool); - if (pRh->Hub.pszName) + vusbUrbPoolDestroy(&pRh->UrbPool); + if (pRh->pszName) { - RTStrFree(pRh->Hub.pszName); - pRh->Hub.pszName = NULL; + RTStrFree(pRh->pszName); + pRh->pszName = NULL; } if (pRh->hSniffer != VUSBSNIFFER_NIL) VUSBSnifferDestroy(pRh->hSniffer); @@ -1304,26 +1582,17 @@ */ pDrvIns->IBase.pfnQueryInterface = vusbRhQueryInterface; /* the usb device */ - pThis->Hub.Dev.enmState = VUSB_DEVICE_STATE_ATTACHED; - pThis->Hub.Dev.u8Address = VUSB_INVALID_ADDRESS; - pThis->Hub.Dev.u8NewAddress = VUSB_INVALID_ADDRESS; - pThis->Hub.Dev.i16Port = -1; - pThis->Hub.Dev.cRefs = 1; - pThis->Hub.Dev.IDevice.pfnReset = vusbRhDevReset; - pThis->Hub.Dev.IDevice.pfnPowerOn = vusbRhDevPowerOn; - pThis->Hub.Dev.IDevice.pfnPowerOff = vusbRhDevPowerOff; - pThis->Hub.Dev.IDevice.pfnGetState = vusbRhDevGetState; - /* the hub */ - pThis->Hub.pOps = &s_VUsbRhHubOps; - pThis->Hub.pRootHub = pThis; + pThis->enmState = VUSB_DEVICE_STATE_ATTACHED; //pThis->hub.cPorts - later - pThis->Hub.cDevices = 0; - pThis->Hub.Dev.pHub = &pThis->Hub; - RTStrAPrintf(&pThis->Hub.pszName, "RootHub#%d", pDrvIns->iInstance); + pThis->cDevices = 0; + RTStrAPrintf(&pThis->pszName, "RootHub#%d", pDrvIns->iInstance); /* misc */ pThis->pDrvIns = pDrvIns; /* the connector */ pThis->IRhConnector.pfnSetUrbParams = vusbRhSetUrbParams; + pThis->IRhConnector.pfnReset = vusbR3RhReset; + pThis->IRhConnector.pfnPowerOn = vusbR3RhPowerOn; + pThis->IRhConnector.pfnPowerOff = vusbR3RhPowerOff; pThis->IRhConnector.pfnNewUrb = vusbRhConnNewUrb; pThis->IRhConnector.pfnFreeUrb = vusbRhConnFreeUrb; pThis->IRhConnector.pfnSubmitUrb = vusbRhSubmitUrb; @@ -1331,11 +1600,15 @@ pThis->IRhConnector.pfnCancelUrbsEp = vusbRhCancelUrbsEp; pThis->IRhConnector.pfnCancelAllUrbs = vusbRhCancelAllUrbs; pThis->IRhConnector.pfnAbortEp = vusbRhAbortEp; - pThis->IRhConnector.pfnAttachDevice = vusbRhAttachDevice; - pThis->IRhConnector.pfnDetachDevice = vusbRhDetachDevice; pThis->IRhConnector.pfnSetPeriodicFrameProcessing = vusbRhSetFrameProcessing; pThis->IRhConnector.pfnGetPeriodicFrameRate = vusbRhGetPeriodicFrameRate; pThis->IRhConnector.pfnUpdateIsocFrameDelta = vusbRhUpdateIsocFrameDelta; + pThis->IRhConnector.pfnDevReset = vusbR3RhDevReset; + pThis->IRhConnector.pfnDevPowerOn = vusbR3RhDevPowerOn; + pThis->IRhConnector.pfnDevPowerOff = vusbR3RhDevPowerOff; + pThis->IRhConnector.pfnDevGetState = vusbR3RhDevGetState; + pThis->IRhConnector.pfnDevIsSavedStateSupported = vusbR3RhDevIsSavedStateSupported; + pThis->IRhConnector.pfnDevGetSpeed = vusbR3RhDevGetSpeed; pThis->hSniffer = VUSBSNIFFER_NIL; pThis->cbHci = 0; pThis->cbHciTd = 0; @@ -1353,8 +1626,8 @@ * Get number of ports and the availability bitmap. * ASSUME that the number of ports reported now at creation time is the max number. */ - pThis->Hub.cPorts = pThis->pIRhPort->pfnGetAvailablePorts(pThis->pIRhPort, &pThis->Bitmap); - Log(("vusbRhConstruct: cPorts=%d\n", pThis->Hub.cPorts)); + pThis->cPorts = pThis->pIRhPort->pfnGetAvailablePorts(pThis->pIRhPort, &pThis->Bitmap); + Log(("vusbRhConstruct: cPorts=%d\n", pThis->cPorts)); /* * Get the USB version of the attached HC. @@ -1363,7 +1636,7 @@ pThis->fHcVersions = pThis->pIRhPort->pfnGetUSBVersions(pThis->pIRhPort); Log(("vusbRhConstruct: fHcVersions=%u\n", pThis->fHcVersions)); - rc = vusbUrbPoolInit(&pThis->Hub.Dev.UrbPool); + rc = vusbUrbPoolInit(&pThis->UrbPool); if (RT_FAILURE(rc)) return rc; @@ -1382,12 +1655,21 @@ * Register ourselves as a USB hub. * The current implementation uses the VUSBIRHCONFIG interface for communication. */ - PCPDMUSBHUBHLP pHlp; /* not used currently */ - rc = PDMDrvHlpUSBRegisterHub(pDrvIns, pThis->fHcVersions, pThis->Hub.cPorts, &g_vusbHubReg, &pHlp); + PCPDMUSBHUBHLP pHlpUsb; /* not used currently */ + rc = PDMDrvHlpUSBRegisterHub(pDrvIns, pThis->fHcVersions, pThis->cPorts, &g_vusbHubReg, &pHlpUsb); if (RT_FAILURE(rc)) return rc; /* + * Register the saved state data unit for attaching devices. + */ + rc = PDMDrvHlpSSMRegisterEx(pDrvIns, VUSB_ROOTHUB_SAVED_STATE_VERSION, 0, + NULL, NULL, NULL, + vusbR3RhSavePrep, NULL, vusbR3RhSaveDone, + vusbR3RhLoadPrep, NULL, vusbR3RhLoadDone); + AssertRCReturn(rc, rc); + + /* * Statistics. (It requires a 30" monitor or extremely tiny fonts to edit this "table".) */ #ifdef VBOX_WITH_STATISTICS @@ -1513,7 +1795,8 @@ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatFramesProcessedThread, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Processed frames in the dedicated thread", "/VUSB/%d/FramesProcessedThread", pDrvIns->iInstance); PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatFramesProcessedClbk, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Processed frames in the URB completion callback", "/VUSB/%d/FramesProcessedClbk", pDrvIns->iInstance); #endif - PDMDrvHlpSTAMRegisterF(pDrvIns, (void *)&pThis->Hub.Dev.UrbPool.cUrbsInPool, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs in the pool.", "/VUSB/%d/cUrbsInPool", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, (void *)&pThis->UrbPool.cUrbsInPool, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs in the pool.", + "/VUSB/%d/cUrbsInPool", pDrvIns->iInstance); return VINF_SUCCESS; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp 2022-09-01 13:27:00.000000000 +0000 @@ -819,7 +819,8 @@ */ static int usbProxyUsbIpDisconnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp) { - int rc = VINF_SUCCESS; + int rc = RTPollSetRemove(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_SOCKET); + Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND); rc = RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/); if (RT_SUCCESS(rc)) @@ -963,8 +964,6 @@ } case USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL: { - /** @todo Verify that the directions match, verify that the length doesn't exceed the buffer. */ - switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)) { case USBIP_RET_SUBMIT: @@ -979,23 +978,43 @@ if (pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_IN) { uint8_t *pbData = NULL; + size_t cbRet = 0; AssertPtr(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb); if (pProxyDevUsbIp->pUrbUsbIp->enmType == VUSBXFERTYPE_MSG) { /* Preserve the setup request. */ pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[sizeof(VUSBSETUP)]; - pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength + sizeof(VUSBSETUP); + cbRet = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength + sizeof(VUSBSETUP); } else { pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[0]; - pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength; + cbRet = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength; } if (pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength) - usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_URB_BUFFER, - pbData, pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength); + { + if (RT_LIKELY(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData >= cbRet)) + { + pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = (uint32_t)cbRet; + usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_URB_BUFFER, + pbData, pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength); + } + else + { + /* + * Bogus length returned from the USB/IP remote server. + * Error out because there is no way to find the end of the current + * URB and the beginning of the next one. The error will cause closing the + * connection to the rogue remote and all URBs get completed with an error. + */ + LogRelMax(10, ("USB/IP: Received reply with sequence number %u contains invalid length %zu (max %zu)\n", + pProxyDevUsbIp->BufRet.Hdr.u32SeqNum, cbRet, + pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData)); + rc = VERR_NET_PROTOCOL_ERROR; + } + } else { pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; @@ -1014,6 +1033,7 @@ LogRel(("USB/IP: Received reply with sequence number %u doesn't match any local URB\n", RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum))); usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + rc = VERR_NET_PROTOCOL_ERROR; } break; case USBIP_RET_UNLINK: @@ -1048,10 +1068,29 @@ for (unsigned i = 0; i < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts; i++) { PVUSBURBISOCPTK pIsocPkt = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->aIsocPkts[i]; - usbProxyUsbIpIsocPktDescN2H(&pProxyDevUsbIp->aIsocPktDesc[i]); - pIsocPkt->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->aIsocPktDesc[i].i32Status); - pIsocPkt->off = pProxyDevUsbIp->aIsocPktDesc[i].u32Offset; - pIsocPkt->cb = pProxyDevUsbIp->aIsocPktDesc[i].u32ActualLength; + PUsbIpIsocPktDesc pIsocPktUsbIp = &pProxyDevUsbIp->aIsocPktDesc[i]; + + usbProxyUsbIpIsocPktDescN2H(pIsocPktUsbIp); + pIsocPkt->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pIsocPktUsbIp->i32Status); + + if (RT_LIKELY( pIsocPktUsbIp->u32Offset < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData + && pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData - pIsocPktUsbIp->u32Offset >= pIsocPktUsbIp->u32ActualLength)) + { + pIsocPkt->off = pIsocPktUsbIp->u32Offset; + pIsocPkt->cb = pIsocPktUsbIp->u32ActualLength; + } + else + { + /* + * The offset and length value in the isoc packet descriptor are bogus and would cause a buffer overflow later on, leave an + * error message and disconnect from the rogue remote end. + */ + LogRelMax(10, ("USB/IP: Received reply with sequence number %u contains invalid isoc packet descriptor %u (offset=%u length=%u)\n", + pProxyDevUsbIp->BufRet.Hdr.u32SeqNum, i, + pIsocPktUsbIp->u32Offset, pIsocPktUsbIp->u32ActualLength)); + rc = VERR_NET_PROTOCOL_ERROR; + break; + } } pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; @@ -1062,18 +1101,29 @@ } } } + + if (RT_SUCCESS(rc)) + *ppUrbUsbIp = pUrbUsbIp; else { - /** @todo Complete all URBs with DNR error and mark device as unplugged. */ -#if 0 - pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; - pUrbUsbIp->pVUsbUrb->enmStatus = VUSBSTATUS_DNR; + /* Complete all URBs with DNR error and mark device as unplugged, the current one is still in the in flight list. */ + pProxyDevUsbIp->pUrbUsbIp = NULL; usbProxyUsbIpResetRecvState(pProxyDevUsbIp); -#endif - } + usbProxyUsbIpDisconnect(pProxyDevUsbIp); - if (RT_SUCCESS(rc)) - *ppUrbUsbIp = pUrbUsbIp; + rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + PUSBPROXYURBUSBIP pIt; + PUSBPROXYURBUSBIP pItNext; + RTListForEachSafe(&pProxyDevUsbIp->ListUrbsInFlight, pIt, pItNext, USBPROXYURBUSBIP, NodeList) + { + if (pIt->pVUsbUrb) /* can be NULL for requests created by usbProxyUsbIpCtrlUrbExchangeSync(). */ + pIt->pVUsbUrb->enmStatus = VUSBSTATUS_CRC; + RTListNodeRemove(&pIt->NodeList); + RTListAppend(&pProxyDevUsbIp->ListUrbsLanded, &pIt->NodeList); + } + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + } return rc; } @@ -1170,7 +1220,6 @@ } break; default: - usbProxyUsbIpUrbFree(pProxyDevUsbIp, pUrbUsbIp); return VERR_INVALID_PARAMETER; /** @todo better status code. */ } @@ -1215,8 +1264,9 @@ rc = usbProxyUsbIpUrbQueueWorker(pProxyDevUsbIp, pIter); if (RT_FAILURE(rc)) { - /** @todo Complete the URB with an error. */ - usbProxyUsbIpUrbFree(pProxyDevUsbIp, pIter); + /* Complete URB with an error and place into landed list. */ + pIter->pVUsbUrb->enmStatus = VUSBSTATUS_DNR; + usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsLanded, pIter); } } @@ -1474,14 +1524,12 @@ LogFlowFunc(("pProxyDev = %p\n", pProxyDev)); PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + if (pDevUsbIp->hSocket != NIL_RTSOCKET) + usbProxyUsbIpDisconnect(pDevUsbIp); + /* Destroy the pipe and pollset if necessary. */ if (pDevUsbIp->hPollSet != NIL_RTPOLLSET) { - if (pDevUsbIp->hSocket != NIL_RTSOCKET) - { - rc = RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_SOCKET); - Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND); - } rc = RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE); AssertRC(rc); rc = RTPollSetDestroy(pDevUsbIp->hPollSet); @@ -1492,8 +1540,6 @@ AssertRC(rc); } - if (pDevUsbIp->hSocket != NIL_RTSOCKET) - usbProxyUsbIpDisconnect(pDevUsbIp); if (pDevUsbIp->pszHost) RTStrFree(pDevUsbIp->pszHost); if (pDevUsbIp->pszBusId) @@ -1761,4 +1807,3 @@ usbProxyUsbIpWakeup, 0 }; - diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/USBProxyDevice.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/USBProxyDevice.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/USBProxyDevice.cpp 2020-10-16 16:36:14.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/USBProxyDevice.cpp 2022-09-01 13:26:59.000000000 +0000 @@ -1206,9 +1206,12 @@ { Log(("usb-proxy: pProxyDev=%s configuration %d with bmAttr=%02X\n", pUsbIns->pszName, paCfgs[iCfg].Core.bmAttributes, iCfg)); + if (paCfgs[iCfg].Core.bmAttributes & RT_BIT(5)) + { paCfgs[iCfg].Core.bmAttributes = paCfgs[iCfg].Core.bmAttributes & ~RT_BIT(5); /* Remote wakeup. */ - fEdited = true; - LogRel(("VUSB: Disabled '%s' remote wakeup for configuration %d\n", pUsbIns->pszName, iCfg)); + fEdited = true; + LogRel(("VUSB: Disabled '%s' remote wakeup for configuration %d\n", pUsbIns->pszName, iCfg)); + } } } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/VUSBDevice.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/VUSBDevice.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/VUSBDevice.cpp 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/VUSBDevice.cpp 2022-09-01 13:27:00.000000000 +0000 @@ -308,11 +308,11 @@ vusbDevSetState(pDev, VUSB_DEVICE_STATE_CONFIGURED); if (pDev->pUsbIns->pReg->pfnUsbSetConfiguration) { - RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices); + RTCritSectEnter(&pDev->pHub->CritSectDevices); int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetConfiguration, 5, pDev->pUsbIns, pNewCfgDesc->Core.bConfigurationValue, pDev->pCurCfgDesc, pDev->paIfStates, pNewCfgDesc); - RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices); + RTCritSectLeave(&pDev->pHub->CritSectDevices); if (RT_FAILURE(rc)) { Log(("vusb: error: %s: failed to set config %i (%Rrc) !!!\n", pDev->pUsbIns->pszName, iCfg, rc)); @@ -458,9 +458,9 @@ if (pDev->pUsbIns->pReg->pfnUsbSetInterface) { - RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices); + RTCritSectEnter(&pDev->pHub->CritSectDevices); int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetInterface, 3, pDev->pUsbIns, iIf, iAlt); - RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices); + RTCritSectLeave(&pDev->pHub->CritSectDevices); if (RT_FAILURE(rc)) { LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u (%Rrc)\n", pDev->pUsbIns->pszName, iIf, iAlt, rc)); @@ -532,10 +532,10 @@ && pSetup->wValue == 0 /* ENDPOINT_HALT */ && pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint) { - RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices); + RTCritSectEnter(&pDev->pHub->CritSectDevices); int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint, 2, pDev->pUsbIns, pSetup->wIndex); - RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices); + RTCritSectLeave(&pDev->pHub->CritSectDevices); return RT_SUCCESS(rc); } break; @@ -927,57 +927,6 @@ /** - * Add a device to the address hash - */ -static void vusbDevAddressHash(PVUSBDEV pDev) -{ - if (pDev->u8Address == VUSB_INVALID_ADDRESS) - return; - uint8_t u8Hash = vusbHashAddress(pDev->u8Address); - pDev->pNextHash = pDev->pHub->pRootHub->apAddrHash[u8Hash]; - pDev->pHub->pRootHub->apAddrHash[u8Hash] = pDev; -} - -/** - * Remove a device from the address hash - */ -static void vusbDevAddressUnHash(PVUSBDEV pDev) -{ - if (pDev->u8Address == VUSB_INVALID_ADDRESS) - return; - - uint8_t u8Hash = vusbHashAddress(pDev->u8Address); - pDev->u8Address = VUSB_INVALID_ADDRESS; - pDev->u8NewAddress = VUSB_INVALID_ADDRESS; - - RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices); - PVUSBDEV pCur = pDev->pHub->pRootHub->apAddrHash[u8Hash]; - if (pCur == pDev) - { - /* special case, we're at the head */ - pDev->pHub->pRootHub->apAddrHash[u8Hash] = pDev->pNextHash; - pDev->pNextHash = NULL; - } - else - { - /* search the list */ - PVUSBDEV pPrev; - for (pPrev = pCur, pCur = pCur->pNextHash; - pCur; - pPrev = pCur, pCur = pCur->pNextHash) - { - if (pCur == pDev) - { - pPrev->pNextHash = pCur->pNextHash; - pDev->pNextHash = NULL; - break; - } - } - } - RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices); -} - -/** * Sets the address of a device. * * Called by status_completion() and vusbDevResetWorker(). @@ -1010,30 +959,43 @@ if (pDev->u8Address == u8Address) return; + /** @todo The following logic belongs to the roothub and should actually be in that file. */ PVUSBROOTHUB pRh = vusbDevGetRh(pDev); AssertPtrReturnVoid(pRh); - if (pDev->u8Address == VUSB_DEFAULT_ADDRESS) - pRh->pDefaultAddress = NULL; - vusbDevAddressUnHash(pDev); + RTCritSectEnter(&pRh->CritSectDevices); + + /* Remove the device from the current address. */ + if (pDev->u8Address != VUSB_INVALID_ADDRESS) + { + Assert(pRh->apDevByAddr[pDev->u8Address] == pDev); + pRh->apDevByAddr[pDev->u8Address] = NULL; + } if (u8Address == VUSB_DEFAULT_ADDRESS) { - if (pRh->pDefaultAddress != NULL) + PVUSBDEV pDevDef = pRh->apDevByAddr[VUSB_DEFAULT_ADDRESS]; + + if (pDevDef) { - vusbDevAddressUnHash(pRh->pDefaultAddress); - vusbDevSetStateCmp(pRh->pDefaultAddress, VUSB_DEVICE_STATE_POWERED, VUSB_DEVICE_STATE_DEFAULT); + pDevDef->u8Address = VUSB_INVALID_ADDRESS; + pDevDef->u8NewAddress = VUSB_INVALID_ADDRESS; + vusbDevSetStateCmp(pDevDef, VUSB_DEVICE_STATE_POWERED, VUSB_DEVICE_STATE_DEFAULT); Log(("2 DEFAULT ADDRS\n")); } - pRh->pDefaultAddress = pDev; + pRh->apDevByAddr[VUSB_DEFAULT_ADDRESS] = pDev; vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT); } else + { + Assert(!pRh->apDevByAddr[u8Address]); + pRh->apDevByAddr[u8Address] = pDev; vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS); + } pDev->u8Address = u8Address; - vusbDevAddressHash(pDev); + RTCritSectLeave(&pRh->CritSectDevices); Log(("vusb: %p[%s]/%i: Assigned address %u\n", pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address)); @@ -1217,9 +1179,9 @@ * * @returns VBox status code. * @param pDev The device to attach. - * @param pHub THe hub to attach to. + * @param pHub The roothub to attach to. */ -int vusbDevAttach(PVUSBDEV pDev, PVUSBHUB pHub) +int vusbDevAttach(PVUSBDEV pDev, PVUSBROOTHUB pHub) { AssertMsg(pDev->enmState == VUSB_DEVICE_STATE_DETACHED, ("enmState=%d\n", pDev->enmState)); @@ -1232,9 +1194,6 @@ /* Create I/O thread and attach to the hub. */ int rc = vusbDevUrbIoThreadCreate(pDev); - if (RT_SUCCESS(rc)) - rc = pHub->pOps->pfnAttach(pHub, pDev); - if (RT_FAILURE(rc)) { pDev->pHub = NULL; @@ -1259,18 +1218,6 @@ VUSBDEV_ASSERT_VALID_STATE(pDev->enmState); Assert(pDev->enmState != VUSB_DEVICE_STATE_RESET); - vusbDevCancelAllUrbs(pDev, true); - vusbDevAddressUnHash(pDev); - - PVUSBROOTHUB pRh = vusbDevGetRh(pDev); - if (!pRh) - AssertMsgFailedReturn(("Not attached!\n"), VERR_VUSB_DEVICE_NOT_ATTACHED); - if (pRh->pDefaultAddress == pDev) - pRh->pDefaultAddress = NULL; - - pDev->pHub->pOps->pfnDetach(pDev->pHub, pDev); - pDev->i16Port = -1; - /* * Destroy I/O thread and request queue last because they might still be used * when cancelling URBs. @@ -1361,10 +1308,9 @@ vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT); pDev->u16Status = 0; vusbDevDoSelectConfig(pDev, &g_Config0); - if (!vusbDevIsRh(pDev)) - vusbDevSetAddress(pDev, VUSB_DEFAULT_ADDRESS); + vusbDevSetAddress(pDev, VUSB_DEFAULT_ADDRESS); if (pfnDone) - pfnDone(&pDev->IDevice, rc, pvUser); + pfnDone(&pDev->IDevice, pDev->i16Port, rc, pvUser); } @@ -1573,16 +1519,6 @@ return VERR_VUSB_DEVICE_IS_RESETTING; } - /* - * If it's a root hub, we will have to cancel all URBs and reap them. - */ - if (vusbDevIsRh(pDev)) - { - PVUSBROOTHUB pRh = (PVUSBROOTHUB)pDev; - VUSBIRhCancelAllUrbs(&pRh->IRhConnector); - VUSBIRhReapAsyncUrbs(&pRh->IRhConnector, pInterface, 0); - } - vusbDevSetState(pDev, VUSB_DEVICE_STATE_ATTACHED); return VINF_SUCCESS; } @@ -1775,8 +1711,6 @@ pDev->IDevice.pfnIsSavedStateSupported = vusbIDeviceIsSavedStateSupported; pDev->IDevice.pfnGetSpeed = vusbIDeviceGetSpeed; pDev->pUsbIns = pUsbIns; - pDev->pNext = NULL; - pDev->pNextHash = NULL; pDev->pHub = NULL; pDev->enmState = VUSB_DEVICE_STATE_DETACHED; pDev->cRefs = 1; @@ -1811,8 +1745,16 @@ /* * Create the reset timer. */ + static const char * const s_apszNamesHack[] = + { + "USB Device Reset Timer 0", "USB Device Reset Timer 1", "USB Device Reset Timer 2", "USB Device Reset Timer 3", + "USB Device Reset Timer 4", "USB Device Reset Timer 5", "USB Device Reset Timer 6", "USB Device Reset Timer 7", + "USB Device Reset Timer 8", "USB Device Reset Timer 9", "USB Device Reset Timer 10", "USB Device Reset Timer 11", + "USB Device Reset Timer 12", "USB Device Reset Timer 13", "USB Device Reset Timer 14", "USB Device Reset Timer 15", + }; + static uint32_t volatile s_idxName = 0; rc = PDMUsbHlpTMTimerCreate(pDev->pUsbIns, TMCLOCK_VIRTUAL, vusbDevResetDoneTimer, pDev, 0 /*fFlags*/, - "USB Device Reset Timer", &pDev->pResetTimer); + s_apszNamesHack[s_idxName++ % RT_ELEMENTS(s_apszNamesHack)], &pDev->pResetTimer); AssertRCReturn(rc, rc); if (pszCaptureFilename) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/VUSBInternal.h virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/VUSBInternal.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/VUSBInternal.h 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/VUSBInternal.h 2022-09-01 13:27:00.000000000 +0000 @@ -3,7 +3,7 @@ * Virtual USB - Internal header. * * This subsystem implements USB devices in a host controller independent - * way. All the host controller code has to do is use VUSBHUB for its + * way. All the host controller code has to do is use VUSBROOTHUB for its * root hub implementation and any emulated USB device may be plugged into * the virtual bus. */ @@ -56,8 +56,6 @@ /** Pointer to a Virtual USB device (core). */ typedef struct VUSBDEV *PVUSBDEV; -/** Pointer to a VUSB hub device. */ -typedef struct VUSBHUB *PVUSBHUB; /** Pointer to a VUSB root hub. */ typedef struct VUSBROOTHUB *PVUSBROOTHUB; @@ -219,19 +217,15 @@ typedef struct VUSBDEV { /** The device interface exposed to the HCI. */ - VUSBIDEVICE IDevice; + VUSBIDEVICE IDevice; /** Pointer to the PDM USB device instance. */ - PPDMUSBINS pUsbIns; - /** Next device in the chain maintained by the roothub. */ - PVUSBDEV pNext; - /** Pointer to the next device with the same address hash. */ - PVUSBDEV pNextHash; - /** Pointer to the hub this device is attached to. */ - PVUSBHUB pHub; + PPDMUSBINS pUsbIns; + /** Pointer to the roothub this device is attached to. */ + PVUSBROOTHUB pHub; /** The device state. */ - VUSBDEVICESTATE volatile enmState; + VUSBDEVICESTATE volatile enmState; /** Reference counter to protect the device structure from going away. */ - uint32_t volatile cRefs; + uint32_t volatile cRefs; /** The device address. */ uint8_t u8Address; @@ -294,16 +288,10 @@ int vusbDevInit(PVUSBDEV pDev, PPDMUSBINS pUsbIns, const char *pszCaptureFilename); void vusbDevDestroy(PVUSBDEV pDev); - -DECLINLINE(bool) vusbDevIsRh(PVUSBDEV pDev) -{ - return (pDev->pHub == (PVUSBHUB)pDev); -} - bool vusbDevDoSelectConfig(PVUSBDEV dev, PCVUSBDESCCONFIGEX pCfg); void vusbDevMapEndpoint(PVUSBDEV dev, PCVUSBDESCENDPOINTEX ep); int vusbDevDetach(PVUSBDEV pDev); -int vusbDevAttach(PVUSBDEV pDev, PVUSBHUB pHub); +int vusbDevAttach(PVUSBDEV pDev, PVUSBROOTHUB pHub); DECLINLINE(PVUSBROOTHUB) vusbDevGetRh(PVUSBDEV pDev); size_t vusbDevMaxInterfaces(PVUSBDEV dev); @@ -319,34 +307,6 @@ */ -/** Virtual method table for USB hub devices. - * Hub and roothub drivers need to implement these functions in addition to the - * vusb_dev_ops. - */ -typedef struct VUSBHUBOPS -{ - int (*pfnAttach)(PVUSBHUB pHub, PVUSBDEV pDev); - void (*pfnDetach)(PVUSBHUB pHub, PVUSBDEV pDev); -} VUSBHUBOPS; -/** Pointer to a const HUB method table. */ -typedef const VUSBHUBOPS *PCVUSBHUBOPS; - -/** A VUSB Hub Device - Hub and roothub drivers need to use this struct - * @todo eliminate this (PDM / roothubs only). - */ -typedef struct VUSBHUB -{ - VUSBDEV Dev; - PCVUSBHUBOPS pOps; - PVUSBROOTHUB pRootHub; - uint16_t cPorts; - uint16_t cDevices; - /** Name of the hub. Used for logging. */ - char *pszName; -} VUSBHUB; -AssertCompileMemberAlignment(VUSBHUB, pOps, 8); -AssertCompileSizeAlignment(VUSBHUB, 8); - /** @} */ @@ -374,8 +334,8 @@ -/** The address hash table size. */ -#define VUSB_ADDR_HASHSZ 5 +/** Pointer to a VUSBROOTHUBLOAD struct. */ +typedef struct VUSBROOTHUBLOAD *PVUSBROOTHUBLOAD; /** * The instance data of a root hub driver. @@ -386,25 +346,32 @@ */ typedef struct VUSBROOTHUB { - /** The HUB. - * @todo remove this? */ - VUSBHUB Hub; - /** Address hash table. */ - PVUSBDEV apAddrHash[VUSB_ADDR_HASHSZ]; - /** The default address. */ - PVUSBDEV pDefaultAddress; - /** Pointer to the driver instance. */ - PPDMDRVINS pDrvIns; + PPDMDRVINS pDrvIns; /** Pointer to the root hub port interface we're attached to. */ - PVUSBIROOTHUBPORT pIRhPort; + PVUSBIROOTHUBPORT pIRhPort; /** Connector interface exposed upwards. */ - VUSBIROOTHUBCONNECTOR IRhConnector; + VUSBIROOTHUBCONNECTOR IRhConnector; - /** Critical section protecting the device list. */ - RTCRITSECT CritSectDevices; - /** Chain of devices attached to this hub. */ - PVUSBDEV pDevices; + /** Critical section protecting the device arrays. */ + RTCRITSECT CritSectDevices; + /** Array of pointers to USB devices indexed by the port the device is on. */ + PVUSBDEV apDevByPort[VUSB_DEVICES_MAX]; + /** Array of pointers to USB devices indexed by the address assigned. */ + PVUSBDEV apDevByAddr[VUSB_DEVICES_MAX]; + /** Structure after a saved state load to re-attach devices. */ + PVUSBROOTHUBLOAD pLoad; + + /** Roothub device state. */ + VUSBDEVICESTATE enmState; + /** Number of ports this roothub offers. */ + uint16_t cPorts; + /** Number of devices attached to this roothub currently. */ + uint16_t cDevices; + /** Name of the roothub. Used for logging. */ + char *pszName; + /** URB pool for URBs from the roothub. */ + VUSBURBPOOL UrbPool; #if HC_ARCH_BITS == 32 uint32_t Alignment0; @@ -514,9 +481,9 @@ void vusbUrbCancel(PVUSBURB pUrb, CANCELMODE mode); void vusbUrbCancelAsync(PVUSBURB pUrb, CANCELMODE mode); void vusbUrbRipe(PVUSBURB pUrb); -void vusbUrbCompletionRh(PVUSBURB pUrb); +void vusbUrbCompletionRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb); int vusbUrbSubmitHardError(PVUSBURB pUrb); -int vusbUrbErrorRh(PVUSBURB pUrb); +int vusbUrbErrorRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb); int vusbDevUrbIoThreadWakeup(PVUSBDEV pDev); int vusbDevUrbIoThreadCreate(PVUSBDEV pDev); int vusbDevUrbIoThreadDestroy(PVUSBDEV pDev); @@ -607,6 +574,26 @@ RTCritSectLeave(&pDev->CritSectAsyncUrbs); } + +DECLINLINE(int) vusbUrbErrorRh(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + AssertPtrReturn(pRh, VERR_VUSB_DEVICE_NOT_ATTACHED); + + return vusbUrbErrorRhEx(pRh, pUrb); +} + + +DECLINLINE(void) vusbUrbCompletionRh(PVUSBURB pUrb) +{ + PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev); + AssertPtrReturnVoid(pRh); + + vusbUrbCompletionRhEx(pRh, pUrb); +} + + /** @def vusbUrbAssert * Asserts that a URB is valid. */ @@ -634,21 +621,6 @@ /** @} */ - - -/** - * Addresses are between 0 and 127 inclusive - */ -DECLINLINE(uint8_t) vusbHashAddress(uint8_t Address) -{ - uint8_t u8Hash = Address; - u8Hash ^= (Address >> 2); - u8Hash ^= (Address >> 3); - u8Hash %= VUSB_ADDR_HASHSZ; - return u8Hash; -} - - /** * Gets the roothub of a device. * @@ -660,7 +632,7 @@ { if (!pDev->pHub) return NULL; - return pDev->pHub->pRootHub; + return pDev->pHub; } @@ -715,12 +687,14 @@ * * @returns New reference count. * @param pThis The VUSB device pointer. + * @param pszWho Caller of the retaining. */ -DECLINLINE(uint32_t) vusbDevRetain(PVUSBDEV pThis) +DECLINLINE(uint32_t) vusbDevRetain(PVUSBDEV pThis, const char *pszWho) { AssertPtrReturn(pThis, UINT32_MAX); uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + LogFlowFunc(("pThis=%p{.cRefs=%u}[%s]\n", pThis, cRefs, pszWho)); RT_NOREF(pszWho); AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); return cRefs; } @@ -730,12 +704,15 @@ * * @returns New reference count. * @retval 0 if no onw is holding a reference anymore causing the device to be destroyed. + * @param pThis The VUSB device pointer. + * @param pszWho Caller of the retaining. */ -DECLINLINE(uint32_t) vusbDevRelease(PVUSBDEV pThis) +DECLINLINE(uint32_t) vusbDevRelease(PVUSBDEV pThis, const char *pszWho) { AssertPtrReturn(pThis, UINT32_MAX); uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + LogFlowFunc(("pThis=%p{.cRefs=%u}[%s]\n", pThis, cRefs, pszWho)); RT_NOREF(pszWho); AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); if (cRefs == 0) vusbDevDestroy(pThis); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/VUSBUrb.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/VUSBUrb.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/VUSBUrb.cpp 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/VUSBUrb.cpp 2022-09-01 13:27:00.000000000 +0000 @@ -215,23 +215,24 @@ * * @returns true if it could be retried. * @returns false if it should be completed with failure. + * @param pRh The roothub the URB originated from. * @param pUrb The URB in question. */ -int vusbUrbErrorRh(PVUSBURB pUrb) +int vusbUrbErrorRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb) { PVUSBDEV pDev = pUrb->pVUsb->pDev; - PVUSBROOTHUB pRh = vusbDevGetRh(pDev); - AssertPtrReturn(pRh, VERR_VUSB_DEVICE_NOT_ATTACHED); LogFlow(("%s: vusbUrbErrorRh: pDev=%p[%s] rh=%p\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "", pRh)); + RT_NOREF(pDev); return pRh->pIRhPort->pfnXferError(pRh->pIRhPort, pUrb); } /** * Does URB completion on roothub level. * + * @param pRh The roothub the URB originated from. * @param pUrb The URB to complete. */ -void vusbUrbCompletionRh(PVUSBURB pUrb) +void vusbUrbCompletionRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb) { LogFlow(("%s: vusbUrbCompletionRh: type=%s status=%s\n", pUrb->pszDesc, vusbUrbTypeName(pUrb->enmType), vusbUrbStatusName(pUrb->enmStatus))); @@ -249,9 +250,6 @@ LogRel(("VUSB: Capturing URB completion event failed with %Rrc\n", rc)); } - PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev); - AssertPtrReturnVoid(pRh); - /* If there is a sniffer on the roothub record the completed URB there too. */ if (pRh->hSniffer != VUSBSNIFFER_NIL) { @@ -303,10 +301,12 @@ { case VUSBSTATUS_OK: if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok); - else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok0); break; + else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok0); + break; case VUSBSTATUS_DATA_UNDERRUN: if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun); - else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun0); break; + else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun0); + break; case VUSBSTATUS_DATA_OVERRUN: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataOverrun); break; case VUSBSTATUS_NOT_ACCESSED: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].NotAccessed); break; default: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Misc); break; @@ -362,7 +362,7 @@ #endif case VUSBXFERTYPE_BULK: if (pUrb->enmStatus != VUSBSTATUS_OK) - vusbUrbErrorRh(pUrb); + vusbUrbErrorRhEx(pRh, pUrb); break; } #ifdef LOG_ENABLED @@ -947,12 +947,14 @@ * will be no status stage. */ uint8_t *pbData = (uint8_t *)(pExtra->pMsg + 1); - if (&pExtra->pbCur[pUrb->cbData] > &pbData[pSetup->wLength]) + if ((uintptr_t)&pExtra->pbCur[pUrb->cbData] > (uintptr_t)&pbData[pSetup->wLength]) { + /** @todo r=bird: This code stinks, esp. the iPhone carp. @bugref{9899} */ if (!pSetup->wLength) /* happens during iPhone detection with iTunes (correct?) */ { - Log(("%s: vusbUrbSubmitCtrl: pSetup->wLength == 0!! (iPhone)\n", pUrb->pszDesc)); - pSetup->wLength = pUrb->cbData; + Log(("%s: vusbUrbSubmitCtrl: pSetup->wLength == 0!! Changing it to RT_MIN(cbData=%u, cbMax=%u) (iPhone hack)\n", + pUrb->pszDesc, pUrb->cbData, pExtra->cbMax)); + pSetup->wLength = RT_MIN(pUrb->cbData, pExtra->cbMax); /** @todo resize? */ } /* Variable length data transfers */ @@ -960,10 +962,9 @@ || pSetup->wLength == 0 || (pUrb->cbData % pSetup->wLength) == 0) /* magic which need explaining... */ { - uint8_t *pbEnd = pbData + pSetup->wLength; - int cbLeft = pbEnd - pExtra->pbCur; + ssize_t const cbLeft = &pbData[pSetup->wLength] - pExtra->pbCur; LogFlow(("%s: vusbUrbSubmitCtrl: Var DATA, pUrb->cbData %d -> %d\n", pUrb->pszDesc, pUrb->cbData, cbLeft)); - pUrb->cbData = cbLeft; + pUrb->cbData = cbLeft >= 0 ? (uint32_t)cbLeft : 0; } else { @@ -995,6 +996,8 @@ else { /* get data for sending when completed. */ + AssertStmt((ssize_t)pUrb->cbData <= pExtra->cbMax - (pExtra->pbCur - pbData), /* paranoia: checked above */ + pUrb->cbData = pExtra->cbMax - (uint32_t)RT_MIN(pExtra->pbCur - pbData, pExtra->cbMax)); memcpy(pExtra->pbCur, pUrb->abData, pUrb->cbData); /* advance */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/VUSBUrbTrace.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/VUSBUrbTrace.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/USB/VUSBUrbTrace.cpp 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/USB/VUSBUrbTrace.cpp 2022-09-01 13:27:00.000000000 +0000 @@ -146,7 +146,7 @@ DECLHIDDEN(void) vusbUrbTrace(PVUSBURB pUrb, const char *pszMsg, bool fComplete) { PVUSBDEV pDev = pUrb->pVUsb ? pUrb->pVUsb->pDev : NULL; /* Can be NULL when called from usbProxyConstruct and friends. */ - PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBPIPE pPipe = pDev ? &pDev->aPipes[pUrb->EndPt] : NULL; const uint8_t *pbData = pUrb->abData; uint32_t cbData = pUrb->cbData; PCVUSBSETUP pSetup = NULL; @@ -159,7 +159,7 @@ Log(("%s: %*s: pDev=%p[%s] rc=%s a=%i e=%u d=%s t=%s cb=%#x(%d) ts=%RU64 (%RU64 ns ago) %s\n", pUrb->pszDesc, s_cchMaxMsg, pszMsg, pDev, - pUrb->pVUsb && pUrb->pVUsb->pDev ? pUrb->pVUsb->pDev->pUsbIns->pszName : "", + pUrb->pVUsb && pUrb->pVUsb->pDev && pUrb->pVUsb->pDev->pUsbIns ? pUrb->pVUsb->pDev->pUsbIns->pszName : "", vusbUrbStatusName(pUrb->enmStatus), pDev ? pDev->u8Address : -1, pUrb->EndPt, diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/VirtIO/VirtioCore.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/VirtIO/VirtioCore.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/VirtIO/VirtioCore.cpp 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/VirtIO/VirtioCore.cpp 2022-09-01 13:27:00.000000000 +0000 @@ -100,6 +100,7 @@ { uint16_t fFlags; /**< flags avail ring guest-to-host flags */ uint16_t uIdx; /**< idx Index of next free ring slot */ + RT_FLEXIBLE_ARRAY_EXTENSION uint16_t auRing[RT_FLEXIBLE_ARRAY]; /**< ring Ring: avail drv to dev bufs */ //uint16_t uUsedEventIdx; /**< used_event (if VIRTQ_USED_F_EVENT_IDX) */ } VIRTQ_AVAIL_T, *PVIRTQ_AVAIL_T; @@ -114,6 +115,7 @@ { uint16_t fFlags; /**< flags used ring host-to-guest flags */ uint16_t uIdx; /**< idx Index of next ring slot */ + RT_FLEXIBLE_ARRAY_EXTENSION VIRTQ_USED_ELEM_T aRing[RT_FLEXIBLE_ARRAY]; /**< ring Ring: used dev to drv bufs */ //uint16_t uAvailEventIdx; /**< avail_event if (VIRTQ_USED_F_EVENT_IDX) */ } VIRTQ_USED_T, *PVIRTQ_USED_T; @@ -315,7 +317,7 @@ PVIRTQUEUE pVirtq = &pVirtio->aVirtqueues[uVirtq]; if (!IS_DRIVER_OK(pVirtio) || !pVirtq->uEnable) { - LogRelFunc(("Driver not ready or queue not enabled\n")); + LogRelFunc(("Driver not ready or queue %s not enabled\n", VIRTQNAME(pVirtio, uVirtq))); return 0; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/VirtIO/VirtioCore.h virtualbox-6.1.38-dfsg/src/VBox/Devices/VirtIO/VirtioCore.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/VirtIO/VirtioCore.h 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/VirtIO/VirtioCore.h 2022-09-01 13:27:00.000000000 +0000 @@ -361,11 +361,11 @@ R3PTRTYPE(PVIRTIO_PCI_CAP_T) pIsrCap; /**< Pointer to struct in PCI config area. */ R3PTRTYPE(PVIRTIO_PCI_CAP_T) pDeviceCap; /**< Pointer to struct in PCI config area. */ - uint32_t cbDevSpecificCfg; /**< Size of client's dev-specific config data */ - R3PTRTYPE(uint8_t *) pbDevSpecificCfg; /**< Pointer to client's struct */ - R3PTRTYPE(uint8_t *) pbPrevDevSpecificCfg; /**< Previous read dev-specific cfg of client */ - bool fGenUpdatePending; /**< If set, update cfg gen after driver reads */ - char pcszMmioName[MAX_NAME]; /**< MMIO mapping name */ + uint32_t cbDevSpecificCfg; /**< Size of client's dev-specific config data */ + R3PTRTYPE(uint8_t *) pbDevSpecificCfg; /**< Pointer to client's struct */ + R3PTRTYPE(uint8_t *) pbPrevDevSpecificCfg; /**< Previous read dev-specific cfg of client */ + bool fGenUpdatePending; /**< If set, update cfg gen after driver reads */ + char pcszMmioName[MAX_NAME]; /**< MMIO mapping name */ } VIRTIOCORER3; /** diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/VMMDev/VMMDev.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/VMMDev/VMMDev.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/VMMDev/VMMDev.cpp 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/VMMDev/VMMDev.cpp 2022-09-01 13:27:00.000000000 +0000 @@ -101,6 +101,7 @@ #include #include #include +#include #include #ifndef IN_RC # include @@ -185,11 +186,13 @@ case VBOXOSTYPE_Win10: pszOs = "Windows 10"; break; case VBOXOSTYPE_Win2k16_x64 & ~VBOXOSTYPE_x64: pszOs = "Windows 2k16"; break; case VBOXOSTYPE_Win2k19_x64 & ~VBOXOSTYPE_x64: pszOs = "Windows 2k19"; break; + case VBOXOSTYPE_Win11_x64 & ~VBOXOSTYPE_x64: pszOs = "Windows 11"; break; case VBOXOSTYPE_OS2: pszOs = "OS/2"; break; case VBOXOSTYPE_OS2Warp3: pszOs = "OS/2 Warp 3"; break; case VBOXOSTYPE_OS2Warp4: pszOs = "OS/2 Warp 4"; break; case VBOXOSTYPE_OS2Warp45: pszOs = "OS/2 Warp 4.5"; break; case VBOXOSTYPE_ECS: pszOs = "OS/2 ECS"; break; + case VBOXOSTYPE_ArcaOS: pszOs = "OS/2 ArcaOS"; break; case VBOXOSTYPE_OS21x: pszOs = "OS/2 2.1x"; break; case VBOXOSTYPE_Linux: pszOs = "Linux"; break; case VBOXOSTYPE_Linux22: pszOs = "Linux 2.2"; break; @@ -211,6 +214,7 @@ case VBOXOSTYPE_NetBSD: pszOs = "NetBSD"; break; case VBOXOSTYPE_Netware: pszOs = "Netware"; break; case VBOXOSTYPE_Solaris: pszOs = "Solaris"; break; + case VBOXOSTYPE_Solaris10U8_or_later: pszOs = "Solaris 10"; break; case VBOXOSTYPE_OpenSolaris: pszOs = "OpenSolaris"; break; case VBOXOSTYPE_Solaris11_x64 & ~VBOXOSTYPE_x64: pszOs = "Solaris 11"; break; case VBOXOSTYPE_MacOS: pszOs = "Mac OS X"; break; @@ -223,6 +227,7 @@ case VBOXOSTYPE_MacOS1012_x64 & ~VBOXOSTYPE_x64: pszOs = "macOS 10.12"; break; case VBOXOSTYPE_MacOS1013_x64 & ~VBOXOSTYPE_x64: pszOs = "macOS 10.13"; break; case VBOXOSTYPE_Haiku: pszOs = "Haiku"; break; + case VBOXOSTYPE_VBoxBS_x64 & ~VBOXOSTYPE_x64: pszOs = "VBox Bootsector"; break; default: pszOs = "unknown"; break; } LogRel(("VMMDev: Guest Additions information report: Interface = 0x%08X osType = 0x%08X (%s, %u-bit)\n", @@ -4410,7 +4415,7 @@ /* * Everything HGCM. */ - vmmdevR3HgcmDestroy(pDevIns, pThisCC); + vmmdevR3HgcmDestroy(pDevIns, PDMDEVINS_2_DATA(pDevIns, PVMMDEV), pThisCC); #endif /* @@ -4560,7 +4565,16 @@ "HeartbeatTimeout|" "TestingEnabled|" "TestingMMIO|" - "TestintXmlOutputFile" + "TestintXmlOutputFile|" + "HGCMHeapBudgetDefault|" + "HGCMHeapBudgetLegacy|" + "HGCMHeapBudgetVBoxGuest|" + "HGCMHeapBudgetOtherDrv|" + "HGCMHeapBudgetRoot|" + "HGCMHeapBudgetSystem|" + "HGCMHeapBudgetReserved1|" + "HGCMHeapBudgetUser|" + "HGCMHeapBudgetGuest" , ""); @@ -4639,6 +4653,70 @@ /** @todo image-to-load-filename? */ #endif +#ifdef VBOX_WITH_HGCM + /* + * Heap budgets for HGCM requestor categories. Take the available host + * memory as a rough hint of how much we can handle. + */ + /** @todo If we reduced the number of categories here, we could alot more to + * each... */ + uint64_t cbDefaultBudget = 0; + if (RT_FAILURE(RTSystemQueryTotalRam(&cbDefaultBudget))) + cbDefaultBudget = 16 * _1G64; + LogFunc(("RTSystemQueryTotalRam -> %'RU64 (%RX64)\n", cbDefaultBudget, cbDefaultBudget)); +# if ARCH_BITS == 32 + cbDefaultBudget = RT_MIN(cbDefaultBudget, _512M); +# endif + cbDefaultBudget /= 8; /* One eighth of physical memory ... */ + cbDefaultBudget /= RT_ELEMENTS(pThisCC->aHgcmAcc); /* over 8 accounting categories. (8GiB -> 64MiB) */ + cbDefaultBudget = RT_MIN(cbDefaultBudget, _512M); /* max 512MiB */ + cbDefaultBudget = RT_MAX(cbDefaultBudget, _32M); /* min 32MiB */ + rc = pHlp->pfnCFGMQueryU64Def(pCfg, "HGCMHeapBudgetDefault", &cbDefaultBudget, cbDefaultBudget); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed querying \"HGCMHeapBudgetDefault\" as a 64-bit unsigned integer")); + + LogRel(("VMMDev: cbDefaultBudget: %'RU64 (%RX64)\n", cbDefaultBudget, cbDefaultBudget)); + static const struct { const char *pszName; unsigned idx; } s_aCfgHeapBudget[] = + { + { "HGCMHeapBudgetLegacy", VMMDEV_REQUESTOR_USR_NOT_GIVEN }, + { "HGCMHeapBudgetVBoxGuest", VMMDEV_REQUESTOR_USR_DRV }, + { "HGCMHeapBudgetOtherDrv", VMMDEV_REQUESTOR_USR_DRV_OTHER }, + { "HGCMHeapBudgetRoot", VMMDEV_REQUESTOR_USR_ROOT }, + { "HGCMHeapBudgetSystem", VMMDEV_REQUESTOR_USR_SYSTEM }, + { "HGCMHeapBudgetReserved1", VMMDEV_REQUESTOR_USR_RESERVED1 }, + { "HGCMHeapBudgetUser", VMMDEV_REQUESTOR_USR_USER }, + { "HGCMHeapBudgetGuest", VMMDEV_REQUESTOR_USR_GUEST }, + }; + AssertCompile(RT_ELEMENTS(s_aCfgHeapBudget) == RT_ELEMENTS(pThisCC->aHgcmAcc)); + for (uintptr_t i = 0; i < RT_ELEMENTS(s_aCfgHeapBudget); i++) + { + uintptr_t const idx = s_aCfgHeapBudget[i].idx; + rc = pHlp->pfnCFGMQueryU64Def(pCfg, s_aCfgHeapBudget[i].pszName, + &pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig, cbDefaultBudget); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("Configuration error: Failed querying \"%s\" as a 64-bit unsigned integer"), + s_aCfgHeapBudget[i].pszName); + pThisCC->aHgcmAcc[idx].cbHeapBudget = pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig; + if (pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig != cbDefaultBudget) + LogRel(("VMMDev: %s: %'RU64 (%#RX64)\n", s_aCfgHeapBudget[i].pszName, + pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig, pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig)); + + const char * const pszCatName = &s_aCfgHeapBudget[i].pszName[sizeof("HGCMHeapBudget") - 1]; + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aHgcmAcc[idx].cbHeapBudget, STAMTYPE_U64, STAMVISIBILITY_ALWAYS, + STAMUNIT_BYTES, "Currently available budget", "HGCM-%s/BudgetAvailable", pszCatName); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig, STAMTYPE_U64, STAMVISIBILITY_ALWAYS, + STAMUNIT_BYTES, "Configured budget", "HGCM-%s/BudgetConfig", pszCatName); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aHgcmAcc[idx].cbHeapTotal, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, + STAMUNIT_BYTES, "Total heap usage", "HGCM-%s/cbHeapTotal", pszCatName); + PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aHgcmAcc[idx].cTotalMessages, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, + STAMUNIT_COUNT, "Total messages", "HGCM-%s/cTotalMessages", pszCatName); + } +#endif + + /* + * + */ pThis->cbGuestRAM = MMR3PhysGetRamSize(PDMDevHlpGetVM(pDevIns)); /* diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/VMMDev/VMMDevHGCM.cpp virtualbox-6.1.38-dfsg/src/VBox/Devices/VMMDev/VMMDevHGCM.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Devices/VMMDev/VMMDevHGCM.cpp 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/VMMDev/VMMDevHGCM.cpp 2022-09-01 13:27:00.000000000 +0000 @@ -181,6 +181,12 @@ /** The PGM lock for GCPhys if pvReqLocked is not NULL. */ PGMPAGEMAPLOCK ReqMapLock; + /** The accounting index (into VMMDEVR3::aHgcmAcc). */ + uint8_t idxHeapAcc; + uint8_t abPadding[3]; + /** The heap cost of this command. */ + uint32_t cbHeapCost; + /** The STAM_GET_TS() value when the request arrived. */ uint64_t tsArrival; /** The STAM_GET_TS() value when the hgcmR3Completed() is called. */ @@ -230,17 +236,24 @@ */ typedef struct VBOXHGCMCMDCACHED { - VBOXHGCMCMD Core; /**< 112 */ + VBOXHGCMCMD Core; /**< 120 */ VBOXHGCMGUESTPARM aGuestParms[6]; /**< 40 * 6 = 240 */ VBOXHGCMSVCPARM aHostParms[6]; /**< 24 * 6 = 144 */ -} VBOXHGCMCMDCACHED; /**< 112+240+144 = 496 */ -AssertCompile(sizeof(VBOXHGCMCMD) <= 112); +} VBOXHGCMCMDCACHED; /**< 120+240+144 = 504 */ +AssertCompile(sizeof(VBOXHGCMCMD) <= 120); AssertCompile(sizeof(VBOXHGCMGUESTPARM) <= 40); AssertCompile(sizeof(VBOXHGCMSVCPARM) <= 24); AssertCompile(sizeof(VBOXHGCMCMDCACHED) <= 512); AssertCompile(sizeof(VBOXHGCMCMDCACHED) > sizeof(VBOXHGCMCMD) + sizeof(HGCMServiceLocation)); +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +DECLINLINE(void *) vmmdevR3HgcmCallMemAllocZ(PVMMDEVCC pThisCC, PVBOXHGCMCMD pCmd, size_t cbRequested); + + + DECLINLINE(int) vmmdevR3HgcmCmdListLock(PVMMDEVCC pThisCC) { int rc = RTCritSectEnter(&pThisCC->critsectHGCMCmdList); @@ -267,6 +280,10 @@ static PVBOXHGCMCMD vmmdevR3HgcmCmdAlloc(PVMMDEVCC pThisCC, VBOXHGCMCMDTYPE enmCmdType, RTGCPHYS GCPhys, uint32_t cbRequest, uint32_t cParms, uint32_t fRequestor) { + /* For heap accounting. */ + uintptr_t const idxHeapAcc = fRequestor != VMMDEV_REQUESTOR_LEGACY + ? fRequestor & VMMDEV_REQUESTOR_USR_MASK + : VMMDEV_REQUESTOR_USR_NOT_GIVEN; #if 1 /* * Try use the cache. @@ -275,26 +292,41 @@ AssertCompile(sizeof(*pCmdCached) >= sizeof(VBOXHGCMCMD) + sizeof(HGCMServiceLocation)); if (cParms <= RT_ELEMENTS(pCmdCached->aGuestParms)) { - int rc = RTMemCacheAllocEx(pThisCC->hHgcmCmdCache, (void **)&pCmdCached); - if (RT_SUCCESS(rc)) + if (sizeof(*pCmdCached) <= pThisCC->aHgcmAcc[idxHeapAcc].cbHeapBudget) { - RT_ZERO(*pCmdCached); - pCmdCached->Core.fMemCache = true; - pCmdCached->Core.GCPhys = GCPhys; - pCmdCached->Core.cbRequest = cbRequest; - pCmdCached->Core.enmCmdType = enmCmdType; - pCmdCached->Core.fRequestor = fRequestor; - if (enmCmdType == VBOXHGCMCMDTYPE_CALL) + int rc = RTMemCacheAllocEx(pThisCC->hHgcmCmdCache, (void **)&pCmdCached); + if (RT_SUCCESS(rc)) { - pCmdCached->Core.u.call.cParms = cParms; - pCmdCached->Core.u.call.paGuestParms = pCmdCached->aGuestParms; - pCmdCached->Core.u.call.paHostParms = pCmdCached->aHostParms; - } - else if (enmCmdType == VBOXHGCMCMDTYPE_CONNECT) - pCmdCached->Core.u.connect.pLoc = (HGCMServiceLocation *)(&pCmdCached->Core + 1); + RT_ZERO(*pCmdCached); + pCmdCached->Core.fMemCache = true; + pCmdCached->Core.GCPhys = GCPhys; + pCmdCached->Core.cbRequest = cbRequest; + pCmdCached->Core.enmCmdType = enmCmdType; + pCmdCached->Core.fRequestor = fRequestor; + pCmdCached->Core.idxHeapAcc = (uint8_t)idxHeapAcc; + pCmdCached->Core.cbHeapCost = sizeof(*pCmdCached); + Log5Func(("aHgcmAcc[%zu] %#RX64 -= %#zx (%p)\n", + idxHeapAcc, pThisCC->aHgcmAcc[idxHeapAcc].cbHeapBudget, sizeof(*pCmdCached), &pCmdCached->Core)); + pThisCC->aHgcmAcc[idxHeapAcc].cbHeapBudget -= sizeof(*pCmdCached); + + if (enmCmdType == VBOXHGCMCMDTYPE_CALL) + { + pCmdCached->Core.u.call.cParms = cParms; + pCmdCached->Core.u.call.paGuestParms = pCmdCached->aGuestParms; + pCmdCached->Core.u.call.paHostParms = pCmdCached->aHostParms; + } + else if (enmCmdType == VBOXHGCMCMDTYPE_CONNECT) + pCmdCached->Core.u.connect.pLoc = (HGCMServiceLocation *)(&pCmdCached->Core + 1); - return &pCmdCached->Core; + Assert(!pCmdCached->Core.pvReqLocked); + + Log3Func(("returns %p (enmCmdType=%d GCPhys=%RGp)\n", &pCmdCached->Core, enmCmdType, GCPhys)); + return &pCmdCached->Core; + } } + else + LogFunc(("Heap budget overrun: sizeof(*pCmdCached)=%#zx aHgcmAcc[%zu].cbHeapBudget=%#RX64 - enmCmdType=%d\n", + sizeof(*pCmdCached), idxHeapAcc, pThisCC->aHgcmAcc[idxHeapAcc].cbHeapBudget, enmCmdType)); return NULL; } STAM_REL_COUNTER_INC(&pThisCC->StatHgcmLargeCmdAllocs); @@ -306,42 +338,57 @@ /* Size of required memory buffer. */ const uint32_t cbCmd = sizeof(VBOXHGCMCMD) + cParms * (sizeof(VBOXHGCMGUESTPARM) + sizeof(VBOXHGCMSVCPARM)) + (enmCmdType == VBOXHGCMCMDTYPE_CONNECT ? sizeof(HGCMServiceLocation) : 0); - - PVBOXHGCMCMD pCmd = (PVBOXHGCMCMD)RTMemAllocZ(cbCmd); - if (pCmd) + if (cbCmd <= pThisCC->aHgcmAcc[idxHeapAcc].cbHeapBudget) { - pCmd->enmCmdType = enmCmdType; - pCmd->GCPhys = GCPhys; - pCmd->cbRequest = cbRequest; - pCmd->fRequestor = fRequestor; - - if (enmCmdType == VBOXHGCMCMDTYPE_CALL) + PVBOXHGCMCMD pCmd = (PVBOXHGCMCMD)RTMemAllocZ(cbCmd); + if (pCmd) { - pCmd->u.call.cParms = cParms; - if (cParms) + pCmd->enmCmdType = enmCmdType; + pCmd->GCPhys = GCPhys; + pCmd->cbRequest = cbRequest; + pCmd->fRequestor = fRequestor; + pCmd->idxHeapAcc = (uint8_t)idxHeapAcc; + pCmd->cbHeapCost = cbCmd; + Log5Func(("aHgcmAcc[%zu] %#RX64 -= %#x (%p)\n", idxHeapAcc, pThisCC->aHgcmAcc[idxHeapAcc].cbHeapBudget, cbCmd, pCmd)); + pThisCC->aHgcmAcc[idxHeapAcc].cbHeapBudget -= cbCmd; + + if (enmCmdType == VBOXHGCMCMDTYPE_CALL) { - pCmd->u.call.paGuestParms = (VBOXHGCMGUESTPARM *)((uint8_t *)pCmd - + sizeof(struct VBOXHGCMCMD)); - pCmd->u.call.paHostParms = (VBOXHGCMSVCPARM *)((uint8_t *)pCmd->u.call.paGuestParms - + cParms * sizeof(VBOXHGCMGUESTPARM)); + pCmd->u.call.cParms = cParms; + if (cParms) + { + pCmd->u.call.paGuestParms = (VBOXHGCMGUESTPARM *)((uint8_t *)pCmd + + sizeof(struct VBOXHGCMCMD)); + pCmd->u.call.paHostParms = (VBOXHGCMSVCPARM *)((uint8_t *)pCmd->u.call.paGuestParms + + cParms * sizeof(VBOXHGCMGUESTPARM)); + } } + else if (enmCmdType == VBOXHGCMCMDTYPE_CONNECT) + pCmd->u.connect.pLoc = (HGCMServiceLocation *)(pCmd + 1); } - else if (enmCmdType == VBOXHGCMCMDTYPE_CONNECT) - pCmd->u.connect.pLoc = (HGCMServiceLocation *)(pCmd + 1); + Log3Func(("returns %p (enmCmdType=%d GCPhys=%RGp cbCmd=%#x)\n", pCmd, enmCmdType, GCPhys, cbCmd)); + return pCmd; } - return pCmd; + LogFunc(("Heap budget overrun: cbCmd=%#x aHgcmAcc[%zu].cbHeapBudget=%#RX64 - enmCmdType=%d\n", + cbCmd, idxHeapAcc, pThisCC->aHgcmAcc[idxHeapAcc].cbHeapBudget, enmCmdType)); + return NULL; } /** Deallocate VBOXHGCMCMD memory. * * @param pDevIns The device instance. + * @param pThis The VMMDev shared instance data. * @param pThisCC The VMMDev ring-3 instance data. * @param pCmd Command to deallocate. */ -static void vmmdevR3HgcmCmdFree(PPDMDEVINS pDevIns, PVMMDEVCC pThisCC, PVBOXHGCMCMD pCmd) +static void vmmdevR3HgcmCmdFree(PPDMDEVINS pDevIns, PVMMDEV pThis, PVMMDEVCC pThisCC, PVBOXHGCMCMD pCmd) { if (pCmd) { + Assert( pCmd->enmCmdType == VBOXHGCMCMDTYPE_CALL + || pCmd->enmCmdType == VBOXHGCMCMDTYPE_CONNECT + || pCmd->enmCmdType == VBOXHGCMCMDTYPE_DISCONNECT + || pCmd->enmCmdType == VBOXHGCMCMDTYPE_LOADSTATE); if (pCmd->enmCmdType == VBOXHGCMCMDTYPE_CALL) { uint32_t i; @@ -389,12 +436,35 @@ pCmd->pvReqLocked = NULL; } + pCmd->enmCmdType = UINT8_MAX; /* poison */ + + /* Update heap budget. Need the critsect to do this safely. */ + Assert(pCmd->cbHeapCost != 0); + uintptr_t idx = pCmd->idxHeapAcc; + AssertStmt(idx < RT_ELEMENTS(pThisCC->aHgcmAcc), idx %= RT_ELEMENTS(pThisCC->aHgcmAcc)); + + PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED); + + Log5Func(("aHgcmAcc[%zu] %#RX64 += %#x (%p)\n", idx, pThisCC->aHgcmAcc[idx].cbHeapBudget, pCmd->cbHeapCost, pCmd)); + pThisCC->aHgcmAcc[idx].cbHeapBudget += pCmd->cbHeapCost; + AssertMsg(pThisCC->aHgcmAcc[idx].cbHeapBudget <= pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig, + ("idx=%d (%d) fRequestor=%#x pCmd=%p: %#RX64 vs %#RX64 -> %#RX64\n", idx, pCmd->idxHeapAcc, pCmd->fRequestor, pCmd, + pThisCC->aHgcmAcc[idx].cbHeapBudget, pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig, + pThisCC->aHgcmAcc[idx].cbHeapBudget - pThisCC->aHgcmAcc[idx].cbHeapBudgetConfig)); + pCmd->cbHeapCost = 0; + #if 1 if (pCmd->fMemCache) + { RTMemCacheFree(pThisCC->hHgcmCmdCache, pCmd); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); /* releasing it after just to be on the safe side. */ + } else #endif + { + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); RTMemFree(pCmd); + } } } @@ -415,6 +485,12 @@ RTListPrepend(&pThisCC->listHGCMCmd, &pCmd->node); + /* stats */ + uintptr_t idx = pCmd->idxHeapAcc; + AssertStmt(idx < RT_ELEMENTS(pThisCC->aHgcmAcc), idx %= RT_ELEMENTS(pThisCC->aHgcmAcc)); + STAM_REL_COUNTER_ADD(&pThisCC->aHgcmAcc[idx].cbHeapTotal, pCmd->cbHeapCost); + STAM_REL_COUNTER_INC(&pThisCC->aHgcmAcc[idx].cTotalMessages); + /* Automatically enable HGCM events, if there are HGCM commands. */ if ( pCmd->enmCmdType == VBOXHGCMCMDTYPE_CONNECT || pCmd->enmCmdType == VBOXHGCMCMDTYPE_DISCONNECT @@ -682,10 +758,11 @@ * * @returns VBox status code that the guest should see. * @param pDevIns The device instance. + * @param pThisCC The VMMDev ring-3 instance data. * @param pCmd Command structure where host parameters needs initialization. * @param pbReq The request buffer. */ -static int vmmdevR3HgcmInitHostParameters(PPDMDEVINS pDevIns, PVBOXHGCMCMD pCmd, uint8_t const *pbReq) +static int vmmdevR3HgcmInitHostParameters(PPDMDEVINS pDevIns, PVMMDEVCC pThisCC, PVBOXHGCMCMD pCmd, uint8_t const *pbReq) { AssertReturn(pCmd->enmCmdType == VBOXHGCMCMDTYPE_CALL, VERR_INTERNAL_ERROR); @@ -727,7 +804,7 @@ if (cbData) { /* Zero memory, the buffer content is potentially copied to the guest. */ - void *pv = RTMemAllocZ(cbData); + void *pv = vmmdevR3HgcmCallMemAllocZ(pThisCC, pCmd, cbData); AssertReturn(pv, VERR_NO_MEMORY); pHostParm->u.pointer.addr = pv; @@ -830,6 +907,58 @@ return VINF_SUCCESS; } +/** + * Heap budget wrapper around RTMemAlloc and RTMemAllocZ. + */ +static void *vmmdevR3HgcmCallMemAllocEx(PVMMDEVCC pThisCC, PVBOXHGCMCMD pCmd, size_t cbRequested, bool fZero) +{ + /* Check against max heap costs for this request. */ + Assert(pCmd->cbHeapCost <= VMMDEV_MAX_HGCM_DATA_SIZE); + if (cbRequested <= VMMDEV_MAX_HGCM_DATA_SIZE - pCmd->cbHeapCost) + { + /* Check heap budget (we're under lock). */ + uintptr_t idx = pCmd->idxHeapAcc; + AssertStmt(idx < RT_ELEMENTS(pThisCC->aHgcmAcc), idx %= RT_ELEMENTS(pThisCC->aHgcmAcc)); + if (cbRequested <= pThisCC->aHgcmAcc[idx].cbHeapBudget) + { + /* Do the actual allocation. */ + void *pv = fZero ? RTMemAllocZ(cbRequested) : RTMemAlloc(cbRequested); + if (pv) + { + /* Update the request cost and heap budget. */ + Log5Func(("aHgcmAcc[%zu] %#RX64 += %#x (%p)\n", idx, pThisCC->aHgcmAcc[idx].cbHeapBudget, cbRequested, pCmd)); + pThisCC->aHgcmAcc[idx].cbHeapBudget -= cbRequested; + pCmd->cbHeapCost += (uint32_t)cbRequested; + return pv; + } + LogFunc(("Heap alloc failed: cbRequested=%#zx - enmCmdType=%d\n", cbRequested, pCmd->enmCmdType)); + } + else + LogFunc(("Heap budget overrun: cbRequested=%#zx cbHeapCost=%#x aHgcmAcc[%u].cbHeapBudget=%#RX64 - enmCmdType=%d\n", + cbRequested, pCmd->cbHeapCost, pCmd->idxHeapAcc, pThisCC->aHgcmAcc[idx].cbHeapBudget, pCmd->enmCmdType)); + } + else + LogFunc(("Request too big: cbRequested=%#zx cbHeapCost=%#x - enmCmdType=%d\n", + cbRequested, pCmd->cbHeapCost, pCmd->enmCmdType)); + return NULL; +} + +/** + * Heap budget wrapper around RTMemAlloc. + */ +DECLINLINE(void *) vmmdevR3HgcmCallMemAlloc(PVMMDEVCC pThisCC, PVBOXHGCMCMD pCmd, size_t cbRequested) +{ + return vmmdevR3HgcmCallMemAllocEx(pThisCC, pCmd, cbRequested, false /*fZero*/); +} + +/** + * Heap budget wrapper around RTMemAllocZ. + */ +DECLINLINE(void *) vmmdevR3HgcmCallMemAllocZ(PVMMDEVCC pThisCC, PVBOXHGCMCMD pCmd, size_t cbRequested) +{ + return vmmdevR3HgcmCallMemAllocEx(pThisCC, pCmd, cbRequested, true /*fZero*/); +} + /** Copy VMMDevHGCMCall request data from the guest to VBOXHGCMCMD command. * * @returns VBox status code that the guest should see. @@ -863,7 +992,6 @@ /* Pointer to the next HGCM parameter of the request. */ const uint8_t *pu8HGCMParm = (uint8_t *)pHGCMCall + offHGCMParms; - uint32_t cbTotalData = 0; for (uint32_t i = 0; i < cParms; ++i, pu8HGCMParm += cbHGCMParmStruct) { VBOXHGCMGUESTPARM * const pGuestParm = &pCmd->u.call.paGuestParms[i]; @@ -926,8 +1054,7 @@ #endif LogFunc(("LinAddr guest parameter %RGv, cb %u\n", GCPtr, cbData)); - ASSERT_GUEST_RETURN(cbData <= VMMDEV_MAX_HGCM_DATA_SIZE - cbTotalData, VERR_INVALID_PARAMETER); - cbTotalData += cbData; + ASSERT_GUEST_RETURN(cbData <= VMMDEV_MAX_HGCM_DATA_SIZE, VERR_INVALID_PARAMETER); const uint32_t offFirstPage = cbData > 0 ? GCPtr & PAGE_OFFSET_MASK : 0; const uint32_t cPages = cbData > 0 ? (offFirstPage + cbData + PAGE_SIZE - 1) / PAGE_SIZE : 0; @@ -943,7 +1070,9 @@ pGuestParm->u.ptr.paPages = &pGuestParm->u.ptr.GCPhysSinglePage; else { - pGuestParm->u.ptr.paPages = (RTGCPHYS *)RTMemAlloc(cPages * sizeof(RTGCPHYS)); + /* (Max 262144 bytes with current limits.) */ + pGuestParm->u.ptr.paPages = (RTGCPHYS *)vmmdevR3HgcmCallMemAlloc(pThisCC, pCmd, + cPages * sizeof(RTGCPHYS)); AssertReturn(pGuestParm->u.ptr.paPages, VERR_NO_MEMORY); } @@ -985,8 +1114,7 @@ #endif LogFunc(("PageList guest parameter cb %u, offset %u\n", cbData, offPageListInfo)); - ASSERT_GUEST_RETURN(cbData <= VMMDEV_MAX_HGCM_DATA_SIZE - cbTotalData, VERR_INVALID_PARAMETER); - cbTotalData += cbData; + ASSERT_GUEST_RETURN(cbData <= VMMDEV_MAX_HGCM_DATA_SIZE, VERR_INVALID_PARAMETER); /** @todo respect zero byte page lists... */ /* Check that the page list info is within the request. */ @@ -1047,8 +1175,9 @@ pGuestParm->u.Pages.fFlags = pPageListInfo->flags; pGuestParm->u.Pages.cPages = (uint16_t)cPages; pGuestParm->u.Pages.fLocked = false; - pGuestParm->u.Pages.paPgLocks = (PPGMPAGEMAPLOCK)RTMemAllocZ( (sizeof(PGMPAGEMAPLOCK) + sizeof(void *)) - * cPages); + pGuestParm->u.Pages.paPgLocks = (PPGMPAGEMAPLOCK)vmmdevR3HgcmCallMemAllocZ(pThisCC, pCmd, + ( sizeof(PGMPAGEMAPLOCK) + + sizeof(void *)) * cPages); AssertReturn(pGuestParm->u.Pages.paPgLocks, VERR_NO_MEMORY); /* Make sure the page offsets are sensible. */ @@ -1089,7 +1218,8 @@ } else { - pGuestParm->u.ptr.paPages = (RTGCPHYS *)RTMemAlloc(pPageListInfo->cPages * sizeof(RTGCPHYS)); + pGuestParm->u.ptr.paPages = (RTGCPHYS *)vmmdevR3HgcmCallMemAlloc(pThisCC, pCmd, + pPageListInfo->cPages * sizeof(RTGCPHYS)); AssertReturn(pGuestParm->u.ptr.paPages, VERR_NO_MEMORY); for (uint32_t iPage = 0; iPage < pGuestParm->u.ptr.cPages; ++iPage) @@ -1112,8 +1242,7 @@ #endif LogFunc(("Embedded guest parameter cb %u, offset %u, flags %#x\n", cbData, offData, fFlags)); - ASSERT_GUEST_RETURN(cbData <= VMMDEV_MAX_HGCM_DATA_SIZE - cbTotalData, VERR_INVALID_PARAMETER); - cbTotalData += cbData; + ASSERT_GUEST_RETURN(cbData <= VMMDEV_MAX_HGCM_DATA_SIZE, VERR_INVALID_PARAMETER); /* Check flags and buffer range. */ ASSERT_GUEST_MSG_RETURN(VBOX_HGCM_F_PARM_ARE_VALID(fFlags), ("%#x\n", fFlags), VERR_INVALID_FLAGS); @@ -1196,7 +1325,7 @@ if (RT_SUCCESS(rc)) { /* Copy guest data to host parameters, so HGCM services can use the data. */ - rc = vmmdevR3HgcmInitHostParameters(pDevIns, pCmd, (uint8_t const *)pHGCMCall); + rc = vmmdevR3HgcmInitHostParameters(pDevIns, pThisCC, pCmd, (uint8_t const *)pHGCMCall); if (RT_SUCCESS(rc)) { /* @@ -1254,7 +1383,7 @@ vmmdevR3HgcmRemoveCommand(pThisCC, pCmd); } } - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); } return rc; } @@ -1687,9 +1816,9 @@ uint64_t const tsComplete = pCmd->tsComplete; #endif - /* Deallocate the command memory. */ + /* Deallocate the command memory. Enter the critsect for proper */ VBOXDD_HGCMCALL_COMPLETED_DONE(pCmd, idFunction, idClient, result); - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); #ifndef VBOX_WITHOUT_RELEASE_STATISTICS /* Update stats. */ @@ -2003,7 +2132,8 @@ { AssertReturn( pGuestParm->enmType != VMMDevHGCMParmType_Embedded && pGuestParm->enmType != VMMDevHGCMParmType_ContiguousPageList, VERR_INTERNAL_ERROR_3); - pPtr->paPages = (RTGCPHYS *)RTMemAlloc(pPtr->cPages * sizeof(RTGCPHYS)); + pPtr->paPages = (RTGCPHYS *)vmmdevR3HgcmCallMemAlloc(pThisCC, pCmd, + pPtr->cPages * sizeof(RTGCPHYS)); AssertStmt(pPtr->paPages, rc = VERR_NO_MEMORY); } @@ -2063,7 +2193,7 @@ { Log(("vmmdevR3HgcmLoadState: Skipping cancelled request: enmCmdType=%d GCPhys=%#RX32 LB %#x\n", enmCmdType, GCPhys, cbRequest)); - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); } } @@ -2127,7 +2257,7 @@ rc = SSMR3GetU32(pSSM, &pPtr->cPages); AssertRCReturn(rc, rc); - pPtr->paPages = (RTGCPHYS *)RTMemAlloc(pPtr->cPages * sizeof(RTGCPHYS)); + pPtr->paPages = (RTGCPHYS *)vmmdevR3HgcmCallMemAlloc(pThisCC, pCmd, pPtr->cPages * sizeof(RTGCPHYS)); AssertReturn(pPtr->paPages, VERR_NO_MEMORY); uint32_t iPage; @@ -2147,7 +2277,7 @@ { Log(("vmmdevR3HgcmLoadState: Skipping cancelled request: enmCmdType=%d GCPhys=%#RX32 LB %#x\n", enmCmdType, GCPhys, cbRequest)); - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); } } @@ -2254,6 +2384,7 @@ * * @returns VBox status code that the guest should see. * @param pDevIns The device instance. + * @param pThis The VMMDev shared instance data. * @param pThisCC The VMMDev ring-3 instance data. * @param uSavedStateVersion The saved state version the command has been loaded from. * @param pLoadedCmd Command loaded from saved state, it is imcomplete and needs restoration. @@ -2262,9 +2393,9 @@ * @param enmRequestType Type of the HGCM request. * @param ppRestoredCmd Where to store pointer to newly allocated restored command. */ -static int vmmdevR3HgcmRestoreCall(PPDMDEVINS pDevIns, PVMMDEVCC pThisCC, uint32_t uSavedStateVersion, const VBOXHGCMCMD *pLoadedCmd, - VMMDevHGCMCall *pReq, uint32_t cbReq, VMMDevRequestType enmRequestType, - VBOXHGCMCMD **ppRestoredCmd) +static int vmmdevR3HgcmRestoreCall(PPDMDEVINS pDevIns, PVMMDEV pThis, PVMMDEVCC pThisCC, uint32_t uSavedStateVersion, + const VBOXHGCMCMD *pLoadedCmd, VMMDevHGCMCall *pReq, uint32_t cbReq, + VMMDevRequestType enmRequestType, VBOXHGCMCMD **ppRestoredCmd) { /* Verify the request. */ ASSERT_GUEST_RETURN(cbReq >= sizeof(*pReq), VERR_MISMATCH); @@ -2314,7 +2445,7 @@ if (RT_SUCCESS(rc)) *ppRestoredCmd = pCmd; else - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); return rc; } @@ -2324,6 +2455,7 @@ * * @returns VBox status code that the guest should see. * @param pDevIns The device instance. + * @param pThis The VMMDev shared instance data. * @param pThisCC The VMMDev ring-3 instance data. * @param uSavedStateVersion Saved state version. * @param pLoadedCmd HGCM command which needs restoration. @@ -2331,7 +2463,7 @@ * @param cbReq Size of the entire request (including HGCM parameters). * @param ppRestoredCmd Where to store pointer to restored command. */ -static int vmmdevR3HgcmRestoreCommand(PPDMDEVINS pDevIns, PVMMDEVCC pThisCC, uint32_t uSavedStateVersion, +static int vmmdevR3HgcmRestoreCommand(PPDMDEVINS pDevIns, PVMMDEV pThis, PVMMDEVCC pThisCC, uint32_t uSavedStateVersion, const VBOXHGCMCMD *pLoadedCmd, const VMMDevHGCMRequestHeader *pReqHdr, uint32_t cbReq, VBOXHGCMCMD **ppRestoredCmd) { @@ -2364,7 +2496,8 @@ case VMMDevReq_HGCMCall32: { VMMDevHGCMCall *pReq = (VMMDevHGCMCall *)pReqHdr; - rc = vmmdevR3HgcmRestoreCall(pDevIns, pThisCC, uSavedStateVersion, pLoadedCmd, pReq, cbReq, enmRequestType, ppRestoredCmd); + rc = vmmdevR3HgcmRestoreCall(pDevIns, pThis, pThisCC, uSavedStateVersion, pLoadedCmd, + pReq, cbReq, enmRequestType, ppRestoredCmd); break; } @@ -2424,7 +2557,7 @@ * * report an error to the guest if resubmit failed. */ VMMDevHGCMRequestHeader *pReqHdr = (VMMDevHGCMRequestHeader *)RTMemAlloc(pCmd->cbRequest); - AssertBreakStmt(pReqHdr, vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); rcFunc = VERR_NO_MEMORY); + AssertBreakStmt(pReqHdr, vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); rcFunc = VERR_NO_MEMORY); PDMDevHlpPhysRead(pDevIns, pCmd->GCPhys, pReqHdr, pCmd->cbRequest); RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); @@ -2440,12 +2573,12 @@ else { PVBOXHGCMCMD pRestoredCmd = NULL; - rcCmd = vmmdevR3HgcmRestoreCommand(pDevIns, pThisCC, pThisCC->uSavedStateVersion, pCmd, + rcCmd = vmmdevR3HgcmRestoreCommand(pDevIns, pThis, pThisCC, pThisCC->uSavedStateVersion, pCmd, pReqHdr, pCmd->cbRequest, &pRestoredCmd); if (RT_SUCCESS(rcCmd)) { Assert(pCmd != pRestoredCmd); /* vmmdevR3HgcmRestoreCommand must allocate restored command. */ - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); pCmd = pRestoredCmd; } } @@ -2476,7 +2609,7 @@ case VBOXHGCMCMDTYPE_CALL: { - rcCmd = vmmdevR3HgcmInitHostParameters(pDevIns, pCmd, (uint8_t const *)pReqHdr); + rcCmd = vmmdevR3HgcmInitHostParameters(pDevIns, pThisCC, pCmd, (uint8_t const *)pReqHdr); if (RT_SUCCESS(rcCmd)) { vmmdevR3HgcmAddCommand(pDevIns, pThis, pThisCC, pCmd); @@ -2519,7 +2652,7 @@ VMMDevNotifyGuest(pDevIns, pThis, pThisCC, VMMDEV_EVENT_HGCM); /* Deallocate the command memory. */ - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); } RTMemFree(pReqHdr); @@ -2530,7 +2663,7 @@ RTListForEachSafe(&listLoadedCommands, pCmd, pNext, VBOXHGCMCMD, node) { RTListNodeRemove(&pCmd->node); - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); } } @@ -2542,9 +2675,10 @@ * Counterpart to vmmdevR3HgcmInit(). * * @param pDevIns The device instance. + * @param pThis The VMMDev shared instance data. * @param pThisCC The VMMDev ring-3 instance data. */ -void vmmdevR3HgcmDestroy(PPDMDEVINS pDevIns, PVMMDEVCC pThisCC) +void vmmdevR3HgcmDestroy(PPDMDEVINS pDevIns, PVMMDEV pThis, PVMMDEVCC pThisCC) { LogFlowFunc(("\n")); @@ -2554,7 +2688,7 @@ RTListForEachSafe(&pThisCC->listHGCMCmd, pCmd, pNext, VBOXHGCMCMD, node) { vmmdevR3HgcmRemoveCommand(pThisCC, pCmd); - vmmdevR3HgcmCmdFree(pDevIns, pThisCC, pCmd); + vmmdevR3HgcmCmdFree(pDevIns, pThis, pThisCC, pCmd); } RTCritSectDelete(&pThisCC->critsectHGCMCmdList); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/VMMDev/VMMDevHGCM.h virtualbox-6.1.38-dfsg/src/VBox/Devices/VMMDev/VMMDevHGCM.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/VMMDev/VMMDevHGCM.h 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/VMMDev/VMMDevHGCM.h 2022-09-01 13:27:00.000000000 +0000 @@ -44,7 +44,7 @@ int vmmdevR3HgcmLoadState(PPDMDEVINS pDevIns, PVMMDEV pThis, PVMMDEVCC pThisCC, PSSMHANDLE pSSM, uint32_t uVersion); int vmmdevR3HgcmLoadStateDone(PPDMDEVINS pDevIns, PVMMDEV pThis, PVMMDEVCC pThisCC); -void vmmdevR3HgcmDestroy(PPDMDEVINS pDevIns, PVMMDEVCC pThisCC); +void vmmdevR3HgcmDestroy(PPDMDEVINS pDevIns, PVMMDEV pThis, PVMMDEVCC pThisCC); int vmmdevR3HgcmInit(PVMMDEVCC pThisCC); RT_C_DECLS_END diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Devices/VMMDev/VMMDevState.h virtualbox-6.1.38-dfsg/src/VBox/Devices/VMMDev/VMMDevState.h --- virtualbox-6.1.16-dfsg/src/VBox/Devices/VMMDev/VMMDevState.h 2020-10-16 16:36:15.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Devices/VMMDev/VMMDevState.h 2022-09-01 13:27:00.000000000 +0000 @@ -399,6 +399,19 @@ /** Saved state version of restored commands. */ uint32_t uSavedStateVersion; RTMEMCACHE hHgcmCmdCache; + /** Accounting by for each requestor VMMDEV_REQUESTOR_USR_XXX group. + * Legacy requests ends up with VMMDEV_REQUESTOR_USR_NOT_GIVEN */ + struct + { + /** The configured heap budget. */ + uint64_t cbHeapBudgetConfig; + /** The currently available heap budget. */ + uint64_t cbHeapBudget; + /** Total sum of all heap usage. */ + STAMCOUNTER cbHeapTotal; + /** Total number of message. */ + STAMCOUNTER cTotalMessages; + } aHgcmAcc[VMMDEV_REQUESTOR_USR_MASK + 1]; STAMPROFILE StatHgcmCmdArrival; STAMPROFILE StatHgcmCmdCompletion; STAMPROFILE StatHgcmCmdTotal; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Disassembler/DisasmCore.cpp virtualbox-6.1.38-dfsg/src/VBox/Disassembler/DisasmCore.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Disassembler/DisasmCore.cpp 2020-10-16 16:36:16.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Disassembler/DisasmCore.cpp 2022-09-01 13:27:01.000000000 +0000 @@ -234,7 +234,7 @@ */ static DECLCALLBACK(int) disReadBytesDefault(PDISSTATE pDis, uint8_t offInstr, uint8_t cbMinRead, uint8_t cbMaxRead) { -#ifdef IN_RING0 +#if 0 /*def IN_RING0 - why? */ RT_NOREF_PV(cbMinRead); AssertMsgFailed(("disReadWord with no read callback in ring 0!!\n")); RT_BZERO(&pDis->abInstr[offInstr], cbMaxRead); diff -Nru virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/BusMouseSample/VBoxBusMouseMain.cpp virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/BusMouseSample/VBoxBusMouseMain.cpp --- virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/BusMouseSample/VBoxBusMouseMain.cpp 2020-10-16 16:36:17.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/BusMouseSample/VBoxBusMouseMain.cpp 2022-09-01 13:27:02.000000000 +0000 @@ -95,7 +95,7 @@ /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, - /* .u32Reserved7 = */ 0, + /* .uReserved7 = */ 0, VBOXEXTPACKREG_VERSION }; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/BusMouseSample/VBoxBusMouseMainVM.cpp virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/BusMouseSample/VBoxBusMouseMainVM.cpp --- virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/BusMouseSample/VBoxBusMouseMainVM.cpp 2020-10-16 16:36:17.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/BusMouseSample/VBoxBusMouseMainVM.cpp 2022-09-01 13:27:02.000000000 +0000 @@ -139,7 +139,7 @@ /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, - /* .u32Reserved7 = */ 0, + /* .uReserved7 = */ 0, VBOXEXTPACKVMREG_VERSION }; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/Skeleton/VBoxSkeletonMain.cpp virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/Skeleton/VBoxSkeletonMain.cpp --- virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/Skeleton/VBoxSkeletonMain.cpp 2020-10-16 16:36:17.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/Skeleton/VBoxSkeletonMain.cpp 2022-09-01 13:27:02.000000000 +0000 @@ -95,7 +95,7 @@ /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, - /* .u32Reserved7 = */ 0, + /* .uReserved7 = */ 0, VBOXEXTPACKREG_VERSION }; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/Skeleton/VBoxSkeletonMainVM.cpp virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/Skeleton/VBoxSkeletonMainVM.cpp --- virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/Skeleton/VBoxSkeletonMainVM.cpp 2020-10-16 16:36:17.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/Skeleton/VBoxSkeletonMainVM.cpp 2022-09-01 13:27:02.000000000 +0000 @@ -95,7 +95,7 @@ /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, - /* .u32Reserved7 = */ 0, + /* .uReserved7 = */ 0, VBOXEXTPACKVMREG_VERSION }; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/VBoxDTrace/Makefile.kmk virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/VBoxDTrace/Makefile.kmk --- virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/VBoxDTrace/Makefile.kmk 2020-10-16 16:36:17.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/VBoxDTrace/Makefile.kmk 2022-09-01 13:27:02.000000000 +0000 @@ -257,11 +257,8 @@ VBoxDTraceR0A.asm \ $(VBOXDT_PATH_UTS)/common/dtrace/dtrace.c VBoxDTraceR0_LIBS = \ - $(PATH_STAGE_LIB)/RuntimeR0$(VBOX_SUFF_LIB) - ifneq ($(filter pe lx,$(VBOX_LDR_FMT)),) - VBoxDTraceR0_LIBS += \ - $(PATH_STAGE_LIB)/SUPR0$(VBOX_SUFF_LIB) - endif + $(PATH_STAGE_LIB)/RuntimeR0$(VBOX_SUFF_LIB) \ + $(VBOX_LIB_SUPR0) $(call VBOX_SET_VER_INFO_R0,VBoxDTraceR0,VBoxDTrace Core (ring-0)) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceMain.cpp virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceMain.cpp --- virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceMain.cpp 2020-10-16 16:36:17.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceMain.cpp 2022-09-01 13:27:02.000000000 +0000 @@ -95,7 +95,7 @@ /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, - /* .u32Reserved7 = */ 0, + /* .uReserved7 = */ 0, VBOXEXTPACKREG_VERSION }; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/VNC/VBoxVNCMain.cpp virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/VNC/VBoxVNCMain.cpp --- virtualbox-6.1.16-dfsg/src/VBox/ExtPacks/VNC/VBoxVNCMain.cpp 2020-10-16 16:36:50.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/ExtPacks/VNC/VBoxVNCMain.cpp 2022-09-01 13:27:36.000000000 +0000 @@ -82,7 +82,7 @@ /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, - /* .u32Reserved7 = */ 0, + /* .uReserved7 = */ 0, VBOXEXTPACKREG_VERSION }; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxHeadless/VBoxHeadless.cpp virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxHeadless/VBoxHeadless.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxHeadless/VBoxHeadless.cpp 2020-10-16 16:36:51.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxHeadless/VBoxHeadless.cpp 2022-09-01 13:27:37.000000000 +0000 @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include #include @@ -55,9 +57,9 @@ # include #endif -//#define VBOX_WITH_SAVESTATE_ON_SIGNAL -#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL +#if !defined(RT_OS_WINDOWS) #include +static void HandleSignal(int sig); #endif #include "PasswordInput.h" @@ -76,6 +78,10 @@ static IConsole *gConsole = NULL; static NativeEventQueue *gEventQ = NULL; +/* keep this handy for messages */ +static com::Utf8Str g_strVMName; +static com::Utf8Str g_strVMUUID; + /* flag whether frontend should terminate */ static volatile bool g_fTerminateFE = false; @@ -388,64 +394,15 @@ VBOX_LISTENER_DECLARE(VirtualBoxClientEventListenerImpl) VBOX_LISTENER_DECLARE(ConsoleEventListenerImpl) -#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL -static void SaveState(int sig) +#if !defined(RT_OS_WINDOWS) +static void +HandleSignal(int sig) { - ComPtr progress = NULL; - -/** @todo Deal with nested signals, multithreaded signal dispatching (esp. on windows), - * and multiple signals (both SIGINT and SIGTERM in some order). - * Consider processing the signal request asynchronously since there are lots of things - * which aren't safe (like RTPrintf and printf IIRC) in a signal context. */ - - RTPrintf("Signal received, saving state.\n"); - - HRESULT rc = gConsole->SaveState(progress.asOutParam()); - if (FAILED(rc)) - { - RTPrintf("Error saving state! rc = 0x%x\n", rc); - return; - } - Assert(progress); - LONG cPercent = 0; - - RTPrintf("0%%"); - RTStrmFlush(g_pStdOut); - for (;;) - { - BOOL fCompleted = false; - rc = progress->COMGETTER(Completed)(&fCompleted); - if (FAILED(rc) || fCompleted) - break; - ULONG cPercentNow; - rc = progress->COMGETTER(Percent)(&cPercentNow); - if (FAILED(rc)) - break; - if ((cPercentNow / 10) != (cPercent / 10)) - { - cPercent = cPercentNow; - RTPrintf("...%d%%", cPercentNow); - RTStrmFlush(g_pStdOut); - } - - /* wait */ - rc = progress->WaitForCompletion(100); - } - - HRESULT lrc; - rc = progress->COMGETTER(ResultCode)(&lrc); - if (FAILED(rc)) - lrc = ~0; - if (!lrc) - { - RTPrintf(" -- Saved the state successfully.\n"); - RTThreadYield(); - } - else - RTPrintf("-- Error saving state, lrc=%d (%#x)\n", lrc, lrc); - + RT_NOREF(sig); + LogRel(("VBoxHeadless: received singal %d\n", sig)); + g_fTerminateFE = true; } -#endif /* VBOX_WITH_SAVESTATE_ON_SIGNAL */ +#endif /* !RT_OS_WINDOWS */ //////////////////////////////////////////////////////////////////////////////// @@ -527,275 +484,308 @@ } #endif /* VBOX_WITH_RECORDING defined */ -#ifdef RT_OS_DARWIN -# include -# include -# include -# include +#ifdef RT_OS_WINDOWS -/** - * Override this one to try hide the fact that we're setuid to root - * orginially. - */ -int issetugid_for_AppKit(void) +#define MAIN_WND_CLASS L"VirtualBox Headless Interface" + +HINSTANCE g_hInstance = NULL; +HWND g_hWindow = NULL; +RTSEMEVENT g_hCanQuit; + +static DECLCALLBACK(int) windowsMessageMonitor(RTTHREAD ThreadSelf, void *pvUser); +static int createWindow(); +static LRESULT CALLBACK WinMainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); +static void destroyWindow(); + + +static DECLCALLBACK(int) +windowsMessageMonitor(RTTHREAD ThreadSelf, void *pvUser) { - Dl_info Info = {0}; - char szMsg[512]; - size_t cchMsg; - const void * uCaller = __builtin_return_address(0); - if (dladdr(uCaller, &Info)) - cchMsg = snprintf(szMsg, sizeof(szMsg), "DEBUG: issetugid_for_AppKit was called by %p %s::%s+%p (via %p)\n", - uCaller, Info.dli_fname, Info.dli_sname, (void *)((uintptr_t)uCaller - (uintptr_t)Info.dli_saddr), __builtin_return_address(1)); - else - cchMsg = snprintf(szMsg, sizeof(szMsg), "DEBUG: issetugid_for_AppKit was called by %p (via %p)\n", uCaller, __builtin_return_address(1)); - write(2, szMsg, cchMsg); - return 0; + RT_NOREF(ThreadSelf, pvUser); + int rc; + + rc = createWindow(); + if (RT_FAILURE(rc)) + return rc; + + RTSemEventCreate(&g_hCanQuit); + + MSG msg; + BOOL b; + while ((b = ::GetMessage(&msg, 0, 0, 0)) > 0) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + if (b < 0) + LogRel(("VBoxHeadless: GetMessage failed\n")); + + destroyWindow(); + return VINF_SUCCESS; } -static bool patchExtSym(mach_header_64_t *pHdr, const char *pszSymbol, uintptr_t uNewValue) + +static int +createWindow() { - /* - * First do some basic header checks and the scan the load - * commands for the symbol table info. - */ - AssertLogRelMsgReturn(pHdr->magic == (ARCH_BITS == 64 ? MH_MAGIC_64 : MH_MAGIC), - ("%p: magic=%#x\n", pHdr, pHdr->magic), false); - uint32_t const cCmds = pHdr->ncmds; - uint32_t const cbCmds = pHdr->sizeofcmds; - AssertLogRelMsgReturn(cCmds < 16384 && cbCmds < _2M, ("%p: ncmds=%u sizeofcmds=%u\n", pHdr, cCmds, cbCmds), false); + /* program instance handle */ + g_hInstance = (HINSTANCE)::GetModuleHandle(NULL); + if (g_hInstance == NULL) + { + LogRel(("VBoxHeadless: failed to obtain module handle\n")); + return VERR_GENERAL_FAILURE; + } - /* - * First command pass: Locate the symbol table and dynamic symbol table info - * commands, also calc the slide (load addr - link addr). - */ - dysymtab_command_t const *pDySymTab = NULL; - symtab_command_t const *pSymTab = NULL; - segment_command_64_t const *pFirstSeg = NULL; - uintptr_t offSlide = 0; - uint32_t offCmd = 0; - for (uint32_t iCmd = 0; iCmd < cCmds; iCmd++) - { - AssertLogRelMsgReturn(offCmd + sizeof(load_command_t) <= cbCmds, - ("%p: iCmd=%u offCmd=%#x cbCmds=%#x\n", pHdr, iCmd, offCmd, cbCmds), false); - load_command_t const * const pCmd = (load_command_t const *)((uintptr_t)(pHdr + 1) + offCmd); - uint32_t const cbCurCmd = pCmd->cmdsize; - AssertLogRelMsgReturn(offCmd + cbCurCmd <= cbCmds && cbCurCmd <= cbCmds, - ("%p: iCmd=%u offCmd=%#x cbCurCmd=%#x cbCmds=%#x\n", pHdr, iCmd, offCmd, cbCurCmd, cbCmds), false); - offCmd += cbCurCmd; - - if (pCmd->cmd == LC_SYMTAB) - { - AssertLogRelMsgReturn(!pSymTab, ("%p: pSymTab=%p pCmd=%p\n", pHdr, pSymTab, pCmd), false); - pSymTab = (symtab_command_t const *)pCmd; - AssertLogRelMsgReturn(cbCurCmd == sizeof(*pSymTab), ("%p: pSymTab=%p cbCurCmd=%#x\n", pHdr, pCmd, cbCurCmd), false); - - } - else if (pCmd->cmd == LC_DYSYMTAB) - { - AssertLogRelMsgReturn(!pDySymTab, ("%p: pDySymTab=%p pCmd=%p\n", pHdr, pDySymTab, pCmd), false); - pDySymTab = (dysymtab_command_t const *)pCmd; - AssertLogRelMsgReturn(cbCurCmd == sizeof(*pDySymTab), ("%p: pDySymTab=%p cbCurCmd=%#x\n", pHdr, pCmd, cbCurCmd), - false); - } - else if (pCmd->cmd == LC_SEGMENT_64 && !pFirstSeg) /* ASSUMES the first seg is the one with the header and stuff. */ - { - /* Note! the fileoff and vmaddr seems to be modified. */ - pFirstSeg = (segment_command_64_t const *)pCmd; - AssertLogRelMsgReturn(cbCurCmd >= sizeof(*pFirstSeg), ("%p: iCmd=%u cbCurCmd=%#x\n", pHdr, iCmd, cbCurCmd), false); - AssertLogRelMsgReturn(/*pFirstSeg->fileoff == 0 && */ pFirstSeg->vmsize >= sizeof(*pHdr) + cbCmds, - ("%p: iCmd=%u fileoff=%llx vmsize=%#llx cbCmds=%#x name=%.16s\n", - pHdr, iCmd, pFirstSeg->fileoff, pFirstSeg->vmsize, cbCmds, pFirstSeg->segname), false); - offSlide = (uintptr_t)pHdr - pFirstSeg->vmaddr; - } - } - AssertLogRelMsgReturn(pSymTab, ("%p: no LC_SYMTAB\n", pHdr), false); - AssertLogRelMsgReturn(pDySymTab, ("%p: no LC_DYSYMTAB\n", pHdr), false); - AssertLogRelMsgReturn(pFirstSeg, ("%p: no LC_SEGMENT_64\n", pHdr), false); + /* window class */ + WNDCLASS wc; + RT_ZERO(wc); - /* - * Second command pass: Locate the memory locations of the symbol table, string - * table and the indirect symbol table by checking LC_SEGMENT_xx. - */ - macho_nlist_64_t const *paSymbols = NULL; - uint32_t const offSymbols = pSymTab->symoff; - uint32_t const cSymbols = pSymTab->nsyms; - AssertLogRelMsgReturn(cSymbols > 0 && offSymbols >= sizeof(pHdr) + cbCmds, - ("%p: cSymbols=%#x offSymbols=%#x\n", pHdr, cSymbols, offSymbols), false); + wc.style = CS_NOCLOSE; + wc.lpfnWndProc = WinMainWndProc; + wc.hInstance = g_hInstance; + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + wc.lpszClassName = MAIN_WND_CLASS; - const char *pchStrTab = NULL; - uint32_t const offStrTab = pSymTab->stroff; - uint32_t const cbStrTab = pSymTab->strsize; - AssertLogRelMsgReturn(cbStrTab > 0 && offStrTab >= sizeof(pHdr) + cbCmds, - ("%p: cbStrTab=%#x offStrTab=%#x\n", pHdr, cbStrTab, offStrTab), false); + ATOM atomWindowClass = ::RegisterClass(&wc); + if (atomWindowClass == 0) + { + LogRel(("VBoxHeadless: failed to register window class\n")); + return VERR_GENERAL_FAILURE; + } + + /* secret window, secret garden */ + g_hWindow = ::CreateWindowEx(0, MAIN_WND_CLASS, MAIN_WND_CLASS, 0, + 0, 0, 1, 1, NULL, NULL, g_hInstance, NULL); + if (g_hWindow == NULL) + { + LogRel(("VBoxHeadless: failed to create window\n")); + return VERR_GENERAL_FAILURE; + } + + return VINF_SUCCESS; +} + + +static void +destroyWindow() +{ + if (g_hWindow == NULL) + return; + + ::DestroyWindow(g_hWindow); + g_hWindow = NULL; - uint32_t const *paidxIndirSymbols = NULL; - uint32_t const offIndirSymbols = pDySymTab->indirectsymboff; - uint32_t const cIndirSymbols = pDySymTab->nindirectsymb; - AssertLogRelMsgReturn(cIndirSymbols > 0 && offIndirSymbols >= sizeof(pHdr) + cbCmds, - ("%p: cIndirSymbols=%#x offIndirSymbols=%#x\n", pHdr, cIndirSymbols, offIndirSymbols), false); + if (g_hInstance == NULL) + return; + + ::UnregisterClass(MAIN_WND_CLASS, g_hInstance); + g_hInstance = NULL; +} + + +static LRESULT CALLBACK +WinMainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + int rc; - offCmd = 0; - for (uint32_t iCmd = 0; iCmd < cCmds; iCmd++) + LRESULT lResult = 0; + switch (msg) { - load_command_t const * const pCmd = (load_command_t const *)((uintptr_t)(pHdr + 1) + offCmd); - uint32_t const cbCurCmd = pCmd->cmdsize; - AssertLogRelMsgReturn(offCmd + cbCurCmd <= cbCmds && cbCurCmd <= cbCmds, - ("%p: iCmd=%u offCmd=%#x cbCurCmd=%#x cbCmds=%#x\n", pHdr, iCmd, offCmd, cbCurCmd, cbCmds), false); - offCmd += cbCurCmd; + case WM_QUERYENDSESSION: + LogRel(("VBoxHeadless: WM_QUERYENDSESSION:%s%s%s%s (0x%08lx)\n", + lParam == 0 ? " shutdown" : "", + lParam & ENDSESSION_CRITICAL ? " critical" : "", + lParam & ENDSESSION_LOGOFF ? " logoff" : "", + lParam & ENDSESSION_CLOSEAPP ? " close" : "", + (unsigned long)lParam)); - if (pCmd->cmd == LC_SEGMENT_64) - { - segment_command_64_t const *pSeg = (segment_command_64_t const *)pCmd; - AssertLogRelMsgReturn(cbCurCmd >= sizeof(*pSeg), ("%p: iCmd=%u cbCurCmd=%#x\n", pHdr, iCmd, cbCurCmd), false); - uintptr_t const uPtrSeg = pSeg->vmaddr + offSlide; - uint64_t const cbSeg = pSeg->vmsize; - uint64_t const offFile = pSeg->fileoff; + /* do not block windows session termination */ + lResult = TRUE; + break; - uint64_t offSeg = offSymbols - offFile; - if (offSeg < cbSeg) - { - AssertLogRelMsgReturn(!paSymbols, ("%p: paSymbols=%p uPtrSeg=%p off=%#llx\n", pHdr, paSymbols, uPtrSeg, offSeg), - false); - AssertLogRelMsgReturn(offSeg + cSymbols * sizeof(paSymbols[0]) <= cbSeg, - ("%p: offSeg=%#llx cSymbols=%#x cbSeg=%llx\n", pHdr, offSeg, cSymbols, cbSeg), false); - paSymbols = (macho_nlist_64_t const *)(uPtrSeg + offSeg); + case WM_ENDSESSION: + lResult = 0; + LogRel(("WM_ENDSESSION:%s%s%s%s%s (%s/0x%08lx)\n", + lParam == 0 ? " shutdown" : "", + lParam & ENDSESSION_CRITICAL ? " critical" : "", + lParam & ENDSESSION_LOGOFF ? " logoff" : "", + lParam & ENDSESSION_CLOSEAPP ? " close" : "", + wParam == FALSE ? " cancelled" : "", + wParam ? "TRUE" : "FALSE", + (unsigned long)lParam)); + if (wParam == FALSE) + break; + + /* tell the user what we are doing */ + ::ShutdownBlockReasonCreate(hwnd, + com::BstrFmt("%s saving state", + g_strVMName.c_str()).raw()); + + /* tell the VM to save state/power off */ + g_fTerminateFE = true; + gEventQ->interruptEventQueueProcessing(); + + if (g_hCanQuit != NIL_RTSEMEVENT) + { + LogRel(("VBoxHeadless: WM_ENDSESSION: waiting for VM termination...\n")); + + rc = RTSemEventWait(g_hCanQuit, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + LogRel(("VBoxHeadless: WM_ENDSESSION: done\n")); + else + LogRel(("VBoxHeadless: WM_ENDSESSION: failed to wait for VM termination: %Rrc\n", rc)); } - - offSeg = offStrTab - offFile; - if (offSeg < cbSeg) + else { - AssertLogRelMsgReturn(!pchStrTab, ("%p: paSymbols=%p uPtrSeg=%p\n", pHdr, pchStrTab, uPtrSeg), false); - AssertLogRelMsgReturn(offSeg + cbStrTab <= cbSeg, - ("%p: offSeg=%#llx cbStrTab=%#x cbSeg=%llx\n", pHdr, offSeg, cbStrTab, cbSeg), false); - pchStrTab = (const char *)(uPtrSeg + offSeg); + LogRel(("VBoxHeadless: WM_ENDSESSION: cannot wait for VM termination\n")); } + break; - offSeg = offIndirSymbols - offFile; - if (offSeg < cbSeg) - { - AssertLogRelMsgReturn(!paidxIndirSymbols, - ("%p: paidxIndirSymbols=%p uPtrSeg=%p\n", pHdr, paidxIndirSymbols, uPtrSeg), false); - AssertLogRelMsgReturn(offSeg + cIndirSymbols * sizeof(paidxIndirSymbols[0]) <= cbSeg, - ("%p: offSeg=%#llx cIndirSymbols=%#x cbSeg=%llx\n", pHdr, offSeg, cIndirSymbols, cbSeg), - false); - paidxIndirSymbols = (uint32_t const *)(uPtrSeg + offSeg); - } - } + default: + lResult = ::DefWindowProc(hwnd, msg, wParam, lParam); + break; } + return lResult; +} + + +static const char * const ctrl_event_names[] = { + "CTRL_C_EVENT", + "CTRL_BREAK_EVENT", + "CTRL_CLOSE_EVENT", + /* reserved, not used */ + "", + "", + /* not sent to processes that load gdi32.dll or user32.dll */ + "CTRL_LOGOFF_EVENT", + "CTRL_SHUTDOWN_EVENT", +}; + + +BOOL WINAPI +ConsoleCtrlHandler(DWORD dwCtrlType) /* RT_NOTHROW_DEF */ +{ + const char *signame; + char namebuf[48]; + int rc; - AssertLogRelMsgReturn(paSymbols, ("%p: offSymbols=%#x\n", pHdr, offSymbols), false); - AssertLogRelMsgReturn(pchStrTab, ("%p: offStrTab=%#x\n", pHdr, offStrTab), false); - AssertLogRelMsgReturn(paidxIndirSymbols, ("%p: offIndirSymbols=%#x\n", pHdr, offIndirSymbols), false); + if (dwCtrlType < RT_ELEMENTS(ctrl_event_names)) + signame = ctrl_event_names[dwCtrlType]; + else + { + /* should not happen, but be prepared */ + RTStrPrintf(namebuf, sizeof(namebuf), + "", (unsigned long)dwCtrlType); + signame = namebuf; + } + LogRel(("VBoxHeadless: got %s\n", signame)); + RTMsgInfo("Got %s\n", signame); + RTMsgInfo(""); + + /* tell the VM to save state/power off */ + g_fTerminateFE = true; + gEventQ->interruptEventQueueProcessing(); /* - * Third command pass: Process sections of types S_NON_LAZY_SYMBOL_POINTERS - * and S_LAZY_SYMBOL_POINTERS + * We don't need to wait for Ctrl-C / Ctrl-Break, but we must wait + * for Close, or we will be killed before the VM is saved. */ - bool fFound = false; - offCmd = 0; - for (uint32_t iCmd = 0; iCmd < cCmds; iCmd++) - { - load_command_t const * const pCmd = (load_command_t const *)((uintptr_t)(pHdr + 1) + offCmd); - uint32_t const cbCurCmd = pCmd->cmdsize; - AssertLogRelMsgReturn(offCmd + cbCurCmd <= cbCmds && cbCurCmd <= cbCmds, - ("%p: iCmd=%u offCmd=%#x cbCurCmd=%#x cbCmds=%#x\n", pHdr, iCmd, offCmd, cbCurCmd, cbCmds), false); - offCmd += cbCurCmd; - if (pCmd->cmd == LC_SEGMENT_64) - { - segment_command_64_t const *pSeg = (segment_command_64_t const *)pCmd; - AssertLogRelMsgReturn(cbCurCmd >= sizeof(*pSeg), ("%p: iCmd=%u cbCurCmd=%#x\n", pHdr, iCmd, cbCurCmd), false); - uint64_t const uSegAddr = pSeg->vmaddr; - uint64_t const cbSeg = pSeg->vmsize; - - uint32_t const cSections = pSeg->nsects; - section_64_t const * const paSections = (section_64_t const *)(pSeg + 1); - AssertLogRelMsgReturn(cSections < _256K && sizeof(*pSeg) + cSections * sizeof(paSections[0]) <= cbCurCmd, - ("%p: iCmd=%u cSections=%#x cbCurCmd=%#x\n", pHdr, iCmd, cSections, cbCurCmd), false); - for (uint32_t iSection = 0; iSection < cSections; iSection++) + if (g_hCanQuit != NIL_RTSEMEVENT) + { + LogRel(("VBoxHeadless: waiting for VM termination...\n")); + + rc = RTSemEventWait(g_hCanQuit, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + LogRel(("VBoxHeadless: Failed to wait for VM termination: %Rrc\n", rc)); + } + + /* tell the system we handled it */ + LogRel(("VBoxHeadless: ConsoleCtrlHandler: return\n")); + return TRUE; +} +#endif /* RT_OS_WINDOWS */ + + +/* + * Simplified version of showProgress() borrowed from VBoxManage. + * Note that machine power up/down operations are not cancelable, so + * we don't bother checking for signals. + */ +HRESULT +showProgress(const ComPtr &progress) +{ + BOOL fCompleted = FALSE; + ULONG ulLastPercent = 0; + ULONG ulCurrentPercent = 0; + HRESULT hrc; + + com::Bstr bstrDescription; + hrc = progress->COMGETTER(Description(bstrDescription.asOutParam())); + if (FAILED(hrc)) + { + RTStrmPrintf(g_pStdErr, "Failed to get progress description: %Rhrc\n", hrc); + return hrc; + } + + RTStrmPrintf(g_pStdErr, "%ls: ", bstrDescription.raw()); + RTStrmFlush(g_pStdErr); + + hrc = progress->COMGETTER(Completed(&fCompleted)); + while (SUCCEEDED(hrc)) + { + progress->COMGETTER(Percent(&ulCurrentPercent)); + + /* did we cross a 10% mark? */ + if (ulCurrentPercent / 10 > ulLastPercent / 10) + { + /* make sure to also print out missed steps */ + for (ULONG curVal = (ulLastPercent / 10) * 10 + 10; curVal <= (ulCurrentPercent / 10) * 10; curVal += 10) { - if ( paSections[iSection].flags == S_NON_LAZY_SYMBOL_POINTERS - || paSections[iSection].flags == S_LAZY_SYMBOL_POINTERS) + if (curVal < 100) { - uint32_t const idxIndirBase = paSections[iSection].reserved1; - uint32_t const cEntries = paSections[iSection].size / sizeof(uintptr_t); - AssertLogRelMsgReturn(idxIndirBase <= cIndirSymbols && idxIndirBase + cEntries <= cIndirSymbols, - ("%p: idxIndirBase=%#x cEntries=%#x cIndirSymbols=%#x\n", - pHdr, idxIndirBase, cEntries, cIndirSymbols), false); - uint64_t const uSecAddr = paSections[iSection].addr; - uint64_t const offInSeg = uSecAddr - uSegAddr; - AssertLogRelMsgReturn(offInSeg < cbSeg && offInSeg + cEntries * sizeof(uintptr_t) <= cbSeg, - ("%p: offInSeg=%#llx cEntries=%#x cbSeg=%#llx\n", pHdr, offInSeg, cEntries, cbSeg), - false); - uintptr_t *pauPtrs = (uintptr_t *)(uSecAddr + offSlide); - for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++) - { - uint32_t const idxSym = paidxIndirSymbols[idxIndirBase + iEntry]; - if (idxSym < cSymbols) - { - macho_nlist_64_t const * const pSym = &paSymbols[idxSym]; - const char * const pszName = pSym->n_un.n_strx < cbStrTab - ? &pchStrTab[pSym->n_un.n_strx] : "!invalid symtab offset!"; - if (strcmp(pszName, pszSymbol) == 0) - { - pauPtrs[iEntry] = uNewValue; - fFound = true; - break; - } - } - else - AssertMsg(idxSym == INDIRECT_SYMBOL_LOCAL || idxSym == INDIRECT_SYMBOL_ABS, ("%#x\n", idxSym)); - } + RTStrmPrintf(g_pStdErr, "%u%%...", curVal); + RTStrmFlush(g_pStdErr); } } + ulLastPercent = (ulCurrentPercent / 10) * 10; } + + if (fCompleted) + break; + + gEventQ->processEventQueue(500); + hrc = progress->COMGETTER(Completed(&fCompleted)); } - AssertLogRel(fFound); - return fFound; -} -/** - * Mac OS X: Really ugly hack to bypass a set-uid check in AppKit. - * - * This will modify the issetugid() function to always return zero. This must - * be done _before_ AppKit is initialized, otherwise it will refuse to play ball - * with us as it distrusts set-uid processes since Snow Leopard. We, however, - * have carefully dropped all root privileges at this point and there should be - * no reason for any security concern here. - */ -static void hideSetUidRootFromAppKit() -{ - void *pvAddr; - /* Find issetguid() and make it always return 0 by modifying the code: */ -# if 0 - pvAddr = dlsym(RTLD_DEFAULT, "issetugid"); - int rc = mprotect((void *)((uintptr_t)pvAddr & ~(uintptr_t)0xfff), 0x2000, PROT_WRITE | PROT_READ | PROT_EXEC); - if (!rc) - ASMAtomicWriteU32((volatile uint32_t *)pvAddr, 0xccc3c031); /* xor eax, eax; ret; int3 */ + /* complete the line. */ + LONG iRc = E_FAIL; + hrc = progress->COMGETTER(ResultCode)(&iRc); + if (SUCCEEDED(hrc)) + { + if (SUCCEEDED(iRc)) + RTStrmPrintf(g_pStdErr, "100%%\n"); +#if 0 + else if (g_fCanceled) + RTStrmPrintf(g_pStdErr, "CANCELED\n"); +#endif + else + { + RTStrmPrintf(g_pStdErr, "\n"); + RTStrmPrintf(g_pStdErr, "Operation failed: %Rhrc\n", iRc); + } + hrc = iRc; + } else -# endif { - /* Failing that, find AppKit and patch its import table: */ - void *pvAppKit = dlopen("/System/Library/Frameworks/AppKit.framework/AppKit", RTLD_NOLOAD); - pvAddr = dlsym(pvAppKit, "NSApplicationMain"); - Dl_info Info = {0}; - if ( dladdr(pvAddr, &Info) - && Info.dli_fbase != NULL) - { - if (!patchExtSym((mach_header_64_t *)Info.dli_fbase, "_issetugid", (uintptr_t)&issetugid_for_AppKit)) - write(2, RT_STR_TUPLE("WARNING: Failed to patch issetugid in AppKit! (patchExtSym)\n")); -# ifdef DEBUG - else - write(2, RT_STR_TUPLE("INFO: Successfully patched _issetugid import for AppKit!\n")); -# endif - } - else - write(2, RT_STR_TUPLE("WARNING: Failed to patch issetugid in AppKit! (dladdr)\n")); + RTStrmPrintf(g_pStdErr, "\n"); + RTStrmPrintf(g_pStdErr, "Failed to obtain operation result: %Rhrc\n", hrc); } - + RTStrmFlush(g_pStdErr); + return hrc; } -#endif /* RT_OS_DARWIN */ /** * Entry point. @@ -899,10 +889,6 @@ const char *pcszNameOrUUID = NULL; -#ifdef RT_OS_DARWIN - hideSetUidRootFromAppKit(); -#endif - // parse the command line int ch; const char *pcszSettingsPw = NULL; @@ -1043,6 +1029,7 @@ } HRESULT rc; + int irc; rc = com::Initialize(); #ifdef VBOX_WITH_XPCOM @@ -1120,14 +1107,23 @@ LogError("Invalid machine name or UUID!\n", rc); break; } - Bstr id; - m->COMGETTER(Id)(id.asOutParam()); + + Bstr bstrVMId; + rc = m->COMGETTER(Id)(bstrVMId.asOutParam()); + AssertComRC(rc); + if (FAILED(rc)) + break; + g_strVMUUID = bstrVMId; + + Bstr bstrVMName; + rc = m->COMGETTER(Name)(bstrVMName.asOutParam()); AssertComRC(rc); if (FAILED(rc)) break; + g_strVMName = bstrVMName; Log(("VBoxHeadless: Opening a session with machine (id={%s})...\n", - Utf8Str(id).c_str())); + g_strVMUUID.c_str())); // set session name CHECK_ERROR_BREAK(session, COMSETTER(Name)(Bstr("headless").raw())); @@ -1352,65 +1348,95 @@ Log(("VBoxHeadless: Powering up the machine...\n")); + + /** + * @todo We should probably install handlers earlier so that + * we can undo any temporary settings we do above in case of + * an early signal and use RAII to ensure proper cleanup. + */ +#if !defined(RT_OS_WINDOWS) + signal(SIGPIPE, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + + struct sigaction sa; + RT_ZERO(sa); + sa.sa_handler = HandleSignal; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGUSR1, &sa, NULL); + /* Don't touch SIGUSR2 as IPRT could be using it for RTThreadPoke(). */ + +#else /* RT_OS_WINDOWS */ + /* + * Register windows console signal handler to react to Ctrl-C, + * Ctrl-Break, Close, non-interactive session termination. + */ + ::SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); +#endif + + ComPtr progress; if (!fPaused) CHECK_ERROR_BREAK(console, PowerUp(progress.asOutParam())); else CHECK_ERROR_BREAK(console, PowerUpPaused(progress.asOutParam())); + rc = showProgress(progress); + if (FAILED(rc)) + { + com::ProgressErrorInfo info(progress); + if (info.isBasicAvailable()) + { + RTPrintf("Error: failed to start machine. Error message: %ls\n", info.getText().raw()); + } + else + { + RTPrintf("Error: failed to start machine. No error message available!\n"); + } + break; + } + +#ifdef RT_OS_WINDOWS + /* + * Spawn windows message pump to monitor session events. + */ + RTTHREAD hThrMsg; + irc = RTThreadCreate(&hThrMsg, + windowsMessageMonitor, NULL, + 0, /* :cbStack */ + RTTHREADTYPE_MSG_PUMP, 0, + "MSG"); + if (RT_FAILURE(irc)) /* not fatal */ + LogRel(("VBoxHeadless: failed to start windows message monitor: %Rrc\n", irc)); +#endif /* RT_OS_WINDOWS */ + + /* - * Wait for the result because there can be errors. - * - * It's vital to process events while waiting (teleportation deadlocks), - * so we'll poll for the completion instead of waiting on it. + * Pump vbox events forever */ + LogRel(("VBoxHeadless: starting event loop\n")); for (;;) { - BOOL fCompleted; - rc = progress->COMGETTER(Completed)(&fCompleted); - if (FAILED(rc) || fCompleted) - break; - - /* Process pending events, then wait for new ones. Note, this - * processes NULL events signalling event loop termination. */ - gEventQ->processEventQueue(0); - if (!g_fTerminateFE) - gEventQ->processEventQueue(500); - } - - if (SUCCEEDED(progress->WaitForCompletion(-1))) - { - /* Figure out if the operation completed with a failed status - * and print the error message. Terminate immediately, and let - * the cleanup code take care of potentially pending events. */ - LONG progressRc; - progress->COMGETTER(ResultCode)(&progressRc); - rc = progressRc; - if (FAILED(rc)) + irc = gEventQ->processEventQueue(RT_INDEFINITE_WAIT); + + /* + * interruptEventQueueProcessing from another thread is + * reported as VERR_INTERRUPTED, so check the flag first. + */ + if (g_fTerminateFE) { - com::ProgressErrorInfo info(progress); - if (info.isBasicAvailable()) - { - RTPrintf("Error: failed to start machine. Error message: %ls\n", info.getText().raw()); - } - else - { - RTPrintf("Error: failed to start machine. No error message available!\n"); - } + LogRel(("VBoxHeadless: processEventQueue: %Rrc, termination requested\n", irc)); break; } - } - -#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL - signal(SIGINT, SaveState); - signal(SIGTERM, SaveState); -#endif - Log(("VBoxHeadless: Waiting for PowerDown...\n")); - - while ( !g_fTerminateFE - && RT_SUCCESS(gEventQ->processEventQueue(RT_INDEFINITE_WAIT))) - /* nothing */ ; + if (RT_FAILURE(irc)) + { + LogRel(("VBoxHeadless: processEventQueue: %Rrc\n", irc)); + RTMsgError("event loop: %Rrc", irc); + break; + } + } Log(("VBoxHeadless: event loop has terminated...\n")); @@ -1435,7 +1461,17 @@ */ MachineState_T machineState = MachineState_Aborted; if (!machine.isNull()) - machine->COMGETTER(State)(&machineState); + { + rc = machine->COMGETTER(State)(&machineState); + if (SUCCEEDED(rc)) + Log(("machine state = %RU32\n", machineState)); + else + Log(("IMachine::getState: %Rhrc\n", rc)); + } + else + { + Log(("machine == NULL\n")); + } /* * Turn off the VM if it's running @@ -1450,22 +1486,21 @@ do { consoleListener->getWrapped()->ignorePowerOffEvents(true); + ComPtr pProgress; - CHECK_ERROR_BREAK(gConsole, PowerDown(pProgress.asOutParam())); - CHECK_ERROR_BREAK(pProgress, WaitForCompletion(-1)); - BOOL completed; - CHECK_ERROR_BREAK(pProgress, COMGETTER(Completed)(&completed)); - ASSERT(completed); - LONG hrc; - CHECK_ERROR_BREAK(pProgress, COMGETTER(ResultCode)(&hrc)); - if (FAILED(hrc)) + if (!machine.isNull()) + CHECK_ERROR_BREAK(machine, SaveState(pProgress.asOutParam())); + else + CHECK_ERROR_BREAK(gConsole, PowerDown(pProgress.asOutParam())); + + rc = showProgress(pProgress); + if (FAILED(rc)) { - RTPrintf("VBoxHeadless: ERROR: Failed to power down VM!"); com::ErrorInfo info; if (!info.isFullAvailable() && !info.isBasicAvailable()) - com::GluePrintRCMessage(hrc); + com::GluePrintRCMessage(rc); else - GluePrintErrorInfo(info); + com::GluePrintErrorInfo(info); break; } } while (0); @@ -1521,8 +1556,21 @@ com::Shutdown(); - LogFlow(("VBoxHeadless FINISHED.\n")); +#ifdef RT_OS_WINDOWS + /* tell the session monitor it can ack WM_ENDSESSION */ + if (g_hCanQuit != NIL_RTSEMEVENT) + { + RTSemEventSignal(g_hCanQuit); + } + + /* tell the session monitor to quit */ + if (g_hWindow != NULL) + { + ::PostMessage(g_hWindow, WM_QUIT, 0, 0); + } +#endif + LogRel(("VBoxHeadless: exiting\n")); return FAILED(rc) ? 1 : 0; } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp 2020-10-16 16:36:51.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp 2022-09-01 13:27:38.000000000 +0000 @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -106,6 +107,55 @@ return rc; } +/** + * Helper routine to parse the ExtraData Utf8Str for a storage controller's + * value or channel value. + * + * @param aExtraData The ExtraData string which can have a format of + * either 'controller=13;channel=3' or '11'. + * @param pszKey The string being looked up, usually either 'controller' + * or 'channel' but can be NULL or empty. + * @param puVal The integer value of the 'controller=' or 'channel=' + * key (or the controller number when there is no key) in + * the ExtraData string. + * @returns COM status code. + */ +static int getStorageControllerDetailsFromStr(const com::Utf8Str &aExtraData, const char *pszKey, uint32_t *puVal) +{ + int vrc; + + if (pszKey && *pszKey) + { + size_t posKey = aExtraData.find(pszKey); + if (posKey == Utf8Str::npos) + return VERR_INVALID_PARAMETER; + vrc = RTStrToUInt32Ex(aExtraData.c_str() + posKey + strlen(pszKey), NULL, 0, puVal); + } + else + { + vrc = RTStrToUInt32Ex(aExtraData.c_str(), NULL, 0, puVal); + } + + if (vrc == VWRN_NUMBER_TOO_BIG || vrc == VWRN_NEGATIVE_UNSIGNED) + return VERR_INVALID_PARAMETER; + + return vrc; +} + +static bool isStorageControllerType(VirtualSystemDescriptionType_T avsdType) +{ + switch (avsdType) + { + case VirtualSystemDescriptionType_HardDiskControllerIDE: + case VirtualSystemDescriptionType_HardDiskControllerSATA: + case VirtualSystemDescriptionType_HardDiskControllerSCSI: + case VirtualSystemDescriptionType_HardDiskControllerSAS: + return true; + default: + return false; + } +} + static const RTGETOPTDEF g_aImportApplianceOptions[] = { { "--dry-run", 'n', RTGETOPT_REQ_NOTHING }, @@ -137,12 +187,8 @@ { "-scsitype", 'T', RTGETOPT_REQ_UINT32 }, // deprecated { "--type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated { "-type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated -#if 0 /* Changing the controller is fully valid, but the current design on how - the params are evaluated here doesn't allow two parameter for one - unit. The target disk path is more important. I leave it for future - improvments. */ { "--controller", 'C', RTGETOPT_REQ_STRING }, -#endif + { "--port", 'E', RTGETOPT_REQ_STRING }, { "--disk", 'D', RTGETOPT_REQ_STRING }, { "--options", 'O', RTGETOPT_REQ_STRING }, @@ -286,6 +332,14 @@ mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("controller%u", ulCurUnit)] = ValueUnion.psz; break; + case 'E': // --port + if (actionType == LOCAL && ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys option.", GetState.pDef->pszLong); + if (ulCurUnit == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit option.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("port%u", ulCurUnit)] = ValueUnion.psz; + break; + case 'D': // --disk if (actionType == LOCAL && ulCurVsys == (uint32_t)-1) return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); @@ -751,6 +805,22 @@ } break; + case VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI: + if (fIgnoreThis) + { + RTPrintf("%2u: VirtioSCSI controller, type %ls -- disabled\n", + a, + aVBoxValues[a]); + aEnabled[a] = false; + } + else + RTPrintf("%2u: VirtioSCSI controller, type %ls" + "\n (disable with \"--vsys %u --unit %u --ignore\")\n", + a, + aVBoxValues[a], + i, a); + break; + case VirtualSystemDescriptionType_HardDiskImage: if (fIgnoreThis) { @@ -762,64 +832,252 @@ else { Utf8StrFmt strTypeArg("disk%u", a); + bool fDiskChanged = false; + int vrc; RTCList optionsList = options.toList(); - bstrFinalValue = aVBoxValues[a]; - if (findArgValue(strOverride, pmapArgs, strTypeArg)) { - if (!optionsList.contains(ImportOptions_ImportToVDI)) + if (optionsList.contains(ImportOptions_ImportToVDI)) + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Option --ImportToVDI can not be used together with " + "a manually set target path."); + RTUUID uuid; + /* Check if this is a uuid. If so, don't touch. */ + vrc = RTUuidFromStr(&uuid, strOverride.c_str()); + if (vrc != VINF_SUCCESS) { - RTUUID uuid; - /* Check if this is a uuid. If so, don't touch. */ - int vrc = RTUuidFromStr(&uuid, strOverride.c_str()); - if (vrc != VINF_SUCCESS) + /* Make the path absolute. */ + if (!RTPathStartsWithRoot(strOverride.c_str())) { - /* Make the path absolute. */ - if (!RTPathStartsWithRoot(strOverride.c_str())) - { - char pszPwd[RTPATH_MAX]; - vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX); - if (RT_SUCCESS(vrc)) - strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride); - } + char pszPwd[RTPATH_MAX]; + vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX); + if (RT_SUCCESS(vrc)) + strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride); } - bstrFinalValue = strOverride; } - else - { - //print some error about incompatible command-line arguments + bstrFinalValue = strOverride; + fDiskChanged = true; + } + + strTypeArg.printf("controller%u", a); + bool fControllerChanged = false; + uint32_t uTargetController = (uint32_t)-1; + VirtualSystemDescriptionType_T vsdControllerType = VirtualSystemDescriptionType_Ignore; + Utf8Str strExtraConfigValue; + if (findArgValue(strOverride, pmapArgs, strTypeArg)) + { + vrc = getStorageControllerDetailsFromStr(strOverride, NULL, &uTargetController); + if (RT_FAILURE(vrc)) return errorSyntax(USAGE_IMPORTAPPLIANCE, - "Option --ImportToVDI shall not be used together with " - "manually set target path."); + "Invalid controller value: '%s'", + strOverride.c_str()); - } + vsdControllerType = retTypes[uTargetController]; + if (!isStorageControllerType(vsdControllerType)) + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Invalid storage controller specified: %u", + uTargetController); - RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n", - a, - aOvfValues[a], - bstrFinalValue.raw(), - aExtraConfigValues[a]); + fControllerChanged = true; } -#if 0 /* Changing the controller is fully valid, but the current design on how - the params are evaluated here doesn't allow two parameter for one - unit. The target disk path is more important I leave it for future - improvments. */ - Utf8StrFmt strTypeArg("controller%u", a); + + strTypeArg.printf("port%u", a); + bool fControllerPortChanged = false; + uint32_t uTargetControllerPort = (uint32_t)-1;; if (findArgValue(strOverride, pmapArgs, strTypeArg)) { - // strOverride now has the controller index as a number, but we - // need a "controller=X" format string - strOverride = Utf8StrFmt("controller=%s", strOverride.c_str()); + vrc = getStorageControllerDetailsFromStr(strOverride, NULL, &uTargetControllerPort); + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Invalid port value: '%s'", + strOverride.c_str()); + + fControllerPortChanged = true; + } + + /* + * aExtraConfigValues[a] has a format of 'controller=12;channel=0' and is set by + * Appliance::interpret() so any parsing errors here aren't due to user-supplied + * values so different error messages here. + */ + uint32_t uOrigController; + Utf8Str strOrigController(Bstr(aExtraConfigValues[a]).raw()); + vrc = getStorageControllerDetailsFromStr(strOrigController, "controller=", &uOrigController); + if (RT_FAILURE(vrc)) + return RTMsgErrorExitFailure("Failed to extract controller value from ExtraConfig: '%s'", + strOrigController.c_str()); + + uint32_t uOrigControllerPort; + vrc = getStorageControllerDetailsFromStr(strOrigController, "channel=", &uOrigControllerPort); + if (RT_FAILURE(vrc)) + return RTMsgErrorExitFailure("Failed to extract channel value from ExtraConfig: '%s'", + strOrigController.c_str()); + + /* + * The 'strExtraConfigValue' string is used to display the storage controller and + * port details for each virtual hard disk using the more accurate 'controller=' and + * 'port=' labels. The aExtraConfigValues[a] string has a format of + * 'controller=%u;channel=%u' from Appliance::interpret() which is required as per + * the API but for consistency and clarity with the CLI options --controller and + * --port we instead use strExtraConfigValue in the output below. + */ + strExtraConfigValue = Utf8StrFmt("controller=%u;port=%u", uOrigController, uOrigControllerPort); + + if (fControllerChanged || fControllerPortChanged) + { + /* + * Verify that the new combination of controller and controller port is valid. + * cf. StorageController::i_checkPortAndDeviceValid() + */ + if (uTargetControllerPort == (uint32_t)-1) + uTargetControllerPort = uOrigControllerPort; + if (uTargetController == (uint32_t)-1) + uTargetController = uOrigController; + + if ( uOrigController == uTargetController + && uOrigControllerPort == uTargetControllerPort) + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Device already attached to controller %u at this port (%u) location.", + uTargetController, + uTargetControllerPort); + + if (vsdControllerType == VirtualSystemDescriptionType_Ignore) + vsdControllerType = retTypes[uOrigController]; + if (!isStorageControllerType(vsdControllerType)) + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Invalid storage controller specified: %u", + uOrigController); + + ComPtr pVirtualBox = arg->virtualBox; + ComPtr systemProperties; + CHECK_ERROR(pVirtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam())); + ULONG maxPorts = 0; + StorageBus_T enmStorageBus = StorageBus_Null;; + switch (vsdControllerType) + { + case VirtualSystemDescriptionType_HardDiskControllerIDE: + enmStorageBus = StorageBus_IDE; + break; + case VirtualSystemDescriptionType_HardDiskControllerSATA: + enmStorageBus = StorageBus_SATA; + break; + case VirtualSystemDescriptionType_HardDiskControllerSCSI: + enmStorageBus = StorageBus_SCSI; + break; + case VirtualSystemDescriptionType_HardDiskControllerSAS: + enmStorageBus = StorageBus_SAS; + break; + default: // Not reached since vsdControllerType validated above but silence gcc. + break; + } + CHECK_ERROR_RET(systemProperties, GetMaxPortCountForStorageBus(enmStorageBus, &maxPorts), + RTEXITCODE_FAILURE); + if (uTargetControllerPort >= maxPorts) + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Illegal port value: %u. For %ls controllers the only valid values " + "are 0 to %lu (inclusive)", + uTargetControllerPort, + aVBoxValues[uTargetController], + maxPorts); + + /* + * The 'strOverride' string will be mapped to the strExtraConfigCurrent value in + * VirtualSystemDescription::setFinalValues() which is then used in the appliance + * import routines i_importVBoxMachine()/i_importMachineGeneric() later. This + * aExtraConfigValues[] array entry must have a format of + * 'controller=;channel=' as per the API documentation. + */ + strExtraConfigValue = Utf8StrFmt("controller=%u;port=%u", uTargetController, + uTargetControllerPort); + strOverride = Utf8StrFmt("controller=%u;channel=%u", uTargetController, + uTargetControllerPort); Bstr bstrExtraConfigValue = strOverride; bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]); - RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n", - a, - aOvfValues[a], - aVBoxValues[a], - aExtraConfigValues[a]); } -#endif + + if (fDiskChanged && !fControllerChanged && !fControllerPortChanged) + { + RTPrintf("%2u: " + "Hard disk image specified with --disk: source image=%ls, target path=%ls, %s" + "\n (change controller with \"--vsys %u --unit %u --controller \";" + "\n change controller port with \"--vsys %u --unit %u --port \")\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + strExtraConfigValue.c_str(), + i, a, + i, a); + } + else if (fDiskChanged && fControllerChanged && !fControllerPortChanged) + { + RTPrintf("%2u: " + "Hard disk image specified with --disk and --controller: source image=%ls, target path=%ls, %s" + "\n (change controller port with \"--vsys %u --unit %u --port \")\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + strExtraConfigValue.c_str(), + i, a); + } + else if (fDiskChanged && !fControllerChanged && fControllerPortChanged) + { + RTPrintf("%2u: " + "Hard disk image specified with --disk and --port: source image=%ls, target path=%ls, %s" + "\n (change controller with \"--vsys %u --unit %u --controller \")\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + strExtraConfigValue.c_str(), + i, a); + } + else if (!fDiskChanged && fControllerChanged && fControllerPortChanged) + { + RTPrintf("%2u: " + "Hard disk image specified with --controller and --port: source image=%ls, target path=%ls, %s" + "\n (change target path with \"--vsys %u --unit %u --disk path\")\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + strExtraConfigValue.c_str(), + i, a); + } + else if (!fDiskChanged && !fControllerChanged && fControllerPortChanged) + { + RTPrintf("%2u: " + "Hard disk image specified with --port: source image=%ls, target path=%ls, %s" + "\n (change target path with \"--vsys %u --unit %u --disk path\";" + "\n change controller with \"--vsys %u --unit %u --controller \")\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + strExtraConfigValue.c_str(), + i, a, + i, a); + } + else if (!fDiskChanged && fControllerChanged && !fControllerPortChanged) + { + RTPrintf("%2u: " + "Hard disk image specified with --controller: source image=%ls, target path=%ls, %s" + "\n (change target path with \"--vsys %u --unit %u --disk path\";" + "\n change controller port with \"--vsys %u --unit %u --port \")\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + strExtraConfigValue.c_str(), + i, a, + i, a); + } + else if (fDiskChanged && fControllerChanged && fControllerPortChanged) + { + RTPrintf("%2u: " + "Hard disk image specified with --disk and --controller and --port: " + "source image=%ls, target path=%ls, %s\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + strExtraConfigValue.c_str()); + } else { strOverride = aVBoxValues[a]; @@ -830,7 +1088,7 @@ * Appliance::i_findMediumFormatFromDiskImage() * and creating one new function which returns * struct ovf::DiskImage for currently processed disk. - */ + */ /* * if user wants to convert all imported disks to VDI format @@ -899,14 +1157,16 @@ bstrFinalValue = strOverride; - RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls" + RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %s" "\n (change target path with \"--vsys %u --unit %u --disk path\";" + "\n change controller with \"--vsys %u --unit %u --controller \";" + "\n change controller port with \"--vsys %u --unit %u --port \";" "\n disable with \"--vsys %u --unit %u --ignore\")\n", - a, - aOvfValues[a], - bstrFinalValue.raw(), - aExtraConfigValues[a], - i, a, i, a); + a, aOvfValues[a], bstrFinalValue.raw(), strExtraConfigValue.c_str(), + i, a, + i, a, + i, a, + i, a); } } break; @@ -1081,6 +1341,7 @@ case VirtualSystemDescriptionType_CloudOCISubnetCompartment: case VirtualSystemDescriptionType_CloudPublicSSHKey: case VirtualSystemDescriptionType_BootingFirmware: + case VirtualSystemDescriptionType_CloudInitScriptPath: /** @todo VirtualSystemDescriptionType_Miscellaneous? */ break; @@ -1176,7 +1437,6 @@ { "--ovf20", '2', RTGETOPT_REQ_NOTHING }, { "--opc10", 'c', RTGETOPT_REQ_NOTHING }, { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options - { "--iso", 'I', RTGETOPT_REQ_NOTHING }, // obsoleted by --options { "--vsys", 's', RTGETOPT_REQ_UINT32 }, { "--vmname", 'V', RTGETOPT_REQ_STRING }, { "--product", 'p', RTGETOPT_REQ_STRING }, @@ -1201,6 +1461,7 @@ { "--cloudlaunchinstance", 'L', RTGETOPT_REQ_STRING }, { "--cloudlaunchmode", 'M', RTGETOPT_REQ_STRING }, { "--cloudprivateip", 'i', RTGETOPT_REQ_STRING }, + { "--cloudinitscriptpath", 'I', RTGETOPT_REQ_STRING }, }; RTEXITCODE handleExportAppliance(HandlerArg *a) @@ -1257,9 +1518,9 @@ strOvfFormat = "opc-1.0"; break; - case 'I': // --iso - fExportISOImages = true; - break; +// case 'I': // --iso +// fExportISOImages = true; +// break; case 'm': // --manifest fManifest = true; @@ -1438,6 +1699,13 @@ mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchmode"] = ValueUnion.psz; break; + case 'I': // --cloudinitscriptpath + if (actionType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudinitscriptpath"] = ValueUnion.psz; + break; + case VINF_GETOPT_NOT_OPTION: { Utf8Str strMachine(ValueUnion.psz); @@ -1630,6 +1898,9 @@ else if (itD->first == "cloudlaunchinstance") pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance, Bstr(itD->second).raw(), NULL); + else if (itD->first == "cloudinitscriptpath") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudInitScriptPath, + Bstr(itD->second).raw(), NULL); } } diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageCloud.cpp virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageCloud.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageCloud.cpp 2020-10-16 16:36:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageCloud.cpp 2022-09-01 13:27:38.000000000 +0000 @@ -484,6 +484,7 @@ { "--privateip", 'P', RTGETOPT_REQ_STRING }, { "--launch", 'l', RTGETOPT_REQ_STRING }, { "--public-ssh-key", 'k', RTGETOPT_REQ_STRING }, + { "--cloud-init-script-path", 'c', RTGETOPT_REQ_STRING }, }; RTGETOPTSTATE GetState; RTGETOPTUNION ValueUnion; @@ -563,6 +564,10 @@ pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicSSHKey, Bstr(ValueUnion.psz).raw(), NULL); break; + case 'c': + pVSD->AddDescription(VirtualSystemDescriptionType_CloudInitScriptPath, + Bstr(ValueUnion.psz).raw(), NULL); + break; case VINF_GETOPT_NOT_OPTION: return errorUnknownSubcommand(ValueUnion.psz); default: @@ -726,7 +731,7 @@ Utf8Str strNotFound; }; - const size_t vsdHReadableArraySize = 12;//the number of items in the vsdHReadableArray + const size_t vsdHReadableArraySize = 13;//the number of items in the vsdHReadableArray vsdHReadable vsdHReadableArray[vsdHReadableArraySize] = { {VirtualSystemDescriptionType_CloudDomain, "Availability domain = %ls\n", "Availability domain wasn't found\n"}, {VirtualSystemDescriptionType_Name, "Instance displayed name = %ls\n", "Instance displayed name wasn't found\n"}, @@ -741,7 +746,8 @@ {VirtualSystemDescriptionType_Memory, "RAM = %ls MB\n", "Value for RAM wasn't found\n"}, {VirtualSystemDescriptionType_CPU, "CPUs = %ls\n", "Numbers of CPUs weren't found\n"}, {VirtualSystemDescriptionType_CloudPublicIP, "Instance public IP = %ls\n", "Public IP wasn't found\n"}, - {VirtualSystemDescriptionType_Miscellaneous, "%ls\n", "Free-form tags or metadata weren't found\n"} + {VirtualSystemDescriptionType_Miscellaneous, "%ls\n", "Free-form tags or metadata weren't found\n"}, + {VirtualSystemDescriptionType_CloudInitScriptPath, "%ls\n", "Cloud-init script wasn't found\n"} }; com::SafeArray retTypes; diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp 2020-10-16 16:36:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp 2022-09-01 13:27:38.000000000 +0000 @@ -1818,6 +1818,14 @@ } else if (!RTStrICmp(pszAction, "get")) { + /* + * Trigger a call to Medium::i_queryInfo()->VDOpen()->pfnOpen() to + * open the virtual device and populate its properties for + * Medium::getProperty() to retrieve. + */ + MediumState_T state; + CHECK_ERROR(pMedium, RefreshState(&state)); + Bstr strVal; CHECK_ERROR(pMedium, GetProperty(Bstr(pszProperty).raw(), strVal.asOutParam())); if (SUCCEEDED(rc)) diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp 2020-10-16 16:36:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp 2022-09-01 13:27:38.000000000 +0000 @@ -3361,11 +3361,6 @@ { AssertPtr(pArg); -#ifdef DEBUG_andy_disabled - if (RT_FAILURE(tstTranslatePath())) - return RTEXITCODE_FAILURE; -#endif - /* * Command definitions. */ diff -Nru virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp --- virtualbox-6.1.16-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp 2020-10-16 16:36:52.000000000 +0000 +++ virtualbox-6.1.38-dfsg/src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp 2022-09-01 13:27:38.000000000 +0000 @@ -751,7 +751,7 @@ #endif #ifdef VBOX_WITH_RECORDING " [--recording on|off]\n" - " [--recordingscreens all| [ ...]]\n" + " [--recordingscreens all|none| [ ...]]\n" " [--recordingfile ]\n" " [--recordingvideores ]\n" " [--recordingvideorate ]\n" @@ -790,7 +790,6 @@ "%s export %s --output|-o .\n" " [--legacy09|--ovf09|--ovf10|--ovf20|--opc10]\n" " [--manifest]\n" - " [--iso]\n" " [--options manifest|iso|nomacs|nomacsbutnat]\n" " [--vsys ]\n" " [--vmname ]\n" @@ -816,6 +815,7 @@ " [--cloudocisubnet ]\n" " [--cloudpublicip ]\n" " [--cloudprivateip ]\n" + " [--cloudinitscriptpath