Aros/Developer/AHIDriversDev

Introduction
AHI is a system that is supposed to utilize the hardware for playing prepared sound.

Open Source Drivers - M Blom and G Steger (SBLive), M Schulz (ac97), D Wentzler (HDAudio, Envy24HT and CMI8738), R Vumbaca (SB128) and Krzysztof Smiechowicz (Alsa driver)

A typical device driver is usually in one of three conditions...


 * inactive - needs to be installed, power startup or enable
 * busy    - uninstall, power down or disable, release a device
 * finished - read or write requests, lock a device

AHI reads a “modefile” that describes the basic features of the sound driver like mono, stereo, 7.1 surround, etc. The driver is opened and goes looking for a matching sound card.

In case of audio drivers yes - pci are probed. But only the init routine defined by resident structure is called in most cases. If the driver cannot find suitable hardware it quits init and gets unloaded. In AHI config you will never see drivers which do not work.

AHI never talks straight to audio chipsets but it does communicate through buffers which contain details like how many channels are needed, how many bits per sample, and how the samples arrive. It builds up one buffer for talking to the sound chip and another that gets the chips' reply.

The typical way to feed continuously is called double-buffering. While the audio chips are playing the first buffer, AHI will be filling the next sounds into the second buffer. Once the playback routine moves onto the second buffer, the first one will be filled with new stuff, and so on. Obviously, it helps if the audio chipset also supports two buffers.

8, 16, or 32 bit in mono or stereo, or 32 bit 7.1 channels to be played by HW. Mixing multiple channels from apps is done by AHI. A less known detail: ALL modes labeled "HiFi" are using 32 bit samples between AHI and the driver, regardless of the resolution of the samples going in to AHI.

All variable quantities exposed to the user (or driver) are of type Fixed, which is a signed 32 bit fixed point value, with the "fixed point" between the upper16 bits and the lower 16. A value of 1 would be stored as 0x00010000, a value of 0.5 would be 0x00008000, 0.25 would be 0x00004000 etc..

This makes for much faster calculations than a floating point variable would offer.

The sound card is expected to create a hard interrupt as it completes each buffer of audio. Assuming two (or more) buffers are in use, this triggers the re-loading of the just completed buffer so that the sound card can run freely back and forth between two buffers and never run out of new sound. (double-buffering).

Drivers are written to conform with whatever AHI expects, and not the other way around. AHI has served us very well, and it has become the standard audio device for all Amiga inspired platforms, but as we grow into more modern hardware, we may be limited by the constraints of AHI design.

USB audio devices have had some trouble structuring an AHI driver that works with DeviceIO commands instead of being driven by hard interrupts. Some of the techniques that AHI used to make accessing the sound card "easy" are now making it more difficult than it should be. USB audio device works on like any other device would. It processes a queue of write requests, each carrying some amount of data to the sound hardware. There are no interrupts involved, just the same DeviceIO techniques that Exec has been doing since Workbench 1.0. changing over to USB would be almost as simple as changing the OpenDevice call.. but we are not so lucky. AHI will have a USB driver but the "old way" can make things more difficult than was originally expected.

AHI provides each program with volume controls.

sounds is interpreted by the ear in a logirithmic fashion so 10db is perceived as approximately twice as loud, note that's not the same as twice as powerful which is 6db of course.

But sound itself is just a pressure wave and is neither logorithmic nor linear, it just is.

Samples are typically store as LPCM (linear Pulse Code Modulation) and so can definitely be added together with simple addition.

As to AHI reducing the volume when mixing multiple samples, I believe there are several different modes, safe, safe dynamic, full volume, and -3 -6

Say you have 24 channels to mix. Given no other knowledge of the sample values in order to prevent clipping (which is very bad, for speakers or your ears if you have earphones in) you have to reduce the volume of each sample to 1/24, this is (I think) analogous to the safe mode.

This throws away a lot of quality by reducing the sample resolution. There may not be any overflow or very little but it still modifies the output volume. Of course, even in the real world, a lot of sounds together will cause saturation. The trick is to combine them together and compensate for this but still retaining the original sample volumes. Naturally louder sounds will drain out softer sounds so perhaps this can be used to an advantage. But I have seen A+B-(AB) a lot just now.

