My AC97 driver is not work

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

My AC97 driver is not work

Post by Klakap »

Good day!

I follow http://www.lowlevel.eu/wiki/AC97, but my AC97 driver is not playing sound. Sound card is on bus 0, device 4 and function 0.

Code: Select all

//NAM offsets
#define AC97_NAM_PORT_RESET 0x00
#define AC97_NAM_PORT_MASTER_VOLUME 0x02
#define AC97_NAM_PORT_MONO_VOLUME 0x06
#define AC97_NAM_PORT_PC_BEEP 0x0A
#define AC97_NAM_PORT_PCM_VOLUME 0x18
#define AC97_NAM_PORT_EXT_AUDIO_ID 0x28
#define AC97_NAM_PORT_EXT_AUDIO_STC 0x2A
#define AC97_NAM_PORT_EXT_FRONT_SPLRATE 0x2C
#define AC97_NAM_PORT_EXT_LR_SPLRATE 0x0032

//NABM offsets
#define AC97_NABM_PORT_POBDBAR 0x10
#define AC97_NABM_PORT_POLVI 0x15
#define AC97_NABM_PORT_POCONTROL 0x1B
#define AC97_NABM_PORT_GLB_CTRL_STAT 0x60

//BUFFERS
struct buffer_desc{

 void *buffer;
 unsigned short length;
 int reserved : 14;
 unsigned int bup : 1;
 unsigned int ioc : 1;
} __attribute__((packed));

struct buffer_desc *BufDescList; //Buffer Descriptor List

uint16_t sound_buffer[65536];

void wait(int miliseconds) {
    while(miliseconds>0) {
        for(int i=0; i<10000; i++) {
            inb(0x3F6);  //wait
        }
        
        miliseconds--;
    }
}

void ac97_init(uint16_t volume) {
    pci_write(sound_card_bus, sound_card_device, sound_card_function, 0x04, (pci_get_command(sound_card_bus, sound_card_device, sound_card_function) | 5) );

    //reset
    outw(sound_card_nam_bar + AC97_NAM_PORT_RESET, 42);
    outb(sound_card_nabm_bar + AC97_NABM_PORT_GLB_CTRL_STAT, 2);
    wait(10);

    //set volume
    outw(sound_card_nam_bar + AC97_NAM_PORT_MASTER_VOLUME, (volume<<8) | volume);
    outw(sound_card_nam_bar + AC97_NAM_PORT_MONO_VOLUME, volume);
    outw(sound_card_nam_bar + AC97_NAM_PORT_PC_BEEP, volume);
    outw(sound_card_nam_bar + AC97_NAM_PORT_PCM_VOLUME, (volume<<8) | volume);
    wait(10); 

    //sample output rate
    outw(sound_card_nam_bar + AC97_NAM_PORT_EXT_AUDIO_STC, (inw(sound_card_nam_bar + AC97_NAM_PORT_EXT_AUDIO_STC) | 1) );
    wait(10);
    outw(sound_card_nam_bar + AC97_NAM_PORT_EXT_FRONT_SPLRATE, 44100);
    outw(sound_card_nam_bar + AC97_NAM_PORT_EXT_LR_SPLRATE, 44100);
    wait(10);

}

void ac97_play_buffer(void) {
    BufDescList[0].buffer=((uint32_t)(sound_buffer));
    BufDescList[0].length=0xFFFE;
    BufDescList[0].bup=1;

    outl(sound_card_nabm_bar + AC97_NABM_PORT_POBDBAR, (uint32_t)(BufDescList));
    outb(sound_card_nabm_bar + AC97_NABM_PORT_POLVI, 0);
    outb(sound_card_nabm_bar + AC97_NABM_PORT_POCONTROL, 0x15);
}
Please, where is bug?
Octocontrabass
Member
Member
Posts: 5586
Joined: Mon Mar 25, 2013 7:01 pm

Re: My AC97 driver is not work

Post by Octocontrabass »

Code: Select all

inb(0x3F6);  //wait
This does not tell the computer to wait.

