├── mhdaplay.bat ├── mhdastat.bat ├── History.txt ├── dpmi.inc ├── license.txt ├── Readme.txt ├── setargv.inc ├── printf.inc ├── hda.inc ├── hdastat.asm └── hdaplay.asm /mhdaplay.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem 3 | rem create hdaplay.exe, using jwasm and HXDEV tools: 4 | rem loadpe.bin: simple PE loader 5 | rem pestub: exchange DOS stub of PE binary 6 | rem patchpe: change PE to PX, set stack and heap values 7 | rem 8 | jwasm -nologo -pe -Sg -Fl=build\hdaplay.lst -Fo=build\hdaplay.exe hdaplay.asm 9 | pestub -n -q build\hdaplay.exe loadpe.bin 10 | patchpe -x -s:8192 -h:0 build\hdaplay.exe 11 | -------------------------------------------------------------------------------- /mhdastat.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem 3 | rem create hdastat.exe, using jwasm and HXDEV tools: 4 | rem loadpe.bin: simple PE loader 5 | rem pestub: exchange DOS stub of PE binary 6 | rem patchpe: change PE to PX, set stack and heap values 7 | rem 8 | jwasm -nologo -pe -Sg -Fl=build\hdastat.lst -Fo=build\hdastat.exe hdastat.asm 9 | pestub -n -q build\hdastat.exe loadpe.bin 10 | patchpe -x -s:8192 -h:0 build\hdastat.exe 11 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | 2 | 03/2024: v1.2 3 | - hdastat: translate PCI capabilities; display if MSI is enabled. 4 | - more detailed output with -v. 5 | - hdaplay: option -h added to route output to headphone. 6 | - hdaplay: changed option -w; added options -c and -d. 7 | 8 | 01/2024: v1.1 9 | - hdaplay: display bit depths and sample rates supported by codec. 10 | - ensured afg power state is enabled. 11 | 12 | 12/2021: v1.0 13 | - initial. 14 | -------------------------------------------------------------------------------- /dpmi.inc: -------------------------------------------------------------------------------- 1 | 2 | ;*** definitions DOS Protected Mode Interface *** 3 | 4 | RMCS struct ;real mode call structure 5 | union 6 | rEDI dd ? 7 | rDI dw ? 8 | ends 9 | union 10 | rESI dd ? 11 | rSI dw ? 12 | ends 13 | union 14 | rEBP dd ? 15 | rBP dw ? 16 | ends 17 | resvrd dd ? 18 | union 19 | rEBX dd ? 20 | rBX dw ? 21 | ends 22 | union 23 | rEDX dd ? 24 | rDX dw ? 25 | ends 26 | union 27 | rECX dd ? 28 | rCX dw ? 29 | ends 30 | union 31 | rEAX dd ? 32 | rAX dw ? 33 | ends 34 | rFlags dw ? 35 | rES dw ? 36 | rDS dw ? 37 | rFS dw ? 38 | rGS dw ? 39 | union 40 | rCSIP dd ? 41 | struct 42 | rIP dw ? 43 | rCS dw ? 44 | ends 45 | ends 46 | union 47 | rSSSP dd ? 48 | struct 49 | rSP dw ? 50 | rSS dw ? 51 | ends 52 | ends 53 | RMCS ends 54 | 55 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2021 Andreas Grech (japheth) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /Readme.txt: -------------------------------------------------------------------------------- 1 | 2 | HDAutils consists of 3 | 4 | 1. HDAstat 5 | 6 | Displays status of HD Audio controller and Audio Function Group (AFG) node. 7 | 8 | 2. HDAplay 9 | 10 | Loads a PCM-coded file ( RIFF format, filenames usually having a .wav 11 | extension ) into memory, searches a path to the lineout pin, then starts 12 | the HDA DMA engine to play and finally launches a shell. Exiting the shell 13 | will terminate the audio and release all resources. 14 | 15 | Hints 16 | 17 | - For notebooks it might be necessary to add option -s to use the speaker 18 | or -h to use the headphone instead of lineout. 19 | - HDAplay is no TSR. It's absolutely inactive while the shell command 20 | processor is running. The only thing that's running in the background is 21 | the HDA controller's DMA engine. 22 | - HDAplay won't do any format conversions. If the codec attached to the HDA 23 | controller doesn't support the format of the audio data supplied, then 24 | the output may sound "strange". 25 | - HDAplay: option -w, possibly combined with -c and/or -d is only useful 26 | in rare cases; usually options -s or -h should suffice to redirect output. 27 | 28 | For license see file license.txt 29 | 30 | -------------------------------------------------------------------------------- /setargv.inc: -------------------------------------------------------------------------------- 1 | 2 | ;--- translate cmdline at psp:80h to argc & argv 3 | 4 | DELIM MACRO target 5 | cmp al,13 ;; Test for end-of-line character 6 | je target 7 | or al,al ;; Either CR or Null 8 | je target 9 | ENDM 10 | 11 | .code 12 | 13 | ;--- DS=ES=SS=flat 14 | 15 | _setargv proc c 16 | 17 | pop esi 18 | mov ebp,esp 19 | sub esp,100h 20 | mov edi,esp 21 | mov byte ptr [edi+1Dh],51h ;AH 22 | mov word ptr [edi+20h],3202h ;Flags 23 | mov dword ptr [edi+2Eh],0 ;SS:SP 24 | mov bx,21h 25 | mov cx,0 26 | mov ax,0300h 27 | int 31h 28 | push esi 29 | movzx esi,word ptr [edi+10h] ;BX 30 | shl esi,4 31 | 32 | xor eax,eax 33 | 34 | push eax ; save this address 35 | xor edi,edi ; init argc 36 | xor edx,edx ; init size of mem block 37 | add esi,81H 38 | jmp arg110 39 | 40 | ;--- EDI holds argc 41 | ;--- EDX holds block size (not including null terminators) 42 | arg100: 43 | push ebx 44 | arg110: 45 | @@: 46 | lodsb 47 | cmp al,' ' 48 | je @B 49 | cmp al,9 50 | je @B 51 | DELIM arg400 ; exit if eol 52 | inc edi ; another argument 53 | mov ebx,0 ; init argument size 54 | mov cx,0920h 55 | cmp al,'"' 56 | jnz @F 57 | mov cx,2222h 58 | jmp quotedstr 59 | @@: 60 | dec esi ; back up to reload character 61 | quotedstr: 62 | push esi ; save start of argument 63 | @@: 64 | lodsb 65 | cmp al,cl ; end argument? 66 | je arg100 67 | cmp al,ch 68 | je arg100 ; white space terminates argument 69 | DELIM arg399 ; exit if eol 70 | inc ebx 71 | inc edx 72 | jmp @B 73 | arg399: 74 | push ebx ; last argument 75 | arg400: 76 | ;--- address & size of arguments are pushed 77 | mov ecx,edi ; Store number of arguments 78 | add edx,edi ; add in terminator bytes 79 | inc edi ; add one for NULL pointer 80 | inc edi ; add one for filename 81 | shl edi,2 ; each pointer needs 4 bytes 82 | add edx,edi ; add space for pointers to space for chars 83 | 84 | add edx,4-1 85 | and dl,0fch 86 | sub ebp,edx 87 | mov eax,ebp 88 | 89 | ;argc = [ebp-4] 90 | ;argv = [ebp-8] 91 | 92 | mov [ebp-4],ecx 93 | mov [ebp-8],eax 94 | mov edx,ecx 95 | add edi,eax ; edi -> behind vector table (strings) 96 | lea ebx,[edi-4] 97 | mov dword ptr [ebx],0 98 | sub ebx,4 99 | cmp edx,0 100 | jz arg500 101 | @@: 102 | pop ecx ; size 103 | pop esi ; address 104 | mov [ebx],edi 105 | sub ebx,4 106 | rep movsb 107 | xor al,al 108 | stosb 109 | dec edx 110 | jnz @B 111 | arg500: 112 | pop eax 113 | mov [ebx],eax ; store address filename as first item 114 | mov eax,[ebp-4] 115 | mov edx,[ebp-8] 116 | inc eax 117 | ret 118 | _setargv endp 119 | 120 | -------------------------------------------------------------------------------- /printf.inc: -------------------------------------------------------------------------------- 1 | 2 | ;--- simple printf implementation for DOS 32-bit 3 | ;--- supports: 4 | ;--- %x : dword 5 | ;--- %lx : qword 6 | ;--- %u : dword 7 | ;--- %lu : qword 8 | ;--- %d : sdword 9 | ;--- %ld : sqword 10 | ;--- %s : near32 string out 11 | ;--- %c : character 12 | 13 | .386 14 | 15 | ;--- i64toa(long long n, char * s, int base); 16 | ;--- convert 64-bit long long to string 17 | 18 | i64toa PROC stdcall uses esi edi number:qword, outb:ptr, base:dword 19 | 20 | mov ch,0 21 | mov edi, base 22 | mov eax, dword ptr number+0 23 | mov esi, dword ptr number+4 24 | cmp edi,-10 25 | jne @F 26 | neg edi 27 | and esi,esi 28 | jns @F 29 | neg esi 30 | neg eax 31 | sbb esi,0 32 | mov ch,'-' 33 | @@: 34 | mov ebx,outb 35 | add ebx,22 36 | mov byte ptr [ebx],0 37 | @@nextdigit: 38 | dec ebx 39 | xor edx,edx 40 | xchg eax,esi 41 | div edi 42 | xchg eax,esi 43 | div edi 44 | add dl,'0' 45 | cmp dl,'9' 46 | jbe @F 47 | add dl,7+20h 48 | @@: 49 | mov [ebx],dl 50 | mov edx, eax 51 | or edx, esi 52 | jne @@nextdigit 53 | cmp ch,0 54 | je @F 55 | dec ebx 56 | mov [ebx],ch 57 | @@: 58 | mov eax,ebx 59 | ret 60 | 61 | i64toa ENDP 62 | 63 | printf PROC c uses ebx esi edi fmt:ptr sbyte, args:VARARG 64 | 65 | local flag:byte 66 | local longarg:byte 67 | local size_:dword 68 | local fillchr:dword 69 | local szTmp[24]:byte 70 | 71 | lea edi,args 72 | @@L335: 73 | mov esi,fmt 74 | nextchar: 75 | lodsb 76 | or al,al 77 | je done 78 | cmp al,'%' 79 | je formatitem 80 | push eax 81 | call handle_char 82 | jmp nextchar 83 | done: 84 | xor eax,eax 85 | ret 86 | 87 | formatitem: 88 | push offset @@L335 89 | xor edx,edx 90 | mov [longarg],dl 91 | mov bl,1 92 | mov cl,' ' 93 | cmp BYTE PTR [esi],'-' 94 | jne @F 95 | dec bl 96 | inc esi 97 | @@: 98 | mov [flag],bl 99 | cmp BYTE PTR [esi],'0' 100 | jne @F 101 | mov cl,'0' 102 | inc esi 103 | @@: 104 | mov [fillchr],ecx 105 | mov ebx,edx 106 | 107 | .while ( byte ptr [esi] >= '0' && byte ptr [esi] <= '9' ) 108 | lodsb 109 | sub al,'0' 110 | movzx eax,al 111 | imul ecx,ebx,10 ;ecx = ebx * 10 112 | add eax,ecx 113 | mov ebx,eax 114 | .endw 115 | 116 | mov [size_],ebx 117 | cmp BYTE PTR [esi],'l' 118 | jne @F 119 | mov [longarg],1 120 | inc esi 121 | @@: 122 | lodsb 123 | mov [fmt],esi 124 | cmp al,'x' 125 | je handle_x 126 | cmp al,'X' 127 | je handle_x 128 | cmp al,'d' 129 | je handle_d 130 | cmp al,'u' 131 | je handle_u 132 | cmp al,'s' 133 | je handle_s 134 | cmp al,'c' 135 | je handle_c 136 | and al,al 137 | jnz @F 138 | pop eax 139 | jmp done 140 | handle_c: 141 | mov eax,[edi] 142 | add edi, 4 143 | @@: 144 | push eax 145 | call handle_char 146 | retn 147 | 148 | handle_s: 149 | mov esi,[edi] 150 | add edi,4 151 | jmp print_string 152 | handle_d: 153 | handle_i: 154 | mov ebx,-10 155 | jmp @F 156 | handle_u: 157 | mov ebx, 10 158 | jmp @F 159 | handle_x: 160 | mov ebx, 16 161 | @@: 162 | xor edx,edx 163 | mov eax,[edi] 164 | add edi,4 165 | cmp longarg,1 166 | jnz @F 167 | mov edx,[edi] 168 | add edi,4 169 | jmp printnum 170 | @@: 171 | and ebx,ebx 172 | jns @F 173 | cdq 174 | @@: 175 | printnum: 176 | lea esi, szTmp 177 | invoke i64toa, edx::eax, esi, ebx 178 | mov esi, eax 179 | 180 | print_string: ;print string ESI, size EAX 181 | mov eax, esi 182 | .while byte ptr [esi] 183 | inc esi 184 | .endw 185 | sub esi, eax 186 | xchg eax, esi 187 | mov ebx,size_ 188 | sub ebx,eax 189 | .if flag == 1 190 | .while sdword ptr ebx > 0 191 | push [fillchr] 192 | call handle_char ;print leading filler chars 193 | dec ebx 194 | .endw 195 | .endif 196 | 197 | .while byte ptr [esi] 198 | lodsb 199 | push eax 200 | call handle_char ;print char of string 201 | .endw 202 | 203 | .while sdword ptr ebx > 0 204 | push [fillchr] 205 | call handle_char ;print trailing spaces 206 | dec ebx 207 | .endw 208 | retn 209 | 210 | handle_char: 211 | pop ecx 212 | pop edx 213 | cmp dl,10 214 | jnz @F 215 | mov dl,13 216 | mov ah,2 217 | int 21h 218 | mov dl,10 219 | @@: 220 | mov ah,2 221 | int 21h 222 | jmp ecx 223 | align 4 224 | 225 | printf ENDP 226 | 227 | 228 | -------------------------------------------------------------------------------- /hda.inc: -------------------------------------------------------------------------------- 1 | 2 | ;--- stream descriptors: 32 bytes, starting at offset 80h (input) 3 | 4 | STREAM struct 8 5 | wCtl dw ? ;+0 15:5 rsvd, 4 DEIE desc err int, 3 FEIE fifo err int, 2 IOCE int on compl, 1 RUN, 0 SRST stream reset 6 | bCtl2316 db ? ;+2 23:20 stream number (0=reserved, 1-15 = stream 1-15) 7 | bSts db ? ;+3 8 | dwLinkPos dd ? ;+4 9 | dwBufLen dd ? ;+8 10 | wLastIdx dw ? ;+12 11 | wFIFOmark dw ? ;+14 12 | wFIFOsize dw ? ;+16 13 | wFormat dw ? ;+18 14 | qwBuffer dq ? ;+24 15 | STREAM ends 16 | 17 | ;--- format: 18 | ;--- [15] stream type: 0=PCM, 1=non-PCM 19 | ;--- [14] sample base rate: 0=48 kHz, 1=44.1 kHz 20 | ;--- [13:11]: sample base rate multiple: 0=x1 or less,1=x2,2=x3,3=x4,4-7 reserved 21 | ;--- [10:8]: sample base rate divisor: 0=div 1,1=div 2,2=div 3,3=div 4,4=div 5,5=div 6,6=div 7,7=div 8 22 | ;--- [7]: reserved 23 | ;--- [6:4]: bits per sample: 0=8, 1=16, 2=20, 3=24, 4=32, 5-7 reserved 24 | ;--- [3:0]: number of channels: 0=1, 1=2, ... 15=16 25 | 26 | ;--- gctl: [0] CRST: writing 0 -> controller resets 27 | ;--- writing 1 -> exit reset 28 | 29 | ;--- structure of HDA controller memory-mapped registers 30 | 31 | ;gcap: 12-15:output streams (max 15); 8-11:input streams (max 15); 3-7:bidir streams; 32 | ; 1-2: number of serial data out signals; 0: 64-bit addr supported 33 | ;intctl/intsts: 34 | ; 31: GIE/GIS global interrupt enable 35 | ; 30: CIE/CIS controller interrupt enable 36 | ; 0-29: SIE/SIS stream interrupt enable (input, output, bidirectional) 37 | 38 | HDAREGS struct 8 39 | gcap dw ? 40 | vminor db ? 41 | vmajor db ? 42 | opayload dw ? 43 | ipayload dw ? 44 | gctl dd ? 45 | wakeen dw ? ;+0Ch same bits as statests 46 | statests dw ? ;+0Eh also named WAKESTS in specs 47 | gsts dd ? ;+10h 48 | dd ?,?,? 49 | intctl dd ? ;+20h 50 | intsts dd ? ;+24h 51 | dd ?,? 52 | walclk dd ? ;+30h 53 | dd ? 54 | ssync dd ? 55 | dd ? 56 | corbbase dq ? ;+40h size of CORB 256*4 = 1024 57 | corbwp dw ? ;+48h RW, size of ptr is a byte only, bits 8-15 are reserved 58 | corbrp dw ? ;+4Ah RW, size of ptr is a byte only, bit 15=1 to reset 59 | corbctl db ? ;+4Ch 60 | corbsts db ? 61 | corbsize db ? ;2, 16 or 256 entries (bits 1:0) 62 | db ? 63 | rirbbase dq ? ;+50h size of RIRB 256*8 = 2048 64 | rirbwp dw ? ;+58h 65 | rirbric dw ? ;+5Ah 66 | rirbctl db ? ;+5Ch 67 | rirbsts db ? ; bit 0; write 1 to clear 68 | rirbsize db ? 69 | db ? 70 | ic dd ? ;+60h immediate command register 71 | ir dd ? ;immediate response register 72 | ics dw ? ;immediate command status register 73 | org 80h 74 | stream0 STREAM <> 75 | stream1 STREAM <> 76 | stream2 STREAM <> 77 | stream3 STREAM <> 78 | stream4 STREAM <> 79 | stream5 STREAM <> 80 | stream6 STREAM <> 81 | stream7 STREAM <> 82 | HDAREGS ends 83 | 84 | ;--- bits in ics 85 | ;--- bit 0 (immediate command busy): 1=busy (set to 1 by software to run command) 86 | ;--- bit 1 (immediate result valid): 1=valid new response. 87 | ;--- reset by software (by writing 1 to it!) before a new response is expected. 88 | 89 | ICS_ICB equ 1 90 | ICS_IRV equ 2 91 | 92 | ;--- DMA position in current buffer 93 | ;--- must be aligned to 128-byte 94 | ;--- for stream descriptor 0: dd pos, rsvd 95 | ;--- for stream descriptor 1: dd pos, rsvd, 96 | ;--- .... 97 | 98 | ;--- structure of BDL entry (size 16 bytes) 99 | ;--- start of BDL itself must be 128-byte aligned 100 | 101 | BDLENTRY struct 102 | qwAddr dq ? 103 | dwLen dd ? 104 | dwFlgs dd ? ;bit 0:1=IOC (interrupt on completion) 105 | BDLENTRY ends 106 | 107 | ;--- CORB: buffer must start on a 128-byte boundary, size in CORBSIZE 108 | ;--- each entry is 32-bits 109 | ;--- corb read pointer (CORBRP): write 1 to [15] to reset this pointer to 0 110 | ;--- if reset is complete, [15] will be 1. 111 | ;--- RIRB: buffer must start on a 128-byte boundary, size in RIRBSIZE 112 | ;--- each entry is 64-bits: response, response_ex (0-3: codec, 4: 1=unsolicited response) 113 | 114 | ;--- codec communication. 115 | ;--- 0003: set amplifier gain/mute 116 | ;--- bit 15: set output amp 117 | ;--- bit 14: set input amp 118 | ;--- bit 13: set left amp 119 | ;--- bit 12: set right amp 120 | ;--- bit 11-8: index (usually 0) 121 | ;--- bit 7: mute 122 | ;--- bit 6-0: gain 123 | ; 124 | ;--- default configuration (F1C) 125 | ;--- 31:30 port connectivity (01=not connected) 126 | ;--- 29:24 location 127 | ;--- 23:20 default device 128 | ;--- 19:16 connection type 129 | ;--- 15:12 color 130 | ;--- 11:8 misc 131 | ;--- 7:4 def. association 132 | ;--- 3:0 sequence 133 | 134 | ;--- widget types 135 | 136 | WTYPE_AUDIOOUT equ 0 137 | WTYPE_AUDIOIN equ 1 138 | WTYPE_MIXER equ 2 139 | WTYPE_SELECTOR equ 3 140 | WTYPE_PIN equ 4 141 | WTYPE_POWER equ 5 142 | WTYPE_VOLKNOB equ 6 143 | WTYPE_BEEPGEN equ 7 144 | 145 | ;--- pin default device types 146 | 147 | DEFDEV_LINEOUT equ 0 148 | DEFDEV_SPEAKER equ 1 149 | DEFDEV_HEADPHONE equ 2 150 | 151 | ;--- supported PCM rates ( bits 8-15 ) 152 | ;--- bits 8-10: div 1,2,3,4,5,6,8 153 | ;--- bits 11-13: mul 1,2,3,4 154 | ;--- bits 14: base, 0=48kHz, 1=44.1 kHz 155 | 156 | -------------------------------------------------------------------------------- /hdastat.asm: -------------------------------------------------------------------------------- 1 | 2 | ;--- display status of HDA controller 3 | 4 | .386 5 | .MODEL FLAT, stdcall 6 | option casemap:none 7 | option proc:private 8 | 9 | ?LOGCODEC equ 0 10 | ?SENDNULL equ 0 11 | ?HDCTL equ 0 ;display PCI register 0x40 (Intel ICH, bit 0: 0=AC97, 1=HDA) 12 | ?TCSEL equ 0 ;display PCI register 0x44 (bits 0-2 to select TC0-TC7) 13 | 14 | lf equ 10 15 | 16 | CStr macro text:vararg ;define a string in .code 17 | local sym 18 | .const 19 | sym db text,0 20 | .code 21 | exitm 22 | endm 23 | 24 | DStr macro text:vararg ;define a string in .data 25 | local sym 26 | .const 27 | sym db text,0 28 | .data 29 | exitm 30 | endm 31 | 32 | @DefStr macro xx:vararg ;define multiple strings in .data 33 | for x, 34 | dd DStr(x) 35 | endm 36 | endm 37 | 38 | @pe_file_flags = @pe_file_flags and not 1 ;create binary with base relocations 39 | 40 | include dpmi.inc 41 | include hda.inc 42 | 43 | .data 44 | 45 | rmstack dd ? ;real-mode stack ( PCI int 1Ah wants 1 kB stack space ) 46 | pCorb dd ? ;linear address CORB 47 | pRirb dd ? ;linear address RIRB 48 | dwPhysBlk dd ? ;physical address XMS block for CORB/RIRB 49 | pLinBlk dd ? ;linear address XMS block for CORB/RIRB 50 | dwXMS dd ? ;XMS driver entry 51 | pMemRg1 dd 0 ;linear address for region 1 ( used to map in HDA controller ) 52 | xmshdl dw 0 53 | bVerbose db 0 54 | bReset db 0 55 | bActiveOnly db 0 56 | bNoCodecs db 0 57 | 58 | align 4 59 | widgettypes label dword 60 | @DefStr "audio output", "audio input", "audio mixer", "audio selector" 61 | @DefStr "pin complex", "power widget", "volume knob", "beep generator" 62 | 63 | defaultdevices label dword 64 | @DefStr "Line Out", "Speaker", "HP Out", "CD" 65 | @DefStr "SPDIF Out", "Digital Other Out", "Modem Line Side", "Modem Handset Side" 66 | @DefStr "Line In", "AUX", "Mic In", "Telephony" 67 | @DefStr "SPDIF In", "Digital Other In", "rsvd:0e", "Other" 68 | 69 | connectiontypes label dword 70 | @DefStr "unknown", '1/8" stereo/mono', '1/4" stereo/mono', "ATAPI internal" 71 | @DefStr "RCA", "Optical", "Other Digital", "Other Analog" 72 | @DefStr "Multichannel Analog", "XLR/Professional", "RJ-11 (Modem)", "Combination" 73 | @DefStr "rsvd:0c", "rsvd:0d", "rsvd:0e", "Other" 74 | 75 | locations label dword 76 | @DefStr "N/A", "Rear panel", "Front panel", "Left" 77 | @DefStr "Right", "Top", "Bottom", "Special:07" 78 | @DefStr "Special:08", "Special:09", "rsvd:0a", "rsvd:0b" 79 | @DefStr "rsvd:0c", "rsvd:0d", "rsvd:0e", "rsvd:0f" 80 | 81 | colors label dword 82 | @DefStr "unknown", "black", "grey", "blue" 83 | @DefStr "green", "red", "orange", "yellow" 84 | @DefStr "purple", "pink", "rsvd:0a", "rsvd:0b" 85 | @DefStr "rsvd:0c", "rsvd:0d", "white", "other" 86 | 87 | capstrgs label dword 88 | dd 0 89 | dd DStr("Power Management") 90 | dd DStr("AGP controller") 91 | dd DStr("vital product data") 92 | dd DStr("slot identification") 93 | dd DStr("MSI") 94 | dd DStr("CompactPCI hot swap") 95 | dd DStr("PCI-X") 96 | dd DStr("HyperTransport") 97 | dd DStr("Vendor Specific") 98 | dd DStr("Debug port") 99 | dd DStr("CompactPCI central resource control") 100 | dd DStr("Hot Plug") 101 | dd DStr("bridge subsystem") 102 | dd DStr("AGP 8x") 103 | dd DStr("Secure Device") 104 | dd DStr("PCIe") 105 | dd DStr("MSI-X") 106 | dd DStr("SATA Data/Index Configuration") 107 | dd DStr("Advanced Features") 108 | NUMCAPSTR equ ($ - offset capstrgs) / 4 109 | 110 | .const 111 | 112 | nullstr db 0 113 | 114 | .CODE 115 | 116 | include printf.inc 117 | 118 | ;--- call Int 1Ah 119 | 120 | int_1a proc 121 | local rmcs:RMCS 122 | mov rmcs.rEDI,edi 123 | mov rmcs.rESI,esi 124 | mov rmcs.rEBX,ebx 125 | mov rmcs.rECX,ecx 126 | mov rmcs.rEDX,edx 127 | mov rmcs.rEAX,eax 128 | mov rmcs.rFlags,3202h 129 | mov rmcs.rES,0 130 | mov rmcs.rDS,0 131 | mov rmcs.rFS,0 132 | mov rmcs.rGS,0 133 | mov eax,rmstack 134 | mov rmcs.rSSSP,eax 135 | lea edi,rmcs 136 | mov bx,1Ah 137 | mov cx,0 138 | mov ax,0300h 139 | push ebp 140 | int 31h 141 | pop ebp 142 | jc @F 143 | mov ah,byte ptr rmcs.rFlags 144 | sahf 145 | @@: 146 | mov edi,rmcs.rEDI 147 | mov esi,rmcs.rESI 148 | mov ebx,rmcs.rEBX 149 | mov ecx,rmcs.rECX 150 | mov edx,rmcs.rEDX 151 | mov eax,rmcs.rEAX 152 | ret 153 | int_1a endp 154 | 155 | ;--- wait a bit 156 | 157 | dowait proc uses eax ecx 158 | 159 | mov ecx,100h 160 | nextloop: 161 | in al,61h 162 | and al,10h 163 | cmp al,ah 164 | mov ah,al 165 | jz nextloop 166 | loop nextloop 167 | ret 168 | dowait endp 169 | 170 | ;--- send command to codec, using CORB and RIRB 171 | 172 | sendcmd proc uses ebx esi pHDA:ptr, codec:dword, node:word, command:word, param:word 173 | 174 | mov ebx, pHDA 175 | mov eax,codec 176 | shl eax,28 177 | movzx ecx,node 178 | shl ecx,20 179 | or eax,ecx 180 | movzx ecx,command 181 | movzx edx,param 182 | .if ch 183 | shl ecx,8 ;some commands have a payload of 16 bits! 184 | .else 185 | shl ecx,16 186 | .endif 187 | or eax, ecx 188 | or eax, edx 189 | if ?LOGCODEC 190 | push eax 191 | endif 192 | mov si,[ebx].HDAREGS.rirbwp 193 | 194 | mov ecx, pCorb 195 | movzx edx,[ebx].HDAREGS.corbwp 196 | inc dl 197 | mov [ecx+edx*4], eax 198 | mov [ebx].HDAREGS.corbwp, dx 199 | 200 | mov ecx,1000h 201 | .while si == [ebx].HDAREGS.rirbwp 202 | call dowait 203 | dec ecx 204 | stc 205 | jecxz exit 206 | .endw 207 | mov ecx,pRirb 208 | movzx edx,[ebx].HDAREGS.rirbwp 209 | mov eax,[ecx+edx*8] 210 | if ?LOGCODEC 211 | pop ecx 212 | push eax 213 | invoke printf, CStr("sendcmd: sent %X, received %X",lf), ecx, eax 214 | pop eax 215 | endif 216 | clc 217 | exit: 218 | ret 219 | sendcmd endp 220 | 221 | ;--- simple bubble-sort, to sort widgets 222 | 223 | sort proc uses ebx edi pTable:ptr, numitems:dword 224 | .repeat 225 | mov ecx, numitems 226 | mov ebx, pTable 227 | xor edi, edi 228 | .while ecx > 1 229 | mov eax,[ebx+0] 230 | mov edx,[ebx+4] 231 | .if eax > edx 232 | inc edi 233 | mov [ebx+0],edx 234 | mov [ebx+4],eax 235 | .endif 236 | add ebx,4 237 | dec ecx 238 | .endw 239 | .until edi == 0 240 | ret 241 | sort endp 242 | 243 | ;--- translate format in EAX in rate (ecx), bits (edx), channels (ebx) 244 | 245 | translateformat proc 246 | mov ecx,48000 247 | bt eax,14 248 | jnc @F 249 | mov ecx,44100 250 | @@: 251 | mov edx,eax 252 | shr edx,11 253 | and edx,7 254 | inc edx 255 | imul ecx,edx 256 | 257 | mov ebx,eax 258 | shr ebx,8 259 | and ebx,7 260 | inc ebx 261 | xchg eax,ecx 262 | xor edx,edx 263 | idiv ebx 264 | xchg eax,ecx 265 | 266 | mov ebx,eax 267 | and ebx,0fh 268 | inc ebx 269 | mov edx,eax 270 | shr edx,4 271 | and edx,7 272 | mov dl,[edx+offset bittab] 273 | ret 274 | bittab db 8,16,20,24,32,-1,-1,-1 275 | translateformat endp 276 | 277 | ;--- map physical memory block into linear memory 278 | ;--- dwPhysBase: physical address 279 | ;--- dwSize: size in bytes 280 | 281 | mapphys proc uses ebx esi edi pLinear:dword, dwPhysBase:dword, dwSize:dword 282 | .if pLinear 283 | mov esi,pLinear 284 | xor ebx,ebx 285 | mov edx,dwPhysBase 286 | mov ecx,dwSize 287 | mov eax,edx 288 | and eax,0FFFh 289 | and dx,0F000h 290 | add ecx,eax 291 | push eax 292 | add ecx,1000h-1 293 | shr ecx,12 294 | mov ax,508h 295 | int 31h 296 | pop ecx 297 | mov eax,pLinear 298 | lea eax,[eax+ecx] 299 | .else 300 | mov cx,word ptr dwPhysBase+0 301 | mov bx,word ptr dwPhysBase+2 302 | mov di,word ptr dwSize+0 303 | mov si,word ptr dwSize+2 304 | mov ax,800h 305 | int 31h 306 | push bx 307 | push cx 308 | pop eax 309 | .endif 310 | ret 311 | mapphys endp 312 | 313 | AllocXMS proc uses edi ebx dwSize:dword 314 | 315 | local rmcs:RMCS 316 | 317 | ;--- find XMM entry point. 318 | ;--- using XMS memory, since physical addresses are needed. 319 | ;--- with HDPMI, one could use VDS, but this won't work generally. 320 | 321 | lea edi,rmcs 322 | xor eax,eax 323 | mov rmcs.rAX,4300h 324 | mov rmcs.rFlags,3202h 325 | mov rmcs.rSSSP,0 326 | mov bx,2fh 327 | mov cx,0 328 | mov ax,0300h 329 | int 31h 330 | mov eax,rmcs.rEAX 331 | .if al != 80h 332 | invoke printf, CStr("no XMM found",lf) 333 | jmp error 334 | .endif 335 | mov rmcs.rAX,4310h 336 | mov ax,0300h 337 | int 31h 338 | 339 | ;--- copy XMS entry point to rmcs.CS:IP 340 | 341 | push rmcs.rES 342 | push rmcs.rBX 343 | pop eax 344 | mov rmcs.rCSIP,eax 345 | mov dwXMS,eax 346 | 347 | ;--- allocate (& lock) XMS memory 348 | ;--- the block is for CORB & RIRB (1024+2048) 349 | 350 | mov rmcs.rAX,8900h 351 | mov rmcs.rEDX,3 352 | mov bx,0 353 | mov ax,0301h 354 | int 31h 355 | .if rmcs.rAX != 1 356 | invoke printf, CStr("XMS memory allocation failed",lf) 357 | jmp error 358 | .endif 359 | mov ax,rmcs.rDX 360 | mov xmshdl,ax 361 | mov rmcs.rAX,0C00h 362 | mov bx,0 363 | mov ax,0301h 364 | int 31h 365 | .if rmcs.rAX != 1 366 | invoke printf, CStr("XMS memory lock failed",lf) 367 | jmp error 368 | .endif 369 | mov ax,rmcs.rDX 370 | shl eax,16 371 | mov ax,rmcs.rBX 372 | push eax 373 | .if bVerbose 374 | invoke printf, CStr(lf,"EMB physical address=%X, used for CORB & RIRB",lf), eax 375 | .endif 376 | pop eax 377 | clc 378 | ret 379 | error: 380 | stc 381 | ret 382 | 383 | AllocXMS endp 384 | 385 | FreeXMS proc 386 | 387 | local rmcs:RMCS 388 | 389 | mov ax,xmshdl 390 | .if ax 391 | mov rmcs.rDX,ax 392 | mov rmcs.rAX,0D00h ;unlock XMS block 393 | mov rmcs.rFlags,3202h 394 | mov eax, dwXMS 395 | mov rmcs.rCSIP,eax 396 | mov rmcs.rSSSP,0 397 | lea edi,rmcs 398 | mov bx,0 399 | mov cx,0 400 | mov ax,0301h 401 | int 31h 402 | mov rmcs.rAX,0A00h ;free XMS block 403 | mov bx,0 404 | mov cx,0 405 | mov ax,0301h 406 | int 31h 407 | .endif 408 | ret 409 | FreeXMS endp 410 | 411 | ;--- display codec, nodes and widgets 412 | 413 | dispcodec proc uses ebx esi edi pHDA:ptr HDAREGS, codec:dword 414 | 415 | local btype:byte 416 | local afgnode:word 417 | local wflags:word 418 | local startnode:dword 419 | local numnodes:dword 420 | local cConn:dword 421 | 422 | mov afgnode,0 423 | 424 | mov ebx, pHDA 425 | 426 | ;--- reset CORB, RIRB 427 | 428 | and [ebx].HDAREGS.corbctl,not 2 429 | and [ebx].HDAREGS.rirbctl,not 2 430 | mov ecx,1000h 431 | @@: 432 | call dowait 433 | test [ebx].HDAREGS.corbctl,2 434 | loopnz @B 435 | 436 | ;--- init CORB & RIRB ring buffers 437 | ;--- its aligned to 1 kB, but to be save, 438 | ;--- ensure that the RIRB is 2kB-aligned. 439 | 440 | mov edi, dwPhysBlk 441 | mov eax, pLinBlk 442 | .if ax & 400h 443 | mov dword ptr [ebx].HDAREGS.corbbase+0, edi 444 | mov dword ptr [ebx].HDAREGS.corbbase+4, 0 445 | mov pCorb, eax 446 | mov ecx, 256*4 447 | add eax, ecx 448 | add edi, ecx 449 | mov dword ptr [ebx].HDAREGS.rirbbase+0, edi 450 | mov dword ptr [ebx].HDAREGS.rirbbase+4, 0 451 | mov pRirb, eax 452 | .else 453 | mov dword ptr [ebx].HDAREGS.rirbbase+0, edi 454 | mov dword ptr [ebx].HDAREGS.rirbbase+4, 0 455 | mov pRirb, eax 456 | mov ecx, 256*8 457 | add eax, ecx 458 | add edi, ecx 459 | mov dword ptr [ebx].HDAREGS.corbbase+0, edi 460 | mov dword ptr [ebx].HDAREGS.corbbase+4, 0 461 | mov pCorb, eax 462 | .endif 463 | 464 | mov [ebx].HDAREGS.corbwp,0 ;reset CORB WP 465 | mov [ebx].HDAREGS.rirbwp,8000h ;reset RIRB WP 466 | mov [ebx].HDAREGS.rirbric,1 ;interrupt after 1 response 467 | 468 | ;--- to reset the CORB RP, first set bit 15 to 1, then back to 0. 469 | ;--- this often doesn't work, so skip wait if corbrp == 0 470 | 471 | or byte ptr [ebx].HDAREGS.corbrp+1,80h ;reset CORB RP 472 | mov ecx,1000h 473 | @@: 474 | call dowait 475 | cmp [ebx].HDAREGS.corbrp,0 476 | jz @F 477 | test byte ptr [ebx].HDAREGS.corbrp+1,80h 478 | loopz @B 479 | @@: 480 | and byte ptr [ebx].HDAREGS.corbrp+1,7fh 481 | mov ecx,1000h 482 | @@: 483 | call dowait 484 | test byte ptr [ebx].HDAREGS.corbrp+1,80h 485 | loopnz @B 486 | 487 | ;--- start DMA engines for CORB and RIRB 488 | 489 | or [ebx].HDAREGS.corbctl,2 490 | or [ebx].HDAREGS.rirbctl,2 491 | mov ecx,1000h 492 | @@: 493 | call dowait 494 | test [ebx].HDAREGS.corbctl,2 495 | loopz @B 496 | mov ecx,1000h 497 | @@: 498 | call dowait 499 | test [ebx].HDAREGS.rirbctl,2 500 | loopz @B 501 | 502 | .if bVerbose 503 | invoke printf, CStr(lf,"CORB/RIRB after init:",lf) 504 | call dispcr 505 | .endif 506 | 507 | if ?SENDNULL 508 | xor eax,eax 509 | mov ecx, pCorb 510 | movzx edx,[ebx].HDAREGS.corbwp 511 | inc dl 512 | mov [ecx+edx*4], eax 513 | mov [ebx].HDAREGS.corbwp, dx 514 | endif 515 | 516 | invoke printf, CStr(lf,"codec/node/cmd/param: value",lf) 517 | invoke printf, CStr("-----------------------------------------------",lf) 518 | invoke sendcmd, ebx, codec, 0, 0F00h, 0 519 | jc timeout 520 | shld edx,eax,16 521 | movzx edx,dx 522 | movzx eax,ax 523 | invoke printf, CStr("%2u/ 0/0F00/0 - vendor/device: 0x%X/0x%X",lf), codec, edx, eax 524 | invoke sendcmd, ebx, codec, 0, 0F00h, 4 525 | movzx ecx, al 526 | mov edi, ecx 527 | shld edx, eax,16 528 | movzx edx,dl 529 | mov esi, edx 530 | invoke printf, CStr("%2u/ 0/0F00/4 - node count: 0x%X (start node=%u, no of nodes=%u)",lf), codec, eax, edx, ecx 531 | ;--- display the type of the function group nodes 532 | .while edi 533 | invoke sendcmd, ebx, codec, si, 0F00h, 5 534 | movzx ecx,al 535 | and cl,7Fh 536 | .if cl == 1 537 | mov afgnode,si 538 | .endif 539 | invoke printf, CStr("%2u/%3u/0F00/5 - function group type: 0x%X ([6:0]=type [afg=1])",lf), codec, esi, eax 540 | inc esi 541 | dec edi 542 | .endw 543 | cmp afgnode,0 544 | jz exit 545 | 546 | ;--- if a AFG has been found, display its pcm rates 547 | invoke sendcmd, ebx, codec, afgnode, 0F00h, 10 548 | invoke printf, CStr("%2u/%3u/0F00/10 - supported PCM rates: 0x%X",lf), codec, afgnode, eax 549 | invoke sendcmd, ebx, codec, afgnode, 0F05h, 0 550 | invoke printf, CStr("%2u/%3u/0F05/0 - power state control=0x%X",lf), codec, afgnode, eax 551 | invoke sendcmd, ebx, codec, afgnode, 0F00h, 4 552 | movzx ecx, al 553 | mov edi, ecx 554 | shld edx, eax,16 555 | movzx edx,dl 556 | mov esi, edx 557 | mov startnode, esi 558 | mov numnodes, edi 559 | invoke printf, CStr("%2u/%3u/0F00/4 - node count: 0x%X (start node=%u, no of nodes=%u)",lf), codec, afgnode, eax, edx, ecx 560 | .while edi 561 | invoke sendcmd, ebx, codec, si, 0F00h, 9 562 | mov ecx, eax 563 | shr ecx,20 564 | and ecx,0Fh 565 | push cx 566 | push si 567 | if 0 568 | .if ecx <= 7 569 | mov ecx,[ecx*4 + offset widgettypes] 570 | invoke printf, CStr("%2u/%3u/0F00/9 - audio widget cap.: 0x%X (%s)",lf), codec, esi, eax, ecx 571 | .else 572 | invoke printf, CStr("%2u/%3u/0F00/9 - audio widget cap.: 0x%X ([23:20] type=0x%X)",lf), codec, esi, eax, ecx 573 | .endif 574 | endif 575 | dec edi 576 | inc esi 577 | .endw 578 | 579 | mov eax,esp 580 | invoke sort, eax, numnodes 581 | 582 | mov esi, esp 583 | mov edi, numnodes 584 | mov btype,-1 585 | .while edi 586 | mov eax,[esi] 587 | push esi 588 | movzx esi,ax 589 | shr eax,16 590 | .if al != btype 591 | push eax 592 | movzx ecx, al 593 | .if ecx <= 7 594 | movzx eax,al 595 | mov ecx,[ecx*4 + offset widgettypes] 596 | invoke printf, CStr(lf," %s ( type %u )",lf,lf), ecx, eax 597 | .else 598 | invoke printf, CStr(lf," widget type 0x%X",lf,lf), ecx 599 | .endif 600 | pop eax 601 | mov btype, al 602 | .endif 603 | 604 | invoke sendcmd, ebx, codec, si, 0F00h, 9 605 | mov wflags, ax 606 | ;--- [4]: Amp Param Override [5]: stripe, [9]: digital, [10] Power Ctrl, 607 | mov ecx,eax 608 | shr ecx,12 609 | and ecx,0eh 610 | bt eax,0 611 | adc ecx,0 612 | bt eax,9 613 | setc dl 614 | movzx edx,dl 615 | invoke printf, CStr("%2u/%3u/0F00/9 - widget cap.: 0x%X ([1]=inp amp, [2]=out amp, digital=%u, chnl cnt-1=%u)",lf), codec, esi, eax, edx, ecx 616 | 617 | .if btype == WTYPE_AUDIOOUT || btype == WTYPE_AUDIOIN ;converters? 618 | invoke sendcmd, ebx, codec, si, 0F00h, 10 619 | invoke printf, CStr("%2u/%3u/0F00/10 - supported PCM rates: 0x%X",lf), codec, si, eax 620 | .endif 621 | .if btype == WTYPE_PIN 622 | invoke sendcmd, ebx, codec, si, 0F00h, 12 623 | bt eax,2 624 | setc cl 625 | movzx ecx,cl 626 | bt eax,4 627 | setc dl 628 | movzx edx,dl 629 | invoke printf, CStr("%2u/%3u/0F00/12 - PIN capabilities: 0x%X (presence detect cap.=%u, output cap.=%u)",lf), codec, si, eax, ecx, edx 630 | .endif 631 | .if wflags & 2 632 | invoke sendcmd, ebx, codec, si, 0F00h, 13 633 | ;--- [31]: mute capable, 22:16 stepsize, 14:8 numsteps, 6:0 offset 634 | invoke printf, CStr("%2u/%3u/0F00/13 - input amplifier details: 0x%X",lf), codec, si, eax 635 | invoke sendcmd, ebx, codec, si, 00Bh, 0 ;b15: 1=output amp, 0=input amp;b13: 1=left, 0=right 636 | invoke printf, CStr("%2u/%3u/000B/0 - amplifier gain/mute: 0x%X ([7] mute, [6:0] gain)",lf), codec, si, eax 637 | .endif 638 | .if wflags & 4 639 | invoke sendcmd, ebx, codec, si, 0F00h, 18 640 | ;--- [31]: mute capable, 22:16 stepsize, 14:8 numsteps, 6:0 offset 641 | invoke printf, CStr("%2u/%3u/0F00/18 - output amplifier details: 0x%X",lf), codec, si, eax 642 | invoke sendcmd, ebx, codec, si, 00Bh, 8000h ;b15: 1=output amp, 0=input amp;b13: 1=left, 0=right 643 | invoke printf, CStr("%2u/%3u/000B/8000 - amplifier gain/mute: 0x%X ([7] mute, [6:0] gain)",lf), codec, si, eax 644 | .endif 645 | .if btype == WTYPE_VOLKNOB 646 | invoke sendcmd, ebx, codec, si, 0F00h, 19 647 | invoke printf, CStr("%2u/%3u/0F00/19 - volume knob caps: 0x%X",lf), codec, si, eax 648 | invoke sendcmd, ebx, codec, si, 0F0Fh, 0 649 | invoke printf, CStr("%2u/%3u/0F0F/0 - volume knob control: 0x%X ([7] direct, [6:0] volume)",lf), codec, si, eax 650 | .endif 651 | .if wflags & 100h 652 | invoke sendcmd, ebx, codec, si, 0F00h, 14 653 | mov cConn, eax 654 | invoke printf, CStr("%2u/%3u/0F00/14 - connection list length: %u",lf), codec, si, eax 655 | push edi 656 | xor edi, edi 657 | invoke printf, CStr("%2u/%3u/0F02/%02u - get entries in connection list:"), codec, si, di 658 | .while edi < cConn 659 | invoke sendcmd, ebx, codec, si, 0F02h, di 660 | push ebx 661 | shld edx, eax,24 662 | shld ecx, eax,16 663 | shld ebx, eax,8 664 | movzx eax,al 665 | movzx ecx,cl 666 | movzx edx,dl 667 | movzx ebx,bl 668 | invoke printf, CStr(" %u %u %u %u"), eax, edx, ecx, ebx 669 | pop ebx 670 | add edi, 4 671 | .endw 672 | invoke printf, CStr(lf) 673 | ;--- mixer, volknob and power widgets don't have a selection control! 674 | .if btype == WTYPE_MIXER || btype == WTYPE_VOLKNOB || btype == WTYPE_POWER 675 | ; 676 | .elseif cConn > 1 677 | invoke sendcmd, ebx, codec, si, 0F01h, 0 678 | invoke printf, CStr("%2u/%3u/0F01/0 - currently selected connection: %u",lf), codec, si, eax 679 | .endif 680 | pop edi 681 | .endif 682 | .if wflags & 400h ;power state control supported? 683 | invoke sendcmd, ebx, codec, si, 0F05h, 0 684 | invoke printf, CStr("%2u/%3u/0F05/0 - power state control=0x%X",lf), codec, si, eax 685 | .endif 686 | .if btype == WTYPE_AUDIOOUT || btype == WTYPE_AUDIOIN 687 | invoke sendcmd, ebx, codec, si, 0F03h, 0 688 | invoke printf, CStr("%2u/%3u/0F03/0 - processing state: 0x%X",lf), codec, si, eax 689 | invoke sendcmd, ebx, codec, si, 0F06h, 0 690 | shld ecx,eax,28 691 | mov edx,eax 692 | and ecx,0fh 693 | and edx,0fh 694 | invoke printf, CStr("%2u/%3u/0F06/0 - link stream/channel: 0x%X (stream=%u, channel=%u)",lf), codec, si, eax, ecx, edx 695 | invoke sendcmd, ebx, codec, si, 00Ah, 0 696 | push ebx 697 | call translateformat 698 | invoke printf, CStr("%2u/%3u/000A/0 - converter format: 0x%X (rate=%u, bits=%u, channels=%u)",lf), codec, si, eax, ecx, edx, ebx 699 | pop ebx 700 | .endif 701 | .if btype == WTYPE_AUDIOOUT 702 | invoke sendcmd, ebx, codec, si, 0F2Dh, 0 ;converter channel count 703 | invoke printf, CStr("%2u/%3u/0F2D/0 - converter channel count: 0x%X",lf), codec, si, eax 704 | .endif 705 | .if btype == WTYPE_PIN 706 | invoke sendcmd, ebx, codec, si, 0F07h, 0 ;get pin widget control 707 | bt ax,7 708 | .if CARRY? 709 | mov ecx, CStr("HP enable ") 710 | .else 711 | mov ecx, offset nullstr 712 | .endif 713 | bt ax,6 714 | .if CARRY? 715 | mov edx, CStr("Out enable ") 716 | .else 717 | mov edx, offset nullstr 718 | .endif 719 | push ebx 720 | bt ax,5 721 | .if CARRY? 722 | mov ebx, CStr("In enable ") 723 | .else 724 | mov ebx, offset nullstr 725 | .endif 726 | invoke printf, CStr("%2u/%3u/0F07/0 - pin widget control: 0x%X - %s%s%s",lf), codec, si, eax, ecx, edx, ebx 727 | pop ebx 728 | invoke sendcmd, ebx, codec, si, 0F1Ch, 0 729 | push eax 730 | invoke printf, CStr("%2u/%3u/0F1C/0 - configuration default: 0x%X",lf), codec, si, eax 731 | pop eax 732 | pushad 733 | shld ecx,eax,2 ;31:30 ecx=port connectivity 734 | shld edx,eax,8 ;29:24 edx=location 735 | shld ebx,eax,12 ;23:20 ebx=default device 736 | shld esi,eax,16 ;19:16 esi=connection type 737 | shr eax,12 ;eax=color 738 | and ecx,3 739 | and edx,3Fh 740 | and ebx,0fh 741 | and esi,0fh 742 | and eax,0fh 743 | mov edi,edx 744 | and edi,0fh 745 | mov ebx,[ebx*4+offset defaultdevices] 746 | mov esi,[esi*4+offset connectiontypes] 747 | .if edx >= 10h 748 | .if edx == 10h && edi == 8 749 | mov edi,CStr("HDMI") 750 | .elseif edx == 10h && edi == 9 751 | mov edi,CStr("ATAPI") 752 | .else 753 | mov edi,CStr("???") 754 | .endif 755 | .else 756 | mov edi,[edi*4+offset locations] 757 | .endif 758 | mov eax,[eax*4+offset colors] 759 | invoke printf, CStr(" port connectivity=%u,location=0x%X (%s), def. device=%s, conn type=%s, color=%s",lf), ecx, edx, edi, ebx, esi, eax 760 | popad 761 | .endif 762 | pop esi 763 | add esi, 4 764 | dec edi 765 | .endw 766 | mov esp, esi 767 | exit: 768 | mov ebx, pHDA 769 | 770 | ;--- stop CORB and RIRB DMA engines 771 | 772 | and [ebx].HDAREGS.corbctl,not 2 773 | and [ebx].HDAREGS.rirbctl,not 2 774 | ;--- and, before releasing memory, wait until at least RIRB is really stopped! 775 | mov ecx,10000h 776 | @@: 777 | call dowait 778 | test [ebx].HDAREGS.rirbctl,2 779 | loopnz @B 780 | 781 | ret 782 | timeout: 783 | invoke printf, CStr(lf,"timeout waiting for codec response",lf) 784 | jmp exit 785 | 786 | dispcodec endp 787 | 788 | dispcr proc 789 | invoke printf, CStr(" +64 CORB base address=0x%lX",lf), [ebx].HDAREGS.corbbase 790 | invoke printf, CStr(" +72 CORB WP=0x%X, RP=0x%X",lf), [ebx].HDAREGS.corbwp,[ebx].HDAREGS.corbrp 791 | invoke printf, CStr(" +76 CORB control=0x%X ([1] 0=DMA Stop, 1=DMA Run)",lf), [ebx].HDAREGS.corbctl 792 | invoke printf, CStr(" +78 CORB size=0x%X ([7:4] size cap [bitmask],[1:0] size [0=2,1=16,2=256,3=rsvd])",lf), [ebx].HDAREGS.corbsize 793 | invoke printf, CStr(" +80 RIRB base address=0x%lX",lf), [ebx].HDAREGS.rirbbase 794 | invoke printf, CStr(" +88 RIRB WP=0x%X, RIC=0x%X",lf), [ebx].HDAREGS.rirbwp,[ebx].HDAREGS.rirbric 795 | invoke printf, CStr(" +92 RIRB control=0x%X ([1] 0=DMA Stop, 1=DMA Run)",lf), [ebx].HDAREGS.rirbctl 796 | invoke printf, CStr(" +94 RIRB size=0x%X ([7:4] size cap [bitmask],[1:0] size [0=2,1=16,2=256,3=rsvd])",lf), [ebx].HDAREGS.rirbsize 797 | ret 798 | dispcr endp 799 | 800 | ;--- display HDA controller's memory-mapped registers 801 | 802 | displayhdamem proc uses ebx esi edi dwLinAddr:dword, dwPhysBase:dword 803 | 804 | local istreams:dword 805 | local ostreams:dword 806 | local bistreams:dword 807 | 808 | mov ebx, dwLinAddr 809 | 810 | .if bReset 811 | and [ebx].HDAREGS.gctl, not 1 812 | .while [ebx].HDAREGS.gctl & 1 813 | call dowait 814 | .endw 815 | .endif 816 | 817 | ;--- move HDA out of reset 818 | .if !([ebx].HDAREGS.gctl & 1) 819 | or [ebx].HDAREGS.gctl, 1 820 | .while !([ebx].HDAREGS.gctl & 1) 821 | call dowait 822 | .endw 823 | .endif 824 | 825 | movzx eax,[ebx].HDAREGS.gcap 826 | push eax 827 | invoke printf, CStr(" +0 Global Capabilities=0x%X",lf), eax 828 | pop eax 829 | movzx ecx,al 830 | movzx esi,al 831 | shr ecx,1 ;bits 2:1 NSDO - number of serial data out signals 832 | and cl,3 833 | shr esi,3 ;bits 7:3 BSS - number of bidirectional streams supported 834 | shr eax,8 835 | movzx edx, al 836 | shr edx,4 ;bits 15:12 OSS - number of output streams supported 837 | and al,0Fh ;bits 11:8 ISS - number of input streams supported 838 | mov istreams,eax 839 | mov ostreams,edx 840 | mov bistreams,esi 841 | invoke printf, CStr(" #input streams=%u, #output streams=%u, #bidirect. streams=%u, #SDO=%u (0=1,1=2,2=4)",lf), eax, edx, esi, ecx 842 | movzx eax,[ebx].HDAREGS.vminor 843 | movzx ecx,[ebx].HDAREGS.vmajor 844 | invoke printf, CStr(" +2 Version=%u.%u",lf), ecx, eax 845 | movzx eax, [ebx].HDAREGS.opayload 846 | mov ecx,eax 847 | shl ecx,4 848 | invoke printf, CStr(" +4 Output Payload Cap=%u (=%u bits/frame)",lf), eax, ecx 849 | invoke printf, CStr(" +6 Input Payload Cap=%u",lf), [ebx].HDAREGS.ipayload 850 | invoke printf, CStr(" +8 GCTL - Global Control=0x%X ([0] 0=in reset)",lf), [ebx].HDAREGS.gctl 851 | invoke printf, CStr(" +12 WAKEEN - Wake Enable=0x%X",lf), [ebx].HDAREGS.wakeen 852 | invoke printf, CStr(" +14 STATESTS - State Change Status=0x%X",lf), [ebx].HDAREGS.statests 853 | invoke printf, CStr(" +16 GSTS - Global Status=0x%X",lf), [ebx].HDAREGS.gsts 854 | invoke printf, CStr(" +32 INTCTL - Interrupt Control=0x%X",lf), [ebx].HDAREGS.intctl 855 | invoke printf, CStr(" +36 INTSTS - Interrupt Status=0x%X",lf), [ebx].HDAREGS.intsts 856 | invoke printf, CStr(" +48 WALCLK - Wall Clock Counter=0x%X",lf), [ebx].HDAREGS.walclk 857 | invoke printf, CStr(" +56 SSYNC - Stream Synchronization=0x%X",lf), [ebx].HDAREGS.ssync 858 | 859 | call dispcr 860 | 861 | .if bVerbose 862 | invoke printf, CStr(" Immediate command=0x%X",lf), [ebx].HDAREGS.ic 863 | invoke printf, CStr(" Immediate response=0x%X",lf), [ebx].HDAREGS.ir 864 | invoke printf, CStr(" Immediate command status=0x%X",lf), [ebx].HDAREGS.ics 865 | .endif 866 | 867 | invoke printf, CStr(" DMA position base address=0x%lX",lf), qword ptr [ebx+70h] 868 | 869 | mov esi, 0 870 | lea edi, [ebx].HDAREGS.stream0 871 | push ebx 872 | mov bl,'I' 873 | .while esi < istreams 874 | call dispstream 875 | inc esi 876 | add edi,sizeof STREAM 877 | .endw 878 | pop ebx 879 | mov esi, 0 880 | ; lea edi, [ebx].HDAREGS.ostream0 881 | push ebx 882 | mov bl,'O' 883 | .while esi < ostreams 884 | call dispstream 885 | inc esi 886 | add edi,sizeof STREAM 887 | .endw 888 | pop ebx 889 | 890 | ;--- scan statests 891 | mov esi,0 892 | movzx ecx, [ebx].HDAREGS.statests 893 | .if !ecx 894 | .if !bNoCodecs 895 | invoke dispcodec, ebx, esi 896 | .endif 897 | jmp exit 898 | .endif 899 | .while ecx 900 | .if ecx & 1 901 | push ecx 902 | .if !bNoCodecs 903 | invoke dispcodec, ebx, esi 904 | .endif 905 | pop ecx 906 | .endif 907 | shr ecx,1 908 | inc esi 909 | .endw 910 | exit: 911 | ret 912 | dispstream: 913 | movzx eax,[edi].STREAM.bCtl2316 914 | shl eax,16 915 | mov ax,[edi].STREAM.wCtl 916 | .if !(al & 2) && bActiveOnly 917 | retn 918 | .endif 919 | invoke printf, CStr(" %cSD%u control=0x%X ([23:20] stream no [0=unused], [1] 1=stream run, [0] 1=in reset)",lf), ebx, esi, eax 920 | invoke printf, CStr(" %cSD%u status=0x%X",lf), ebx, esi, byte ptr [edi].STREAM.bSts 921 | invoke printf, CStr(" %cSD%u link position in buffer=0x%X",lf), ebx, esi, [edi].STREAM.dwLinkPos 922 | invoke printf, CStr(" %cSD%u cyclic buffer length=0x%X (size in bytes of complete cyclic buffer)",lf), ebx, esi, [edi].STREAM.dwBufLen 923 | invoke printf, CStr(" %cSD%u last valid index=0x%X (no of entries in BDL-1)",lf), ebx, esi, [edi].STREAM.wLastIdx 924 | invoke printf, CStr(" %cSD%u FIFO watermark=%u ([2:0] 2=8, 3=16, 4=32)",lf), ebx, esi, [edi].STREAM.wFIFOmark 925 | invoke printf, CStr(" %cSD%u FIFO size=%u (bytes-1)",lf), ebx, esi, [edi].STREAM.wFIFOsize 926 | movzx eax,[edi].STREAM.wFormat 927 | push ebx 928 | push edi 929 | mov edi, ebx 930 | call translateformat 931 | invoke printf, CStr(" %cSD%u format=0x%X (base rate=%u, bits=%u, channels=%u)",lf), edi, esi, eax, ecx, edx, ebx 932 | pop edi 933 | pop ebx 934 | invoke printf, CStr(" %cSD%u buffer description list base address=0x%lX",lf), ebx, esi, [edi].STREAM.qwBuffer 935 | retn 936 | 937 | displayhdamem endp 938 | 939 | ;--- map HDA controller's register into linear address space 940 | 941 | displayhda proc uses ebx esi edi dwPath:dword 942 | 943 | local dwPhysBase:dword 944 | 945 | mov edi, 4*4 946 | mov ebx,dwPath 947 | mov ax,0B10Ah 948 | call int_1a 949 | jc exit 950 | mov eax, ecx 951 | and cl,0F0h 952 | mov dwPhysBase, ecx 953 | test al, 4 ; 64-bit address? 954 | jz @F 955 | mov edi, 5*4 956 | mov ebx,dwPath 957 | mov ax,0B10Ah 958 | call int_1a 959 | jc exit 960 | .if ecx 961 | mov eax,dwPhysBase 962 | invoke printf, CStr(" HDA Base Address=0x%lX beyond 4 GB limit, can't be accessed.",lf), ecx::eax 963 | jmp exit 964 | .endif 965 | @@: 966 | invoke printf, CStr(lf," HDA Base Address=0x%X",lf), dwPhysBase 967 | invoke mapphys, pMemRg1, dwPhysBase, 1000h 968 | jc exit 969 | invoke displayhdamem, eax, dwPhysBase 970 | exit: 971 | ret 972 | displayhda endp 973 | 974 | ;--- display PCI registers of HDA controller 975 | 976 | disppci proc uses ebx edi dwClass:dword, path:dword 977 | 978 | local satacap:byte 979 | local status:word 980 | 981 | mov ebx,path 982 | mov edi,0 983 | mov ax,0B10Ah 984 | call int_1a 985 | .if ah == 0 986 | movzx eax,cx 987 | shr ecx,16 988 | invoke printf, CStr(" vendor=0x%X, device=0x%X",lf), eax, ecx 989 | .endif 990 | if 1 991 | mov edi,4 ;PCI CMD 992 | mov ax,0B109h 993 | call int_1a 994 | .if ah == 0 995 | movzx ecx,cx 996 | push ecx 997 | invoke printf, CStr(" CMD=0x%X ([0]=IOSE,[1]=MSE (Memory Space Enable),[2]=BME (Bus Master Enable)",lf), ecx 998 | pop ecx 999 | test cl,4 ;busmaster enabled? 1000 | jnz @F 1001 | or cl,4 ;enable busmaster - needed for CORB/RIRB access 1002 | mov ax,0B10Ch 1003 | call int_1a 1004 | @@: 1005 | .endif 1006 | endif 1007 | mov edi,6 ;PCI STS (device status 1008 | mov ax,0B109h 1009 | call int_1a 1010 | .if ah == 0 1011 | mov status,cx 1012 | .else 1013 | mov status,0 1014 | .endif 1015 | 1016 | .if status & 10h ;new capabilities present? 1017 | mov edi,34h 1018 | mov ax,0B108h 1019 | call int_1a 1020 | .if ah == 0 1021 | movzx ecx,cl 1022 | mov edi,ecx 1023 | .repeat 1024 | mov ax,0B10Ah ; read config dword 1025 | call int_1a 1026 | .break .if ah != 0 1027 | push ecx 1028 | movzx eax,ch 1029 | movzx ecx,cl 1030 | mov edi, eax 1031 | .if ecx < NUMCAPSTR 1032 | mov eax, [ecx*4][capstrgs] 1033 | .else 1034 | mov eax, CStr("unknown") 1035 | .endif 1036 | invoke printf, CStr(" capabilities ID=0x%X (%s)",lf), ecx, eax 1037 | pop ecx 1038 | .if cl == 5 ; MSI caps? 1039 | bt ecx, 16 ; MSI enabled? 1040 | .if CARRY? 1041 | invoke printf, CStr(" MSI enabled",lf) 1042 | .endif 1043 | .endif 1044 | .until edi == 0 1045 | .endif 1046 | .endif 1047 | 1048 | mov edi,3Ch 1049 | mov ax,0B108h 1050 | call int_1a 1051 | .if ah == 0 1052 | movzx eax,cl 1053 | invoke printf, CStr(" interrupt line=%u",lf), eax 1054 | .endif 1055 | if ?HDCTL 1056 | mov edi,40h 1057 | mov ax,0B108h 1058 | call int_1a 1059 | .if ah == 0 1060 | movzx ecx, cl 1061 | invoke printf, CStr(" HDCTL - register 40h=0x%X",lf), ecx 1062 | .endif 1063 | endif 1064 | if ?TCSEL 1065 | ;--- bits 0-2 used only, to select TC0-TC7 1066 | mov edi,44h 1067 | mov ax,0B108h 1068 | call int_1a 1069 | .if ah == 0 1070 | movzx ecx, cl 1071 | invoke printf, CStr(" TCSEL - register 44h=0x%X",lf), ecx 1072 | .endif 1073 | endif 1074 | .if dwClass == 040300h 1075 | invoke displayhda, ebx 1076 | .endif 1077 | exit: 1078 | ret 1079 | disppci endp 1080 | 1081 | ;--- search HDA controller, using class 1082 | 1083 | finddevice proc uses ebx esi edi dwClass:dword, pszType:ptr, bSilent:byte 1084 | 1085 | xor esi,esi 1086 | .repeat 1087 | mov ecx,dwClass 1088 | mov ax,0B103h 1089 | call int_1a 1090 | .break .if ah != 0 1091 | .if bVerbose 1092 | movzx eax,ax 1093 | invoke printf, CStr("Int 1ah, ax=B103h, ecx=%X, si=%u: ax=%X, ebx=%X",lf),dwClass,esi,eax,ebx 1094 | .endif 1095 | movzx eax,bh 1096 | movzx ecx,bl 1097 | shr ecx,3 1098 | movzx edx,bl 1099 | and dl,7 1100 | invoke printf, CStr(lf,"%s device (class=0x%06X) found at bus/device/function=%u/%u/%u:",lf),pszType,dwClass,eax,ecx,edx 1101 | invoke disppci, dwClass, ebx 1102 | inc esi 1103 | .until 0 1104 | .if esi==0 && !bSilent 1105 | invoke printf, CStr("no %s device (class=0x%06X) found",lf), pszType, dwClass 1106 | .endif 1107 | mov eax, esi 1108 | ret 1109 | 1110 | finddevice endp 1111 | 1112 | ;--- display HDA information 1113 | 1114 | main proc c argc:dword,argv:dword 1115 | 1116 | local dwClass:dword 1117 | local pszType:dword 1118 | local pMemRg2:dword 1119 | 1120 | mov esi, argc 1121 | mov ebx,argv 1122 | add ebx,4 1123 | .while esi > 1 1124 | mov edi,[ebx] 1125 | mov ax,[edi] 1126 | .if al == '-' || al == '/' 1127 | or ah,20h 1128 | .if ah == 'a' 1129 | mov bActiveOnly, 1 1130 | .elseif ah == 'n' 1131 | mov bNoCodecs, 1 1132 | .elseif ah == 'r' 1133 | mov bReset, 1 1134 | .elseif ah == 'v' 1135 | mov bVerbose, 1 1136 | .else 1137 | jmp usage 1138 | .endif 1139 | .else 1140 | jmp usage 1141 | .endif 1142 | dec esi 1143 | add ebx,4 1144 | .endw 1145 | 1146 | ;--- allocate real-mode stack of 4kB 1147 | 1148 | mov ax,100h 1149 | mov bx,40h 1150 | int 31h 1151 | jc exit 1152 | mov word ptr rmstack+0,400h 1153 | mov word ptr rmstack+2,ax 1154 | 1155 | ;--- allocate 2 uncommitted regions; 1156 | ;--- will be used to map HDA controller and CORB/RIRB 1157 | mov pMemRg2,0 1158 | xor ebx,ebx 1159 | mov ecx,2000h 1160 | xor edx,edx 1161 | mov ax,504h 1162 | int 31h 1163 | jc @F 1164 | mov pMemRg1,ebx 1165 | @@: 1166 | xor ebx,ebx 1167 | mov ecx,2000h 1168 | xor edx,edx 1169 | mov ax,504h 1170 | int 31h 1171 | jc @F 1172 | mov pMemRg2,ebx 1173 | @@: 1174 | invoke AllocXMS, 3 ;alloc 3 kB physical memory for CORB/RIRB 1175 | jc exit 1176 | mov dwPhysBlk,eax 1177 | 1178 | ;--- map the block into linear memory so it can be accessed 1179 | 1180 | invoke mapphys, pMemRg2, eax, 1024 * 3 1181 | jc exit 1182 | mov pLinBlk, eax 1183 | 1184 | xor edi,edi 1185 | mov ax,0B101h 1186 | call int_1a 1187 | movzx eax,ax 1188 | .if bVerbose 1189 | push edx 1190 | push eax 1191 | movzx ebx,bx 1192 | movzx ecx,cl 1193 | invoke printf, CStr("Int 1ah, ax=B101h: ax=%X (ok if ah=0), edi=%X (PM entry), edx=%X ('PCI'), bx=%X (Version), cl=%X (last bus)",lf),eax,edi,edx,ebx,ecx 1194 | pop eax 1195 | pop edx 1196 | .endif 1197 | cmp ah,0 1198 | jnz error1 1199 | cmp edx," ICP" 1200 | jnz error1 1201 | 1202 | mov pszType, CStr("HD Audio") 1203 | mov dwClass, 040300h 1204 | invoke finddevice, dwClass, pszType, 0 1205 | jmp exit 1206 | usage: 1207 | invoke printf, CStr("hdastat v1.0",lf) 1208 | invoke printf, CStr("displays current status of HDA controller",lf) 1209 | invoke printf, CStr("usage: hdastat [ options ]",lf) 1210 | invoke printf, CStr("options:",lf) 1211 | invoke printf, CStr(" -a : display active streams only",lf) 1212 | invoke printf, CStr(" -n : don't display codecs",lf) 1213 | invoke printf, CStr(" -r : reset controller",lf) 1214 | invoke printf, CStr(" -v : display more details",lf) 1215 | exit: 1216 | call FreeXMS 1217 | ret 1218 | error1: 1219 | invoke printf, CStr("no PCI BIOS implemented",lf) 1220 | jmp exit 1221 | 1222 | main endp 1223 | 1224 | include setargv.inc 1225 | 1226 | start32 proc c public 1227 | call _setargv 1228 | invoke main, eax, edx 1229 | mov ax,4c00h 1230 | int 21h 1231 | start32 endp 1232 | 1233 | END start32 1234 | 1235 | -------------------------------------------------------------------------------- /hdaplay.asm: -------------------------------------------------------------------------------- 1 | 2 | ;--- Play a PCM file using HD Audio. 3 | ;--- The sound will be emitted to the "rear panel" lineout pin. 4 | ;--- After the HDA controller's dma engine has been started, 5 | ;--- a command processor ("C:\command.com") is launched. Exiting 6 | ;--- this program will also exit hdaplay.exe. 7 | 8 | .386 9 | .MODEL FLAT, stdcall 10 | option casemap:none 11 | option proc:private 12 | 13 | ?LOGCODEC equ 0 ;1 for debugging 14 | ?SENDNULL equ 0 ;1 send NULL verb after CORB init 15 | ?STREAM equ 1 ;stream # to use 16 | ?CHANNEL equ 0 ;channel start # to use 17 | ?DISPCR equ 1 ;1 display corb/rirp status 18 | ?SHELL equ 1 ;1 launch a shell, 0 wait for ESC key 19 | ?INTCNT equ 128;value for RIRB response interrupt count register 20 | 21 | lf equ 10 22 | 23 | @pe_file_flags = @pe_file_flags and not 1 ;create binary with base relocations 24 | 25 | CStr macro text:vararg ;define a string in .code 26 | local sym 27 | .const 28 | sym db text,0 29 | .code 30 | exitm 31 | endm 32 | 33 | DStr macro text:vararg ;define a string in .data 34 | local sym 35 | .const 36 | sym db text,0 37 | .data 38 | exitm 39 | endm 40 | 41 | @DefStr macro xx:vararg ;define multiple strings in .data 42 | for x, 43 | dd DStr(x) 44 | endm 45 | endm 46 | 47 | include dpmi.inc 48 | include hda.inc 49 | 50 | RIFFHDR struct 51 | chkId dd ? 52 | chkSiz dd ? 53 | format dd ? 54 | RIFFHDR ends 55 | 56 | RIFFCHKHDR struct 57 | subchkId dd ? 58 | subchkSiz dd ? 59 | RIFFCHKHDR ends 60 | 61 | WAVEFMT struct 62 | RIFFCHKHDR <> 63 | wFormatTag dw ? 64 | nChannels dw ? 65 | nSamplesPerSec dd ? 66 | nAvgBytesPerSec dd ? 67 | nBlockAlign dw ? 68 | wBitsPerSample dw ? 69 | WAVEFMT ends 70 | 71 | .data 72 | 73 | rmstack dd ? ;real-mode stack ( PCI int 1Ah wants 1 kB stack space ) 74 | pCorb dd ? ;linear address CORB 75 | pRirb dd ? ;linear address RIRB 76 | pMemRg1 dd 0 77 | pMemRg2 dd 0 78 | 79 | wWidget dw 0 ;-w option 80 | wCodec dw 0 ;-c option 81 | wDevice dw 0 ;-d option 82 | 83 | bQuiet db 0 ;-q option 84 | bReset db 0 ;-r option 85 | bDefDev db DEFDEV_LINEOUT ;-s option 86 | bVerbose db 0 ;-v option 87 | 88 | if ?SHELL 89 | fcb db 0, " ", 0, 0, 0, 0 90 | cmdl db 0,13 91 | endif 92 | 93 | widgettypes label dword 94 | @DefStr "audio output", "audio input", "audio mixer", "audio selector" 95 | @DefStr "pin complex", "power widget", "volume knob", "beep generator" 96 | 97 | .CODE 98 | 99 | defdevices label dword 100 | dd CStr("lineout"), CStr("speaker"), CStr("headphone") 101 | 102 | include printf.inc 103 | 104 | ;--- call PCI BIOS Int 1Ah 105 | 106 | int_1a proc 107 | local rmcs:RMCS 108 | mov rmcs.rEDI,edi 109 | mov rmcs.rESI,esi 110 | mov rmcs.rEBX,ebx 111 | mov rmcs.rECX,ecx 112 | mov rmcs.rEDX,edx 113 | mov rmcs.rEAX,eax 114 | mov rmcs.rFlags,3202h 115 | mov rmcs.rES,0 116 | mov rmcs.rDS,0 117 | mov rmcs.rFS,0 118 | mov rmcs.rGS,0 119 | mov eax,rmstack 120 | mov rmcs.rSSSP,eax 121 | lea edi,rmcs 122 | mov bx,1Ah 123 | mov cx,0 124 | mov ax,0300h 125 | int 31h 126 | jc @F 127 | mov ah,byte ptr rmcs.rFlags 128 | sahf 129 | @@: 130 | mov edi,rmcs.rEDI 131 | mov esi,rmcs.rESI 132 | mov ebx,rmcs.rEBX 133 | mov ecx,rmcs.rECX 134 | mov edx,rmcs.rEDX 135 | mov eax,rmcs.rEAX 136 | ret 137 | int_1a endp 138 | 139 | ;--- map physical memory block into linear memory 140 | 141 | mapphys proc uses ebx esi edi pLinear:dword, dwPhysBase:dword, dwSize:dword 142 | 143 | .if pLinear 144 | mov esi,pLinear 145 | xor ebx,ebx 146 | mov edx,dwPhysBase 147 | mov ecx,dwSize 148 | mov eax,edx 149 | and eax,0FFFh 150 | and dx,0F000h 151 | add ecx,eax 152 | push eax 153 | add ecx,1000h-1 154 | shr ecx,12 155 | mov ax,508h 156 | int 31h 157 | pop ecx 158 | mov eax,pLinear 159 | lea eax,[eax+ecx] 160 | .else 161 | mov cx,word ptr dwPhysBase+0 162 | mov bx,word ptr dwPhysBase+2 163 | mov di,word ptr dwSize+0 164 | mov si,word ptr dwSize+2 165 | mov ax,0800h 166 | int 31h 167 | push bx 168 | push cx 169 | pop eax 170 | .endif 171 | ret 172 | mapphys endp 173 | 174 | ;--- wait a bit 175 | 176 | dowait proc uses eax ecx 177 | 178 | mov ecx,100h 179 | nextloop: 180 | in al,61h 181 | and al,10h 182 | cmp al,ah 183 | mov ah,al 184 | jz nextloop 185 | loop nextloop 186 | ret 187 | dowait endp 188 | 189 | ;--- send command to codec, using CORB and RIRB 190 | 191 | sendcmd proc uses ebx esi pHDA:ptr, codec:dword, node:word, command:word, param:word 192 | 193 | mov ebx, pHDA 194 | mov eax,codec 195 | shl eax,28 196 | movzx ecx,node 197 | shl ecx,20 198 | or eax,ecx 199 | movzx ecx,command 200 | movzx edx,param 201 | .if ch 202 | shl ecx,8 ;some commands have a payload of 16 bits! 203 | .else 204 | shl ecx,16 205 | .endif 206 | or eax, ecx 207 | or eax, edx 208 | if ?LOGCODEC 209 | push eax 210 | endif 211 | mov si,[ebx].HDAREGS.rirbwp 212 | 213 | mov ecx, pCorb 214 | movzx edx,[ebx].HDAREGS.corbwp 215 | inc dl 216 | mov [ecx+edx*4], eax 217 | mov [ebx].HDAREGS.corbwp, dx 218 | if ?LOGCODEC 219 | mov ecx,10000h 220 | push eax 221 | @@: 222 | mov ax,[ebx].HDAREGS.corbrp 223 | cmp ax,dx 224 | loopnz @B ; wait till cmd has been sent 225 | pop eax 226 | .if ecx 227 | invoke printf, CStr("sendcmd: cmd %X send, waiting for response, rirbwp=%X",lf), eax, si 228 | .else 229 | invoke printf, CStr("sendcmd: timeout waiting for cmd %X to be sent",lf), eax 230 | .endif 231 | endif 232 | 233 | ;--- we don't check rirbsts, just check if rirbwp has changed 234 | 235 | .while si == [ebx].HDAREGS.rirbwp 236 | call dowait 237 | .endw 238 | mov ecx,pRirb 239 | movzx edx,[ebx].HDAREGS.rirbwp 240 | mov eax,[ecx+edx*8] 241 | if ?LOGCODEC 242 | pop ecx 243 | push eax 244 | invoke printf, CStr("sendcmd: sent %X, received %X",lf), ecx, eax 245 | pop eax 246 | endif 247 | ret 248 | sendcmd endp 249 | 250 | ;--- check connections of a widget, recursively, 251 | ;--- until an "audio output converter" is found 252 | ;--- out: PIN in EAX 253 | 254 | checkconn proc uses esi edi codec:dword, node:word, wtype:word 255 | 256 | local nConn:dword 257 | local currConn:dword 258 | 259 | movzx esi, node 260 | invoke sendcmd, ebx, codec, si, 0F00h, 14 ;get connections of widget 261 | mov nConn, eax 262 | xor edi, edi 263 | xor eax, eax 264 | .while edi < nConn 265 | .if !eax 266 | invoke sendcmd, ebx, codec, si, 0F02h, di ;get (up to 4) connection nodes 267 | .endif 268 | mov currConn, eax 269 | movzx esi,al 270 | invoke sendcmd, ebx, codec, si, 0F00h, 9 ;get widgettype 271 | shld ecx, eax, 12 272 | and ecx,0fh 273 | .if ecx == WTYPE_AUDIOOUT ;audio output converter found? 274 | push ecx 275 | invoke sendcmd, ebx, codec, si, 0705h, 0 ;set power state 276 | invoke sendcmd, ebx, codec, si, 0003h, 0B040h;set amplifier 277 | pop ecx 278 | .if bVerbose 279 | mov ecx, [ecx*4][widgettypes] 280 | invoke printf, CStr("codec %u, widget %u (%s), power state & amplifier set",lf), codec, esi, ecx 281 | .endif 282 | .break 283 | .endif 284 | invoke checkconn, codec, si, cx 285 | mov esi, eax 286 | .break .if eax 287 | mov eax, currConn 288 | shr eax,8 289 | inc edi 290 | .endw 291 | ;--- if audio output converter was found in this path, 292 | ;--- activate the widget (mixer, selector) and "unmute" it. 293 | .if edi < nConn 294 | .if nConn > 1 && wtype != WTYPE_MIXER 295 | invoke sendcmd, ebx, codec, node, 0701h, di ;select connection 296 | .endif 297 | invoke sendcmd, ebx, codec, node, 0705h, 0 ;set power state 298 | invoke sendcmd, ebx, codec, node, 0003h, 0F040h;set amplifier 299 | .if bVerbose 300 | invoke sendcmd, ebx, codec, node, 0F00h, 9 ;get widgettype 301 | shld ecx, eax, 12 302 | and ecx,0fh 303 | mov ecx, [ecx*4][widgettypes] 304 | invoke printf, CStr("codec %u, widget %u (%s), power state & amplifier set",lf), codec, node, ecx 305 | .endif 306 | mov eax, esi 307 | .endif 308 | ret 309 | 310 | checkconn endp 311 | 312 | ;--- display bit depths and sample rates supported by codec 313 | 314 | .const 315 | samplerates dd 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 316 | 176400, 192000, 384000, 0 317 | bitdepths db 8, 16, 20, 24, 32, 0 318 | .code 319 | 320 | dispsuppcaps proc uses esi ebx codec:dword, caps:dword 321 | 322 | invoke printf, CStr("codec %u, supported sample rates:"), codec 323 | mov ebx, caps 324 | mov esi, offset samplerates 325 | .while dword ptr [esi] 326 | lodsd 327 | .if ebx & 1 328 | invoke printf, CStr(" %u"), eax 329 | .endif 330 | shr ebx, 1 331 | .endw 332 | invoke printf, CStr(lf) 333 | 334 | invoke printf, CStr("codec %u, supported bit depths:"), codec 335 | mov ebx, caps 336 | shr ebx, 16 337 | mov esi, offset bitdepths 338 | .while byte ptr [esi] 339 | lodsb 340 | .if ebx & 1 341 | invoke printf, CStr(" %u"), eax 342 | .endif 343 | shr ebx, 1 344 | .endw 345 | invoke printf, CStr(lf) 346 | ret 347 | 348 | dispsuppcaps endp 349 | 350 | ;--- get "lineout", "speaker" or "headphone" pin widget 351 | ;--- and search a path to corresponding "audio output" widget 352 | 353 | searchaopath proc uses ebx esi edi pHDA:ptr HDAREGS, device:dword, codec:dword, wFormat:word 354 | 355 | local startnode:dword 356 | local numnodes:dword 357 | local defcfg:dword 358 | local widgetcaps:dword 359 | local afgnode:word 360 | local pinnode:word 361 | 362 | ;--- get start of root nodes 363 | 364 | mov pinnode,0 365 | mov ebx, pHDA 366 | invoke sendcmd, ebx, codec, 0, 0F00h, 4 367 | movzx ecx, al 368 | mov edi, ecx ;no of nodes 369 | shld edx, eax,16 370 | movzx edx,dl 371 | mov esi, edx ;start node 372 | 373 | ;--- search afg node 374 | 375 | .while edi 376 | invoke sendcmd, ebx, codec, si, 0F00h, 5 377 | and al,7Fh 378 | .break .if al == 1 ;audio function group found? 379 | inc esi 380 | dec edi 381 | .endw 382 | cmp edi,0 383 | jz exit 384 | mov afgnode,si 385 | 386 | invoke sendcmd, ebx, codec, afgnode, 0705h, 0 ;set power state 387 | 388 | ;--- get start of afg widgets 389 | 390 | invoke sendcmd, ebx, codec, si, 0F00h, 4 391 | movzx ecx, al 392 | mov edi, ecx 393 | shld edx, eax,16 394 | movzx edx,dl 395 | mov esi, edx 396 | mov startnode, esi 397 | mov numnodes, edi 398 | 399 | ;--- pin set with -w option? 400 | mov ax,wWidget 401 | .if ax 402 | add edi, esi 403 | .if ax < si || ax > di 404 | jmp exit 405 | .endif 406 | invoke sendcmd, ebx, codec, ax, 0F00h, 9 ;get widgettype of -w option parameter 407 | shr eax,20 408 | and al,0Fh 409 | .if al == WTYPE_PIN 410 | mov ax,wWidget 411 | mov pinnode, ax 412 | jmp pinnode_found 413 | .else 414 | invoke printf, CStr("widget %u is no pin - ignored",lf), wWidget 415 | .endif 416 | .endif 417 | 418 | ;--- scan afg widgets, searching "lineout", "speaker" and "headphone" pins 419 | 420 | .while edi 421 | invoke sendcmd, ebx, codec, si, 0F00h, 9 ;get widgettype 422 | shld ecx, eax, 12 ; widget type is in bits 20-23 423 | and ecx,0fh 424 | mov widgetcaps, eax 425 | .if cl == WTYPE_PIN 426 | invoke sendcmd, ebx, codec, si, 0F1Ch, 0 ;get default config 427 | mov defcfg, eax 428 | shld ecx,eax,12 429 | and ecx,0Fh 430 | mov edx,eax 431 | shr edx,12 432 | and edx,0fh 433 | .if cl == bDefDev 434 | bt widgetcaps, 9 435 | ; digital? 436 | .if CARRY? 437 | .if bVerbose 438 | mov ecx,[ecx*4+offset defdevices] 439 | invoke printf, CStr("codec %u, %s pin widget: %u is digital (caps=%X), ignored",lf), codec, ecx, esi, widgetcaps 440 | .endif 441 | ;--- if more than one lineout exist, prefer the one with color green (=4)! 442 | .elseif pinnode == 0 || ( cl == DEFDEV_LINEOUT && edx == 4 ) 443 | mov pinnode, si 444 | .if bVerbose 445 | mov ecx,[ecx*4+offset defdevices] 446 | invoke printf, CStr("codec %u, %s pin widget: %u (default config=%X)",lf), codec, ecx, esi, defcfg 447 | .endif 448 | .endif 449 | .endif 450 | .endif 451 | inc esi 452 | dec edi 453 | .endw 454 | 455 | pinnode_found: 456 | 457 | ;--- without a (lineout/speaker/headphone) pin, there's nothing to do 458 | cmp pinnode,0 459 | jz exit 460 | 461 | ; invoke sendcmd, ebx, codec, afgnode, 0705h, 0 ;set power state 462 | .if bReset 463 | invoke sendcmd, ebx, codec, afgnode, 7ffh, 0 ;reset afg 464 | .endif 465 | 466 | ;--- check connections of pin to find audio output converter 467 | 468 | invoke checkconn, codec, pinnode, WTYPE_PIN 469 | .if eax 470 | mov esi, eax 471 | .if !bQuiet 472 | invoke printf, CStr("codec %u, audio converter widget used: %u",lf), codec, si 473 | .endif 474 | ; check amplifier data 475 | 476 | invoke sendcmd, ebx, codec, si, 0002h, wFormat;set converter format 477 | 478 | ;--- display supported bit depths and sample rates 479 | .if !bQuiet 480 | invoke sendcmd, ebx, codec, afgnode, 0F00h, 10 481 | invoke dispsuppcaps, codec, eax 482 | .endif 483 | 484 | ;--- set stream & start channel - stream is in [7:4], start channel in [3:0] 485 | invoke sendcmd, ebx, codec, si, 0706h, ?STREAM shl 4 or ?CHANNEL 486 | .else 487 | movzx ecx, bDefDev 488 | mov ecx,[ecx*4+offset defdevices] 489 | invoke printf, CStr("codec %u: no audio output converter connected to %s pin",lf), codec, ecx 490 | mov pinnode,0 491 | jmp exit 492 | .endif 493 | invoke sendcmd, ebx, codec, pinnode, 0705h, 0 ;set power state 494 | invoke sendcmd, ebx, codec, pinnode, 0003h, 0F040h;set amplifier 495 | invoke sendcmd, ebx, codec, pinnode, 0707h, 040h ;set pin widget control (out enable) 496 | 497 | exit: 498 | movzx eax,pinnode 499 | ret 500 | searchaopath endp 501 | 502 | ;--- translate format in EAX to rate (ecx), bits (edx), channels (ebx) 503 | 504 | format2rbc proc 505 | mov ecx,48000 506 | bt eax,14 507 | jnc @F 508 | mov ecx,44100 509 | @@: 510 | mov edx,eax 511 | shr edx,11 512 | and edx,7 513 | inc edx 514 | imul ecx,edx 515 | 516 | mov ebx,eax 517 | shr ebx,8 518 | and ebx,7 519 | inc ebx 520 | xchg eax,ecx 521 | xor edx,edx 522 | idiv ebx 523 | xchg eax,ecx 524 | 525 | mov ebx,eax 526 | and ebx,0fh 527 | inc ebx 528 | mov edx,eax 529 | shr edx,4 530 | and edx,7 531 | mov dl,[edx+offset bittab] 532 | ret 533 | 534 | bittab db 8,16,20,24,32,-1,-1,-1 535 | 536 | format2rbc endp 537 | 538 | ;--- translate rate (ecx), bits (dx), channels (bx) to format in ax 539 | ;--- returned format: 540 | ;--- bits 0-3: channels - 1 541 | ;--- bits 4-6: bits/sample (0=8,1=16,2=20,3=24,4=32) 542 | ;--- bit 7: reserved 543 | ;--- bits 8-10: divisor 544 | ;--- bits 11-13: multiple 545 | ;--- bit 14: base (0=48000,1=44100) 546 | ;--- bit 15: 0=PCM 547 | 548 | rbc2format proc uses esi 549 | 550 | xor esi,esi 551 | .while esi < numrates 552 | cmp ecx,[esi*4+offset rates] 553 | jz found 554 | inc esi 555 | .endw 556 | stc 557 | ret 558 | found: 559 | mov ax, [esi*2+offset rateparms] 560 | xor esi,esi 561 | .while esi < numbits 562 | cmp dl,[esi+offset bitstab] 563 | jz found2 564 | inc esi 565 | .endw 566 | stc 567 | ret 568 | found2: 569 | or al, [esi+offset bitsparam] 570 | dec bl 571 | or al,bl 572 | ret 573 | 574 | B441 equ 4000h 575 | B480 equ 0 576 | MUL1 equ 0 577 | MUL2 equ (001b shl 11) 578 | MUL3 equ (010b shl 11) 579 | MUL4 equ (011b shl 11) 580 | DIV2 equ (001b shl 8) 581 | DIV3 equ (010b shl 8) 582 | DIV4 equ (011b shl 8) 583 | DIV5 equ (100b shl 8) 584 | DIV6 equ (101b shl 8) 585 | DIV8 equ (111b shl 8) 586 | 587 | rates dd 176400,88200,44100,22050,11025 588 | dd 192000,144000,96000,48000,32000,24000,16000,9600,8000,6000 589 | numrates equ ($ - rates) / 4 590 | rateparms dw B441+MUL4,B441+MUL2,B441+MUL1,B441+DIV2,B441+DIV4 591 | dw B480+MUL4,B480+MUL3,B480+MUL2,B480+MUL1,B480+MUL2+DIV3,B480+DIV2,B480+DIV3,B480+DIV5,B480+DIV6,B480+DIV8 592 | bitstab db 8,16,20,24,32 593 | numbits equ $ - bitstab 594 | bitsparam db 0,1 shl 4, 2 shl 4, 3 shl 4, 4 shl 4 595 | 596 | rbc2format endp 597 | 598 | ;--- display CORB & RIRB registers 599 | 600 | if ?DISPCR 601 | dispcr proc 602 | invoke printf, CStr("CORB address=0x%lX, WP=%u, RP=%u",lf), 603 | [ebx].HDAREGS.corbbase, [ebx].HDAREGS.corbwp,[ebx].HDAREGS.corbrp 604 | mov dl,[ebx].HDAREGS.corbctl 605 | .if dl & 2 606 | mov ecx, CStr("DMA running") 607 | .else 608 | mov ecx, CStr("DMA stopped") 609 | .endif 610 | invoke printf, CStr("CORB status=0x%X, size=0x%X, ctrl=0x%X - %s",lf), [ebx].HDAREGS.corbsts, [ebx].HDAREGS.corbsize, dl, ecx 611 | 612 | invoke printf, CStr("RIRB address=0x%lX, WP=%u, RIC=%u",lf), 613 | [ebx].HDAREGS.rirbbase, [ebx].HDAREGS.rirbwp,[ebx].HDAREGS.rirbric 614 | mov dl,[ebx].HDAREGS.rirbctl 615 | .if dl & 2 616 | mov ecx, CStr("DMA running") 617 | .else 618 | mov ecx, CStr("DMA stopped") 619 | .endif 620 | invoke printf, CStr("RIRB status=0x%X, size=0x%X, ctrl=0x%X - %s",lf), [ebx].HDAREGS.rirbsts, [ebx].HDAREGS.rirbsize, dl, ecx 621 | ret 622 | dispcr endp 623 | endif 624 | 625 | ;--- display HDA STREAM registers 626 | 627 | dispstream proc uses ebx esi edi pStream:ptr, no:dword 628 | 629 | mov edi,pStream 630 | movzx eax,[edi].STREAM.bCtl2316 631 | mov ecx,eax 632 | shr ecx,4 633 | shl eax,16 634 | mov ax,[edi].STREAM.wCtl 635 | invoke printf, CStr("SD%u control=0x%X (stream#=%u)",lf), no, eax, ecx 636 | invoke printf, CStr("SD%u status=0x%X",lf), no, [edi].STREAM.bSts 637 | invoke printf, CStr("SD%u link position in buffer=0x%X",lf), no, [edi].STREAM.dwLinkPos 638 | invoke printf, CStr("SD%u cyclic buffer length=0x%X",lf), no, [edi].STREAM.dwBufLen 639 | invoke printf, CStr("SD%u last valid index=0x%X",lf), no, [edi].STREAM.wLastIdx 640 | invoke printf, CStr("SD%u FIFO watermark=%u",lf), no, [edi].STREAM.wFIFOmark 641 | invoke printf, CStr("SD%u FIFO size=%u",lf), no, [edi].STREAM.wFIFOsize 642 | movzx eax,[edi].STREAM.wFormat 643 | call format2rbc 644 | invoke printf, CStr("SD%u format=0x%X (base rate=%u, bits=%u, channels=%u)",lf), no, eax, ecx, edx, ebx 645 | invoke printf, CStr("SD%u buffer description list base address=0x%lX",lf), no, [edi].STREAM.qwBuffer 646 | ret 647 | dispstream endp 648 | 649 | ;--- get HDA controller's register map 650 | 651 | getHDAaddress proc uses ebx esi edi dwPath:dword 652 | 653 | local dwPhysBase:dword 654 | 655 | mov edi, 4*4 656 | mov ebx,dwPath 657 | mov ax,0B10Ah 658 | call int_1a 659 | jc exit 660 | and cl,0F0h 661 | mov dwPhysBase, ecx 662 | mov edi, 5*4 663 | mov ebx,dwPath 664 | mov ax,0B10Ah 665 | call int_1a 666 | jc exit 667 | .if ecx 668 | mov eax,dwPhysBase 669 | invoke printf, CStr("HDA Base Address=0x%lX beyond 4 GB limit, can't be accessed.",lf), ecx::eax 670 | jmp exit 671 | .endif 672 | .if !bQuiet 673 | invoke printf, CStr("HDA Base Address=0x%X",lf), dwPhysBase 674 | .endif 675 | if 1 ;ensure that busmaster is enabled 676 | mov edi,4 ;PCI CMD 677 | mov ax,0B109h ;read word 678 | call int_1a 679 | test cl,4 680 | jnz @F 681 | or cl,4 682 | mov ax,0B10Ch ;write word 683 | call int_1a 684 | @@: 685 | endif 686 | mov eax, dwPhysBase 687 | ret 688 | exit: 689 | xor eax,eax 690 | ret 691 | getHDAaddress endp 692 | 693 | ;--- play .wav file directly with HDA. 694 | ;--- to store the samples, XMS memory is used, 695 | ;--- because DPMI memory allocation isn't guaranteed to 696 | ;--- be physically contiguous. Also, it's a problem in DPMI to 697 | ;--- get the physical address of a linear address. 698 | 699 | playwavewithHDA proc pszFN:ptr 700 | 701 | local pHDALin:dword 702 | local hFile:dword 703 | local currdevice:dword 704 | local dwXMSPhys1:dword 705 | local dwXMSPhys2:dword 706 | local pBDL:dword 707 | local xmshdl1:word 708 | local xmshdl2:word 709 | local wFormat:word 710 | local riffhdr:RIFFHDR 711 | local wavefmt:WAVEFMT 712 | local datahdr:RIFFCHKHDR 713 | local rmcs:RMCS 714 | 715 | mov hFile,-1 716 | mov xmshdl1,0 717 | mov xmshdl2,0 718 | 719 | ;--- open the .wav file 720 | 721 | mov esi,pszFN 722 | mov bx,3040h 723 | mov cx,0 724 | mov dx,1 725 | mov di,0 726 | mov ax,716Ch 727 | stc 728 | int 21h 729 | jnc @F 730 | .if ax == 7100h 731 | mov ax,6c00h 732 | int 21h 733 | .endif 734 | .if CARRY? 735 | invoke printf, CStr("cannot open '%s'",lf), esi 736 | jmp exit 737 | .endif 738 | @@: 739 | mov ebx,eax 740 | mov hFile,eax 741 | 742 | ;--- now load the RIFF headers to get the PCM format and size for the samples block 743 | 744 | lea edx,riffhdr 745 | mov ecx,sizeof riffhdr 746 | mov ax,3F00h 747 | int 21h 748 | .if eax != ecx 749 | invoke printf, CStr("file %s: cannot read riff header",lf), pszFN 750 | jmp exit 751 | .endif 752 | .if (riffhdr.chkId != "FFIR") 753 | invoke printf, CStr("file %s: no RIFF header found",lf), pszFN 754 | jmp exit 755 | .endif 756 | .if (riffhdr.format != "EVAW") 757 | invoke printf, CStr("file %s: not a WAVE format",lf), pszFN 758 | jmp exit 759 | .endif 760 | lea edx, wavefmt 761 | mov ecx, sizeof wavefmt 762 | mov ax,3F00h 763 | int 21h 764 | .if eax != ecx 765 | invoke printf, CStr("file %s: cannot read wave format",lf), pszFN 766 | jmp exit 767 | .endif 768 | .if (wavefmt.subchkId != " tmf") 769 | invoke printf, CStr("file %s: no fmt chunk found",lf), pszFN 770 | jmp exit 771 | .endif 772 | .if !bQuiet 773 | invoke printf, CStr("Channels=%u",lf), wavefmt.nChannels 774 | invoke printf, CStr("Samples/Second=%u",lf), wavefmt.nSamplesPerSec 775 | invoke printf, CStr("Bits/Sample=%u",lf), wavefmt.wBitsPerSample 776 | .endif 777 | 778 | lea edx, datahdr 779 | mov ecx, sizeof datahdr 780 | mov ax,3F00h 781 | int 21h 782 | .if eax != ecx 783 | invoke printf, CStr("file %s: cannot read data header",lf), pszFN 784 | jmp exit 785 | .endif 786 | .if (datahdr.subchkId != "atad") 787 | invoke printf, CStr("file %s: no data chunk found",lf), pszFN 788 | jmp exit 789 | .endif 790 | .if !bQuiet 791 | invoke printf, CStr("data subchunk size=%u",lf), datahdr.subchkSiz 792 | .endif 793 | 794 | ;--- translate format of file into wFormat for HDA 795 | mov ecx, wavefmt.nSamplesPerSec 796 | mov dx, wavefmt.wBitsPerSample 797 | mov bx, wavefmt.nChannels 798 | call rbc2format 799 | .if CARRY? 800 | invoke printf, CStr("format not supported",lf) 801 | jmp exit 802 | .endif 803 | mov wFormat, ax 804 | 805 | ;--- XMS memory is to be allocated, since physical addresses are needed. 806 | ;--- first find XMM entry point. 807 | 808 | lea edi,rmcs 809 | xor eax,eax 810 | mov rmcs.rAX,4300h 811 | mov rmcs.rFlags,3202h 812 | mov rmcs.rSSSP,0 813 | mov bx,2fh 814 | mov cx,0 815 | mov ax,0300h 816 | int 31h 817 | mov eax,rmcs.rEAX 818 | .if al != 80h 819 | invoke printf, CStr("no XMM found",lf) 820 | jmp exit 821 | .endif 822 | mov rmcs.rAX,4310h ;get driver entry address 823 | mov ax,0300h 824 | int 31h 825 | 826 | ;--- copy XMS entry point to rmcs.CS:IP. 827 | ;--- the XMM will be called via a DPMI "call real-mode proc with RETF frame". 828 | 829 | mov ax,rmcs.rES 830 | mov dx,rmcs.rBX 831 | mov rmcs.rCS,ax 832 | mov rmcs.rIP,dx 833 | 834 | ;--- allocate (& lock) XMS memory 835 | ;--- first a block for CORB, RIRB & BDL, size 1024+2048+32 836 | 837 | mov rmcs.rAX,8900h 838 | mov rmcs.rEDX,4 ;alloc 4 kB 839 | mov bh,0 840 | mov ax,0301h 841 | int 31h 842 | .if rmcs.rAX != 1 843 | invoke printf, CStr("XMS memory allocation failed",lf) 844 | jmp exit 845 | .endif 846 | mov ax,rmcs.rDX 847 | mov xmshdl1,ax 848 | mov rmcs.rAX,0C00h ;lock the block 849 | mov bh,0 850 | mov ax,0301h 851 | int 31h 852 | .if rmcs.rAX != 1 853 | invoke printf, CStr("XMS memory lock failed",lf) 854 | jmp exit 855 | .endif 856 | mov ax,rmcs.rDX 857 | shl eax,16 858 | mov ax,rmcs.rBX 859 | mov dwXMSPhys1,eax 860 | .if !bQuiet 861 | invoke printf, CStr("EMB physical address=%X, used for CORB, RIRB and BDL",lf), eax 862 | .endif 863 | 864 | ;--- map the block into linear memory so it can be accessed 865 | 866 | invoke mapphys, pMemRg2, dwXMSPhys1, 1024 * 4 867 | jc exit 868 | .if ax & 400h 869 | mov pCorb, eax 870 | add eax, 256*4 871 | mov pRirb, eax 872 | add eax, 256*8 873 | .else 874 | mov pRirb, eax 875 | add eax, 256*8 876 | mov pCorb, eax 877 | add eax, 256*4 878 | .endif 879 | mov pBDL, eax 880 | 881 | ;--- allocate second XMS block for samples 882 | 883 | mov eax,datahdr.subchkSiz 884 | add eax, 400h-1 885 | shr eax,10 ;convert to kB 886 | 887 | mov rmcs.rAX,8900h 888 | mov rmcs.rEDX,eax 889 | mov bh,0 890 | mov cx,0 891 | mov ax,0301h 892 | int 31h 893 | .if rmcs.rAX != 1 894 | invoke printf, CStr("can't allocate %u kB XMS memory for samples",lf), rmcs.rEDX 895 | jmp exit 896 | .endif 897 | mov ax,rmcs.rDX 898 | mov xmshdl2,ax 899 | mov rmcs.rAX,0C00h 900 | mov bh,0 901 | mov ax,0301h 902 | int 31h 903 | .if rmcs.rAX != 1 904 | invoke printf, CStr("XMS memory lock failed",lf) 905 | jmp exit 906 | .endif 907 | mov ax,rmcs.rDX 908 | shl eax,16 909 | mov ax,rmcs.rBX 910 | mov dwXMSPhys2,eax 911 | .if !bQuiet 912 | invoke printf, CStr("EMB physical address=%X, used for samples",lf), eax 913 | .endif 914 | 915 | ;--- init 2 entries for BDL 916 | 917 | mov eax, pBDL 918 | mov edx, dwXMSPhys2 919 | mov dword ptr [eax].BDLENTRY.qwAddr+0, edx 920 | mov dword ptr [eax].BDLENTRY.qwAddr+4, 0 921 | mov ecx, datahdr.subchkSiz 922 | mov dword ptr [eax].BDLENTRY.dwLen, ecx 923 | mov dword ptr [eax].BDLENTRY.dwFlgs, 0 924 | mov dword ptr [eax+sizeof BDLENTRY].BDLENTRY.qwAddr+0, edx 925 | mov dword ptr [eax+sizeof BDLENTRY].BDLENTRY.qwAddr+4, 0 926 | mov dword ptr [eax+sizeof BDLENTRY].BDLENTRY.dwLen, ecx 927 | mov dword ptr [eax+sizeof BDLENTRY].BDLENTRY.dwFlgs, 0 928 | 929 | ;--- find HDA device(s) that have a lineout/speaker/headphone pin. 930 | ;--- expect to find more than one HDA controller - HDMI 931 | ;--- video output may have it's own controller/codec. 932 | 933 | movzx eax, wDevice ; use the index set with -d as start (default 0) 934 | mov currdevice, eax 935 | nextdevice: 936 | mov esi,currdevice 937 | mov ecx,040300h ;search for HD Audio device(s) 938 | mov ax,0B103h 939 | call int_1a 940 | .if ah != 0 941 | movzx eax, wDevice 942 | .if eax == currdevice 943 | .if eax 944 | invoke printf, CStr("no HDA device #%u found",lf), eax 945 | .else 946 | invoke printf, CStr("no HDA device found",lf) 947 | .endif 948 | .elseif !bQuiet 949 | movzx ecx,bDefDev 950 | mov ecx,[ecx*4+offset defdevices] 951 | invoke printf, CStr("no HDA device with %s pin found",lf), ecx 952 | .endif 953 | jmp exit 954 | .endif 955 | invoke getHDAaddress, ebx 956 | .if !eax 957 | invoke printf, CStr("can't get HDA register map address",lf) 958 | jmp exit 959 | .endif 960 | 961 | invoke mapphys, pMemRg1, eax, 1000h ;map the HDA registers into linear address space 962 | jc exit 963 | mov pHDALin, eax 964 | 965 | ;--- HDA controller in reset? 966 | 967 | mov ebx, eax 968 | test [ebx].HDAREGS.gctl,1 969 | jnz hda_running 970 | or [ebx].HDAREGS.gctl,1 971 | mov ecx,10000h 972 | @@: 973 | call dowait 974 | test [ebx].HDAREGS.gctl, 1 975 | loopz @B 976 | .if (!ecx) 977 | invoke printf, CStr("timeout starting HDA controller",lf) 978 | jmp exit 979 | .endif 980 | hda_running: 981 | 982 | if ?DISPCR 983 | .if bVerbose 984 | invoke printf, CStr(lf,"CORB/RIRB before init",lf) 985 | call dispcr 986 | .endif 987 | endif 988 | 989 | ;--- reset CORB, RIRB 990 | 991 | and [ebx].HDAREGS.corbctl,not 2 992 | and [ebx].HDAREGS.rirbctl,not 2 993 | mov ecx,1000h 994 | @@: 995 | call dowait 996 | test [ebx].HDAREGS.corbctl,2 997 | loopnz @B 998 | 999 | ;--- set CORB & RIRB ring buffers 1000 | 1001 | mov edx, dwXMSPhys1 1002 | .if dx & 400h 1003 | mov dword ptr [ebx].HDAREGS.corbbase+0, edx 1004 | mov dword ptr [ebx].HDAREGS.corbbase+4, 0 1005 | add edx, 256*4 1006 | mov dword ptr [ebx].HDAREGS.rirbbase+0, edx 1007 | mov dword ptr [ebx].HDAREGS.rirbbase+4, 0 1008 | .else 1009 | mov dword ptr [ebx].HDAREGS.rirbbase+0, edx 1010 | mov dword ptr [ebx].HDAREGS.rirbbase+4, 0 1011 | add edx, 256*8 1012 | mov dword ptr [ebx].HDAREGS.corbbase+0, edx 1013 | mov dword ptr [ebx].HDAREGS.corbbase+4, 0 1014 | .endif 1015 | 1016 | mov [ebx].HDAREGS.corbwp,0 ;reset CORB WP 1017 | mov [ebx].HDAREGS.rirbwp,8000h ;reset RIRB WP 1018 | mov [ebx].HDAREGS.rirbric,?INTCNT ;interrupt after x responses 1019 | 1020 | ;--- to reset the CORB RP, first set bit 15 to 1, then back to 0 1021 | ;--- seems not to work on many? machines, so do with timeout. 1022 | 1023 | or byte ptr [ebx].HDAREGS.corbrp+1,80h ;reset CORB RP 1024 | mov ecx,1000h 1025 | @@: 1026 | call dowait 1027 | cmp [ebx].HDAREGS.corbrp,0 ;skip wait if corbrp == 0 1028 | jz @F 1029 | test byte ptr [ebx].HDAREGS.corbrp+1,80h 1030 | loopz @B 1031 | @@: 1032 | and byte ptr [ebx].HDAREGS.corbrp+1,7fh 1033 | mov ecx,1000h 1034 | @@: 1035 | call dowait 1036 | test byte ptr [ebx].HDAREGS.corbrp+1,80h 1037 | loopnz @B 1038 | 1039 | ;--- start DMA engines for CORB and RIRB 1040 | 1041 | or [ebx].HDAREGS.corbctl,2 1042 | or [ebx].HDAREGS.rirbctl,2 1043 | mov ecx,1000h 1044 | @@: 1045 | call dowait 1046 | test [ebx].HDAREGS.corbctl,2 1047 | loopz @B 1048 | if ?SENDNULL 1049 | xor eax,eax 1050 | mov ecx, pCorb 1051 | movzx edx,[ebx].HDAREGS.corbwp 1052 | inc dl 1053 | mov [ecx+edx*4], eax 1054 | mov [ebx].HDAREGS.corbwp, dx 1055 | endif 1056 | 1057 | if ?DISPCR 1058 | .if bVerbose 1059 | invoke printf, CStr(lf,"CORB/RIRB after init",lf) 1060 | call dispcr 1061 | .endif 1062 | endif 1063 | 1064 | ;--- scan STATESTS for attached codecs; 1065 | ;--- search the codec's lineout/speaker/headphone pin. 1066 | 1067 | mov esi,0 1068 | movzx ecx, [ebx].HDAREGS.statests 1069 | .if ( ecx == 0 && wCodec == 0 ) 1070 | invoke searchaopath, ebx, currdevice, esi, wFormat 1071 | .else 1072 | xor eax, eax 1073 | ;--- multiple codecs, scan them until a valid pin has been found 1074 | .while ecx 1075 | .if ( ecx & 1 ) && ( si >= wCodec ) 1076 | push ecx 1077 | invoke searchaopath, ebx, currdevice, esi, wFormat 1078 | pop ecx 1079 | .break .if eax 1080 | .endif 1081 | shr ecx,1 1082 | inc esi 1083 | .endw 1084 | .endif 1085 | .if !eax 1086 | .if !bQuiet 1087 | movzx ecx,bDefDev 1088 | mov ecx,[ecx*4+offset defdevices] 1089 | .if wCodec || wDevice 1090 | movzx edx, wCodec 1091 | movzx eax, wDevice 1092 | invoke printf, CStr("no %s pin found for device %u, codec %u",lf), ecx, eax, edx 1093 | .else 1094 | invoke printf, CStr("no %s pin found for this device",lf), ecx 1095 | .endif 1096 | .endif 1097 | ;--- stop CORB and RIRB DMA engines 1098 | call stopcr 1099 | call unmaphda 1100 | inc currdevice 1101 | jmp nextdevice 1102 | .endif 1103 | 1104 | ;--- a HDA with lineout/speaker/headphone pin has been found 1105 | 1106 | ;--- reset first output stream 1107 | ;--- first, position EDI to the first output stream 1108 | movzx edi,[ebx].HDAREGS.gcap 1109 | shr edi,8 1110 | and edi,0fh ;no of input streams 1111 | shl edi,5 ;*32 (=sizeof STREAM) 1112 | lea edi,[ebx+edi+HDAREGS.stream0] 1113 | 1114 | ;--- reset stream 1115 | 1116 | mov ecx,1000h 1117 | or [edi].STREAM.wCtl, 1 1118 | @@: 1119 | call dowait 1120 | test [edi].STREAM.wCtl,1 1121 | loopz @B 1122 | mov ecx,1000h 1123 | and [edi].STREAM.wCtl, not 1 1124 | @@: 1125 | call dowait 1126 | test [edi].STREAM.wCtl,1 1127 | loopnz @B 1128 | 1129 | ;--- init stream[x] in HDA controller memory 1130 | 1131 | mov al,?STREAM 1132 | shl al,4 1133 | mov [edi].STREAM.bCtl2316, al 1134 | mov [edi].STREAM.dwLinkPos, 0 1135 | mov eax,datahdr.subchkSiz 1136 | mov [edi].STREAM.dwBufLen, eax 1137 | mov [edi].STREAM.wLastIdx, 1 1138 | mov ax, wFormat 1139 | mov [edi].STREAM.wFormat, ax 1140 | mov edx, dwXMSPhys1 1141 | add edx, 256*(4+8) ;calculate BDL physical address 1142 | mov dword ptr [edi].STREAM.qwBuffer+0, edx 1143 | mov dword ptr [edi].STREAM.qwBuffer+4, 0 1144 | .if bVerbose 1145 | mov eax, edi 1146 | sub eax, ebx 1147 | sub eax, 80h 1148 | shr eax, 5 ; eax = ( edi - ( ebx + 80h ) ) / 32 -> SD# 1149 | invoke printf, CStr("stream descriptor %u initialized",lf), eax 1150 | .endif 1151 | 1152 | ;--- the HDA is ready to start the DMA process 1153 | ;--- map memory for samples in address space, read samples, and unmap buffer 1154 | 1155 | invoke mapphys, 0, dwXMSPhys2, datahdr.subchkSiz 1156 | jc exit 1157 | mov edx, eax 1158 | push eax 1159 | mov ecx, datahdr.subchkSiz 1160 | mov ebx,hFile 1161 | mov ax,3F00h 1162 | int 21h 1163 | pop cx 1164 | pop bx 1165 | mov ax,0801h ;unmap linear region in BX:CX 1166 | int 31h 1167 | mov ebx, pHDALin 1168 | 1169 | ;--- run the DMA engine 1170 | 1171 | or [edi].STREAM.wCtl, 2 1172 | 1173 | .if bVerbose 1174 | movzx eax,[ebx].HDAREGS.gcap 1175 | shr eax,8 1176 | and eax,0Fh 1177 | invoke dispstream, edi, eax 1178 | .endif 1179 | 1180 | call stopcr 1181 | 1182 | if ?SHELL 1183 | push ds 1184 | push offset fcb 1185 | push ds 1186 | push offset fcb 1187 | push ds 1188 | push offset cmdl 1189 | mov edx,CStr("C:\COMMAND.COM") 1190 | mov ebx,esp 1191 | mov ax,4B00h 1192 | int 21h 1193 | add esp,6*4 1194 | else 1195 | invoke printf, CStr("press a key to continue...") 1196 | mov ah,10h 1197 | int 16h 1198 | invoke printf, CStr(lf) 1199 | endif 1200 | 1201 | ;--- stop DMA engine 1202 | and [edi].STREAM.wCtl, not 2 1203 | call unmaphda 1204 | 1205 | exit: 1206 | mov ax,xmshdl2 1207 | .if ax 1208 | mov rmcs.rDX,ax 1209 | mov rmcs.rAX,0D00h ;unlock XMS block 1210 | lea edi,rmcs 1211 | mov bh,0 1212 | mov cx,0 1213 | mov ax,0301h 1214 | int 31h 1215 | mov rmcs.rAX,0A00h ;free XMS block 1216 | mov bh,0 1217 | mov ax,0301h 1218 | int 31h 1219 | .endif 1220 | 1221 | mov ax,xmshdl1 1222 | .if ax 1223 | mov rmcs.rDX,ax 1224 | mov rmcs.rAX,0D00h ;unlock XMS block 1225 | lea edi,rmcs 1226 | mov bh,0 1227 | mov cx,0 1228 | mov ax,0301h 1229 | int 31h 1230 | mov rmcs.rAX,0A00h ;free XMS block 1231 | mov bh,0 1232 | mov ax,0301h 1233 | int 31h 1234 | .endif 1235 | 1236 | .if hFile != -1 1237 | mov ebx,hFile 1238 | mov ah,3Eh 1239 | int 21h 1240 | .endif 1241 | ret 1242 | 1243 | stopcr: 1244 | and [ebx].HDAREGS.corbctl, not 2 1245 | and [ebx].HDAREGS.rirbctl, not 2 1246 | mov ecx,10000h 1247 | @@: 1248 | call dowait 1249 | test [ebx].HDAREGS.rirbctl,2 1250 | loopnz @B 1251 | retn 1252 | 1253 | unmaphda: 1254 | 1255 | ;--- unmap HDA memory map 1256 | mov ecx,ebx 1257 | shr ebx,16 1258 | mov ax,0801h 1259 | int 31h 1260 | retn 1261 | 1262 | playwavewithHDA endp 1263 | 1264 | ;--- get widget number 1265 | 1266 | getnumber proc 1267 | xor edx,edx 1268 | nextnum: 1269 | xor ecx,ecx 1270 | mov al,[edi] 1271 | .while al 1272 | sub al,'0' 1273 | jb error 1274 | cmp al,9 1275 | ja error 1276 | movzx eax,al 1277 | imul ecx,10 1278 | add ecx,eax 1279 | inc edi 1280 | mov al,[edi] 1281 | .endw 1282 | mov eax, ecx 1283 | clc 1284 | ret 1285 | error: 1286 | stc 1287 | ret 1288 | getnumber endp 1289 | 1290 | ;--- find a path to lineout/speaker/headphone and stream a .wav file 1291 | 1292 | main proc c argc:dword,argv:dword 1293 | 1294 | local dwClass:dword 1295 | local pszType:dword 1296 | local pszFN:dword 1297 | 1298 | mov pszFN,0 1299 | mov esi, argc 1300 | mov ebx,argv 1301 | add ebx,4 1302 | .while esi > 1 1303 | mov edi,[ebx] 1304 | mov ax,[edi] 1305 | .if al == '-' || al == '/' 1306 | or ah,20h 1307 | .if ah == 'q' 1308 | or bQuiet, 1 1309 | .elseif ah == 'r' 1310 | or bReset, 1 1311 | .elseif ah == 's' 1312 | mov bDefDev, DEFDEV_SPEAKER 1313 | .elseif ah == 'h' 1314 | mov bDefDev, DEFDEV_HEADPHONE 1315 | .elseif ah == 'v' 1316 | or bVerbose, 1 1317 | .elseif ah == 'w' 1318 | add edi,2 1319 | call getnumber 1320 | jc usage 1321 | mov wWidget, ax 1322 | .elseif ah == 'd' 1323 | add edi,2 1324 | call getnumber 1325 | jc usage 1326 | mov wDevice, ax 1327 | .elseif ah == 'c' 1328 | add edi,2 1329 | call getnumber 1330 | jc usage 1331 | mov wCodec, ax 1332 | .else 1333 | jmp usage 1334 | .endif 1335 | .elseif pszFN == 0 1336 | mov pszFN, edi 1337 | .else 1338 | jmp usage 1339 | .endif 1340 | dec esi 1341 | add ebx,4 1342 | .endw 1343 | cmp pszFN,0 1344 | jz usage 1345 | 1346 | mov ax,100h 1347 | mov bx,40h ; allocate 1 kB (40h paragraphs) DOS memory for PCI BIOS calls 1348 | int 31h 1349 | jc exit 1350 | mov word ptr rmstack+0,400h 1351 | mov word ptr rmstack+2,ax 1352 | 1353 | ;--- allocate 2 uncommitted regions; 1354 | ;--- will be used to map HDA controller and CORB/RIRB 1355 | xor ebx,ebx 1356 | mov ecx,2000h 1357 | xor edx,edx 1358 | mov ax,504h 1359 | int 31h 1360 | jc @F 1361 | mov pMemRg1,ebx 1362 | @@: 1363 | xor ebx,ebx 1364 | mov ecx,2000h 1365 | xor edx,edx 1366 | mov ax,504h 1367 | int 31h 1368 | jc @F 1369 | mov pMemRg2,ebx 1370 | @@: 1371 | 1372 | xor edi,edi 1373 | mov ax,0B101h 1374 | call int_1a 1375 | movzx eax,ax 1376 | cmp ah,0 1377 | jnz error1 1378 | cmp edx," ICP" 1379 | jnz error1 1380 | 1381 | invoke playwavewithHDA, pszFN 1382 | exit: 1383 | ret 1384 | usage: 1385 | invoke printf, CStr("hdaplay v1.2",lf) 1386 | invoke printf, CStr("play PCM file (.wav) with HD Audio",lf) 1387 | invoke printf, CStr("usage: hdaplay [ options ] filename",lf) 1388 | invoke printf, CStr("options:",lf) 1389 | invoke printf, CStr(" -c : select codec index to start scan (default 0)",lf) 1390 | invoke printf, CStr(" -d : select device index to start scan (default 0)",lf) 1391 | invoke printf, CStr(" -h : use headphone instead of lineout widget",lf) 1392 | invoke printf, CStr(" -q : quiet (means: no displays)",lf) 1393 | invoke printf, CStr(" -r : reset Audio Function Group",lf) 1394 | invoke printf, CStr(" -s : use speaker instead of lineout widget",lf) 1395 | invoke printf, CStr(" -v : more displays",lf) 1396 | invoke printf, CStr(" -w: set pin widget number for output",lf) 1397 | ret 1398 | error1: 1399 | invoke printf, CStr("no PCI BIOS implemented",lf) 1400 | ret 1401 | main endp 1402 | 1403 | include setargv.inc 1404 | 1405 | start32 proc c public 1406 | call _setargv 1407 | invoke main, eax, edx 1408 | mov ax,4c00h 1409 | int 21h 1410 | start32 endp 1411 | 1412 | END start32 1413 | 1414 | --------------------------------------------------------------------------------