So if you have a variable number of samples you can reduce the volume by only the current number of used channels. So only 6 channels in use reduce to 1/6 much less loss in clarity but the unreal effect of more sounds reducing the apparent volume of each sub sound, (safe, dynamic)

Or you can take the computer out of the loop and assume the human (or software the human is running like a game doing its own mixing, or AudioEvolution etc.) make sane judgements, no attenuation, no loss of quality, but get it wrong and you blow your ears out. (Ful Volume mode). Since 90% of the time I'm only playing a single stereo stream I always use this option.

The -3 and -6 db modes just add bit of head room into the full volume equation.

Quick Building
simple way to only recompile the HDAudio driver? running 'make AHI-quick' or 'make workbench-devs-AHI-quick' in AROS directory should to the trick. 'make AHI' will also rebuild dependencies of AHI

only says "MMAKE: Nothing to do for xxxx", despite changes being made The AHI build is done by using calling configure and calling make (%build_with_configure mmake macro). Because of that mmake does not see the dependencies; it just uses some stamp files. You can delete bin/__ARCH__-__CPU__/gen/workbench/devs/AHI and then call 'make AHI-quick'. I hope that should do the trick.

rebuilding AHI drivers is tricky. I use a command like this:

rm bin/pc-i386/gen/workbench/devs/AHI/.installed ; make workbench-devs-AHI-quick && cp bin/pc-i386/AROS/Devs/AHI/hdaudio.audio ...

Overview of an AHI driver
The AHI source drivers are contained in the main source.bz2 and not contrib, each drivers sources has its own drawer in workbench/devs/AHI/Drivers.

Playback just does a straight copy to the buffer, and recording does nothing at all with the buffer. The buffer size is set by AHI based on sample rate and size, so that the interrupt rate will be 50 Hz. Typically they are around 8KB if I recall correctly for 44100/16bit. The size should not be changed.

If the hardware does not support 32 bit samples, you should re-enable HiFi in the AUDIOMODE so that AHI mixes at "HiFi" quality. The driver already takes care of skipping the upper part when a HiFi mode is used.


 * MODEID

30    FM 801          Benjamin Vernoux         31    ???/Mediator    Pawel Stypula           <> 32    FM 801/AOS4     Davy Wentzler           <> +33    OSS/AROS        Martin Blom             <> 34    SB128/AOS4      Ross Vumbaca            <> 35    CMI8738/AOS4    Davy Wentzler           <> 36    ICE1724/AOS4    Davy Wentzler           <> 37    ICE1712/AOS4    Davy Wentzler           <> 38    Trid. 4DWAVE-DX Marcus Comstedt +39    ac97/AROS       Michal Schulz           <> +3E    HDAudio/AROS    Davy Wentzler           <> +42    Alsa/AROS       Krzysztof Smiechowicz   <> 200    via-ac97/AROS   Davy Wentzler           <>

name.s name-init.c name-main.c name-accel.c name-interrupt.c

DriverData.h

pci.c

The Void drawer contains a dummy playback only driver

name.s
FORM_START	AHIM CHUNK_START	AUDN .asciz		"ac97" CHUNK_END CHUNK_START	AUDM 1:		LONG2		AHIDB_AudioID,	0x00390004 LONG2		AHIDB_Volume,	TRUE LONG2		AHIDB_Panning,	TRUE LONG2		AHIDB_Stereo,	TRUE LONG2		AHIDB_HiFi,	FALSE LONG2		AHIDB_MultTable,FALSE LONG2		AHIDB_Name,	2f-1b LONG		TAG_DONE 2:	.asciz		"ac97:16 bit stereo++" CHUNK_END FORM_END

.balign	4,0 .END

name-init.c

 * 1) include 

struct ExecBase* SysBase = NULL; struct DosLibrary* DOSBase;
 * 1) include 
 * 2) include 
 * 3) include 
 * 4) include 
 * 5) ifdef __AROS__
 * 6) include 
 * 1) endif
 * 2) include 


 * 1) include "library.h"
 * 2) include "version.h"
 * 3) include "pci_wrapper.h"
 * 4) include "misc.h"

struct DriverBase* AHIsubBase;

