HDA problems in Qemu

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.
Post Reply
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

HDA problems in Qemu

Post by xeyes »

I've got a simple HDA driver going inside Qemu, it can play sound now but needs the following workarounds:

1. To get the CORB DMA to run, I have to set RINTCNT (BAR0 + 0x5A) to a large value. If I leave it at its reset value of 0, the CORB DMA doesn't run at all and its read ptr always reads out as 0.

I saw the following check in Qemu code which prevents CORB DMA from doing anything if RINTCNT is 0 (d->rirb_cnt in this code is RINTCNT)

Code: Select all

        if (d->rirb_count == d->rirb_cnt) {
            dprint(d, 2, "%s: rirb count reached\n", __func__);
            return;
        }
This looks like a nice way to prevent any RIRB overrun and lost responses, but I didn't find anything in the spec that says RINTCNT must be set to non zero value for the CORB DMA to run. I'm not using interrupts from the controller and my reading of the spec is that this value should not affect CORB DMA operation in this case.

2. Spec says that to reset a stream, this sequence is needed:
a. set reset bit
b. read until it is set
c. clear reset bit
d. read until it is clear

But Qemu clears the reset bit right away on setting, thus causing step b above to hang:

Code: Select all

    
    if (st->ctl & 0x01) {
        /* reset */
        dprint(d, 1, "st #%d: reset\n", reg->stream);
        st->ctl = SD_STS_FIFO_READY << 24;
    }
That said, the above code snippets have been in Qemu for many years, and mainstream OSes don't seem to have any issue with HDA in Qemu. Thus I probably missed something.

Could anyone help point out what I might have missed or did you have to apply similar workarounds for HDA to work in Qemu?
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

Re: HDA problems in Qemu

Post by Klakap »

1. According to the spec, CORBCTL and RIRBCTL have bit 1 for running/stopping DMA. If bit 1 is set, DMA is running, if is clear, DMA is halted. So RIRBCTL=0 mean RIRB DMA is halted. Also I do not know what you mean by large value, but make sure you are not modifing reserved bytes. From my experience, RIRB and CORB should be running when you simple write 0x2 to their control register.

2. Do step b only to some time like 10 miliseconds, and after that you can move forward to step c.
linuxyne
Member
Member
Posts: 211
Joined: Sat Jul 02, 2016 7:02 am

Re: HDA problems in Qemu

Post by linuxyne »

xeyes wrote:1. To get the CORB DMA to run, I have to set RINTCNT (BAR0 + 0x5A) to a large value. If I leave it at its reset value of 0, the CORB DMA doesn't run at all and its read ptr always reads out as 0.
The intel-hda emulation debug can be enabled by setting the debug property: "-device intel-hda,debug=##". Replace ## with 1,2,3, etc. for increasing detail. See code for the max level needed. I think 3 or 4 should be enough. That should give more info on the stderr. There's -D option to specify a log file to write into.
xeyes wrote:2. Spec says that to reset a stream, this sequence is needed:
a. set reset bit
b. read until it is set
c. clear reset bit
d. read until it is clear
For this reset sequence, there's a recent bug with qemu. See [1] and [2].
Linux and FreeBSD wait with a timeout on step (b) and (d).

Are you able to test on other emulators like VirtualBox?

[1] https://lore.kernel.org/all/20211226154 ... online.de/
[2] https://gitlab.com/qemu-project/qemu/-/issues/757
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

Re: HDA problems in Qemu

Post by xeyes »

linuxyne wrote:
xeyes wrote:1. To get the CORB DMA to run, I have to set RINTCNT (BAR0 + 0x5A) to a large value. If I leave it at its reset value of 0, the CORB DMA doesn't run at all and its read ptr always reads out as 0.
The intel-hda emulation debug can be enabled by setting the debug property: "-device intel-hda,debug=##". Replace ## with 1,2,3, etc. for increasing detail. See code for the max level needed. I think 3 or 4 should be enough. That should give more info on the stderr. There's -D option to specify a log file to write into.
Thanks for the tip! Qemu indeed hits that check and won't run the CORB DMA if I don't set RINTCNT to a large number:

Code: Select all

  
  151 intel-hda: write CORBWP          : 0x3 (ffff)
  152 intel-hda: intel_hda_corb_run: rirb count reached
  153 intel-hda: read  RIRBCTL         : 0x2 (ff)
  154 intel-hda: read  RIRBSTS         : 0x0 (ff)
  155 intel-hda: read  RIRBWP          : 0x0 (ffff)
  156 intel-hda: read  CORBCTL         : 0x2 (ff)
  157 intel-hda: read  CORBRP          : 0x0 (ffff)
Any idea on whether this is expected though?
linuxyne wrote:
xeyes wrote:2. Spec says that to reset a stream, this sequence is needed:
a. set reset bit
b. read until it is set
c. clear reset bit
d. read until it is clear
For this reset sequence, there's a recent bug with qemu. See [1] and [2].
Linux and FreeBSD wait with a timeout on step (b) and (d).

Are you able to test on other emulators like VirtualBox?

