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.
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

How to play sound Intel HD Audio?

Post by Klakap »

Good day!

I want to have sound in my OS, by I dont know how to get it work. I am study wiki page, but I dont know how right use it. My actual code is in https://github.com/Klaykap/LightningOS/ ... hd_audio.c

//edit: My driver is on https://github.com/VendelinSlezak/Blesk ... ound/hda.c It can play sound through speakers and headphone output. It works with at least 6 different codecs, and is was tested on multiple real computers.
Last edited by Klakap on Sat Mar 09, 2024 1:06 pm, edited 4 times in total.
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 »

Hi,

I'll try to help you if I can, but you need to be specific about which part you need help with. Your first step is to see if you can get the codec to talk to you, and that means setting up CORB and RIRB and then testing to see if you can get a response. Have you got that far yet?

You need to get enough set up so that you can put a command in CORB (or a series of commands), wait a moment, and then read the response(s) from the codec out of RIRB.

If you have got a response from it, you should then attempt to work your way through the 11 point list on the wiki page (https://wiki.osdev.org/Intel_High_Definition_Audio), and if any of those steps cause problems, you can ask for help using the relevant number to make it clear which part you're having trouble with.
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
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 would advise you to read chapter 4 of the HDA specification, though I have always been confused on how to "communicate with the codecs". Section 4.3 says:
When the link is enabled by the assertion of CRST, the codecs will detect the de-assertion of the RESET# signal and request a status change and enumeration by the controller. As the controller hardware detects these requests, it will provide the codecs with their unique addresses and set the controller STATESTS bits to indicate that a Status Change event was detected on the appropriate SDATA_INx signals. Software can use these bits to determine the addresses of the codecs attached to the link. A 1 in a given bit position indicates that a codec at that associated address is present.
For instance, a value of 05h means that there are codecs with addresses 0 and 2 attached to the link.
From RESET# de-assertion until codecs requesting the enumeration can be as late as 25 frames. The software must wait at least 521 us (25 frames) after reading CRST as a 1 before assuming that codecs have all made status change requests and have been registered by the controller. This gives codecs sufficient time to perform self-initialization.
If software wishes to get an interrupt when new codecs are attached, such as during a mobile docking event, the software can set the CIE bit in the INTCTL register to a 1 to enable Controller interrupts which include the Status Change event. When the interrupt is received, the STATESTS bits can be examined to determine if a codec not previously identified has requested a status change.
Does this mean that checking the STATESTS register (offset 0Ah), and determining what links are active, is the right path? Then, for ach bit that is set, does this mean the offset from 80H?
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

Re: How to play sound Intel HD Audio?

Post by Klakap »

Thank for answers, today I havent method for communicating with CORB and RIRB. On Github is some code for it, I try write something but it dont 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 »

Ethin wrote:Does this mean that checking the STATESTS register (offset 0Ah), and determining what links are active, is the right path? Then, for each bit that is set, does this mean the offset from 80H?
My code never looks at the STATESTS register. It appears to be there partly to support machines with a detachable codec, but I don't expect to encounter any of those. It also refers to the whole codec, and it doesn't tell you the location of anything. When you reset the codec, you do it using CRST in the Global Control Register at 08h and there's no need to check STATESTS.

* Edit (important correction) - STATESTS is used to tell you which codecs are present and it enables you to work out the value for the CAd field in the commands that you send via CORB. I haven't worked with HDA for a few years so I've forgotten a lot of the details. (Expect more mistakes along the way - I'll put them right as we go.)

To help get you and Klakap started, I'll describe what my code originally did to get CORB and RIRB functioning. I always write directly in machine code and that is my source code, but I'll translate from there directly into English:-

(1) I start by putting the address of the memory mapped ports for HDA in register ESI (and you need to get that information from the PCI ports in advance). I later use instructions that take offsets to address different ports while keeping the value in ESI unchanged, as you'll see as we go along.

(2) I then load EAX with the address of the buffer I want to use for CORB.

(3) I post that address to ESI+40h which is the HDA register used by HDA to determine where my CORB buffer is. That's just the lower 32 bits of the address, but I send the upper bits later.

(4) I copy the address of the CORB buffer to register EDI for further use later when I start writing commands to it.

(5) I load EAX with the address of the buffer I want to use for RIRB.

(6) I post that address to ESI+50h which is the HDA register used by HDA to determine where my RIRB buffer is.

(7) I set EAX to 0 then post that to ESI+44h and also to ESI+54h to set the higher bits of both of the 64-bit buffer addresses.

(8) I then write lots of commands into my CORB buffer with the help of EDI which contains its address, starting with a null instruction in case the first instruction isn't run. I'll leave it to you to work out your own commands.

(9) I put the number of commands in AL, then post that value to ESI+48h to tell HDA how many commands it needs to process.

(10) I create a value in AX which has AL=0 and AH=80h, then push it onto the stack. (In other words, I set bit-15 and reset the rest.)

(11) I send that value to ESI+4A to reset the CORB read-pointer.

(12) I set up a delay loop value of 256 in ECX, then loop repeatedly to run it down to zero. I do this solely because I don't like hammering ports while waiting for things to be reset. The spec does not tell you to do this and it is likely completely unnecessary, but I've burned out ports in the past by polling them aggressively and so I don't take chances with them.

(13) I read the port back (ESI+4A) and jump back to repeat step (12) if it isn't ready. To tell if it's ready, I rotate bit-15 into the carry flag.

(14) I set AX to zero and send that to the same port (ESI+4A).

(15) I set up a delay loop as before and run it down to zero.

(16) I read the port back and jump back to step (15) if it isn't ready. Again I rotate bit-15 into the carry flag, but this time the value of the bit must be 0 before we can move on.

(17) I recover (to EAX) the value on the stack which was put there in step (10). I then post that value to ESI+58h to set the RIRB write-pointer to the right place for the replies from the codec.

(18) I read (into AL) RIRB's control register using ESI+5C. I then OR it with 2 (to set the bit that will start the DMA engine, then AND it with 250 (to make sure bits 0 and 2 are zero because I don't want interrupts generated), then write the result back to the same control register to start RIRB's DMA engine.

(19) I read (into AL) CORB's control register using ESI+4C. I then OR it with 2 (to set the bit that will start the DMA engine), then AND it with 254 (to make sure bit-0 is zero, again to avoid interrupts), then write the result back to the same control register to start CORB's DMA engine.

(20) I now wait for the codec's replies to appear in my RIRB buffer and watch for changes to the write-pointer to see when replies arrive. (Note that some responses can come in without responding to a command, such as when a microphone or headphones are plugged in or removed, so if you don't want to use interrupts at all, you'll want your software to check the write pointer perhaps once a second, but you don't have to worry about that at this stage.)