struct VendorDevice {   UWORD vendor; UWORD device; };

struct VendorDevice *vendor_device_list = NULL; static int vendor_device_list_size = 0;

static void parse_config_file; static int hex_char_to_int(char c);
 * 1) define MAX_DEVICE_VENDORS 512

/******************************************************************************
 * Custom driver init *********************************************************

BOOL DriverInit(struct DriverBase* ahisubbase) {

bug("exit init\n"); return TRUE; }

/******************************************************************************
 * Custom driver clean-up *****************************************************

VOID DriverCleanup(struct DriverBase* AHIsubBase) {

}

name-main.c
Read more here


 * 1) include 


 * 1) include 
 * 2) include 
 * 3) include 
 * 4) include 


 * 1) include 
 * 2) include 
 * 3) include 
 * 4) include <proto/utility.h>
 * 5) ifdef __AROS__
 * 6) include <aros/debug.h>
 * 7) define DebugPrintF bug
 * 8) endif
 * 9) include <string.h>


 * 1) include "library.h"
 * 2) include "regs.h"
 * 3) include "misc.h"
 * 4) include "pci_wrapper.h"

extern int z, timer;

/******************************************************************************
 * Globals ********************************************************************


 * 1) define uint32 unsigned int

/******************************************************************************
 * AHIsub_AllocAudio **********************************************************

ULONG _AHIsub_AllocAudio(struct TagItem* taglist,                        struct AHIAudioCtrlDrv* AudioCtrl,                         struct DriverBase* AHIsubBase) {

}

/******************************************************************************
 * AHIsub_FreeAudio ***********************************************************

void _AHIsub_FreeAudio(struct AHIAudioCtrlDrv* AudioCtrl,                      struct DriverBase* AHIsubBase) {

}

/******************************************************************************
 * AHIsub_Disable *************************************************************

void _AHIsub_Disable(struct AHIAudioCtrlDrv* AudioCtrl,                    struct DriverBase* AHIsubBase) {

// V6 drivers do not have to preserve all registers

Disable; }

/******************************************************************************
 * AHIsub_Enable **************************************************************

void _AHIsub_Enable(struct AHIAudioCtrlDrv* AudioCtrl,                   struct DriverBase* AHIsubBase) {

// V6 drivers do not have to preserve all registers

Enable; }

/******************************************************************************
 * AHIsub_Start ***************************************************************

ULONG _AHIsub_Start(ULONG flags,                   struct AHIAudioCtrlDrv* AudioCtrl,                    struct DriverBase* AHIsubBase) {

return AHIE_OK; }

/******************************************************************************
 * AHIsub_Update **************************************************************

void _AHIsub_Update(ULONG flags,                   struct AHIAudioCtrlDrv* AudioCtrl,                    struct DriverBase* AHIsubBase) {

}

/******************************************************************************
 * AHIsub_Stop ****************************************************************

void _AHIsub_Stop(ULONG flags,                 struct AHIAudioCtrlDrv* AudioCtrl,                  struct DriverBase* AHIsubBase) {

}

/******************************************************************************
 * AHIsub_GetAttr *************************************************************

LONG _AHIsub_GetAttr(ULONG attribute,		            LONG argument,		             LONG def,		             struct TagItem* taglist,		             struct AHIAudioCtrlDrv* AudioCtrl,		             struct DriverBase* AHIsubBase) {

}

/******************************************************************************
 * AHIsub_HardwareControl *****************************************************

ULONG _AHIsub_HardwareControl(ULONG attribute,                             LONG argument,                              struct AHIAudioCtrlDrv* AudioCtrl,                              struct DriverBase* AHIsubBase) {

}

name-accel.c

 * 1) include <config.h>


 * 1) include <devices/ahi.h>
 * 2) include <libraries/ahi_sub.h>


 * 1) include "library.h"

/******************************************************************************
 * AHIsub_SetVol **************************************************************

ULONG _AHIsub_SetVol(UWORD           channel,		Fixed                   volume,		sposition               pan,		struct AHIAudioCtrlDrv* AudioCtrl,		ULONG                   flags,		struct DriverBase*      AHIsubBase) { return AHIS_UNKNOWN; }

/******************************************************************************
 * AHIsub_SetFreq *************************************************************

ULONG _AHIsub_SetFreq(UWORD           channel,		 ULONG                   freq,		 struct AHIAudioCtrlDrv* AudioCtrl,		 ULONG                   flags,		 struct DriverBase*      AHIsubBase) { return AHIS_UNKNOWN; }

/******************************************************************************
 * AHIsub_SetSound ************************************************************

ULONG _AHIsub_SetSound(UWORD           channel,		  UWORD                   sound,		  ULONG                   offset,		  LONG                    length,		  struct AHIAudioCtrlDrv* AudioCtrl,		  ULONG                   flags,		  struct DriverBase*      AHIsubBase) { return AHIS_UNKNOWN; }

/******************************************************************************
 * AHIsub_SetEffect ***********************************************************

ULONG _AHIsub_SetEffect(APTR            effect,		   struct AHIAudioCtrlDrv* AudioCtrl,		   struct DriverBase*      AHIsubBase) { return AHIS_UNKNOWN; }

/******************************************************************************
 * AHIsub_LoadSound ***********************************************************

ULONG _AHIsub_LoadSound(UWORD           sound,		   ULONG                   type,		   APTR                    info,		   struct AHIAudioCtrlDrv* AudioCtrl,		   struct DriverBase*      AHIsubBase) {  return AHIS_UNKNOWN; }

/******************************************************************************
 * AHIsub_UnloadSound *********************************************************

ULONG _AHIsub_UnloadSound(UWORD           sound,		     struct AHIAudioCtrlDrv* AudioCtrl,		     struct DriverBase*      AHIsubBase) { return AHIS_UNKNOWN; }

name-interrupt.c

 * 1) include <config.h>


 * 1) include <proto/expansion.h>
 * 2) include <libraries/ahi_sub.h>
 * 3) include <proto/exec.h>
 * 4) include <stddef.h>
 * 5) include "library.h"
 * 6) include "regs.h"
 * 7) include "interrupt.h"
 * 8) include "misc.h"
 * 9) include "pci_wrapper.h"
 * 10) ifdef __AROS__
 * 11) include <aros/debug.h>
 * 12) endif


 * 1) define min(a,b) ((a)<(b)?(a):(b))

int z = 0; ULONG timer = 0; // for demo/test


 * 1) define TIME_LIMIT 150 // 150 irq's

/******************************************************************************
 * Hardware interrupt handler *************************************************

ULONG CardInterrupt( struct ExceptionContext *pContext, struct ExecBase *SysBase, struct HDAudioChip* card ) ULONG CardInterrupt( struct HDAudioChip* card ) {   struct AHIAudioCtrlDrv* AudioCtrl = card->audioctrl; struct DriverBase* AHIsubBase = (struct DriverBase*) card->ahisubbase; struct PCIDevice *dev = (struct PCIDevice *) card->pci_dev;
 * 1) ifdef __AMIGAOS4__
 * 1) else
 * 1) endif

ULONG intreq, status; LONG handled = 0; UBYTE rirb_status; int i;

intreq = pci_inl(HD_INTSTS, card);

if (intreq & HD_INTCTL_GLOBAL) {              if (intreq & 0x3fffffff) // stream interrupt {           ULONG position; BOOL playback = FALSE; BOOL recording = FALSE; //bug("Stream irq\n"); for (i = 0; i < card->nr_of_streams; i++) {               if (intreq & (1 << card->streams[i].index)) {                   // acknowledge stream interrupt pci_outb(0x1C, card->streams[i].sd_reg_offset + HD_SD_OFFSET_STATUS, card);

if (i < card->nr_of_input_streams) {                       recording = TRUE; }                   else {                       playback = TRUE; }               }            }            pci_outb(0xFF, HD_INTSTS, card);

z++; timer++; if (timer > TIME_LIMIT) // stop playback {               outb_clearbits(HD_SD_CONTROL_STREAM_RUN, card->streams[card->nr_of_input_streams].sd_reg_offset + HD_SD_OFFSET_CONTROL, card); }           //bug("SIRQ\n"); if (playback) {             //  bug("PB\n"); position = pci_inl(card->streams[card->nr_of_input_streams].sd_reg_offset + HD_SD_OFFSET_LINKPOS, card);
 * 1) ifdef TIME_LIMITED
 * 1) endif

if (card->flip == 1) //position <= card->current_bytesize + 64)               {                   if (card->flip == 0)                   {                      bug("Lost IRQ!\n");                   }                   card->flip = 0;                   card->current_buffer = card->playback_buffer1;                }                else                {                   if (card->flip == 1)                   {                      bug("Lost IRQ!\n");                   }                   card->flip = 1;                   card->current_buffer = card->playback_buffer2;                }

Cause(&card->playback_interrupt); }

if (recording) {               position = pci_inl(card->streams[0].sd_reg_offset + HD_SD_OFFSET_LINKPOS, card);

if (card->recflip == 1) //position <= card->current_record_bytesize + 64)               {                   if (card->recflip == 0)                   {                      bug("Lost rec IRQ!\n");                   }                   card->recflip = 0;                   card->current_record_buffer = card->record_buffer1;                }                else                {                   if (card->recflip == 1)                   {                      bug("Lost rec IRQ!\n");                   }                   card->recflip = 1;                   card->current_record_buffer = card->record_buffer2;                }

Cause(&card->record_interrupt); }       }        if (intreq & HD_INTCTL_CIE) {           //bug("CIE\n"); pci_outb(0x4, HD_INTSTS + 3, card); // only byte access allowed //         if (card->is_playing) //           bug("CIE irq! rirb is %x, STATESTS = %x\n", pci_inb(HD_RIRBSTS, card), pci_inw(HD_STATESTS, card)); // check for RIRB status rirb_status = pci_inb(HD_RIRBSTS, card); if (rirb_status & 0x5) {               if (rirb_status & 0x4) // RIRBOIS { //                   bug("RIRB overrun!\n"); }               if (rirb_status & 0x1) // RINTFL {                   card->rirb_irq++; /*if (card->rirb_irq > 1) {                      bug("IRQ: rirb_irq = %d\n", card->rirb_irq); }*/                   //bug("RIRB IRQ!\n"); }               pci_outb(0x5, HD_RIRBSTS, card); }       }        handled = 1; }