[1] https://lore.kernel.org/all/20211226154 ... online.de/
[2] https://gitlab.com/qemu-project/qemu/-/issues/757
Interesting find, guess I'll leave my workaround in place for now.

I'll try to give Virualbox another try. When I tried it recently it can't output any audio (regardless of guest) and the host uses pulseaudio which is also quite challenging to fight with and I wasn't able to get Virualbox's audio output working.

But it should be good for the CORB issue and also a good test for the codec route building code.
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

Re: HDA problems in Qemu

Post by xeyes »

Klakap wrote:1. According to the spec, CORBCTL and RIRBCTL have bit 1 for running/stopping DMA. If bit 1 is set, DMA is running, if is clear, DMA is halted. So RIRBCTL=0 mean RIRB DMA is halted. Also I do not know what you mean by large value, but make sure you are not modifing reserved bytes. From my experience, RIRB and CORB should be running when you simple write 0x2 to their control register.

2. Do step b only to some time like 10 miliseconds, and after that you can move forward to step c.
I know they look sort of similar, but RINTCNT is different from RIRBCTL though?

The large value I wrote was 0xFF as the field is 8 bit wide.
linuxyne
Member
Member
Posts: 211
Joined: Sat Jul 02, 2016 7:02 am

Re: HDA problems in Qemu

Post by linuxyne »

xeyes wrote:Qemu indeed hits that check and won't run the CORB DMA if I don't set RINTCNT to a large number:
The check is even before the qemu sends the command. Qemu isn't considering the value 0 to represent 256.

The check "d->rirb_count == d->rirb_cnt" is at two places:
(1) Within the corb_run is the one you hit, and
(2) Within intel_hda_response, where it decides raise an interrupt.

Qemu does seem to be at fault for mismanaging the rirb_cnt register. Default values for both rirb_count and rirb_cnt must be 0, but rirb_cnt being 0 should be treated as it being 256 instead.

One can fix this in qemu, at both places, like so:

Code: Select all

rirb_cnt = d->rirb_cnt ? d->rirb_cnt : 256;
if (d->rirb_count == rirb_cnt) ...
---

I think that by setting the rirb_cnt register to 1, and utilizing the RIRB interrupt (RIRBSTS->RINTFL), we can make qemu reset its internal counter rirb_count to 0 every time the interrupt handler clears the RINTFL. In this manner, you won't need to set rirb_cnt to a large value. A small, non-zero value should work if you are willing to utilize the interrupt. This still does not let qemu off the hook for not treating 0 as 256.

It seems that qemu, for proper functioning, assumes that the OS (1) sets a non-zero value in rirb_cnt, and (2) uses RIRB interrupts. These two conditions seem to be satisfied by Linux/BSDs.
xeyes wrote:Any idea on whether this is expected though?
Unexpected from the point of view of QEMU's current design, as it may not have previously encountered any OS/client that doesn't satisfy the two conditions listed above. Not sure how an actual hw behaves.
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

Re: HDA problems in Qemu

Post by xeyes »

linuxyne wrote:
xeyes wrote:Qemu indeed hits that check and won't run the CORB DMA if I don't set RINTCNT to a large number:
The check is even before the qemu sends the command. Qemu isn't considering the value 0 to represent 256.

The check "d->rirb_count == d->rirb_cnt" is at two places:
(1) Within the corb_run is the one you hit, and
(2) Within intel_hda_response, where it decides raise an interrupt.

Qemu does seem to be at fault for mismanaging the rirb_cnt register. Default values for both rirb_count and rirb_cnt must be 0, but rirb_cnt being 0 should be treated as it being 256 instead.

One can fix this in qemu, at both places, like so:

Code: Select all

rirb_cnt = d->rirb_cnt ? d->rirb_cnt : 256;
if (d->rirb_count == rirb_cnt) ...
---

I think that by setting the rirb_cnt register to 1, and utilizing the RIRB interrupt (RIRBSTS->RINTFL), we can make qemu reset its internal counter rirb_count to 0 every time the interrupt handler clears the RINTFL. In this manner, you won't need to set rirb_cnt to a large value. A small, non-zero value should work if you are willing to utilize the interrupt. This still does not let qemu off the hook for not treating 0 as 256.

It seems that qemu, for proper functioning, assumes that the OS (1) sets a non-zero value in rirb_cnt, and (2) uses RIRB interrupts. These two conditions seem to be satisfied by Linux/BSDs.
You are right! once I turn on the interrupt bit, Qemu can clear the accumulated counter and a small number in RINTCNT suffices. Perhaps an edge case that they never noticed/bother to fix.
linuxyne wrote:
xeyes wrote:Any idea on whether this is expected though?
Unexpected from the point of view of QEMU's current design, as it may not have previously encountered any OS/client that doesn't satisfy the two conditions listed above. Not sure how an actual hw behaves.
Tried Virtualbox, short story: it doesn't have the same issue with RINTCNT but I can't make its HDA card to work nearly as well as Qemu's.

Long story:

