How to play sound Intel HD Audio?
-
- Posts: 1
- Joined: Wed Feb 17, 2021 10:44 am
- Libera.chat IRC: MishaTheOsmaker
Re: How to play sound Intel HD Audio?
Any update in 2021?
Re: How to play sound Intel HD Audio?
Hey guys, today I got first sound from my hda driver!
https://github.com/Klaykap/BleskOS/blob ... nd_hda.asm
Main feature is that I use immediate interface instead CORB/RIRB interface. So there is still a lot of work, but I am happy that I reached some point.
https://github.com/Klaykap/BleskOS/blob ... nd_hda.asm
Main feature is that I use immediate interface instead CORB/RIRB interface. So there is still a lot of work, but I am happy that I reached some point.
Re: How to play sound Intel HD Audio?
Well, I finally have it. I can send verb through CORB/RIRB interface.
Initalizing CORB:
And now you can send verb by this:
Initalizing CORB:
- Turn CORB off - outb(CORBCTL, 0x0);
- Set CORB memory - outd(CORB_LBA, low_base_address_of_corb); outd(CORB_HBA, high_base_address_of_corb);
- Set CORB size to 256 entries - outb(CORBSIZE, 0x2);
- Set write pointer - outw(CORB_WP, 0);
- Reset read pointer - outw(CORB_RP, 0x8000); here wait until bit 15 is set outw(CORB_RP, 0x0000); here wait until bit 15 is clear
- Turn RIRB off - outb(RIRBCTL, 0x0);
- Set RIRB memory - outd(RIRB_LBA, low_base_address_of_rirb); outd(RIRB_HBA, high_base_address_of_rirb);
- Set RIRB size to 256 entries - outb(RIRBSIZE, 0x2);
- Reset write pointer - outw(RIRB_WP, 0x8000); here wait until bit 15 is set outw(RIRB_WP, 0x0000); here wait until bit 15 is clear
- Set interrupt control - outw(RIRB_INTCTL, 0x0);
And now you can send verb by this:
- Write verb to CORB memory (important: you have to start writing verbs from entry 1, not entry 0 in your memory)
- Update CORB write pointer - outw(CORB_WP, entry_number); So for first entry this will be 1
- Wait until RIRB write pointer is the same value as CORB write pointer
- Read response from RIRB memory
Re: How to play sound Intel HD Audio?
After CORB & RIRB you need to setup the codec so the sound flows on the correct path. This is a bit complicated. First you need to discover what is connected, and then you need to enable paths so sound flows to the enabled device. You probably also want to be able to change volume.
Re: How to play sound Intel HD Audio?
Here is my understanding of playing sound through nodes.
Stream -> Audio Output -> optional Mixer/Selector -> Out Pin -> Device
Stream:
Is controlled by Output Stream set of ports.
Audio Output:
Set format of stream by verb 0x2
Set stream and channel number by verb 0x706
Set volume by verb 0x3
Mixer/Selector:
Set volume by verb 0x3
In selector also select node by verb 0x701
Out Pin:
Enable pin by set bit 6 by verb 0x707
Enable external speaker by verb 0x70C
Set volume by verb 0x3
Now I can load sound data, describe it in Buffer Descriptor List, run stream and I will hear sound.
Please is this right? Or am I missing something? Thank you for responses.
Stream -> Audio Output -> optional Mixer/Selector -> Out Pin -> Device
Stream:
Is controlled by Output Stream set of ports.
Audio Output:
Set format of stream by verb 0x2
Set stream and channel number by verb 0x706
Set volume by verb 0x3
Mixer/Selector:
Set volume by verb 0x3
In selector also select node by verb 0x701
Out Pin:
Enable pin by set bit 6 by verb 0x707
Enable external speaker by verb 0x70C
Set volume by verb 0x3
Now I can load sound data, describe it in Buffer Descriptor List, run stream and I will hear sound.
Please is this right? Or am I missing something? Thank you for responses.
Re: How to play sound Intel HD Audio?
I think the above is a bit simplistic. First, you can have several output PINs, and you should check if they are connected or not before assuming you should enable them. Based on which connected output PINs you have, you then need to find a path from the source to the connected output PINs. You then need to send the correct commands to mixers and selectors so this path is properly enabled. You also might need to set volume controls on the path to reasonable values, and/or set the global volume control. The values set should depend on the volume the user has selected.Klakap wrote:Here is my understanding of playing sound through nodes.
Stream -> Audio Output -> optional Mixer/Selector -> Out Pin -> Device
Stream:
Is controlled by Output Stream set of ports.
Audio Output:
Set format of stream by verb 0x2
Set stream and channel number by verb 0x706
Set volume by verb 0x3
Mixer/Selector:
Set volume by verb 0x3
In selector also select node by verb 0x701
Out Pin:
Enable pin by set bit 6 by verb 0x707
Enable external speaker by verb 0x70C
Set volume by verb 0x3
Now I can load sound data, describe it in Buffer Descriptor List, run stream and I will hear sound.
Please is this right? Or am I missing something? Thank you for responses.
Re: How to play sound Intel HD Audio?
My driver is already able to find output pins nodes. So now I should check every pin if is some device connected to it. If yes, I can enable this pin and start going on his connection list. By connection lists I must find path to some audio output node. When I have this, I can enable all nodes I found. Am I understand it right?
And I also found that on real computer there are many speaker output pins. It is little confusing to me. Should I enable only one pin, if is some device connected to it, or should I enable all pins to whose are connected some devices?
And I also found that on real computer there are many speaker output pins. It is little confusing to me. Should I enable only one pin, if is some device connected to it, or should I enable all pins to whose are connected some devices?
Re: How to play sound Intel HD Audio?
I think so.Klakap wrote:My driver is already able to find output pins nodes. So now I should check every pin if is some device connected to it. If yes, I can enable this pin and start going on his connection list. By connection lists I must find path to some audio output node. When I have this, I can enable all nodes I found. Am I understand it right?
It's a bit complicated. If I remember it correctly, there is also some output (speaker?) that doesn't have connection checking support. I think I prioritize if something is plugged into an output, and then I only enable that output & path. If nothing is connected, then I route to the "default speaker" which has no support for connection status. This should work on real computers.Klakap wrote: And I also found that on real computer there are many speaker output pins. It is little confusing to me. Should I enable only one pin, if is some device connected to it, or should I enable all pins to whose are connected some devices?
Re: How to play sound Intel HD Audio?
Hi, I'm from 2022. I want to play audio with HD Audio on a small operating system (for teaching purposes), and I am just wondering, what should be done for the PCI configuration and Memory mapped configuration part? (or, what should I do with all the registers listed in Chapter 6?)
Re: How to play sound Intel HD Audio?
Finally, now I can say that I have fully functioning driver. Last and very tricky part for me was figuring out how to properly update buffer. It was issue because sound card read buffer entries directly from RAM memory, but because it is such small part of data, many computers wrote it in fact only to processor cache. So then sound card was playing previous content of buffer what was of course extremely weird. But today I finally found that flushing processor cache fixes this. I tested that my actual driver works with 6 different codecs, and on multiple real computers. Feel free to check it out: https://github.com/VendelinSlezak/Blesk ... ound/hda.c
Re: How to play sound Intel HD Audio?
Hey everyone,
First of all, thanks to all posters in this thread! I am writing an Intel HD Audio driver for a research operating system in Rust and this thread was a real good help so far in addition to the specification and online resources like https://wiki.osdev.org/Intel_High_Definition_Audio. I get the impression that through all the years since this thread's creation, people still run into the same problems and I am no exception.
I am developing in a self compiled QEMU 8.2 on Ubuntu 22.02, adding a virtual sound card to my machine via the flag "-audio alsa,model=hda". I already set up my CORB and RIRB in the way that @Klakap summarised in his Post from Mon Aug 30, 2021. Now, I place commands in the the CORB and increase the CORBWP accordingly and expect my CORBRP to catch up with the CORBWP as well as to find responses from the sound card in the RIRB. But this never happened so far and I am a little stuck on finding a promising direction for debugging. I know that the sound card already can communicate with the OS and that my registers are mapped correctly. This gets proven by the fact that I can read reasonable time stamps from my Wall Clock Counter and Wall Clock Counter Alias registers. Moreover, I already can use the Immediate Command Interface to send Commands to the sound card and the sound card writes back reasonable responses to the Immediate Response Input Interface register. The MMIO mapping of the base address for all the registers, which gets provided by the BAR register of the PCI configuration space, therefore seems to be setup and working properly.
I can't exclude that I made a stupid mistake somewhere, but I checked my CORB and RIRB setup several times and all the registers that I think to be relevant (GCTL, WAKEEN, INTCTL, CORBCTL, CORBSIZE, CORBLBASE, CORBUBASE, CORBWP, CORBRP, RIRBCTL, RIRBLBASE, RIRBUBASE, RIRBWP) and also the way that I reset my CORBRP look like they should according to the specification.
In contrast to the registers, CORB and RIRB each get mapped to their individual MMIO spaces, which get allocated to addresses chosen by the OS. One explanation for the observed behavior might be that the mapping is not setup correctly and the sound card doesn't understand the addresses that I placed in the CORBLBASE, CORBUBASE, RIRBLBASE and RIRBUBASE registers. I would then expect the CMEI bit in the CORBSTS register to be set in order to indicate a CORB Memory Error Indication. But the bit stays clear all the time, so I assume that the mapping was successful.
But was it really successful? I was told that QEMU can be pretty forgiving in comparison to real hardware. The example that I was given was the Bus Master Enable bit in the PCI configuration space. While a real PCI device won't be able to transfer data at all if you forget to set this bit during device initialisation, QEMU just lets you get throug with it and a virtual PCI device will work even if you don't set the bit. Yet another example is the fact that you don't need to consider the time it takes for real hardware to set or clear a bit and therefore can completely leave out any waiting loops for asserting a bit after setting it. The same code would probably fail on real harware as you would continue your program before the bit is set. Although, this forgiveness seems to be pretty advantageous at first glance for someone like me, who develops in a virtual environment, it makes debugging more opaque as I can never be sure, if setting a bit really does something or if QEMU just ignores it...
If necessary, I can share my code, but I think that some general advices of someone with more experience in driver development on virtual hardware might already help a lot. I am already using a GDB debugger, but it can't look inside the sound card. So I think that it would be especially helpful to find a way to get any kind of debugging information on my virtual sound card in order to take a look inside the black box that at the moment just doesn't feedback anything at all. As I am running out of ideas on how to continue my debugging journey, I am also very grateful for any fingerpoint to some place that didn't cross to my mind, yet. In the meantime, I will try to test my code on real hardware and report back if I observe the same behavior or something different.
Maybe, my post will be able to wake up this thread from its slumber and I am looking forward to any responses. Please don't behave like my RIRB...
First of all, thanks to all posters in this thread! I am writing an Intel HD Audio driver for a research operating system in Rust and this thread was a real good help so far in addition to the specification and online resources like https://wiki.osdev.org/Intel_High_Definition_Audio. I get the impression that through all the years since this thread's creation, people still run into the same problems and I am no exception.
I am developing in a self compiled QEMU 8.2 on Ubuntu 22.02, adding a virtual sound card to my machine via the flag "-audio alsa,model=hda". I already set up my CORB and RIRB in the way that @Klakap summarised in his Post from Mon Aug 30, 2021. Now, I place commands in the the CORB and increase the CORBWP accordingly and expect my CORBRP to catch up with the CORBWP as well as to find responses from the sound card in the RIRB. But this never happened so far and I am a little stuck on finding a promising direction for debugging. I know that the sound card already can communicate with the OS and that my registers are mapped correctly. This gets proven by the fact that I can read reasonable time stamps from my Wall Clock Counter and Wall Clock Counter Alias registers. Moreover, I already can use the Immediate Command Interface to send Commands to the sound card and the sound card writes back reasonable responses to the Immediate Response Input Interface register. The MMIO mapping of the base address for all the registers, which gets provided by the BAR register of the PCI configuration space, therefore seems to be setup and working properly.
I can't exclude that I made a stupid mistake somewhere, but I checked my CORB and RIRB setup several times and all the registers that I think to be relevant (GCTL, WAKEEN, INTCTL, CORBCTL, CORBSIZE, CORBLBASE, CORBUBASE, CORBWP, CORBRP, RIRBCTL, RIRBLBASE, RIRBUBASE, RIRBWP) and also the way that I reset my CORBRP look like they should according to the specification.
In contrast to the registers, CORB and RIRB each get mapped to their individual MMIO spaces, which get allocated to addresses chosen by the OS. One explanation for the observed behavior might be that the mapping is not setup correctly and the sound card doesn't understand the addresses that I placed in the CORBLBASE, CORBUBASE, RIRBLBASE and RIRBUBASE registers. I would then expect the CMEI bit in the CORBSTS register to be set in order to indicate a CORB Memory Error Indication. But the bit stays clear all the time, so I assume that the mapping was successful.
But was it really successful? I was told that QEMU can be pretty forgiving in comparison to real hardware. The example that I was given was the Bus Master Enable bit in the PCI configuration space. While a real PCI device won't be able to transfer data at all if you forget to set this bit during device initialisation, QEMU just lets you get throug with it and a virtual PCI device will work even if you don't set the bit. Yet another example is the fact that you don't need to consider the time it takes for real hardware to set or clear a bit and therefore can completely leave out any waiting loops for asserting a bit after setting it. The same code would probably fail on real harware as you would continue your program before the bit is set. Although, this forgiveness seems to be pretty advantageous at first glance for someone like me, who develops in a virtual environment, it makes debugging more opaque as I can never be sure, if setting a bit really does something or if QEMU just ignores it...
If necessary, I can share my code, but I think that some general advices of someone with more experience in driver development on virtual hardware might already help a lot. I am already using a GDB debugger, but it can't look inside the sound card. So I think that it would be especially helpful to find a way to get any kind of debugging information on my virtual sound card in order to take a look inside the black box that at the moment just doesn't feedback anything at all. As I am running out of ideas on how to continue my debugging journey, I am also very grateful for any fingerpoint to some place that didn't cross to my mind, yet. In the meantime, I will try to test my code on real hardware and report back if I observe the same behavior or something different.
Maybe, my post will be able to wake up this thread from its slumber and I am looking forward to any responses. Please don't behave like my RIRB...
Re: How to play sound Intel HD Audio?
Sounds like you are using the wrong page attributes on the sound buffer. You should set it to non-cachable or something to make sure the CPU writes it to physical memory.
-
- Member
- Posts: 5560
- Joined: Mon Mar 25, 2013 7:01 pm
Re: How to play sound Intel HD Audio?
Wait a minute, PCI DMA is supposed to be cache-coherent by default. You shouldn't need to flush the caches unless you've intentionally told the HDA controller to use non-snooped reads.
Re: How to play sound Intel HD Audio?
Thank you for pointing this out. I digged deeper and I found out that in there is PCI register 0x78 in which bit 11 enables non snoop. When I cleared it, problem is fixed even without flushing processor cache. However I really do not understand why it is even set as default. It makes no sense for me, because it is great way to mess things up with cache / RAM data. But anyway, it is nice solution for this problem.
-
- Member
- Posts: 5560
- Joined: Mon Mar 25, 2013 7:01 pm
Re: How to play sound Intel HD Audio?
Huh. I took a look at the HDA specification and it says HDA controllers are allowed to force non-snoop DMA. So, the correct fix is what rdos suggested: change the buffer memory type to prevent caching.
I also checked a random Windows PC, and the Intel HDA driver leaves the Enable No Snoop bit set.
Oh, and that bit is set by default because it's part of the PCI Express Capability Structure, and the PCIe specification says that bit should be set by default.
I also checked a random Windows PC, and the Intel HDA driver leaves the Enable No Snoop bit set.
Oh, and that bit is set by default because it's part of the PCI Express Capability Structure, and the PCIe specification says that bit should be set by default.