How to play sound Intel HD Audio?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

No luck with the PIO mode either, just times out and returns 0x0.
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Re: How to play sound Intel HD Audio?

Post by Ethin »

I haven't been working on my OS in some time -- I'm in College now and so I've lost a lot of time to actually tinker with it and get audio working. If I ever get the time to do so, I'll definitely work on implementing HDA. I've been monitoring this topic though. This does raise an important question: how would I actually play audio streams? After I've gotten the controller set up and the driver working, how would I then make it play audio? I take it it expects raw PCM samples -- what else would it want -- but where would I actually place them? Would I ust allocate a dedicated memory region for audio samples, store them and then give the controller a pointer to that memory region? If so, what would you recommend for the size for the smoothest audio playback?
reapersms
Member
Member
Posts: 48
Joined: Fri Oct 04, 2019 10:10 am

Re: How to play sound Intel HD Audio?

Post by reapersms »

Sizing audio buffers depends a lot on the use case. Larger buffers make it easier to avoid dropouts, but increases the latency.

A music player might want large buffers, as it can easily generate audio for the future in bulk, and a quarter second delay between hitting play or stop and hearing the effect isn't a huge deal. A game will generally want a smaller buffer, and dps work for audio recording and effects will want even less.

The tradeoff of a smaller buffer is being more vulnerable to scheduler trickery. If the program generating the audio doesn't reliably get enough cpu time to stay ahead of the audio device, bad things happen.

Where is a matter of what the hardware requires. Old stuff will need isa dma, newer stuff would presumably care less. Audio is ridiculously low bandwidth compared to everything else, so it is less likely to have dedicated memory or caching requirements.

The details come down to things like what api you present to the application, and whether you have a reliable playback position or sample counter. Api wise things come down to push or pull. Push means the app gives you a buffer of N samples often enough to not drop. The easy way to deal with that is to block until the provided buffer starts playing, and let the app deal with the threading implications. Pull means the app provides a callback, and the driver, library, or os calls it whenever it has space in its buffer to fill.

The old soundblaster days generally involved a dma buffer and an irq that would trigger at halfway and wrapping, with a queryable counter to track the current playback position.

A better answer will depend on what you is is trying to do.
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Re: How to play sound Intel HD Audio?

Post by Ethin »