Installed Virtualbox on a Windows host, added quite a bit more code for better detection and setup of the CODEC including connection types, default devices, power states, and support for Audio Function Group's default amplifier and format capability read outs.

But while the stream DMA runs (FIFO RDY is always zero, although its position shows that it is going through the sample buffers) and an output source showed up in the host's mixer, there's no sound from it. So probably more CODEC setup issue?

Dug more into it and realized that I'm either looking in the wrong directions or Virtualbox deviates from the spec a lot more than Qemu:
  • HDA's only AFG claims that it does NOT support 48Khz which is the only sample rate required by the spec
    You can set up input amplifier (should not exist) for a DAC and read back the written gain correctly
    You can set up input and output amplifier for most pin complexes that claim to contain neither input nor output amp and read back the written gain correctly
    For pin complexes that claim EAPD support, trying to turn that on always fail (read back zero again)
Perhaps better to wait until I have more time to continue the game of guessing who misinterpreted the spec in what way.

Will also try to look around for actual HW that has HDA on a PCI bus (no PCIe support yet).

Before any of these can happen, Qemu is still the dev platform of choice :lol:

Thanks again for the help, and happy new year!
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

Re: HDA problems in Qemu

Post by Klakap »

xeyes wrote: I know they look sort of similar, but RINTCNT is different from RIRBCTL though?
Oh I apologize, my mistake, I somehow read RIRBCTL instead of RINTCTL. #-o
linuxyne
Member
Member
Posts: 211
Joined: Sat Jul 02, 2016 7:02 am

Re: HDA problems in Qemu

Post by linuxyne »

xeyes wrote:once I turn on the interrupt bit, Qemu can clear the accumulated counter and a small number in RINTCNT suffices. Perhaps an edge case that they never noticed/bother to fix.
Nice. Yes, the emulation seems just enough to allow the operating systems to function properly.

---
xeyes wrote:HDA's only AFG claims that it does NOT support 48Khz which is the only sample rate required by the spec
You can set up input amplifier (should not exist) for a DAC and read back the written gain correctly
You can set up input and output amplifier for most pin complexes that claim to contain neither input nor output amp and read back the written gain correctly
For pin complexes that claim EAPD support, trying to turn that on always fail (read back zero again)
I can't comment on the rest of the points, but research on the first one (about 48KHz) shows that some drivers assume (perhaps owing to the authoritative statements in the spec) that the bit rate is indeed supported, regardless of its representation (or its absence) within the PCM parameters.

On FreeBSD, the HDA driver unconditionally selects 48KHz as available without checking if the 48KHz bit (R7 in the spec) is set within the PCM Capability parameters.

---

VirtualBox does give the reason it doesn't set the 48KHz bit:

Code: Select all

234	            /* Note! We do not set CODEC_F00_0A_48KHZ here because we end up with
235	                     S/PDIF output showing up in windows and it trying to configure
236	                     streams other than 0 and 4 and stuff going sideways in the
237	                     stream setup/removal area. */
The emulators seem to take certain liberties with the specs (not only for audio), for faster shipping of the product/feature, for performance reasons, for workarounds like the one above, or due to plain oversight, etc.

VBox emulates a Sigmatel/IDT STAC9220 codec; from the looks of its source, it seems to be quite an extensive support. But, as it is still an emulation, VBox might not follow the spec to the letter.
xeyes wrote:Perhaps better to wait until I have more time to continue the game of guessing who misinterpreted the spec in what way.

Will also try to look around for actual HW that has HDA on a PCI bus (no PCIe support yet).

Before any of these can happen, Qemu is still the dev platform of choice
Working with actual hardware, one can avoid dealing with the idiosyncrasies of an emulation, though it does require a separate machine/SBC/NUC. Another option is the qemu-KVM-vfio-passthrough of the host machine's HDA audio controller device into a VM. One can try attaching the controller as an integrated endpoint to the default pcie bus of a q35 machine. I think much of the complexity of pcie remains hidden with such a setup, and one gets to program actual hw while still benefitting from the ease of development afforded by VMs.
xeyes wrote:Thanks again for the help, and happy new year!
Happy New Year, and to all as well!
amyk
Posts: 1
Joined: Sun Jan 30, 2022 9:07 pm

Re: HDA problems in Qemu

Post by amyk »

Here is another bug of QEMU's HDA emulation you might run into while writing drivers (makes you wonder, did they really read the spec when they wrote it?): https://bugs.launchpad.net/qemu/+bug/1904490

PCIe is 100% a superset of PCI from the software perspective, and for HDA the differences don't matter. I am not aware of any HDA controllers on a pure PCI bus - HDA first appeared on the ICH6 (which was HDA/AC97 dual-mode), and that was already in the PCIe era.
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Re: HDA problems in Qemu

Post by Ethin »

Okay this is just sad. I didn't know that Qemu's implementation of Intel HDA was so poorly written. I'm definitely going to be using pass-through in this case -- at least then I'll get expected behavior and I won't need to do stupid weird things just because Qemu violated the spec in (now) several places.
Post Reply