return handled; }

/******************************************************************************
 * Playback interrupt handler *************************************************

void PlaybackInterrupt( struct ExceptionContext *pContext, struct ExecBase *SysBase, struct HDAudioChip* card ) void PlaybackInterrupt( struct HDAudioChip* card ) {   struct AHIAudioCtrlDrv* AudioCtrl = card->audioctrl; struct DriverBase* AHIsubBase = (struct DriverBase*) card->ahisubbase;
 * 1) ifdef __AMIGAOS4__
 * 1) else
 * 1) endif

if (card->mix_buffer != NULL && card->current_buffer != NULL && card->is_playing) {       BOOL   skip_mix;

WORD* src; int   i;        LONG* srclong, *dstlong, left, right; int frames = card->current_frames;

skip_mix = CallHookPkt(AudioCtrl->ahiac_PreTimerFunc, (Object*) AudioCtrl, 0); CallHookPkt(AudioCtrl->ahiac_PlayerFunc, (Object*) AudioCtrl, NULL);

if (! skip_mix) {           CallHookPkt(AudioCtrl->ahiac_MixerFunc, (Object*) AudioCtrl, card->mix_buffer); }

/* Now translate and transfer to the DMA buffer */ srclong = (LONG*) card->mix_buffer; dstlong = (LONG*) card->current_buffer;