I later wrote more complex code to keep pace with CORB and RIRB write and read locations so that my device driver can send commands and read responses whenever it needs to, but you'll want to design code for all of that yourself. Whenever you need to send more commands, you simply write them at the current read location in the CORB buffer and then use port 48h to tell the DMA engine how many new commands it needs to process. You shouldn't need to use any ports other than the ones I used in those 20 steps for handling CORB and RIRB.
Last edited by DavidCooper on Tue Sep 17, 2019 11:50 am, edited 1 time in total.
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
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 »

DavidCooper wrote:
Ethin wrote:Does this mean that checking the STATESTS register (offset 0Ah), and determining what links are active, is the right path? Then, for each bit that is set, does this mean the offset from 80H?
My code never looks at the STATESTS register. It appears to be there partly to support machines with a detachable codec, but I don't expect to encounter any of those. It also refers to the whole codec, and it doesn't tell you the location of anything. When you reset the codec, you do it using CRST in the Global Control Register at 08h and there's no need to check STATESTS.

To help get you and Klakap started, I'll describe what my code originally did to get CORB and RIRB functioning. I always write directly in machine code and that is my source code, but I'll translate from there directly into English:-

(1) I start by putting the address of the memory mapped ports for HDA in register ESI (and you need to get that information from the PCI ports in advance). I later use instructions that take offsets to address different ports while keeping the value in ESI unchanged, as you'll see as we go along.

(2) I then load EAX with the address of the buffer I want to use for CORB.

(3) I post that address to ESI+40h which is the HDA register used by HDA to determine where my CORB buffer is. That's just the lower 32 bits of the address, but I send the upper bits later.

(4) I copy the address of the CORB buffer to register EDI for further use later when I start writing commands to it.

(5) I load EAX with the address of the buffer I want to use for RIRB.

(6) I post that address to ESI+50h which is the HDA register used by HDA to determine where my RIRB buffer is.