What I'm asking is what I should do as a general use-case. I can't run games (my OS can't even run programs yet). How large should I make the buffer just for general audio playback? Should I make it (say) 1000 samples at a time? Or do a second of audio (which would mean a second of latency).
Then again, I would need to figure out how exactly I should wrap this. I wonder if a similar approach to how I've done the keyboard would work? That works by doing this:
1. Timer interrupt is fired:
a. Keyboard queue mutex is locked.
b. The OS asks the keyboard for a 'fetched command'. Every command to the keyboard is stored in a command queue to be dequeued on every timer interrupt.
c. If there are no commands in the queue, nothing happens.
In either case, the PIC is sent an EOI and the mutex is released.
2. On a keyboard interrupt:
a. The keyboard is immediately read so we don't lose the information.
b. The global keyboard and key queue mutexes are locked to prevent possible MT issues.
c. The OS scans the byte and does one of the following depending on what it is:
i. If the byte is either 00h or ffh, the keyboard driver is sent a notification indicating a key error.
ii. If the byte is aah, the driver is notified that a self-test has concluded and has succeeded. An identical action for self-test failure is sent if the byte is fch or fdh.
iii. If the byte is fah, the driver is notified of this acknowledgement. If the most recent command was an identity byte (f2h), the keyboard is read twice to capture these ID bytes and the driver is sent these bytes, in sequence.
iv. A resend request is sent to the driver if a resend command is sent to the keyboard.
v. In any other case, the byte is sent directly to the driver as either an Unicode character or a raw key code.
d. An eOI is sent to the PIC and all mutexes are released.
This seems complicated when written like this, but its not in code. Could something similar to this model be adapted to HDA?
Side note: the mutexes are a requirement to satisfy Rust. Rust enforces multithreading safety guarantees no matter what environment your writing your code for; the compiler does not care and will always enforce this, so we need to obey it.
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

I'd say as a first step don't worry about the nitty gritty details about what kind of audio you want to play, just make it play "something".

The HDA audio codecs support potentially a range of formats, but you can pretty much guarantee PCM, 16bit, 44.1/48khz.
The HDA Codec will normally have multiple DAC widgets, some have different format capabilities.

In my case I load up a 16bit/44.1khz PCM file (a wav file.. just drop the header).

I setup the HDA to use a buffer the full size of the entire audio file and let it play. My case is slightly unique in that audio will play until I'm done and then I will reset/reboot/exit anyway so I don't need to consider latency and feeding the buffer etc although my driver is fully configurable to do so.

You load the pcm data into normal memory anywhere, you setup the HDA Buffer descriptor list entries to point at it, configure the stream to use that BDL and start the stream, job done (assuming all your configuration is right).
The HDA controller fetches the pcm data with DMA so you don't have to do anything once it's going.

For more flexibility you'd want a configurable buffer/latency option, when you get the end of buffer interrupt you'd re-fill the buffer with the next set of samples. The BDL has at last 2 buffers, so I would use it as a ping-pong arrangement to keep the audio playing smoothly while you reload the next set of samples.

I'm not sure if the buffer size is going to directly determine all types of latency as long as the dma hasn't already fetched the data you can stop a stream instantly so for stop/start latency it might be lower than buffer size and that would only apply to dynamically changing sample data latency (ie. playing notes).
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

Interestingly, the Intel series 100/200/300 chipset HDA controller has a bunch more stuff on it that all previous ones, there is now an Audio DSP block (from BAR2), a few more config registers although none of them look like they would affect normal operation. I don't see any info in the manuals about the contents or operation of the Audio DSP stuff.

My Mac is using a broadwell chip so I think it must be a series 9 chipset.. I should probably add a chipset identification to my OS so i can at least implement the stubs of a per-chipset driver setup.
User avatar
DavidCooper
Member
Member
Posts: 1150
Joined: Wed Oct 27, 2010 4:53 pm
Location: Scotland

Re: How to play sound Intel HD Audio?

Post by DavidCooper »

johnsa wrote:...the approach we've probably all used is a generic widget enumeration...
I think it's the only sane option for an individual writing an OS who doesn't want to spend all their time writing drivers. The most important thing is just to get sound to speakers and/or headphones, plus volume control for each, and that isn't too hard to do. Some may be connected up in such a way that you can either use speakers or headphones and not both at once, but I suspect most put them on different pins with independent volume controls while allowing them to get input from the same or different DACs. Microphone sockets are disappearing, which means we should write USB audio drivers for sound input instead (or just rely on a built-in microphone, which is far from ideal, or use the headphone socket for the microphone and be forced to use the speakers for sound output). That push towards further reliance on USB makes the idea of spending a lot of time on drivers for individual hda codecs less attractive, unless something very specific needs to be done to get a particular type working.
That's the first page I've seen anywhere that gives any clue as to how to set vref for microphones.

The big priority though is just to get sound output so that blind people can use some kind of speech synthesis up and running. That speech synthesis needn't sound great, but merely be intelligible. I will be happy to help build that with them.
Ethin wrote:how would I actually play audio streams?
That's the easiest bit as you can just write wav file content into the buffer. If you've loaded an entire sound file into memory in wav form (minus the header), you can simply make that the buffer and the dma engine will run through it over and over again until you tell it to stop. If you want to save space, you could use a ten second buffer and set a timer to maintain its content every few seconds, loading new data into it far ahead of the current read position. There's no great pressure on the software to keep up with this, which is why using hda interrupts isn't necessary. The only kind of thing you might need to do in a hurry is make warning beeps, and that doesn't need interrupts: you just need to write as close to the current read position as you can without being overtaken by the dma engine. You can hear Windows get this wrong sometimes when the start of a sound is missing because it was written too close to the read position.

If your OS is keeping well on top of everything, your buffer could be under a second in length, and it maybe should be that short. If you're playing a music track, you should be copying small chunks of it from elsewhere in memory to it on a regular and frequent basis. If there's additional sound data needing to be played simultaneously, that data may only be generated a moment before it needs to be played, and it should in theory be more efficient to add it to the data from the main sound track and write the result into the buffer that the dma is reading rather than writing the main track to that buffer in advance and then adding the extra sounds to it there. The difference may be insignificant though: the latter approach would involve copying data to the buffer, some of which will then need to be replaced, and adding the other sound track to it involves the processor dragging data back from the buffer to add the new data and then writing the result back to the buffer. It'll collect it from the closest cache though, so it'll still be quick if the cache doesn't have to be reloaded. There is a big advantage with the latter approach though as the sound track will keep playing for longer if something goes wrong. Indeed, I can have a sound file go on playing while my OS switches back into real mode to use the BIOS to load data from (or save data to) a flash drive. (That isn't a capability that my OS will need once I have working USB drivers, but it illustrates what's possible.) However, nothing should be going wrong: there should be a regular timer interrupt triggering a process into making sure the buffer holds enough content to last beyond the next such interrupt.
Help the people of Laos by liking - https://www.facebook.com/TheSBInitiative/?ref=py_c

MSB-OS: http://www.magicschoolbook.com/computing/os-project - direct machine code programming
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

I've tried the immediate mode command option on all my machines and it doesn't seem to send back anything on any of them, which seems wrong to me.. I would think it should work in at least some places.. so worth getting it right before ruling it out on the mac..

Here is the code:

Code: Select all

HDA_SendImmediateCmd PROC FRAME USES rsi command:DWORD

	mov rsi,_hda_ctrl_ptr

	; Wait for PIO to be ready for a new command
	mov ecx,100000
@@:
	mov ax,(HDA_CTRL_REGS PTR [rsi]).ics
	test ax,1	
	jz short pioReady	; Wait for ICB (bit 0) to be clear.
	dec ecx
	jnz short @B
	xor ax,ax
	mov (HDA_CTRL_REGS PTR [rsi]).ics,ax
	jmp short @B

pioReady:
	; Set the command dword
	mov eax,command
	mov (HDA_CTRL_REGS PTR [rsi]).icoi,eax

	; Reset IRV bit
	mov ecx,100000
	mov ax,2
	mov (HDA_CTRL_REGS PTR [rsi]).ics,ax
@@:
	mov ax,(HDA_CTRL_REGS PTR [rsi]).ics
	test ax,2
	jz short @F
	dec ecx
	jnz short @B
@@:

	; Initiate the command send
	mov ax,1
	mov (HDA_CTRL_REGS PTR [rsi]).ics,ax

	; Wait for response
	mov ecx,1000000
@@:
	mov ax,(HDA_CTRL_REGS PTR [rsi]).ics
	test ax,2
	jnz short @F	; Wait for IRV (bit 1) to be set.
	dec ecx
	jnz short @B
@@:

	; Get Response
	mov eax,(HDA_CTRL_REGS PTR [rsi]).irii

	ret
HDA_SendImmediateCmd ENDP
Apart from ensuring CORB/RIRB are disabled is there anything else that needs to be set to allow PIO mode to work ?
User avatar
DavidCooper
Member
Member
Posts: 1150
Joined: Wed Oct 27, 2010 4:53 pm
Location: Scotland

Re: How to play sound Intel HD Audio?

Post by DavidCooper »

It isn't easy for me to follow uncommented code as I don't know the programming language used (I assume it's C), but what I'll do is give you the code I originally used to explore the codec (which I still have). I collected all the data about all the widgets on my main programming machine using the immediate command interface before I switched to using CORB and RIRB. I've simplified it a little here to avoid bringing in values like the base address from variables. My OS allows me to write machine code straight into memory and then run it at the press of a key, and I can edit it and run it again a moment later, so I was editing the command repeatedly and then running the code to get a response back, and that response would immediately be displayed on the screen.

190 0 128 163 254 (load ESI with the base address of hda ports),
184 64 176 35 0 (load EAX with a command - this is that part I would edit each time),
137 134 96 0 0 0 (this writes the command in EAX to port 60h),
138 134 104 0 0 0 (this reads port 68h),
12 1 (OR AL with 1 to set bit 0),
136 134 104 0 0 0 (write result back to same port to trigger it into sending the command to the codec),
185 0 0 10 0 226 254 (this is just a long delay loop to give the response time to arrive),
139 134 100 0 0 0 (read port 64h),
163 resp ... (store result to variable),
139 134 100 0 0 0 (read port 64h again to collect second half of response - no need to store it),
195 (ret).

That code worked despite not conforming to this:-

5. Software polls for IRV (Bit 1 of ICS) being set, then the PIO verb response is read from the IRR register and the IRV is cleared by writing a 1 to it

Because I was using a delay loop and there were even longer delays between commands as I had to type them in each time, I just ignored what bit 1 was doing and everything worked fine regardless. For the same reason, I also never bothered to look to see if the ports were ready for a command to be sent, but just set bit 0 each time to send them through.
Help the people of Laos by liking - https://www.facebook.com/TheSBInitiative/?ref=py_c

MSB-OS: http://www.magicschoolbook.com/computing/os-project - direct machine code programming
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

Found my bug! I had some erroneous padding in my HDA registers struct!

The immediate mode commands are now working everywhere, including on the mac.
I've implemented the pio mode for the first command on the codec enumeration so-far and it's reporting happily that the iMac has a Cirrus logic HD Codec (0x1013:0x4208).
So we know the link is working and the codec is responding, it's just the RIRB that doesn't.
Presently my CORB and RIRB buffers are allocated in normal memory, I'm wondering if I should put them in un-cached, although DMA transfers shouldn't have an issue from normal WB memory, and they don't only any other machine I have.
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

https://github.com/torvalds/linux/blob/ ... h_cirrus.c
https://bugzilla.kernel.org/show_bug.cgi?id=195671

Interesting, some cirrus codec specific stuff..

I managed to find a datasheet for 4207 but nothing for 4208.

The widget list for 4207 looks nothing like what I'm getting back on the 4208. Even with immediate mode now I still get no output on the mac, and the link position register and stream dma don't seem to run either..
so it seems to me as if the rirb dma issue is a more general dma problem.
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

Ok, found a potential lead.. on boot on the mac the hd controller pcie device is:

Code: Select all

Vendor ID(0): 8086                     Device ID(2): 8CA0

Command(4): 0006
  (00)I/O space access enabled:       0  (01)Memory space access enabled:    1
  (02)Behave as bus master:           1  (03)Monitor special cycle enabled:  0
  (04)Mem Write & Invalidate enabled: 0  (05)Palette snooping is enabled:    0
  (06)Assert PERR# when parity error: 0  (07)Do address/data stepping:       0
  (08)SERR# driver enabled:           0  (09)Fast back-to-back transact...:  0

Status(6): 0010
  (04)New Capabilities linked list:   1  (05)66MHz Capable:                  0
  (07)Fast Back-to-Back Capable:      0  (08)Master Data Parity Error:       0
  (09)DEVSEL timing:               Fast  (11)Signaled Target Abort:          0
  (12)Received Target Abort:          0  (13)Received Master Abort:          0
  (14)Signaled System Error:          0  (15)Detected Parity Error:          0
After trying to initialise the controller, bit (13) of the Status register reports a Received Master Abort.
So something is dying quite horribly I suspect and causing no dma to work.
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

Ok, so by editing the entire process and slowly isolating the stage the causes the master abort error bit to be set, it's the last step .. starting the stream
User avatar
DavidCooper
Member
Member
Posts: 1150
Joined: Wed Oct 27, 2010 4:53 pm
Location: Scotland

Re: How to play sound Intel HD Audio?

Post by DavidCooper »

I admire your patience and wish you luck with that. There are clearly a lot of people putting astronomical numbers of man-hours (including woman-hours) into trying to get awkward devices to behave. My preference would be just to refuse to support them. There should be a special label which would be awarded to machines which play by the rules and the public would learn to avoid buying anything that lacks it.
Help the people of Laos by liking - https://www.facebook.com/TheSBInitiative/?ref=py_c

MSB-OS: http://www.magicschoolbook.com/computing/os-project - direct machine code programming
johnsa
Member
Member
Posts: 296
Joined: Mon Oct 15, 2007 3:04 pm

Re: How to play sound Intel HD Audio?

Post by johnsa »

It is very frustrating, some days I think it would be better to design my own hardware and only support that rather than bother with other half-baked semi-working h/w!
Build a single PCIe board with my own 2d gpu + audio engine, might actually be less work that trying to fix issues with supporting other vendors :)

I've got access now to another Mac Pro, which I'm going to be testing on to see if this problem is a general "mac" issue or specific to this iMac, of course even then it could be due to the mac or to the specific HDA controller.
The iMac is using a Broadwell-U HDA controller, unfortunately I don't have a PC with the same controller to test on.
Post Reply