Code: Select all

BufDescList[0].buffer=((uint32_t)(sound_buffer));
...
outl(sound_card_nabm_bar + AC97_NABM_PORT_POBDBAR, (uint32_t)(BufDescList));
If you're using paging, these are both wrong, since the hardware can only work with physical addresses.

There may be other issues that I can't spot since I'm not familiar with AC'97.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: My AC97 driver is not work

Post by Schol-R-LEA »

Sorry for the late response, but: is this on live hardware, or virtualized/emulated? I don't know if it is relevant, but as with video, audio hardware is not automatically multiplexed in virtual systems. While (to the best of my knowledge) QEMU, Virtualbox, and Bochs do support AC97 emulation, you need to have it enabled in the settings.
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.
Klakap
Member
Member
Posts: 299
Joined: Sat Mar 10, 2018 10:16 am

Re: My AC97 driver is not work

Post by Klakap »

Very thank you! I change setting for qemu and some strange sound is play! But, after than, irq11 is fire forever. Please, did you know "stop" command for AC97? Or how stop firing irq?
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: My AC97 driver is not work

Post by BenLunt »

Hi,

I, myself, have not done much with the AC97, but have started looking over it, and am curious to what you have found.

I did see a few things in your code though:

Code: Select all

//NABM offsets
#define AC97_NABM_PORT_POBDBAR 0x10
#define AC97_NABM_PORT_POLVI 0x15
#define AC97_NABM_PORT_POCONTROL 0x1B
#define AC97_NABM_PORT_GLB_CTRL_STAT 0x60
I don't think that last one is at offset 0x60. Did you mean the Global Control and Global Status registers? They are at 0x2C and 0x30 respectively, and each are 32-bits in size.

Code: Select all

//BUFFERS
struct buffer_desc{
 void *buffer;
 unsigned short length;
 int reserved : 14;
 unsigned int bup : 1;
 unsigned int ioc : 1;
} __attribute__((packed));
I don't like...I can't even remember...oh bit-fields like this. It all depends on the compiler whether it places them in big-endian or little-endian order, and whether it actually only uses the said amount of bits or not. It is best to not use bit-fields and do the read/write yourself.

Code: Select all

void wait(int miliseconds) {
    while(miliseconds>0) {
        for(int i=0; i<10000; i++) {
            inb(0x3F6);  //wait
        }
        miliseconds--;
    }
}
As mentioned before, this may or may not wait for very long. In fact, it could wait for an extremely long time if the hardware you are reading from faults due to the consecutive fast reading of the same register over and over.... Does happen....

Code: Select all

    //reset
    outw(sound_card_nam_bar + AC97_NAM_PORT_RESET, 42);
    outb(sound_card_nabm_bar + AC97_NABM_PORT_GLB_CTRL_STAT, 2);
The specs say any write to the reset register is a reset. Why 42?
Also, writing 2 to 0x60 is probably not where you think it should be.

Code: Select all