(7) I set EAX to 0 then post that to ESI+44h and also to ESI+54h to set the higher bits of both of the 64-bit buffer addresses.

(8) I then write lots of commands into my CORB buffer with the help of EDI which contains its address, starting with a null instruction in case the first instruction isn't run. I'll leave it to you to work out your own commands.

(9) I put the number of commands in AL, then post that value to ESI+48h to tell HDA how many commands it needs to process.

(10) I create a value in AX which has AL=0 and AH=80h, then push it onto the stack. (In other words, I set bit-15 and reset the rest.)

(11) I send that value to ESI+4A to reset the CORB read-pointer.

(12) I set up a delay loop value of 256 in ECX, then loop repeatedly to run it down to zero. I do this solely because I don't like hammering ports while waiting for things to be reset. The spec does not tell you to do this and it is likely completely unnecessary, but I've burned out ports in the past by polling them aggressively and so I don't take chances with them.

(13) I read the port back (ESI+4A) and jump back to repeat step (12) if it isn't ready. To tell if it's ready, I rotate bit-15 into the carry flag.

(14) I set AX to zero and send that to the same port (ESI+4A).

(15) I set up a delay loop as before and run it down to zero.

(16) I read the port back and jump back to step (15) if it isn't ready. Again I rotate bit-15 into the carry flag, but this time the value of the bit must be 0 before we can move on.

(17) I recover (to EAX) the value on the stack which was put there in step (10). I then post that value to ESI+58h to set the RIRB write-pointer to the right place for the replies from the codec.

(18) I read (into AL) RIRB's control register using ESI+5C. I then OR it with 2 (to set the bit that will start the DMA engine, then AND it with 250 (to make sure bits 0 and 2 are zero because I don't want interrupts generated), then write the result back to the same control register to start RIRB's DMA engine.

(19) I read (into AL) CORB's control register using ESI+4C. I then OR it with 2 (to set the bit that will start the DMA engine), then AND it with 254 (to make sure bit-0 is zero, again to avoid interrupts), then write the result back to the same control register to start CORB's DMA engine.

(20) I now wait for the codec's replies to appear in my RIRB buffer and watch for changes to the write-pointer to see when replies arrive. (Note that some responses can come in without responding to a command, such as when a microphone or headphones are plugged in or removed, so if you don't want to use interrupts at all, you'll want your software to check the write pointer perhaps once a second, but you don't have to worry about that at this stage.)

I later wrote more complex code to keep pace with CORB and RIRB write and read locations so that my device driver can send commands and read responses whenever it needs to, but you'll want to design code for all of that yourself. Whenever you need to send more commands, you simply write them at the current read location in the CORB buffer and then use port 48h to tell the DMA engine how many new commands it needs to process. You shouldn't need to use any ports other than the ones I used in those 20 steps for handling CORB and RIRB.
Not sure why you'd use machine language in this modern day to write *anything* but I won't stop you either (its your OS, not mine). Anyway, would this pseudocode be a good translation into an easier to read format on how to get this going? If so, I'd recommend adding it (or an equivalent) to the HDA wiki article to make this process a bit easier and less daunting.

Code: Select all

procedure init_hda()
let bar = get_hda_addr_pci();
let corb buffer = 0xDEADBEEF;
write_memory(bar + 0x40, corb buffer);
let rirb buffer = 0x451554a0;
write_memory(bar + 0x50, rirb buffer);
write_memory(bar + 0x44, 0);
write_memory(bar + 0x54, 0);
# we just write some get commands here for now
write_command(corb buffer, 0xF00);
write_command(corb buffer, 0xF07);
write_command(corb buffer, 0xF2D);
write_memory(bar + 0x48, 3);
let corb rp = 1 << 15;
write_memory(bar + 0x4A, corb rp);
loop
for i = 256 to 0 inclusive do
continue;
end for
let tmp corb rp = read_memory(bar + 0x4A);
if tmp corb rp & 15 then
break;
end if
end loop
write_memory(bar + 0x4A, 0);
loop
for i = 256 to 0 inclusive do
continue;
end for
let tmp corb rp = read_memory(bar + 0x4A);
if (tmp corb rp & 15) == 0 then
break;
end if
end loop
write_memory(bar + 0x58, corb rp);
let mut rirbctl = read_memory(bar + 0x5C);
rirbctl |= 2;
rirbctl &= 250;
write_memory(bar + 0x5C, rirbctl);
let mut corbctl = read_memory(bar + 0x4C);
corbctl |= 2;
corbctl &= 254;
write_memory(bar + 0x4C, corbctl);
monitor_rirb();
end procedure
(Sorry about the nonexistence of indents.)
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 can't make sense of that - it might help other people to have it in that form, but it's too abstracted for me to get my head around it so I can't judge it. Rather than bloating the wiki page though, it would be simplest just to link to this thread to provide extra help to anyone who needs to see code examples.

