OSDev.org
https://forum.osdev.org/

My AC97 driver is not work
https://forum.osdev.org/viewtopic.php?f=1&t=33531
Page 1 of 1

Author:  Klakap [ Mon Feb 25, 2019 12:03 pm ]
Post subject:  My AC97 driver is not work

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:
//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?

Author:  Octocontrabass [ Mon Feb 25, 2019 12:49 pm ]
Post subject:  Re: My AC97 driver is not work

Code:
inb(0x3F6);  //wait

This does not tell the computer to wait.

Code:
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.

Author:  Schol-R-LEA [ Wed Feb 27, 2019 1:03 pm ]
Post subject:  Re: My AC97 driver is not work

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.

Author:  Klakap [ Thu Feb 28, 2019 10:41 am ]
Post subject:  Re: My AC97 driver is not work

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?

Author:  BenLunt [ Tue Mar 05, 2019 4:07 pm ]
Post subject:  Re: My AC97 driver is not work

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:
//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:
//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:
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:
    //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:
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

Author:  BenLunt [ Tue Mar 05, 2019 9:05 pm ]
Post subject:  Re: My AC97 driver is not work

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

Author:  Klakap [ Thu Mar 07, 2019 1:11 pm ]
Post subject:  Re: My AC97 driver is not work

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

Code:
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:

//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:
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:
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:
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.

Author:  BenLunt [ Thu Mar 07, 2019 3:33 pm ]
Post subject:  Re: My AC97 driver is not work

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

Page 1 of 1 All times are UTC - 6 hours
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/