i = frames;

if (AudioCtrl->ahiac_Flags & AHIACF_HIFI) {           while(i > 0) {               *dstlong++ = *srclong++; *dstlong++ = *srclong++;

--i; }       }        else {           while(i > 0) {               *dstlong++ = (*srclong & 0xFF00) >> 16; srclong++; // tbd *dstlong++ = (*srclong & 0xFF000000) >> 16; srclong++;

--i; }       }

CallHookPkt(AudioCtrl->ahiac_PostTimerFunc, (Object*) AudioCtrl, 0); } }

/******************************************************************************
 * Record interrupt handler ***************************************************

void RecordInterrupt( struct ExceptionContext *pContext, struct ExecBase *SysBase, struct HDAudioChip* card ) void RecordInterrupt( struct HDAudioChip* card ) {   struct AHIAudioCtrlDrv* AudioCtrl = card->audioctrl; struct DriverBase* AHIsubBase = (struct DriverBase*) card->ahisubbase; int i = 0; int frames = card->current_record_bytesize / 2; struct AHIRecordMessage rm = {       AHIST_S16S, card->current_record_buffer, RECORD_BUFFER_SAMPLES };
 * 1) ifdef __AMIGAOS4__
 * 1) else
 * 1) endif

WORD *src = card->current_record_buffer; WORD* dst = card->current_record_buffer;

while( i < frames ) {      *dst = ( ( *src & 0x00FF ) << 8 ) | ( ( *src & 0xFF00 ) >> 8 ); ++i; ++src; ++dst; }    /*while( i < frames ) {      *dst = (*src); ++i; ++src; ++dst; }*/
 * 1) ifdef __AMIGAOS4__
 * 1) else
 * 1) endif