You won't know if your CORB & RIRB buffers are working until you've sent some commands to the codec and got some replies back, so the next thing you need to do is learn the language spoken by the codec so that you can send commands to it and understand the replies that are returned from it.

I was wrong about not using STATESTS - it tells you which codecs exist, so you do need it in order to know which values to use in CAd when looking for the right kind of codec. I've forgotten most of the details of this as its a few years since I last worked with it, and most of my notes are written in fading ink on scraps of paper. Here's the command structure though:-

Code: Select all

bits 28-31: CAd (codec address - that's four bits used to represent up to 15 codecs, so you have to convert from the 16 bits in STATESTS to the appropriate numbers).

bit 27: I (0 means direct NID reference, while 1 means indirect NID reference.

bits 20-26: NID (node identification code)

Bits 8-19: verb (e.g. F00h)

Bits 0-7: command data (which works differently with different verbs).
So, for each bit set in STATESTS, you convert from that bit's value to a CAd number (which will end up as a number between 0 and 14) and then try it out to see if it's the right kind of codec. There's no guarantee that there's only one codec of the right kind there, and there's also no guarantee that all of them will actually be functional, but I suspect that in most cases the first codec you try will be the right one, which will lead to CAd being zero.

Let's assume then we're working with CAd 0. We'll build our first command using that.

Next, we set both I and NID to 0.

We then set the verb to F00 and the command data to 4 to ask the codec to return the number of function groups that it provides.

You can now write that command into CORB's buffer and see if you get a reply. (Remember to tell the controller how many commands you've added so that it knows to read them - use the register at offset 48h.) The reply, if one comes, will contain the starting node number (bits 16-23) and number of nodes (bits 0-7). When you use this verb with NID=0, the result you get gives you the number of function groups and the NID of the first of them. You will then use that starting node number as the NID in your next command and you'll use the parameter 5 to see if it's the AFG function group. The reply to that will have the value 1 in bits 0-7 if it's an AFG group. If it isn't, try the next NID, and keep looking through as many NIDs as the number of function groups returned in your original reply.

Having identified the AFG function group, you then use verb F00 and parameter 4 with the NID of the AFG function group to find out its starting node number (first widget) and the number of nodes that follow it (all the other widgets in the codec needed for HDA).
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
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

Re: How to play sound Intel HD Audio?

Post by Klakap »

Very thank for reply! I try write code, but rirb return only zeroes. please where I have error? https://github.com/Klaykap/LightningOS/ ... hd_audio.c
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 »

Code: Select all

corb[0]=0x04F00000;
If that's intended to use verb F00h with parameter 4, the four should be a the end and the F00 should be further to the right: 0x000F0004 (CAd=0 for first codec; NID=00 for root node; Verb "get param" = F00; param = 04). Any command that's not recognised by the codec will be treated as a null command and I think they get all-zero replies.
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
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

Re: How to play sound Intel HD Audio?

Post by Klakap »

Thank, it was my mistake, but rirb is return zeroes too. Please, where can be error?
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 »

Okay, I've taken a closer look at your code to try to understand it. There are several issues with it.

Code: Select all

hda_outw(0x4A, 0x0080); //reset corb
It should be 0x8000 rather than 0x0080 - bit-15 is the one used for reset rather than bit-7.

Code: Select all

	wait();
	wait();

	hda_outw(0x4A, 0x0000); //reset corb
The next problem is that when you reset CORB's read pointer you have to follow the instructions in the specification. You are required to read the port back after writing to it and you must wait until you have seen the right value being set in those values that you read back before you can move on. So, sending 0x8000 to the port, you must read it back until the value you read back has bit-15 set in it. This may not happen immediately, so you may need to read it many times before that bit is set. Once it is set, then you send 0x0000 to the same port, and again you cannot move on until you have read it back and found bit-15 to be 0, so again it may take multiple reads before that happens.

Code: Select all

hda_outw(0x58, 16); //number of commands to rirb
The next issue is that you should not be telling RIRB how many commands CORB is going to send. RIRB uses this port to tell you how many replies it has written. There is a long delay between the commands being sent across the link and the replies returning from the codec, so you have to read this port to see when replies have arrived, and they may not all come at once because there are limits to how many commands and replies can be sent across the link in one "frame". Your wait(); wait(); may not be long enough - you should be checking the port instead to find out when and how many replies are available.

Code: Select all

	hda_outb(0x5C, (hda_inb(0x5C) | 2));
	hda_outb(0x4C, (hda_inb(0x4C) | 2));
That's fine if you aren't bothered about whether interrupts are going to be generated or not. If you want to set them, you should do so deliberately, and if you want to prevent them you should copy what I did in steps 18 and 19:-

Code: Select all

(18) I read (into AL) RIRB's control register using ESI+5C. I then OR it with 2 (to set the bit that will start the DMA engine, then AND it with 250 (to make sure bits 0 and 2 are zero because I don't want interrupts generated), then write the result back to the same control register to start RIRB's DMA engine.