void ac97_play_buffer(void) {
    BufDescList[0].buffer=((uint32_t)(sound_buffer));
    BufDescList[0].length=0xFFFE;
    BufDescList[0].bup=1;
Again, I would not rely upon the fact that the compiler did or did not create these bit-fields like you think it should have.

Anyway, I am just curious to see what you have found. Does your code continue to see an interrupt continuously afterward?

The specs say that you can clear the IOC and RUN bits in that channel's Control register (x_CR, offset 0x[channel]B) or simply "empty" the descriptor table.

Ben
- http://www.fysnet.net/osdesign_book_series.htm
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: My AC97 driver is not work

Post by BenLunt »

Digging a little deeper, once the interrupt fires, the x_SR register (the status register for that channel) will be set to the status. It looks like the hardware will fire an interrupt until you clear that register, remembering it is a WC register (Write/Clear, writing a 1 to a bit clears it).

Ben
- http://www.fysnet.net/osdesign_book_series.htm
Klakap
Member
Member
Posts: 299
Joined: Sat Mar 10, 2018 10:16 am

Re: My AC97 driver is not work

Post by Klakap »

Thank you! I am grow up with code. The Buffer Descriptor List must be:

Code: Select all

struct buffer_desc{
    uint32_t buffer;  //reference to buffer
    uint16_t length;  //lenght of buffer
    uint16_t type;  //type of action after play buffer
};

struct buffer_desc BufDescList[32]; //It must contain 32 buffers
Maximal lenght of buffer is 0xFFFE what mean array of lenght 0xFFFF.

Type of action can be:
0x8000 - IOC = after play buffer is fire irq(probably 0x0000 is too same)
0x4000 - BUP = after play buffer isn`t fire irq, only is start play next buffer.

I am change defines:

Code: Select all


//NABM offsets
#define AC97_NABM_PORT_PISTATUS 0x06
#define AC97_NABM_PORT_PICONTROL 0x0B
#define AC97_NABM_PORT_POBDBAR 0x10
#define AC97_NABM_PORT_POLVI 0x15
#define AC97_NABM_PORT_POSTATUS 0x16
#define AC97_NABM_PORT_MCSTATUS 0x26
#define AC97_NABM_PORT_POCONTROL 0x1B
#define AC97_NABM_PORT_MCCONTROL 0x2B
Irq was fired because it must be acknowledge with reading and writing to status registers:

Code: Select all

void ac97_irq(void) {
    int pi=0;
    int po=0;
    int mc=0;

    pi = (inb(sound_card_nabm_bar + AC97_NABM_PORT_PISTATUS) & 0x1C);
    po = (inb(sound_card_nabm_bar + AC97_NABM_PORT_POSTATUS) & 0x1C);
    mc = (inb(sound_card_nabm_bar + AC97_NABM_PORT_MCSTATUS) & 0x1C);

    outb(sound_card_nabm_bar + AC97_NABM_PORT_PISTATUS, pi);
    outb(sound_card_nabm_bar + AC97_NABM_PORT_POSTATUS, po);
    outb(sound_card_nabm_bar + AC97_NABM_PORT_MCSTATUS, mc);
}
Method for playing is probably:

Code: Select all

void ac97_play(void) {
    BufDescList[0].buffer=((uint32_t)(sound_buffer));
    BufDescList[0].length=0xFFFE;
    BufDescList[0].type=0x8000;

    outl(sound_card_nabm_bar + AC97_NABM_PORT_POBDBAR, (uint32_t)(BufDescList));
    outb(sound_card_nabm_bar + AC97_NABM_PORT_POLVI, 0);
    outb(sound_card_nabm_bar + AC97_NABM_PORT_POCONTROL, 0x15);
}
And for stopping:

Code: Select all

void ac97_stop(void) {
    outb(sound_card_nabm_bar + AC97_NABM_PORT_POCONTROL, 0x00); 
}
For resetting, I copy it code from lowlevel tutorial, but it contains lot of errors, than maybe it is error too. But from my experiments sound is work also without this code.

But I have new problem. In Qemu, sound is only one tone(and it have bad quality) and changing buffer isn`t change it. And sometimes Qemu isn`t play tone, only fire irq. I don`t now why.
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: My AC97 driver is not work

Post by BenLunt »

From what I gather so far:

The value of 0xFFFE in the Buffer List Entry is not the length of the buffer but the count of 16-bit samples within the buffer. Therefore, a value of 0xFFFE means 65534 16-samples, a buffer size of 131,068 bytes.

Also, since the Current Entry register is read-only, you must start with that entry when inserting into the Buffer List. For example, after you play an audio buffer that used 2 of the 32 entries, entry 0 and 1, the next time you play something, the next entry played will start at entry 2, not entry 0 as you might think. You must use a head/tail type buffer ring and keep track of the Current Entry and Last Valid Entry when inserting buffers into the ring. The Last Valid Entry (LVE) register is writable, the Current Entry register is not.

Ben
- http://www.fysnet.net/osdesign_book_series.htm
Post Reply