CallHookPkt(AudioCtrl->ahiac_SamplerFunc, (Object*) AudioCtrl, &rm); }

DriverData.h

 * 1) ifndef AHI_Drivers_Card_DriverData_h
 * 2) define AHI_Drivers_Card_DriverData_h


 * 1) include <exec/types.h>
 * 2) include <exec/interrupts.h>
 * 3) include <devices/ahi.h>

/** Make the common library code initialize a global SysBase for us. It's required for hwaccess.c */


 * 1) define DRIVER "hdaudio.audio"
 * 2) define DRIVER_NEEDS_GLOBAL_EXECBASE
 * 3) define INPUTS 5


 * 1) ifdef __AROS__
 * 2) define DRIVER_NEED_GLOBAL_EXECBASE
 * 3) endif


 * 1) ifdef __amigaos4__
 * 2) define DRIVER_NEED_GLOBAL_EXECBASE
 * 3) endif


 * 1) include "DriverBase.h"

{

};


 * 1) endif /* AHI_Drivers_Card_DriverData_h */

Structure of OpenSound OSS driver
The OSS API is designed to use the traditional Unix framework of open, read, write, and ioctl, via special devices. For instance, the default device for sound input and output is /dev/dsp.

[http://www.opensound.com/pguide/oss.pdf. PDF] and Manual Page and sources.

Home Page and OSS BSD Source which is sometimes down.

AC97 Codec/Mixer support in /drv/ sources

via AC97 has Mixer, Read, Write, Interrupts, Audio Set Rate, Audio Set Channels, Audio IOctl, Audio Reset, Audio Reset Input and Output, Audio Open, Audio Close, Audio Start Input, Audio Trigger, Audio Prepare Input, Audio Prepare Output, Alloc Buffers, Get Buffer Pointer, SGD buffers, Control.

SBpci has SRC init, SRC Reg Read, SRC Reg Write, SRC Get Rate, SRC Set Rate, Writemem, Readmem, Interrupt, Audio Set Rate, Audio Set Channels, Audio Set Format, Audio IOctl, Audio Reset, Audio Reset Input and Output, Audio Open, Audio Close, Audio Output Block, Audio Start Input, Audio Trigger, Audio Prepare Input, Audio Prepare Output, Get Buffer Pointer, Control, Mix Init, SBPCI Attach, SBPCI Detach.

cmpci has Defines, Set SPDIF Rate, Setup AC3, Set Mixer, Get Mixer, Change Bits, Mixer Set, Outsw, Mixer IOCtl, Set Recmask, Outsw, Mix Init, Mixer Reset, Interrupts, Audio Set Rate, Audio Set Channels, Audio Set Format, Audio IOctl, Audio Reset, Audio Reset Input and Output, Audio Open, Audio Close, Audio Output Block, Audio Start Input, Audio Trigger, Set DAC Rate, Set DAC Fmt, Set ADC Rate, Set ADC Fmt, Setup Record, Audio Prepare Input, Setup Play, Audio Prepare Output, Get Buffer Pointer, Control, Mix Init, cmpci Attach, cmpci Detach.

Interrupts
Interrupts can be triggered by the CPU (limited number) or through an additional chip which will have many more.


 * asynchronously - resets or power failures
 * synchronously - system calls, illegal instructions