(19) I read (into AL) CORB's control register using ESI+4C. I then OR it with 2 (to set the bit that will start the DMA engine), then AND it with 254 (to make sure bit-0 is zero, again to avoid interrupts), then write the result back to the same control register to start CORB's DMA engine.
There's another issue with the replies because they are always 64-bits long rather than 32. See end of page 67 and start of 68 in the specification.

Code: Select all

0x00 4 Response Response is the response data received from the codec.
0x04 4 Resp_Ex Response Extended contains information added to the response by the controller.   Bits 3:0 is the codec; i.e.,  the SDATA_INx line on which the response was received; this will correspond to the codec address.    Bit 4 is a bit indicating whether the response is a solicited response (0) or an unsolicited response (1).
The second double-word can usually be ignored as you're only likely to be using one codec (so you won't need to read its number from there, but bit-4 is important for identifying replies to non-existent commands, and you get those when headphones or a microphone have just been plugged in or removed. At this stage, you won't get any of those, but you do have to take into account the fact that the responses are 8 bytes long rather than just 4. (Your RIRB buffer should be twice as big as your CORB buffer.) There can also be complications if your buffers aren't properly aligned, and having one buffer end one byte below where another buffer begins can cause DMA engine collision problems, so don't make any of your HDA buffers directly adjacent to any others - I don't think the specification warns you about this.
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
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

Re: How to play sound Intel HD Audio?

Post by Klakap »

Thank for your response, I was trying to rewrite code but I get forever cycle. Please where I am not right?
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 »

Code: Select all

	hda_outw(0x4A, 0x8000); //reset corb

	wait();
	wait();

	while(hda_inw(0x4A)!=0x8000) {}  //wait
	hda_outw(0x4A, 0x0000); //reset corb
The specification is very clear: you are required to read register 0x4A again after sending 0x0000 to it and you must keep reading it until bit-15=0. If you don't read the port and see that bit-15 is zero then the reset has not been completed. You have to follow the rules precisely.
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
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

Re: How to play sound Intel HD Audio?

Post by Klakap »

Thank, but cycle is forever too.
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 must be close to working.

Code: Select all

hda_outw(0x48, 16); //number of commands to corb
Bits 8 to 14 are reserved and bit 15 is the reset bit, so it's best not to involve them. I out a byte to the port instead of a word. This is most likely where the problem is.

Code: Select all

while(hda_inw(0x4A)!=0x8000) {}  //wait
...
while(hda_inw(0x4A)!=0x0000) {}  //wait
In both these cases you should be testing bit-15 rather than the value of the entire word, but I think the way you're doing it should work too.

Code: Select all

while(hda_inw(0x58)==0) {} //here is forever cycle
You only need to read a byte from the port rather than a word, but again this shouldn't be the problem.

Code: Select all

	hda_outb(0x5C, (hda_inb(0x5C) | 2));
	hda_outb(0x5C, (hda_inb(0x5C) & 250));
	hda_outb(0x4C, (hda_inb(0x4C) | 2));
	hda_outb(0x4C, (hda_inb(0x4C) & 0xFE));
You can do this with two INs and two OUTs instead of four of each, but again your way should probably work.

What hardware are you running this on? Arm or x86? Emulator? (If so, then which one?)
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
Post Reply