Difference between revisions of "Minivosc"

From AlsaProject
Jump to: navigation, search
(moved last added links up)
(added link to paper; note about pulseaudio; fixed some pre tag formatting)
 
(One intermediate revision by one user not shown)
Line 2: Line 2:
 
== minivosc ALSA driver ==
 
== minivosc ALSA driver ==
  
''For comments and discussion, see: [http://thread.gmane.org/gmane.linux.alsa.devel/75709/ Minivosc entry on Wiki - discuss])
+
 
  
 +
''For comments and discussion, see: [http://thread.gmane.org/gmane.linux.alsa.devel/75709/ Minivosc entry on Wiki - discuss]. Copy of this page also found on [http://imi.aau.dk/~sd/phd/index.php?title=Minivosc author webpage].''
  
 
 
 
 
=== Introduction ===
 
  
This is a brief documentation/tutorial on creation of ''snd-minivosc'' [http://www.alsa-project.org/ ALSA ( Advanced Linux Sound Architecture )] driver. The name '''minivosc''' should stand for '''''mini'''mal '''v'''irtual '''osc'''illator'', and aims to be an example of a minimal ALSA driver, that simply represents a soundcard with a single capture interface, which streams a predefined waveform (and thus behaves as an oscillator in music technology terms). Note that playback is not handled in this driver (nor any sort of realtime control of the oscillator, such as pitch).
+
=== '''Paper''' ===
  
At the time of writing, there are but a few documents dealing with writing ALSA drivers (some of these are listed under [http://www.alsa-project.org/main/index.php/Tutorials_and_Presentations Tutorials and Presentations - AlsaProject]):  
+
* [http://lac.linuxaudio.org/2012/index.php?page=program&mode=list&details=1&pdb_filterday=4&pdb_filtertype=0&pdb_filterauthor=0 Linux Audio Conference LAC2012] (''full paper'', 8pg) version here:
 +
** [http://lac.linuxaudio.org/2012/papers/2.pdf Minivosc.pdf] (''Minivosc - a minimal virtual oscillator driver for ALSA (Advanced Linux Sound Architecture)'')
 +
** <small>''Slides and video of presentation also available from the LAC2012 page (in addition to complete proceedings); please allow JavaScript for <code>linuxaudio.org</code>''</small>
 +
 
 +
 
 +
=== '''Introduction''' ===
 +
 
 +
This is a brief documentation/tutorial on creation of ''snd-minivosc'' [http://www.alsa-project.org/ ALSA ( Advanced Linux Sound Architecture )] driver. The name '''minivosc''' should stand for '''''mini'''mal '''v'''irtual '''osc'''illator'', and aims to be an example of a minimal ALSA driver, that simply represents a soundcard with a single capture interface, which streams a predefined waveform (and thus behaves as an oscillator in music technology terms). Note that playback is not handled in this driver (nor any sort of realtime control of the oscillator, such as pitch).
 +
 
 +
At the time of writing, there are but a few documents dealing with writing ALSA drivers (some of these are listed under [http://www.alsa-project.org/main/index.php/Tutorials_and_Presentations Tutorials and Presentations - AlsaProject]):
 
* Takashi Iwai's [http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/ Writing an ALSA Driver] ([http://mirror.leaseweb.com/kernel/people/tiwai/docs/writing-an-alsa-driver.pdf writing-an-alsa-driver.pdf])
 
* Takashi Iwai's [http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/ Writing an ALSA Driver] ([http://mirror.leaseweb.com/kernel/people/tiwai/docs/writing-an-alsa-driver.pdf writing-an-alsa-driver.pdf])
 
* [http://ben-collins.blogspot.com/2010/04/writing-alsa-driver.html Ben Collins: Writing an ALSA driver]
 
* [http://ben-collins.blogspot.com/2010/04/writing-alsa-driver.html Ben Collins: Writing an ALSA driver]
Line 17: Line 26:
  
 
<i><font color="gray">Edit: see also following links:
 
<i><font color="gray">Edit: see also following links:
* [https://bugtrack.alsa-project.org/wiki/wikka.php?wakka=FramesPeriods Official Alsa Wiki for Developers: FramesPeriods]
+
* [http://www.alsa-project.org/main/index.php/FramesPeriods FramesPeriods - AlsaProject] (old version: [https://bugtrack.alsa-project.org/wiki/wikka.php?wakka=FramesPeriods Official Alsa Wiki for Developers: FramesPeriods])
 
* [http://mailman.alsa-project.org/pipermail/alsa-devel/2007-April/000474.html (alsa-devel) Questions about writing a new ALSA driver for a very limitted device]
 
* [http://mailman.alsa-project.org/pipermail/alsa-devel/2007-April/000474.html (alsa-devel) Questions about writing a new ALSA driver for a very limitted device]
 
* [http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html ALSA project - the C library reference: PCM (digital audio) interface]
 
* [http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html ALSA project - the C library reference: PCM (digital audio) interface]
 
</font></i>
 
</font></i>
 
 
 
 
While all these documents certainly provide valuable introductory points, they aren't excesivelly verbose about basic problems inherent in programming soundcard drivers; and they do not provide a full working code example of a driver. Iwai's document discusses an example of a hypothetical PCI device, whereas Collins' tutorial works with a real, though undisclosed device.
 
  
Minivosc, on the other hand, is a 'virtual' device driver, in the sense that it does not communicate with real external hardware - and therefore can be used to illustrate problems in soundcard device writing, that exist entirely on the PC side.  
+
 
 +
While all these documents certainly provide valuable introductory points, they aren't excesivelly verbose about basic problems inherent in programming soundcard drivers; and they do not provide a full working code example of a driver. Iwai's document discusses an example of a hypothetical PCI device, whereas Collins' tutorial works with a real, though undisclosed device.
 +
 
 +
Minivosc, on the other hand, is a 'virtual' device driver, in the sense that it does not communicate with real external hardware - and therefore can be used to illustrate problems in soundcard device writing, that exist entirely on the PC side.
  
 
Browse the [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-minivosc-src/ minivosc source here], or check it out from svn:
 
Browse the [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-minivosc-src/ minivosc source here], or check it out from svn:
 
<small><pre>
 
<small><pre>
  svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-minivosc-src  
+
  svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-minivosc-src
 
</pre></small>
 
</pre></small>
  
This tutorial/write-up aims to serve as documentation of the development of <code>minivosc</code>, and in doing that, to introduce basic problems in soundcard drivers in as simple terms as possible; and as such, to serve as an addition to already existing ALSA driver resources.  
+
This tutorial/write-up aims to serve as documentation of the development of <code>minivosc</code>, and in doing that, to introduce basic problems in soundcard drivers in as simple terms as possible; and as such, to serve as an addition to already existing ALSA driver resources.
  
The development machine has the following specs at time of writing:  
+
The development machine has the following specs at time of writing:
 
<small><pre>
 
<small><pre>
 
user@mypc:/$ uname -a
 
user@mypc:/$ uname -a
Line 47: Line 56:
 
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.3-4ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
 
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.3-4ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
 
Thread model: posix
 
Thread model: posix
gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)  
+
gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)
  
 
user@mypc:/$ gdb -v
 
user@mypc:/$ gdb -v
Line 56: Line 65:
  
 
user@mypc:/$ sudo lshw -class multimedia
 
user@mypc:/$ sudo lshw -class multimedia
   *-multimedia          
+
   *-multimedia
 
       description: Audio device
 
       description: Audio device
 
       product: N10/ICH 7 Family High Definition Audio Controller
 
       product: N10/ICH 7 Family High Definition Audio Controller
Line 74: Line 83:
  
 
&nbsp;
 
&nbsp;
=== Starting points ===
+
=== '''Starting points''' ===
  
Initially, the search for source code suitable as a starting point, began with looking in the ALSA source files integrated as part of the current linux kernel source (2.6.32 on the development machine at the time). In those, the most obvious place to start is [http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.32.y.git;a=blob;f=sound/drivers/dummy.c sound/drivers/dummy.c], which produces the '''snd-dummy''' driver. It should be a good place to start, because ''snd-dummy'' is also a virtual driver (in the sense that it doesn't need external hardware); however, in spite of the name, this example is not trivial at all for a beginner to understand (see below for further discussion on <code>dummy.c</code>).  
+
Initially, the search for source code suitable as a starting point, began with looking in the ALSA source files integrated as part of the current linux kernel source (2.6.32 on the development machine at the time). In those, the most obvious place to start is [http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.32.y.git;a=blob;f=sound/drivers/dummy.c sound/drivers/dummy.c], which produces the '''snd-dummy''' driver. It should be a good place to start, because ''snd-dummy'' is also a virtual driver (in the sense that it doesn't need external hardware); however, in spite of the name, this example is not trivial at all for a beginner to understand (see below for further discussion on <code>dummy.c</code>).
  
Additionally, one can go along [http://ben-collins.blogspot.com/2010/04/writing-alsa-driver.html Ben Collins: Writing an ALSA driver], and produce a minimal driver code that will compile and load. However, such a driver will not do anything in particular when it is 'captured' (read from) or 'played' (written to), and as such it is difficult to use it as an example for gaining further insight into internals of ALSA. In spite of this, <code>minivosc</code> copies its <code>snd_pcm_hardware</code> structure (and some other code portions) from it.  
+
Additionally, one can go along [http://ben-collins.blogspot.com/2010/04/writing-alsa-driver.html Ben Collins: Writing an ALSA driver], and produce a minimal driver code that will compile and load. However, such a driver will not do anything in particular when it is 'captured' (read from) or 'played' (written to), and as such it is difficult to use it as an example for gaining further insight into internals of ALSA. In spite of this, <code>minivosc</code> copies its <code>snd_pcm_hardware</code> structure (and some other code portions) from it.
  
 
The <code>alsa-devel</code> mailing list helpfully supplied a pointer in the post: "[http://mailman.alsa-project.org/pipermail/alsa-devel/2010-July/029170.html (alsa-devel) Help with dummy.c (where/how to write?)]" to a file present in ALSA sources (but not kernel sources), [http://git.alsa-project.org/?p=alsa-driver.git;a=blob_plain;f=drivers/aloop-kernel.c;hb=e0570c46e3c4563f38e44a25cfac1f07ff5a02a8 drivers/aloop-kernel.c], representing a virtual 'loopback soundcard' device. It is this file that is taken as a base for <code>minivosc</code> - in fact, it can be said that <code>minivosc.c</code> is a somewhat simplified version of <code>aloop-kernel.c</code>.
 
The <code>alsa-devel</code> mailing list helpfully supplied a pointer in the post: "[http://mailman.alsa-project.org/pipermail/alsa-devel/2010-July/029170.html (alsa-devel) Help with dummy.c (where/how to write?)]" to a file present in ALSA sources (but not kernel sources), [http://git.alsa-project.org/?p=alsa-driver.git;a=blob_plain;f=drivers/aloop-kernel.c;hb=e0570c46e3c4563f38e44a25cfac1f07ff5a02a8 drivers/aloop-kernel.c], representing a virtual 'loopback soundcard' device. It is this file that is taken as a base for <code>minivosc</code> - in fact, it can be said that <code>minivosc.c</code> is a somewhat simplified version of <code>aloop-kernel.c</code>.
Line 84: Line 93:
  
 
&nbsp;
 
&nbsp;
==== Source files ====
+
==== '''Source files''' ====
  
 
As mentioned above, the <code>minivosc</code> source can be browsed [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-minivosc-src/ here], or checked out from svn through:
 
As mentioned above, the <code>minivosc</code> source can be browsed [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-minivosc-src/ here], or checked out from svn through:
 
<small><pre>
 
<small><pre>
  svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-minivosc-src  
+
  svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-minivosc-src
 
</pre></small>
 
</pre></small>
  
It simply consists of a [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-minivosc-src/Makefile?revision=44&view=markup Makefile] and [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-minivosc-src/minivosc.c?view=markup minivosc.c] source file. Follow the instructions in '[[#buildrun|Building and running]]' in order to work with it; note that by default it has debug build, and debug statements (viewable in <code>/var/log/syslog</code>) enabled - see the '[[#debug|Debugging]]' section for more.  
+
It simply consists of a [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-minivosc-src/Makefile?revision=44&view=markup Makefile] and [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-minivosc-src/minivosc.c?view=markup minivosc.c] source file. Follow the instructions in '[[#buildrun|Building and running]]' in order to work with it; note that by default it has debug build, and debug statements (viewable in <code>/var/log/syslog</code>) enabled - see the '[[#debug|Debugging]]' section for more.
  
 
'''In addition''', ALSA driver beginners may want to take a look at the <code>bencol</code> source, that can be browsed [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-bencol-src/ here], or checked out from svn through:
 
'''In addition''', ALSA driver beginners may want to take a look at the <code>bencol</code> source, that can be browsed [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-bencol-src/ here], or checked out from svn through:
 
<small><pre>
 
<small><pre>
  svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-bencol-src  
+
  svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-bencol-src
 
</pre></small>
 
</pre></small>
  
Line 103: Line 112:
 
* [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-bencol-src/bencol-alsa-timer.c?view=markup bencol-alsa-timer.c] - same as above, with timer from <code>aloop-kernel.c</code> added
 
* [http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/alsa-bencol-src/bencol-alsa-timer.c?view=markup bencol-alsa-timer.c] - same as above, with timer from <code>aloop-kernel.c</code> added
  
The Makefile contains entries to build any of these source files as <code>snd-bencol.ko</code>; (un)comment relevant lines before building. Also, note that from the three, only <code>bencol-alsa-timer.c</code> can produce some sort of a waveform (the others build and can be <code>insmod</code>-ded, but fail at capturing).  
+
The Makefile contains entries to build any of these source files as <code>snd-bencol.ko</code>; (un)comment relevant lines before building. Also, note that from the three, only <code>bencol-alsa-timer.c</code> can produce some sort of a waveform (the others build and can be <code>insmod</code>-ded, but fail at capturing).
  
  
 
&nbsp;
 
&nbsp;
=== <span id="buildrun">Building and running</span> ===
+
=== '''<span id="buildrun">Building and running</span>''' ===
  
 
One does not need to rebuild the entire linux kernel (which can take up to several hours) in order to build the kernel module for the <code>minivosc</code> driver. Simply, the build dependencies needed for building the linux kernel need to be installed (see [https://help.ubuntu.com/community/Kernel/Compile#B)%20Download%20the%20source%20archive Kernel/Compile - Community Ubuntu Documentation]); after this, the files <code>minivosc.c</code> and a <code>Makefile</code> can be placed in a folder; and then in a terminal, after <code>cd</code>-ing to the folder, the following can be issued:
 
One does not need to rebuild the entire linux kernel (which can take up to several hours) in order to build the kernel module for the <code>minivosc</code> driver. Simply, the build dependencies needed for building the linux kernel need to be installed (see [https://help.ubuntu.com/community/Kernel/Compile#B)%20Download%20the%20source%20archive Kernel/Compile - Community Ubuntu Documentation]); after this, the files <code>minivosc.c</code> and a <code>Makefile</code> can be placed in a folder; and then in a terminal, after <code>cd</code>-ing to the folder, the following can be issued:
Line 115: Line 124:
 
which should result with a kernel module file in the same folder, <code>snd-minivosc.c</code> (which follows the ALSA naming convention, where related kernel modules are prefixed with '<code>snd-</code>')
 
which should result with a kernel module file in the same folder, <code>snd-minivosc.c</code> (which follows the ALSA naming convention, where related kernel modules are prefixed with '<code>snd-</code>')
  
If this module was built as part of the linux kernel, when one could have used '<code>modprobe snd_minivosc</code>' to load the module, and '<code>modprobe -r snd_minivosc</code>' to unload it. However, since in the above example the module is built separately, then we should, instead, use:  
+
If this module was built as part of the linux kernel, when one could have used '<code>modprobe snd_minivosc</code>' to load the module, and '<code>modprobe -r snd_minivosc</code>' to unload it. However, since in the above example the module is built separately, then we should, instead, use:
 
<small><pre>
 
<small><pre>
 
sudo insmod ./snd-minivosc.ko # to load the module
 
sudo insmod ./snd-minivosc.ko # to load the module
 
sudo rmmod snd_minivosc # to inload the module
 
sudo rmmod snd_minivosc # to inload the module
 
</pre></small>
 
</pre></small>
Note that the <code>insmod</code> command (unlike <code>modprobe</code>) demands a relative or absolute path to the <code>.ko</code> kernel module object.  
+
Note that the <code>insmod</code> command (unlike <code>modprobe</code>) demands a relative or absolute path to the <code>.ko</code> kernel module object.
  
 
To check whether the driver has been loaded after <code>insmod</code> (or unloaded after <code>rmmod</code>), issue
 
To check whether the driver has been loaded after <code>insmod</code> (or unloaded after <code>rmmod</code>), issue
 
<small><pre>
 
<small><pre>
 
$ lsmod | grep minivosc
 
$ lsmod | grep minivosc
snd_minivosc            9028  0  
+
snd_minivosc            9028  0
 
snd_pcm                70662  4 snd_minivosc,snd_hda_intel,snd_hda_codec,snd_pcm_oss
 
snd_pcm                70662  4 snd_minivosc,snd_hda_intel,snd_hda_codec,snd_pcm_oss
 
snd                    54148  14 snd_minivosc,snd_hda_codec_realtek,snd_hda_intel,snd_hda_codec,snd_hwdep,snd_pcm_oss,snd_mixer_oss,snd_pcm,snd_seq_oss,snd_rawmidi,snd_seq,snd_timer,snd_seq_device
 
snd                    54148  14 snd_minivosc,snd_hda_codec_realtek,snd_hda_intel,snd_hda_codec,snd_hwdep,snd_pcm_oss,snd_mixer_oss,snd_pcm,snd_seq_oss,snd_rawmidi,snd_seq,snd_timer,snd_seq_device
 
</pre></small>
 
</pre></small>
where <code>lsmod</code> lists the currently loaded modules in memory.  
+
where <code>lsmod</code> lists the currently loaded modules in memory.
  
 
Finally, once the driver has been loaded in memory, we can try to use it. First, we need to check whether it is registered as a soundcard by ALSA, so we issue:
 
Finally, once the driver has been loaded in memory, we can try to use it. First, we need to check whether it is registered as a soundcard by ALSA, so we issue:
Line 147: Line 156:
 
</pre></small>
 
</pre></small>
  
Note that <code>snd_minivosc</code> does not show up as a playback device at all - it is shown strictly as a capture device. Also, note that if you run <code>alsamixer</code>, and try to select the <code>minivosc</code> card, the program will respond with "<code>This sound device does not have any controls.</code>", which it indeed doesn't (however, the corresponding mixer controls code sections could be easily copied from <code>aloop-kernel.c</code>). Note that another great way to inspect audio devices is by using the [http://git.alsa-project.org/?p=alsa-driver.git;a=blob_plain;f=utils/alsa-info.sh alsa-info.sh] script.  
+
Note that <code>snd_minivosc</code> does not show up as a playback device at all - it is shown strictly as a capture device. Also, note that if you run <code>alsamixer</code>, and try to select the <code>minivosc</code> card, the program will respond with "<code>This sound device does not have any controls.</code>", which it indeed doesn't (however, the corresponding mixer controls code sections could be easily copied from <code>aloop-kernel.c</code>). Note that another great way to inspect audio devices is by using the [http://git.alsa-project.org/?p=alsa-driver.git;a=blob_plain;f=utils/alsa-info.sh alsa-info.sh] script.
  
In the device list above, <code>snd_minivosc</code> is the second soundcard (<code>card 1</code>), and it has one capture interface (<code>subdevice #0</code>). Therefore, in order to capture from it, we can issue:  
+
In the device list above, <code>snd_minivosc</code> is the second soundcard (<code>card 1</code>), and it has one capture interface (<code>subdevice #0</code>). Therefore, in order to capture from it, we can issue:
 
<small><pre>
 
<small><pre>
 
arecord -D hw:1,0 -d 2 foo.wav
 
arecord -D hw:1,0 -d 2 foo.wav
 
</pre></small>
 
</pre></small>
where <code>-D hw:1,0</code> would refer to choice of second card, first capture interface. Note that if we do not specify any format parameters, <code>man arecord</code> states that:  
+
where <code>-D hw:1,0</code> would refer to choice of second card, first capture interface. Note that if we do not specify any format parameters, <code>man arecord</code> states that:
 
<small><pre>
 
<small><pre>
 
The default is one channel.
 
The default is one channel.
Line 161: Line 170:
 
The default rate is 8000 Hertz.
 
The default rate is 8000 Hertz.
 
</pre></small>
 
</pre></small>
which means the above command will ask for 2 seconds of 8 KHz mono stream with 8-bit resolution (''that is, each sample will be represented by 8 bits - a <code>byte</code>, or an <code>unsigned char</code>'') - and that is pretty much the only format that <code>snd-minivosc</code> will accept, as well. If the <code>arecord</code> command executes succesfully, then we can also use an audio editor like [http://audacity.sourceforge.net/ Audacity] to record (capture) from the <code>minivosc</code> soundcard.  
+
which means the above command will ask for 2 seconds of 8 KHz mono stream with 8-bit resolution (''that is, each sample will be represented by 8 bits - a <code>byte</code>, or an <code>unsigned char</code>'') - and that is pretty much the only format that <code>snd-minivosc</code> will accept, as well. If the <code>arecord</code> command executes succesfully, then we can also use an audio editor like [http://audacity.sourceforge.net/ Audacity] to record (capture) from the <code>minivosc</code> soundcard.
  
Also, note that in Ubuntu 10.04, [http://en.wikipedia.org/wiki/PulseAudio PulseAudio] is started by default; when it's running, it allows access to the ''Sound Preferences'' mixer in Ubuntu. While <code>pulseaudio</code> is running, one can insert <code>minivosc</code> driver module without a problem; however, trying to <code>rmmod</code> the module afterwards will fail. In that case, one can try to shutdown <code>pulseaudio</code> with  
+
 
 +
==== '''<span id="pulseaudio">Note about pulseaudio</span>''' ====
 +
 
 +
Also, note that in Ubuntu 10.04, [http://en.wikipedia.org/wiki/PulseAudio PulseAudio] is started by default; when it's running, it allows access to the ''Sound Preferences'' mixer in Ubuntu. While <code>pulseaudio</code> is running, one can insert <code>minivosc</code> driver module without a problem; however, trying to <code>rmmod</code> the module afterwards will fail. In that case, one can try to shutdown <code>pulseaudio</code> with
 
<small><pre>
 
<small><pre>
 
pulseaudio --kill
 
pulseaudio --kill
Line 172: Line 184:
 
</pre></small>
 
</pre></small>
  
 +
However, there is a workaround for the above. Note first, that in Ubuntu 11.04 (Natty), the main volume mixer is <code>gnome-volume-control</code>:
 +
 +
<span id="gnome-volume-control-png">
 +
[[Image:Minivosc-gnome-volume.png|thumb|center|600px|gnome-volume-control with minivosc + pulseaudio.]] <br clear="all" />
 +
</span>
 +
 +
Upon running <code>pulseaudio --kill</code>, this volume control will get muted (see image above right) - however, after a couple of mouseovers, the applet will "recover" - and then the "Sound Preferences" mixer can be raised again; the <code>minivosc</code> driver will still be present in "Sound Preferences" under "Input".
 +
 +
However, there is also an additional volume control, which is associated with the <code>pulseaudio</code> package - this one is known as <code>pavucontrol</code> (it can be separately installed through <code>apt-get</code>):
 +
 +
<span id="pavucontrol-png">
 +
[[Image:Minivosc-pavucontrol.png|thumb|center|600px|pavucontrol with minivosc + pulseaudio.]] <br clear="all" />
 +
</span>
 +
 +
In this <code>pavucontrol</code> "Volume Control", note that the <code>minivosc</code> driver should show up in the "Configuration" tab, where by default it is set on the "Analog Mono Input" profile. If you change this profile to "Off", then <code>pulseaudio</code> will ''no longer claim'' the <code>minivosc</code> driver; and so the driver can be easily <code>insmod</code>'ded and <code>rmmod</code>'ded multiple times without a problem, even while <code>pulseaudio</code> is running.
  
 
&nbsp;
 
&nbsp;
=== Understanding ALSA driver architecture ===
+
=== '''Understanding ALSA driver architecture''' ===
  
 
Understanding the ALSA driver architecture can be quite a mouthful, as there are plenty of functions and structs that need to be present in order for a driver to function. Before we review those, let's first revisit the context of use of an ALSA driver, and consider the following diagram: <br/>
 
Understanding the ALSA driver architecture can be quite a mouthful, as there are plenty of functions and structs that need to be present in order for a driver to function. Before we review those, let's first revisit the context of use of an ALSA driver, and consider the following diagram: <br/>
Line 180: Line 207:
 
[[Image:Snd-driver-context.png|thumb|center|450px|Soundcard driver context]]  <br clear="all" />
 
[[Image:Snd-driver-context.png|thumb|center|450px|Soundcard driver context]]  <br clear="all" />
  
The diagram represents a simplified abstraction of a mono in, mono out soundcard, connected to a PC through some sort of a bus (PCI, USB, ISA...). Obviously, for each input or output, we need an ADC and DAC device (respectively) present on the soundcard; all the rest of the digital circuitry needed to interface these convertors to the bus (and the rest of the PC) is abstracted in the diagram as "Controller". As the CPU is, ultimately, in control of the bus, we can consider the ''soundcard driver'' to be a piece of software running on the CPU, which handles the transfer of data, in each direction (playback or capture), between the soundcard and the rest of the PC (meaning CPU and memory).  
+
The diagram represents a simplified abstraction of a mono in, mono out soundcard, connected to a PC through some sort of a bus (PCI, USB, ISA...). Obviously, for each input or output, we need an ADC and DAC device (respectively) present on the soundcard; all the rest of the digital circuitry needed to interface these convertors to the bus (and the rest of the PC) is abstracted in the diagram as "Controller". As the CPU is, ultimately, in control of the bus, we can consider the ''soundcard driver'' to be a piece of software running on the CPU, which handles the transfer of data, in each direction (playback or capture), between the soundcard and the rest of the PC (meaning CPU and memory).
  
In this case, the <code>minivosc</code> driver will - without actual hardware - present a soundcard with a single mono input to the rest of the system (and thus, the whole playback direction as on the diagram above would not exist for it).  
+
In this case, the <code>minivosc</code> driver will - without actual hardware - present a soundcard with a single mono input to the rest of the system (and thus, the whole playback direction as on the diagram above would not exist for it).
  
At this point, it is important to mention a few words about the Linux driver model (see [http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.32.y.git;a=tree;f=Documentation/driver-model Documentation/driver-model/]). We can inspect <tt>/sys/devices</tt> in a bash shell:  
+
At this point, it is important to mention a few words about the Linux driver model (see [http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.32.y.git;a=tree;f=Documentation/driver-model Documentation/driver-model/]). We can inspect <tt>/sys/devices</tt> in a bash shell:
 
<small><pre>
 
<small><pre>
 
$ ls -la /sys/devices/
 
$ ls -la /sys/devices/
Line 213: Line 240:
  
 
This tells us that the system recognizes 'isa', 'pci', 'platform' etc. devices (with a note, that USB devices show under the 'pci' bus). Now, this is important, because an ALSA driver must receive a pointer to a corresponding driver structure in the _init and _exit functions:
 
This tells us that the system recognizes 'isa', 'pci', 'platform' etc. devices (with a note, that USB devices show under the 'pci' bus). Now, this is important, because an ALSA driver must receive a pointer to a corresponding driver structure in the _init and _exit functions:
* In [http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/ Writing an ALSA Driver], a PCI ALSA driver is discussed;  
+
* In [http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/ Writing an ALSA Driver], a PCI ALSA driver is discussed;
 
** hence <tt>struct pci_driver driver</tt> is used, along with <tt>pci_register_driver(&driver)</tt> in _init
 
** hence <tt>struct pci_driver driver</tt> is used, along with <tt>pci_register_driver(&driver)</tt> in _init
 
* In [http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.32.y.git;a=blob;f=sound/usb/usbaudio.c usbaudio.c], a USB ALSA driver is given;
 
* In [http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.32.y.git;a=blob;f=sound/usb/usbaudio.c usbaudio.c], a USB ALSA driver is given;
Line 223: Line 250:
  
 
&nbsp;
 
&nbsp;
==== Driver / device initialization ====
+
==== '''Driver / device initialization''' ====
  
 
Assume now, that the diagram above represents a soundcard connected to the USB bus. Since USB devices are meant to support [http://en.wikipedia.org/wiki/Hot_swapping hot-plugging], the driver should be able to handle the situations where the device is plugged or unplugged while the computer is still on. Hence, the driver must differentiate between the moments when the ''driver'' is loaded or unloaded (in our case, that is when <code>insmod</code> and <code>rmmod</code> commands are executed); and the moments when the ''device'' itself is connected to, or disconnected from, the bus. The Linux kernel (see [http://www.ibm.com/developerworks/linux/library/l-lkm/#N1013C Anatomy of a kernel module object]) and ALSA driver architectures provide several such predefined functions, which in the case of <code>minivosc</code> are:
 
Assume now, that the diagram above represents a soundcard connected to the USB bus. Since USB devices are meant to support [http://en.wikipedia.org/wiki/Hot_swapping hot-plugging], the driver should be able to handle the situations where the device is plugged or unplugged while the computer is still on. Hence, the driver must differentiate between the moments when the ''driver'' is loaded or unloaded (in our case, that is when <code>insmod</code> and <code>rmmod</code> commands are executed); and the moments when the ''device'' itself is connected to, or disconnected from, the bus. The Linux kernel (see [http://www.ibm.com/developerworks/linux/library/l-lkm/#N1013C Anatomy of a kernel module object]) and ALSA driver architectures provide several such predefined functions, which in the case of <code>minivosc</code> are:
Line 256: Line 283:
 
* <code>_remove</code> - runs when the ''device'' is removed from bus if hotpluggable
 
* <code>_remove</code> - runs when the ''device'' is removed from bus if hotpluggable
  
In this case, once the <code>minivosc</code> driver is loaded via <code>insmod</code>, it always runs the <code>alsa_card_minivosc_init</code> and <code>minivosc_probe</code> functions one after another; and these two functions are enough to get the ALSA system to recognize and list a soundcard.  
+
In this case, once the <code>minivosc</code> driver is loaded via <code>insmod</code>, it always runs the <code>alsa_card_minivosc_init</code> and <code>minivosc_probe</code> functions one after another; and these two functions are enough to get the ALSA system to recognize and list a soundcard.
  
 
In addition, the following structures should be defined for the driver and device initialization functions - in <code>minivosc.c</code>:
 
In addition, the following structures should be defined for the driver and device initialization functions - in <code>minivosc.c</code>:
Line 265: Line 292:
 
static struct snd_device_ops dev_ops =
 
static struct snd_device_ops dev_ops =
 
{
 
{
.dev_free = minivosc_pcm_dev_free,  
+
.dev_free = minivosc_pcm_dev_free,
 
};
 
};
  
Line 282: Line 309:
  
 
&nbsp;
 
&nbsp;
==== Hardware parameters and PCM Interface functions ====
+
==== '''Hardware parameters and PCM Interface functions''' ====
  
 
Now, it needs to be defined what happens when the driver gets used by [http://en.wikipedia.org/wiki/User_space userland] audio software (such as <code>arecord</code> or <code>audacity</code>). Typically, audio software will request the driver to play back (or capture) at a given ''format'' (number of streams as in mono or stereo, choice of sampling rate and sampling resolution); the driver then should transfer data from userspace memory to the soundcard (in case of playback) or transfer data from the soundcard to userspace memory (in case of capture) at the requested format. These types of operations are handled by so called ''PCM operations'' ALSA functions. Note that in the case of ALSA, the 'PCM' doesn't mean specifically [http://en.wikipedia.org/wiki/Pulse-code_modulation pulse-code modulation], as noted in "[http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html ALSA project - the C library reference: PCM (digital audio) interface]":
 
Now, it needs to be defined what happens when the driver gets used by [http://en.wikipedia.org/wiki/User_space userland] audio software (such as <code>arecord</code> or <code>audacity</code>). Typically, audio software will request the driver to play back (or capture) at a given ''format'' (number of streams as in mono or stereo, choice of sampling rate and sampling resolution); the driver then should transfer data from userspace memory to the soundcard (in case of playback) or transfer data from the soundcard to userspace memory (in case of capture) at the requested format. These types of operations are handled by so called ''PCM operations'' ALSA functions. Note that in the case of ALSA, the 'PCM' doesn't mean specifically [http://en.wikipedia.org/wiki/Pulse-code_modulation pulse-code modulation], as noted in "[http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html ALSA project - the C library reference: PCM (digital audio) interface]":
Line 326: Line 353:
 
</pre></small>
 
</pre></small>
  
Note that <code>snd_pcm_ops</code> can usually be separate <code>*_playback_ops</code> and <code>*_capture_ops</code> structs; however since <code>minivosc</code> presents only a single capture interface, only the above <code>minivosc_pcm_ops</code> struct exists.  
+
Note that <code>snd_pcm_ops</code> can usually be separate <code>*_playback_ops</code> and <code>*_capture_ops</code> structs; however since <code>minivosc</code> presents only a single capture interface, only the above <code>minivosc_pcm_ops</code> struct exists.
  
Next, note that in the <code>snd_pcm_hardware</code> structure (copied from Ben Collins' tutorial): <code>rate_max = rate_min = 8000</code> and <code>.formats = SNDRV_PCM_FMTBIT_U8, .rates = SNDRV_PCM_RATE_8000,</code>; this means that the driver will accept '''only''' 8KHz as a sampling rate, and '''only''' 8 bit as sampling resolution - and requesting, say, CD quality (44.1KHz, 16bit) from <code>minivosc</code> will therefore fail:  
+
Next, note that in the <code>snd_pcm_hardware</code> structure (copied from Ben Collins' tutorial): <code>rate_max = rate_min = 8000</code> and <code>.formats = SNDRV_PCM_FMTBIT_U8, .rates = SNDRV_PCM_RATE_8000,</code>; this means that the driver will accept '''only''' 8KHz as a sampling rate, and '''only''' 8 bit as sampling resolution - and requesting, say, CD quality (44.1KHz, 16bit) from <code>minivosc</code> will therefore fail:
 
<small><pre>
 
<small><pre>
 
$ arecord -f cd -D hw:1,0 -d 2 foo.wav
 
$ arecord -f cd -D hw:1,0 -d 2 foo.wav
Line 377: Line 404:
  
  
Note that if one goes along Ben Collins' tutorial, and builds an example out of it, <code>_pcm_prepare</code>, <code>_pcm_trigger</code> and <code>_pcm_pointer</code> can be left essentially empty:  
+
Note that if one goes along Ben Collins' tutorial, and builds an example out of it, <code>_pcm_prepare</code>, <code>_pcm_trigger</code> and <code>_pcm_pointer</code> can be left essentially empty:
 
<small><pre>
 
<small><pre>
 
static int my_pcm_prepare(struct snd_pcm_substream *ss)
 
static int my_pcm_prepare(struct snd_pcm_substream *ss)
Line 419: Line 446:
 
</pre></small>
 
</pre></small>
  
... which means, <code>_pcm_prepare</code>, <code>_pcm_trigger</code> and <code>_pcm_pointer</code> '''''cannot''' be left empty'', if we expect the driver to work :)  
+
... which means, <code>_pcm_prepare</code>, <code>_pcm_trigger</code> and <code>_pcm_pointer</code> '''''cannot''' be left empty'', if we expect the driver to work :)
  
  
 
&nbsp;
 
&nbsp;
==== Device structure ====
+
==== '''Device structure''' ====
  
 
As different functions of the driver may need access to information at different times, we must provide a structure that can be accessed and modified by these functions. In the case of <code>minivosc</code>, we use a single structure to represent both the device and the only available substream:
 
As different functions of the driver may need access to information at different times, we must provide a structure that can be accessed and modified by these functions. In the case of <code>minivosc</code>, we use a single structure to represent both the device and the only available substream:
Line 461: Line 488:
 
</pre></small>
 
</pre></small>
  
Note that in most part, these variables are taken from <code>aloop-kernel.c</code>; however both <code>aloop-kernel.c</code> and <code>dummy.c</code> are capable of handling multiple capture and playback substreams - and thus in those drivers, several structs (instead of a single) are used, because arrays of structs must be implemented so as to represent multiple substreams.  
+
Note that in most part, these variables are taken from <code>aloop-kernel.c</code>; however both <code>aloop-kernel.c</code> and <code>dummy.c</code> are capable of handling multiple capture and playback substreams - and thus in those drivers, several structs (instead of a single) are used, because arrays of structs must be implemented so as to represent multiple substreams.
  
Note also, that the "_open" PCM operation is the first time when the ALSA system makes a real pointer to a <code>snd_pcm_substream</code> available; hence, it is in this callback where we need to make sure that we ''set '''ourselves''''' the pointer <code>minivosc_device->substream</code> to the real pointer passed by the system; otherwise, the rest of the PCM functions will not have the right pointer to work with (and hence kernel oops and crashes can be expected).  
+
Note also, that the "_open" PCM operation is the first time when the ALSA system makes a real pointer to a <code>snd_pcm_substream</code> available; hence, it is in this callback where we need to make sure that we ''set '''ourselves''''' the pointer <code>minivosc_device->substream</code> to the real pointer passed by the system; otherwise, the rest of the PCM functions will not have the right pointer to work with (and hence kernel oops and crashes can be expected).
  
More detailed information about the substream pointer and its use can be found in [http://mirror.leaseweb.com/kernel/people/tiwai/docs/writing-an-alsa-driver.pdf writing-an-alsa-driver.pdf], under [http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/ch05s05.html Runtime Pointer - The Chest of PCM Information].  
+
More detailed information about the substream pointer and its use can be found in [http://mirror.leaseweb.com/kernel/people/tiwai/docs/writing-an-alsa-driver.pdf writing-an-alsa-driver.pdf], under [http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/ch05s05.html Runtime Pointer - The Chest of PCM Information].
  
  
 
&nbsp;
 
&nbsp;
==== Timing and memory (buffer) management ====
+
==== '''Timing and memory (buffer) management''' ====
  
We now arrive at a slightly more complex part of an ALSA driver. We have already mentioned that <code>minivosc</code> corresponds to (or simulates the context in) the diagram above - except with only a single DAC (and no ADC); and thus with only the 'Capture' data transfer direction present. Even though this driver can thus, by definition, only support a single direction of data transfer (from the card to the PC), there could be several strategies involved with this:  
+
We now arrive at a slightly more complex part of an ALSA driver. We have already mentioned that <code>minivosc</code> corresponds to (or simulates the context in) the diagram above - except with only a single DAC (and no ADC); and thus with only the 'Capture' data transfer direction present. Even though this driver can thus, by definition, only support a single direction of data transfer (from the card to the PC), there could be several strategies involved with this:
* The PC repeatedly keeps on asking the card if it has data to supply ('''''poll'''ing''); if it does it handles the data transfer (copies data from the card to PC memory).  
+
* The PC repeatedly keeps on asking the card if it has data to supply ('''''poll'''ing''); if it does it handles the data transfer (copies data from the card to PC memory).
 
* The soundcard generates a signal when it has data ready for the PC; upon this signal, the PC stops whatever its doing, and it handles the data transfer (copies data from the card to PC memory) ('''''interrupt''''')
 
* The soundcard generates a signal when it has data ready for the PC; upon this signal, the PC stops whatever its doing, and it handles the data transfer (copies data from the card to PC memory) ('''''interrupt''''')
  
In principle, either of these approaches could be used so that the PC would receive data of one sample (''which in <code>minivosc</code> case is 8 bit, or a byte'') at a time - however, that would be inefficient use of computer resources. That is why within ALSA, data transfer encompasses multiple samples - chunks - at a time.  
+
In principle, either of these approaches could be used so that the PC would receive data of one sample (''which in <code>minivosc</code> case is 8 bit, or a byte'') at a time - however, that would be inefficient use of computer resources. That is why within ALSA, data transfer encompasses multiple samples - chunks - at a time.
  
Going back to the Ben Collins tutorial, where capture is discussed, we can already start guessing why the minimal example built from that tutorial will not do anything: "<code>The buffer I've shown we assume to have been filled during interrupt.</code>" ([http://ben-collins.blogspot.com/2010/05/writing-alsa-driver-pcm-handler.html Ben Collins: Writing an ALSA driver: PCM handler callbacks]) - seemingly, an interrupt generated by a device; however the interrupt function is in any case not provided.  
+
Going back to the Ben Collins tutorial, where capture is discussed, we can already start guessing why the minimal example built from that tutorial will not do anything: "<code>The buffer I've shown we assume to have been filled during interrupt.</code>" ([http://ben-collins.blogspot.com/2010/05/writing-alsa-driver-pcm-handler.html Ben Collins: Writing an ALSA driver: PCM handler callbacks]) - seemingly, an interrupt generated by a device; however the interrupt function is in any case not provided.
  
So we can take a look again at <code>aloop-kernel.c</code> and <code>dummy.c</code>, where we can find that:  
+
So we can take a look again at <code>aloop-kernel.c</code> and <code>dummy.c</code>, where we can find that:
 
* <code>aloop-kernel.c</code> uses <code>struct timer_list</code> along with <code>setup_timer</code> and <code>add_timer</code>; (linux kernel ''timer API'')
 
* <code>aloop-kernel.c</code> uses <code>struct timer_list</code> along with <code>setup_timer</code> and <code>add_timer</code>; (linux kernel ''timer API'')
 
* <code>dummy.c</code> uses <code>struct timer_list</code> (''timer API'') in its '_systimer_pcm' part; and a <code>struct hrtimer</code> (linux kernel ''high resolution timer API'') in its '_hrtimer_pcm' part
 
* <code>dummy.c</code> uses <code>struct timer_list</code> (''timer API'') in its '_systimer_pcm' part; and a <code>struct hrtimer</code> (linux kernel ''high resolution timer API'') in its '_hrtimer_pcm' part
 
** <small>More about Linux kernel timers at [http://www.ibm.com/developerworks/linux/library/l-timers-list/index.html?ca=drs- Kernel APIs, Part 3: Timers and lists in the 2.6 kernel]; [http://elinux.org/Kernel_Timer_Systems Kernel Timer Systems - eLinux.org]; [http://lwn.net/Articles/212724/ The timer API: size or type safety? (LWN.net)].</small>
 
** <small>More about Linux kernel timers at [http://www.ibm.com/developerworks/linux/library/l-timers-list/index.html?ca=drs- Kernel APIs, Part 3: Timers and lists in the 2.6 kernel]; [http://elinux.org/Kernel_Timer_Systems Kernel Timer Systems - eLinux.org]; [http://lwn.net/Articles/212724/ The timer API: size or type safety? (LWN.net)].</small>
  
In other words: if there isn't an actual hardware to generate interrupts; then we '''must''' set up some sort of a timer, that will repeatedly trigger a function (that would correspond to a polling function) in our virtual soundcard driver. In this case, <code>minivosc</code> copies the ''timer API'' approach from <code>aloop-kernel.c</code>.  
+
In other words: if there isn't an actual hardware to generate interrupts; then we '''must''' set up some sort of a timer, that will repeatedly trigger a function (that would correspond to a polling function) in our virtual soundcard driver. In this case, <code>minivosc</code> copies the ''timer API'' approach from <code>aloop-kernel.c</code>.
  
At this point, let's take a look at which PCM functions get called in <code>minivosc</code> after it had been triggered for start:  
+
At this point, let's take a look at which PCM functions get called in <code>minivosc</code> after it had been triggered for start:
  
 
<span id="log_pcm_triggered">.</span>
 
<span id="log_pcm_triggered">.</span>
Line 493: Line 520:
 
[48810.488486] : minivosc_timer_start: mydev->period_size_frac: 12000; mydev->ir
 
[48810.488486] : minivosc_timer_start: mydev->period_size_frac: 12000; mydev->ir
 
q_pos: 0 jiffies: 12127621 pcm_bps 8000
 
q_pos: 0 jiffies: 12127621 pcm_bps 8000
[48810.488497] : +minivosc_pointer  
+
[48810.488497] : +minivosc_pointer
[48810.488500] : *minivosc_pos_update: running  
+
[48810.488500] : *minivosc_pos_update: running
 
[48810.488505] : *      : jiffies 12127621, ->last_jiffies 12127621, delta 0
 
[48810.488505] : *      : jiffies 12127621, ->last_jiffies 12127621, delta 0
 
[48810.488510] : +      bytes_to_frames(: 0, mydev->buf_pos: 0
 
[48810.488510] : +      bytes_to_frames(: 0, mydev->buf_pos: 0
[48810.493135] : minivosc_timer_function: running  
+
[48810.493135] : minivosc_timer_function: running
[48810.493141] : *minivosc_pos_update: running  
+
[48810.493141] : *minivosc_pos_update: running
 
[48810.493147] : *      : jiffies 12127623, ->last_jiffies 12127621, delta 2
 
[48810.493147] : *      : jiffies 12127623, ->last_jiffies 12127621, delta 2
 
[48810.493152] : *      : last_pos 0, c->irq_pos 16000, count 64
 
[48810.493152] : *      : last_pos 0, c->irq_pos 16000, count 64
[48810.493157] : >minivosc_xfer_buf: count: 64  
+
[48810.493157] : >minivosc_xfer_buf: count: 64
[48810.493163] : _ minivosc_fill_capture_buf ss 1536 bs 1536 bytes 64 buf_pos 0  
+
[48810.493163] : _ minivosc_fill_capture_buf ss 1536 bs 1536 bytes 64 buf_pos 0
 
sizeof 1 jiffies 12127623
 
sizeof 1 jiffies 12127623
 
[48810.493182] : *      : mydev->irq_pos >= mydev->period_size_frac 12000
 
[48810.493182] : *      : mydev->irq_pos >= mydev->period_size_frac 12000
Line 508: Line 535:
 
q_pos: 4000 jiffies: 12127623 pcm_bps 8000
 
q_pos: 4000 jiffies: 12127623 pcm_bps 8000
 
[48810.493194] :        : calling snd_pcm_period_elapsed
 
[48810.493194] :        : calling snd_pcm_period_elapsed
[48810.493199] : +minivosc_pointer  
+
[48810.493199] : +minivosc_pointer
[48810.493202] : *minivosc_pos_update: running  
+
[48810.493202] : *minivosc_pos_update: running
 
[48810.493207] : *      : jiffies 12127623, ->last_jiffies 12127623, delta 0
 
[48810.493207] : *      : jiffies 12127623, ->last_jiffies 12127623, delta 0
 
[48810.493212] : +      bytes_to_frames(: 64, mydev->buf_pos: 64
 
[48810.493212] : +      bytes_to_frames(: 64, mydev->buf_pos: 64
[48810.493324] : +minivosc_pointer  
+
[48810.493324] : +minivosc_pointer
 
</pre></small>
 
</pre></small>
  
Let's briefly discuss the snippet above:  
+
Let's briefly discuss the snippet above:
* The timer is repeatedly activated (set off) by calling <code>_timer_start</code>.  
+
* The timer is repeatedly activated (set off) by calling <code>_timer_start</code>.
* When the time set for the timer expires, the <code>_timer_function</code> callback function runs, which calls <code>_pos_update</code> .  
+
* When the time set for the timer expires, the <code>_timer_function</code> callback function runs, which calls <code>_pos_update</code> .
 
* If <code>_pos_update</code> detects a difference in <code>jiffies</code>, it calls <code>_xfer_buf</code> which in turn calls <code>_fill_capture_buf</code>
 
* If <code>_pos_update</code> detects a difference in <code>jiffies</code>, it calls <code>_xfer_buf</code> which in turn calls <code>_fill_capture_buf</code>
 
** in this case, when all is finished, <code>_timer_start</code> is called again
 
** in this case, when all is finished, <code>_timer_start</code> is called again
 
* <code>_pos_update</code> can also be called independently by <code>_pcm_pointer</code>.
 
* <code>_pos_update</code> can also be called independently by <code>_pcm_pointer</code>.
** in this case, usually there is no difference in <code>jiffies</code> (delta is 0), in which case <code>_pos_update</code> quickly exits, not calling any other function.  
+
** in this case, usually there is no difference in <code>jiffies</code> (delta is 0), in which case <code>_pos_update</code> quickly exits, not calling any other function.
  
 
At this point, lets include an excerpt from [http://en.wikipedia.org/wiki/Jiffy_(time) Jiffy (time)]:<br>
 
At this point, lets include an excerpt from [http://en.wikipedia.org/wiki/Jiffy_(time) Jiffy (time)]:<br>
Line 531: Line 558:
  
 
So, to be more precise - <code>_pos_update</code> actually measures time (in jiffies), elapsed since the last call to <code>_timer_start</code>, as the variable <code>delta</code>; only if <code>delta</code> is more than 0 jiffies, a call to <code>_xfer_buf</code> is made, requesting a transfer of ammount of samples (data) ''that corresponds to the elapsed time in jiffies, according to the requested sampling rate, resolution and number of streams''. Let's use this for the [[#log_pcm_triggered|log above]]:
 
So, to be more precise - <code>_pos_update</code> actually measures time (in jiffies), elapsed since the last call to <code>_timer_start</code>, as the variable <code>delta</code>; only if <code>delta</code> is more than 0 jiffies, a call to <code>_xfer_buf</code> is made, requesting a transfer of ammount of samples (data) ''that corresponds to the elapsed time in jiffies, according to the requested sampling rate, resolution and number of streams''. Let's use this for the [[#log_pcm_triggered|log above]]:
* A sampling rate of 8000 KHz, means we have to transfer data for 8000 samples each second for a single (mono) stream.  
+
* A sampling rate of 8000 KHz, means we have to transfer data for 8000 samples each second for a single (mono) stream.
* A sampling resolution of 8 bits = byte, means we have to transfer 8000 bytes each second.  
+
* A sampling resolution of 8 bits = byte, means we have to transfer 8000 bytes each second.
* And 8000 Bps will be equivalent to (8000 / 250) = 32 bytes per jiffy (given a jiffy is 4ms for a 2.6 kernel)  
+
* And 8000 Bps will be equivalent to (8000 / 250) = 32 bytes per jiffy (given a jiffy is 4ms for a 2.6 kernel)
* Thus, when delta==2, then 2*32 = 64 bytes will be requested for transfer; when delta==1, then 32 bytes will be requested.  
+
* Thus, when delta==2, then 2*32 = 64 bytes will be requested for transfer; when delta==1, then 32 bytes will be requested.
  
On the development machine, a typical pattern of changes of <code>delta</code> looked like:
+
On the development machine, a typical pattern of changes of <code>delta</code> looked like:
 
<small><pre>
 
<small><pre>
 
* : jiffies 12127837, ->last_jiffies 12127836, delta 1
 
* : jiffies 12127837, ->last_jiffies 12127836, delta 1
Line 551: Line 578:
 
* : jiffies 12127842, ->last_jiffies 12127842, delta 0
 
* : jiffies 12127842, ->last_jiffies 12127842, delta 0
 
</pre></small>
 
</pre></small>
which means the requests for byte transfers will repeatedly change between 32 and 64 bytes.  
+
which means the requests for byte transfers will repeatedly change between 32 and 64 bytes.
  
 
Now, how does the rest of ALSA know that such a requested transfer has been executed succesfully? It does so by asking the driver, what is its current position in the buffer, by calling its <code>_pcm_pointer</code> function; <code>_pcm_pointer</code> should return the buffer position in frames (''and in our <code>minivosc</code> case, since we use a mono 8 bit stream, a frame will be equivalent to a size of a single sample, which is a byte''). The important thing to remember is that here 'buffer' does '''not''' refer to the sizes of these 'individual' transfers of 32 and 64 bytes - it refers to the size of the PCM buffer of the substream, which is determined in <code>_prepare</code>!
 
Now, how does the rest of ALSA know that such a requested transfer has been executed succesfully? It does so by asking the driver, what is its current position in the buffer, by calling its <code>_pcm_pointer</code> function; <code>_pcm_pointer</code> should return the buffer position in frames (''and in our <code>minivosc</code> case, since we use a mono 8 bit stream, a frame will be equivalent to a size of a single sample, which is a byte''). The important thing to remember is that here 'buffer' does '''not''' refer to the sizes of these 'individual' transfers of 32 and 64 bytes - it refers to the size of the PCM buffer of the substream, which is determined in <code>_prepare</code>!
  
This is why we need to keep a variable for the position within this PCM buffer, <code>minivosc_device->buf_pos</code>, within our device struct; we can then update this variable for each 'individual' transfer, and return it back whenever the ALSA middle layer asks for it through <code>_pcm_pointer</code>. (<small>''this becomes obvious, if we comment all commands that update <code>minivosc_device->buf_pos</code> - in that case, running a capture from Audacity will visibly show the record cursor being unable to move, and the process will eventually fail.''</small>) Note that in <code>aloop-kernel.c</code>, the main calculation of <code>buf_pos</code> occurs in <code>_xfer_buf</code> (<small>''in <code>minivosc</code>, it depends on the choice of copying algorithm''</small>).  
+
This is why we need to keep a variable for the position within this PCM buffer, <code>minivosc_device->buf_pos</code>, within our device struct; we can then update this variable for each 'individual' transfer, and return it back whenever the ALSA middle layer asks for it through <code>_pcm_pointer</code>. (<small>''this becomes obvious, if we comment all commands that update <code>minivosc_device->buf_pos</code> - in that case, running a capture from Audacity will visibly show the record cursor being unable to move, and the process will eventually fail.''</small>) Note that in <code>aloop-kernel.c</code>, the main calculation of <code>buf_pos</code> occurs in <code>_xfer_buf</code> (<small>''in <code>minivosc</code>, it depends on the choice of copying algorithm''</small>).
  
 
Also note that, after how much time after _start does the timer expire and _timer_function runs, is calculated in <code>_timer_start</code> as:
 
Also note that, after how much time after _start does the timer expire and _timer_function runs, is calculated in <code>_timer_start</code> as:
Line 567: Line 594:
 
<small><pre>
 
<small><pre>
 
// in include/asm-generic/param.h
 
// in include/asm-generic/param.h
# define HZ            CONFIG_HZ      /* Internal kernel timer frequency */  
+
# define HZ            CONFIG_HZ      /* Internal kernel timer frequency */
  
 
// minivosc
 
// minivosc
Line 574: Line 601:
 
// _prepare:
 
// _prepare:
 
mydev->pcm_bps = runtime->rate * runtime->channels
 
mydev->pcm_bps = runtime->rate * runtime->channels
* snd_pcm_format_width(runtime->format)  
+
* snd_pcm_format_width(runtime->format)
/ 8;  
+
/ 8;
 
mydev->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size);
 
mydev->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size);
 
mydev->pcm_period_size =
 
mydev->pcm_period_size =
Line 588: Line 615:
 
</pre></small>
 
</pre></small>
  
This tells us that we must differentiate between size of PCM buffer, <code>pcm_buffer_size</code> (1536 bytes) - and <code>pcm_period_size</code> (48 bytes). In simple terms, we could understand this as: as soon as a new batch of 48 bytes have been written in the PCM buffer, the ALSA middle layer should be informed by calling <code>snd_pcm_period_elapsed</code>; and it is this call that finally, after all the buffer operations performed within the driver, makes the data available to audio software like <code>arecord</code> that can proceed with, say, recording this data to disk.  
+
This tells us that we must differentiate between size of PCM buffer, <code>pcm_buffer_size</code> (1536 bytes) - and <code>pcm_period_size</code> (48 bytes). In simple terms, we could understand this as: as soon as a new batch of 48 bytes have been written in the PCM buffer, the ALSA middle layer should be informed by calling <code>snd_pcm_period_elapsed</code>; and it is this call that finally, after all the buffer operations performed within the driver, makes the data available to audio software like <code>arecord</code> that can proceed with, say, recording this data to disk.
  
 
[http://mirror.leaseweb.com/kernel/people/tiwai/docs/writing-an-alsa-driver.pdf writing-an-alsa-driver.pdf], in regards to <code>snd_pcm_period_elapsed</code>, mentions:
 
[http://mirror.leaseweb.com/kernel/people/tiwai/docs/writing-an-alsa-driver.pdf writing-an-alsa-driver.pdf], in regards to <code>snd_pcm_period_elapsed</code>, mentions:
Line 615: Line 642:
  
 
&nbsp;
 
&nbsp;
==== More on memory (buffer) management ====
+
==== '''More on memory (buffer) management''' ====
 
+
 
+
At this point, let us recall that <code>minivosc</code> simply repeats a short waveform, in order to generate a continuous tone. This waveform is specified as the array <code>wvfdat</code> within the driver code. Additionally, at each repetition, the waveform can be 'lifted' - that is, a constant value can be added to it - which is controlled by the <code>minivosc_device->wvf_lift</code> variable.
+
  
In the case of actual capture hardware, the driver would have to first collect the data from the card in some intermediate buffer (array) - and in the case of <code>minivosc</code>, that intermediate buffer is in fact <code>wvfdat</code>; the only difference from the hardware case being, that it is pre-filled with data (and in a real soundcard, it would have to be continuosly updated with data from the soundcard).  
+
At this point, let us recall that <code>minivosc</code> simply repeats a short waveform, in order to generate a continuous tone. This waveform is specified as the array <code>wvfdat</code> within the driver code. Additionally, at each repetition, the waveform can be 'lifted' - that is, a constant value can be added to it - which is controlled by the <code>minivosc_device->wvf_lift</code> variable.
  
Thus, we can state the following: regardless if we talk about a virtual or a real hardware driver, a key part of the driver job, is to transfer data from an intermediate buffer/array (here <code>wvfdat</code>) to the PCM buffer for that substream (here <code>minivosc_device->substream->runtime->dma_area</code>) - in 'individual' transfers of chunks, whose size is determined by the time elapsed since the last 'individual' transfer (or in other words, the time between two consecutive <code>_timer_function</code>s).  
+
In the case of actual capture hardware, the driver would have to first collect the data from the card in some intermediate buffer (array) - and in the case of <code>minivosc</code>, that intermediate buffer is in fact <code>wvfdat</code>; the only difference from the hardware case being, that it is pre-filled with data (and in a real soundcard, it would have to be continuosly updated with data from the soundcard).
 +
 
 +
Thus, we can state the following: regardless if we talk about a virtual or a real hardware driver, a key part of the driver job, is to transfer data from an intermediate buffer/array (here <code>wvfdat</code>) to the PCM buffer for that substream (here <code>minivosc_device->substream->runtime->dma_area</code>) - in 'individual' transfers of chunks, whose size is determined by the time elapsed since the last 'individual' transfer (or in other words, the time between two consecutive <code>_timer_function</code>s).
  
 
In other words, in the case of <code>minivosc</code> we can distinguish between:
 
In other words, in the case of <code>minivosc</code> we can distinguish between:
* <font color="red">intermediate (waveform) buffer/array</font> - <code>wvfdat</code> - size 21 bytes  
+
* <font color="red">intermediate (waveform) buffer/array</font> - <code>wvfdat</code> - size 21 bytes
** size preset by driver programmer  
+
** size preset by driver programmer
 
* <font color="green">'individual' transfer chunk size</font> - given by <code>bytes / count</code> - size 32 (or 64) bytes
 
* <font color="green">'individual' transfer chunk size</font> - given by <code>bytes / count</code> - size 32 (or 64) bytes
 
** size dependent on timing between consecutive executions of <code>_timer_function</code> & stream(s) format
 
** size dependent on timing between consecutive executions of <code>_timer_function</code> & stream(s) format
* <font color="blue">PCM substream buffer/array</font> - <code>dev->substream->runtime->dma_area</code> - size 816 (or 1536) bytes  
+
* <font color="blue">PCM substream buffer/array</font> - <code>dev->substream->runtime->dma_area</code> - size 816 (or 1536) bytes
** size chosen by software (?): audacity usually claims 816 bytes, arecord 1536 bytes  
+
** size chosen by software (?): audacity usually claims 816 bytes, arecord 1536 bytes
* <code>pcm_period_size</code> - size 48 bytes,  
+
* <code>pcm_period_size</code> - size 48 bytes,
 
** for calling <code>snd_pcm_period_elapsed</code>, size set by stream(s) format & kernel timer frequency
 
** for calling <code>snd_pcm_period_elapsed</code>, size set by stream(s) format & kernel timer frequency
  
And, since it turns out that, in this case, the <font color="red">intermediate buffer size</font> (21) is less than the <font color="green">'individual' transfer chunk size</font> (32 or 64), we come to an interesting situation, not accounted for in the original <code>aloop-kernel.c</code> - displayed on the diagram below (the colors on the diagram match the colors used in the list above).  
+
And, since it turns out that, in this case, the <font color="red">intermediate buffer size</font> (21) is less than the <font color="green">'individual' transfer chunk size</font> (32 or 64), we come to an interesting situation, not accounted for in the original <code>aloop-kernel.c</code> - displayed on the diagram below (the colors on the diagram match the colors used in the list above).
  
 
<span id="minivosc_buffer_viz">
 
<span id="minivosc_buffer_viz">
Line 640: Line 666:
 
</span><br>
 
</span><br>
  
As shown in the [[#minivosc_buffer_viz|buffer visualisation diagram]], due to <font color="red">intermediate (waveform) buffer/array</font> size (<code>wvfsz</code>) being smaller than <font color="green">'individual' transfer chunk size</font> (<code>count</code>), we need to loop through the <font color="red">waveform buffer/array</font> in order to fill a  <font color="green">chunk</font> request - and the waveform piece will not end at the end of the chunk request. In other words, the data will not be ''aligned'' at boundary.
+
As shown in the [[#minivosc_buffer_viz|buffer visualisation diagram]], due to <font color="red">intermediate (waveform) buffer/array</font> size (<code>wvfsz</code>) being smaller than <font color="green">'individual' transfer chunk size</font> (<code>count</code>), we need to loop through the <font color="red">waveform buffer/array</font> in order to fill a  <font color="green">chunk</font> request - and the waveform piece will not end at the end of the chunk request. In other words, the data will not be ''aligned'' at boundary.
  
 
That is why, although <font color="green">'individual' transfer chunk size</font> is not a real array, we have to treat it as such, because we need to keep a pointer for it (here <code>dpos</code>). In other words, if we want seamless looping of the <font color="red">waveform buffer</font>, we need to keep three buffer pointers:
 
That is why, although <font color="green">'individual' transfer chunk size</font> is not a real array, we have to treat it as such, because we need to keep a pointer for it (here <code>dpos</code>). In other words, if we want seamless looping of the <font color="red">waveform buffer</font>, we need to keep three buffer pointers:
Line 648: Line 674:
  
 
To illustrate this, there are three copying algorithms one can choose from in <code>minivosc</code>'s function <code>_fill_capture_buf</code> - simply by uncommenting the corresponding <code>#define</code> (and commenting the others):
 
To illustrate this, there are three copying algorithms one can choose from in <code>minivosc</code>'s function <code>_fill_capture_buf</code> - simply by uncommenting the corresponding <code>#define</code> (and commenting the others):
* <code>COPYALG_V1</code> - here, bytes are copied in chunks using <code>memcpy</code> (which slightly complicates buffer pointer calculation)  
+
* <code>COPYALG_V1</code> - here, bytes are copied in chunks using <code>memcpy</code> (which slightly complicates buffer pointer calculation)
 
* <code>COPYALG_V2</code> - here, bytes are copied one by one from <code>wvfdat</code> to <code>dma_area</code> through assignment in a loop
 
* <code>COPYALG_V2</code> - here, bytes are copied one by one from <code>wvfdat</code> to <code>dma_area</code> through assignment in a loop
 
* <code>COPYALG_V3</code> - a copy of <code>copy_play_buf</code> function's algorithm from <code>aloop-kernel.c</code>
 
* <code>COPYALG_V3</code> - a copy of <code>copy_play_buf</code> function's algorithm from <code>aloop-kernel.c</code>
Line 659: Line 685:
  
 
V3 uses the same calculation of <code>buf_pos</code> as originally in <code>aloop-kernel.c</code>, and it does show that the waveform looping in that case is not seamless:
 
V3 uses the same calculation of <code>buf_pos</code> as originally in <code>aloop-kernel.c</code>, and it does show that the waveform looping in that case is not seamless:
+
 
 
<span id="minivosc_seamprob_loop"><small>Problems in looping between audacity and arecord, due to differing buffer sizes, when the buf_pos is calculated in _xfer_buf as in aloop-kernel.c.</small></span><br>
 
<span id="minivosc_seamprob_loop"><small>Problems in looping between audacity and arecord, due to differing buffer sizes, when the buf_pos is calculated in _xfer_buf as in aloop-kernel.c.</small></span><br>
 
[[Image:Minivosc-comp03.png|thumb|center|800px|Problems in looping...]] <br clear="all" />
 
[[Image:Minivosc-comp03.png|thumb|center|800px|Problems in looping...]] <br clear="all" />
Line 671: Line 697:
 
Note that while <code>audacity</code> seems to 'speed up' its buffers after some periods, it will still report the same 32 and 64 bytes requests in the logs as <code>arecord</code> (?!)
 
Note that while <code>audacity</code> seems to 'speed up' its buffers after some periods, it will still report the same 32 and 64 bytes requests in the logs as <code>arecord</code> (?!)
  
Finally, let's include these snippets related to buffers:  
+
Finally, let's include these snippets related to buffers:
  
From [http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html ALSA project - the C library reference: PCM (digital audio) interface]:  
+
From [http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html ALSA project - the C library reference: PCM (digital audio) interface]:
<small><pre>
+
<small><pre style="white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;">
 
One digital value is called sample. More samples are collected to frames (frame is terminology for ALSA) depending on count of converters used at one specific time. One frame might contain one sample (when only one converter is used - mono) or more samples (for example: stereo has signals from two converters recorded at same time). Digital audio stream contains collection of frames recorded at boundaries of continuous time periods.
 
One digital value is called sample. More samples are collected to frames (frame is terminology for ALSA) depending on count of converters used at one specific time. One frame might contain one sample (when only one converter is used - mono) or more samples (for example: stereo has signals from two converters recorded at same time). Digital audio stream contains collection of frames recorded at boundaries of continuous time periods.
  
Line 688: Line 714:
  
 
And from [http://alsa.opensrc.org/index.php/HowTo_Asynchronous_Playback HowTo Asynchronous Playback - ALSA wiki]:
 
And from [http://alsa.opensrc.org/index.php/HowTo_Asynchronous_Playback HowTo Asynchronous Playback - ALSA wiki]:
<small><pre>
+
<small><pre style="white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;">
 
snd_pcm_hw_params_set_access is used to set the transfer mode I've been talking about at the start of this document. There are two types of transfer modes:
 
snd_pcm_hw_params_set_access is used to set the transfer mode I've been talking about at the start of this document. There are two types of transfer modes:
  
 
     * Regular - using the snd_pcm_write* functions
 
     * Regular - using the snd_pcm_write* functions
     * Mmap'd - writing directly to a memory pointer  
+
     * Mmap'd - writing directly to a memory pointer
  
 
Besides this, there are also two ways to represent the data transfered, interleaved and non-interleaved. If the stream you're playing is mono, this won't make a difference. In all other cases, interleaved means the data is transfered in individual frames, where each frame is composed of a single sample from each channel. Non-interleaved means data is transfered in periods, where each period is composed of a chunk of samples from each channel.
 
Besides this, there are also two ways to represent the data transfered, interleaved and non-interleaved. If the stream you're playing is mono, this won't make a difference. In all other cases, interleaved means the data is transfered in individual frames, where each frame is composed of a single sample from each channel. Non-interleaved means data is transfered in periods, where each period is composed of a chunk of samples from each channel.
Line 699: Line 725:
  
 
     * interleaved would look like: LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR ...
 
     * interleaved would look like: LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR ...
     * non-interleaved might look like: LL LL LL LL LL RR RR RR RR RR LL LL LL LL LL RR RR RR RR RR ...  
+
     * non-interleaved might look like: LL LL LL LL LL RR RR RR RR RR LL LL LL LL LL RR RR RR RR RR ...
  
 
where each character represents a byte in the buffer, and padding should of course be ignored (it's just for clarity).
 
where each character represents a byte in the buffer, and padding should of course be ignored (it's just for clarity).
Line 707: Line 733:
  
  
So, given that we have <code>SNDRV_PCM_INFO_MMAP_VALID</code> in our <code>_pcm_hw</code> struct, and we never use <code>snd_pcm_write*</code> functions (but instead we use say <code>memcpy</code> to transfer data) - it would be safe to say that in <code>minivosc</code>, the ''mmap'' transfer mode is being used.  
+
So, given that we have <code>SNDRV_PCM_INFO_MMAP_VALID</code> in our <code>_pcm_hw</code> struct, and we never use <code>snd_pcm_write*</code> functions (but instead we use say <code>memcpy</code> to transfer data) - it would be safe to say that in <code>minivosc</code>, the ''mmap'' transfer mode is being used.
  
  
 
&nbsp;
 
&nbsp;
 +
=== '''dummy.c revisited''' ===
  
=== dummy.c revisited ===
+
Some documentation for it can be found on [http://www.alsa-project.org/main/index.php/Matrix:Module-dummy Matrix:Module-dummy - AlsaProject], where it is described as:
 
+
Some documentation for it can be found on [http://www.alsa-project.org/main/index.php/Matrix:Module-dummy Matrix:Module-dummy - AlsaProject], where it is described as:  
+
  
 
<small><code>
 
<small><code>
This driver provides up to 4 devices with up to 16 substreams. It uses a timer to sink and generate data. Useful for initial testing of an ALSA installation.  
+
This driver provides up to 4 devices with up to 16 substreams. It uses a timer to sink and generate data. Useful for initial testing of an ALSA installation.
 
</code></small>
 
</code></small>
  
Line 727: Line 752:
 
> and all) but none of
 
> and all) but none of
 
> the recording application seem to record from the driver.
 
> the recording application seem to record from the driver.
>  
+
>
 
> Can the driver in current state work as the loop back cable between
 
> Can the driver in current state work as the loop back cable between
 
> applications?
 
> applications?
  
No, it's really dummy driver which eats playback samples and returns zero  
+
No, it's really dummy driver which eats playback samples and returns zero
 
samples for capture. Try use the snd-aloop driver.
 
samples for capture. Try use the snd-aloop driver.
 
</pre></small>
 
</pre></small>
  
And in [http://tldp.org/HOWTO/Alsa-sound-4.html Alsa-sound-mini-HOWTO: How to install ALSA sound drivers]:  
+
And in [http://tldp.org/HOWTO/Alsa-sound-4.html Alsa-sound-mini-HOWTO: How to install ALSA sound drivers]:
  
 
<small><code>
 
<small><code>
Line 741: Line 766:
 
</code></small>
 
</code></small>
  
If we look again at the <code>dummy.c</code> driver, and we try to apply the same approach as in <code>minivosc</code> - that is, we simply try to copy bytes into <code>substream->runtime->dma_area</code> right before <code>snd_pcm_period_elapsed</code> is called - we will experience SEVERE crashes/freezes. The reason for this is a variable <code>fake_buffers</code> being set to 1 - in which case, the <code>dma_area</code> is, in fact, not allocated at all!  
+
If we look again at the <code>dummy.c</code> driver, and we try to apply the same approach as in <code>minivosc</code> - that is, we simply try to copy bytes into <code>substream->runtime->dma_area</code> right before <code>snd_pcm_period_elapsed</code> is called - we will experience SEVERE crashes/freezes. The reason for this is a variable <code>fake_buffers</code> being set to 1 - in which case, the <code>dma_area</code> is, in fact, not allocated at all!
  
 
Therefore, the patch below shows some minimal changes that need to be implemented on [http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.32.y.git;a=blob;f=sound/drivers/dummy.c dummy.c]; so that a few bytes at the beginning of PCM buffer are written during capturing (which results with pulses at PCM buffer boundaries in the captured audio):
 
Therefore, the patch below shows some minimal changes that need to be implemented on [http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.32.y.git;a=blob;f=sound/drivers/dummy.c dummy.c]; so that a few bytes at the beginning of PCM buffer are written during capturing (which results with pulses at PCM buffer boundaries in the captured audio):
Line 751: Line 776:
 
   *
 
   *
 
   */
 
   */
+
 
 
+
 
+
 
+static int debug = 1;
 
+static int debug = 1;
Line 770: Line 795:
 
+
 
+
 
+//static int fake_buffer = 1;
 
+//static int fake_buffer = 1;
+// NOTE: IF WE INTEND TO WRITE TO  
+
+// NOTE: IF WE INTEND TO WRITE TO
 
+// DMA_AREA, fake_buffer CANNOT BE 1
 
+// DMA_AREA, fake_buffer CANNOT BE 1
 
+// ELSE VERY SERIOUS CRASHES HAPPEN
 
+// ELSE VERY SERIOUS CRASHES HAPPEN
 
+static int fake_buffer = 0;
 
+static int fake_buffer = 0;
+
 
 
  module_param_array(index, int, NULL, 0444);
 
  module_param_array(index, int, NULL, 0444);
 
  MODULE_PARM_DESC(index, "Index value for dummy soundcard.");
 
  MODULE_PARM_DESC(index, "Index value for dummy soundcard.");
Line 782: Line 807:
 
  struct dummy_hrtimer_pcm *dpcm = (struct dummy_hrtimer_pcm *)priv;
 
  struct dummy_hrtimer_pcm *dpcm = (struct dummy_hrtimer_pcm *)priv;
 
- if (atomic_read(&dpcm->running))
 
- if (atomic_read(&dpcm->running))
+
+
+
 
+ if (atomic_read(&dpcm->running)) {
 
+ if (atomic_read(&dpcm->running)) {
+ // we should write into the buffer right before snd_pcm_period_elapsed? probably yes... and probably only if dpcm->running...  
+
+ // we should write into the buffer right before snd_pcm_period_elapsed? probably yes... and probably only if dpcm->running...
 
+ // trying to write the value 230 (e6) four times
 
+ // trying to write the value 230 (e6) four times
+ // note that this works in arecord without specifying format (which defaults to 8KHz, 8bit);  
+
+ // note that this works in arecord without specifying format (which defaults to 8KHz, 8bit);
+ // but arecord completely messes the buffers up, for say, stereo 16 bit 44.1 KHz (as in audacity - although sometimes even audacity gives OK pulses, as long as one makes sure to reclick to select correct input).  
+
+ // but arecord completely messes the buffers up, for say, stereo 16 bit 44.1 KHz (as in audacity - although sometimes even audacity gives OK pulses, as long as one makes sure to reclick to select correct input).
 
+ if (dpcm->substream->stream == SNDRV_PCM_STREAM_CAPTURE)
 
+ if (dpcm->substream->stream == SNDRV_PCM_STREAM_CAPTURE)
 
+ memset(dpcm->substream->runtime->dma_area, 230, 4);
 
+ memset(dpcm->substream->runtime->dma_area, 230, 4);
+
+
+
+
+
+
 
  snd_pcm_period_elapsed(dpcm->substream);
 
  snd_pcm_period_elapsed(dpcm->substream);
 
+ }
 
+ }
 
  }
 
  }
+
 
 
  static enum hrtimer_restart dummy_hrtimer_callback(struct hrtimer *timer)
 
  static enum hrtimer_restart dummy_hrtimer_callback(struct hrtimer *timer)
 
  {
 
  {
 
  struct dummy_hrtimer_pcm *dpcm;
 
  struct dummy_hrtimer_pcm *dpcm;
+
 
 
+ dbg("%s: dummy_hrtimer_callback", __func__);
 
+ dbg("%s: dummy_hrtimer_callback", __func__);
+
+
+
 
  dpcm = container_of(timer, struct dummy_hrtimer_pcm, timer);
 
  dpcm = container_of(timer, struct dummy_hrtimer_pcm, timer);
 
  if (!atomic_read(&dpcm->running))
 
  if (!atomic_read(&dpcm->running))
Line 808: Line 833:
 
  dummy->timer_ops = &dummy_hrtimer_ops;
 
  dummy->timer_ops = &dummy_hrtimer_ops;
 
  #endif
 
  #endif
+
 
 
- err = dummy->timer_ops->create(substream);
 
- err = dummy->timer_ops->create(substream);
 
+ err = dummy->timer_ops->create(substream); // this calls dummy_hrtimer_create, where dpcm->substream is set to substream
 
+ err = dummy->timer_ops->create(substream); // this calls dummy_hrtimer_create, where dpcm->substream is set to substream
 
  if (err < 0)
 
  if (err < 0)
 
  return err;
 
  return err;
+
 
 
  runtime->hw = dummy_pcm_hardware;
 
  runtime->hw = dummy_pcm_hardware;
 
+ //dpcm->substream = substream; // already done in _create
 
+ //dpcm->substream = substream; // already done in _create
Line 828: Line 853:
 
  },
 
  },
 
  };
 
  };
+
 
 
</pre></small>
 
</pre></small>
  
Let's just note that in this case, we use <code>memset</code> to write 4 bytes at the beginning of the PCM buffers; if we request (say via <code>arecord</code>) a 8-bit mono stream, then we will see four samples in the captured audio; if we asked for a floating point (32 bit) mono stream, then we will see a single sample in the captured audio (which makes sense, since a <code>float</code> is usually encoded using 4 bytes, that is - <code>sizeof(float)</code> is 4).  
+
Let's just note that in this case, we use <code>memset</code> to write 4 bytes at the beginning of the PCM buffers; if we request (say via <code>arecord</code>) a 8-bit mono stream, then we will see four samples in the captured audio; if we asked for a floating point (32 bit) mono stream, then we will see a single sample in the captured audio (which makes sense, since a <code>float</code> is usually encoded using 4 bytes, that is - <code>sizeof(float)</code> is 4).
  
  
 
&nbsp;
 
&nbsp;
=== <span id="debug">Debugging</span> ===
+
=== '''<span id="debug">Debugging</span>''' ===
  
One of the most problematic things in driver development is their debugging as kernel modules - and especially problematic are errors such as <code>memset</code>ting a null pointer (which is what happens, if we try to write to <code>dma_area</code> in <code>dummy.c</code>, while <code>fake_buffers = 1</code>). In such a case, the computer freezes, without time to generate <code>printk</code> kernel debug messages in <code>/var/log/syslog</code> - and the only way out from such a freeze is a hard reboot (power off and on).  
+
One of the most problematic things in driver development is their debugging as kernel modules - and especially problematic are errors such as <code>memset</code>ting a null pointer (which is what happens, if we try to write to <code>dma_area</code> in <code>dummy.c</code>, while <code>fake_buffers = 1</code>). In such a case, the computer freezes, without time to generate <code>printk</code> kernel debug messages in <code>/var/log/syslog</code> - and the only way out from such a freeze is a hard reboot (power off and on).
  
In such a case, pretty much the first thing that pops to mind is to step the code in a debugger and identify the offending line. However, since in case of drivers we are talking about kernel modules (not userspace programs), the procedure for debugging them is not trivial.  
+
In such a case, pretty much the first thing that pops to mind is to step the code in a debugger and identify the offending line. However, since in case of drivers we are talking about kernel modules (not userspace programs), the procedure for debugging them is not trivial.
  
Fortunately, there is a kernel debugger built into the Linux kernel since version 2.6.26, known as [http://en.wikipedia.org/wiki/KGDB kgdb light]. What this means is that, while for earlier kernels this functionality required recompiling the kernel - for kernels newer than 2.6.26, we can simply add arguments like  
+
Fortunately, there is a kernel debugger built into the Linux kernel since version 2.6.26, known as [http://en.wikipedia.org/wiki/KGDB kgdb light]. What this means is that, while for earlier kernels this functionality required recompiling the kernel - for kernels newer than 2.6.26, we can simply add arguments like
  
 
<small><code>
 
<small><code>
Line 847: Line 872:
 
</code></small>
 
</code></small>
  
to the GRUB boot entry for the operating system - and then when the OS boots, instead of loading the desktop etc., the boot process will in fact halt, and wait for a signal from the GNU debugger <code>gdb</code>. This signal needs to be delivered through a serial connection - and so, debugging a kernel using <code>kgdb</code> assumes ''having a second machine'' that will run <code>gdb</code> for debugging, ''connected to'' the machine that runs the kernel / module to be debugged ''via serial cable''.  
+
to the GRUB boot entry for the operating system - and then when the OS boots, instead of loading the desktop etc., the boot process will in fact halt, and wait for a signal from the GNU debugger <code>gdb</code>. This signal needs to be delivered through a serial connection - and so, debugging a kernel using <code>kgdb</code> assumes ''having a second machine'' that will run <code>gdb</code> for debugging, ''connected to'' the machine that runs the kernel / module to be debugged ''via serial cable''.
  
Notably, since newer PCs don't even have a real RS-232 serial port, the only remaining approach to debugging with <code>kgdb</code> is on a single PC, through usage of a virtual machine. Here [http://www.virtualbox.org/wiki/Editions VirtualBox OSE] was used (<small>although in principle also Qemu or KVM could be used, since the Intel Atom processor used here [http://arstechnica.com/civis/viewtopic.php?f=8&t=121613 does not support hardware virtualization], VirtualBox is as good as any</small>), a virtual hard drive created from it, and Ubuntu 10.04 command line version was installed using the [https://help.ubuntu.com/community/Installation/MinimalCD Ubuntu minimal CD image]. VirtualBox can then be set up in its Settings / Serial Ports to: 'Enable Serial Port', and 'Create Pipe', where 'Port/File Path' would be a file like <code>/tmp/vboxpipe</code>.  
+
Notably, since newer PCs don't even have a real RS-232 serial port, the only remaining approach to debugging with <code>kgdb</code> is on a single PC, through usage of a virtual machine. Here [http://www.virtualbox.org/wiki/Editions VirtualBox OSE] was used (<small>although in principle also Qemu or KVM could be used, since the Intel Atom processor used here [http://arstechnica.com/civis/viewtopic.php?f=8&t=121613 does not support hardware virtualization], VirtualBox is as good as any</small>), a virtual hard drive created from it, and Ubuntu 10.04 command line version was installed using the [https://help.ubuntu.com/community/Installation/MinimalCD Ubuntu minimal CD image]. VirtualBox can then be set up in its Settings / Serial Ports to: 'Enable Serial Port', and 'Create Pipe', where 'Port/File Path' would be a file like <code>/tmp/vboxpipe</code>.
  
 
Then, after adding 'kgdboc=ttyS0 kgdbwait' as GRUB2 boot options to the virtual image OS installation, we can boot the virtual image; after a while the booting process starts, and should show: <br>
 
Then, after adding 'kgdboc=ttyS0 kgdbwait' as GRUB2 boot options to the virtual image OS installation, we can boot the virtual image; after a while the booting process starts, and should show: <br>
Line 861: Line 886:
 
</code></small>
 
</code></small>
  
and in another:  
+
and in another:
 
<small><pre>
 
<small><pre>
 
$ gdb /path/to/linux-2.6.32/vmlinux
 
$ gdb /path/to/linux-2.6.32/vmlinux
Line 877: Line 902:
 
$ echo g | sudo tee /proc/sysrq-trigger
 
$ echo g | sudo tee /proc/sysrq-trigger
 
</pre></small>
 
</pre></small>
at the virtual image <code>bash</code> prompt; or by using  
+
at the virtual image <code>bash</code> prompt; or by using
 
<small><pre>
 
<small><pre>
 
#define BREAKPOINT() asm("  int $3");
 
#define BREAKPOINT() asm("  int $3");
Line 883: Line 908:
 
in the driver kernel module code, and then calling <code>BREAKPOINT();</code> wherever in the driver code we want. Obviously, if we have a breakpoint in, say, "<code>_prepare</code>" function, we first <code>insmod</code> the driver module in the VM image OS, and then should call <code>arecord</code> in the VM so that the driver is activated there.
 
in the driver kernel module code, and then calling <code>BREAKPOINT();</code> wherever in the driver code we want. Obviously, if we have a breakpoint in, say, "<code>_prepare</code>" function, we first <code>insmod</code> the driver module in the VM image OS, and then should call <code>arecord</code> in the VM so that the driver is activated there.
  
Note that the <code>vmlinux</code> file used in the <code>gdb</code> call above is, in fact, the symbol file for the kernel; and the only way to obtain it is to rebuild the kernel. So although you don't need to rebuild the kernel simply to be able to break into <code>gdb</code>, you must [https://help.ubuntu.com/community/Kernel/Compile rebuild the kernel] in order to obtain the symbol file, and be able to step through source - without a symbol file, the <code>gdb</code> session above would look like:  
+
Note that the <code>vmlinux</code> file used in the <code>gdb</code> call above is, in fact, the symbol file for the kernel; and the only way to obtain it is to rebuild the kernel. So although you don't need to rebuild the kernel simply to be able to break into <code>gdb</code>, you must [https://help.ubuntu.com/community/Kernel/Compile rebuild the kernel] in order to obtain the symbol file, and be able to step through source - without a symbol file, the <code>gdb</code> session above would look like:
 
<small><pre>
 
<small><pre>
$ gdb  
+
$ gdb
 
(gdb) target remote 127.0.0.1:8040
 
(gdb) target remote 127.0.0.1:8040
 
Remote debugging using 127.0.0.1:8040
 
Remote debugging using 127.0.0.1:8040
Line 892: Line 917:
 
</pre></small>
 
</pre></small>
  
Of course, after you rebuild your kernel (it should automatically be set to generate debug symbols), you should also install your new debug kernel in the VM OS - and set it to boot by default, with the <code>kgdboc=ttyS0 kgdbwait</code> options appended to its boot entry.  
+
Of course, after you rebuild your kernel (it should automatically be set to generate debug symbols), you should also install your new debug kernel in the VM OS - and set it to boot by default, with the <code>kgdboc=ttyS0 kgdbwait</code> options appended to its boot entry.
  
Finally, since the driver modules we're working with here are not built as part of the kernel, <code>gdb</code> will need their symbol files as well. As such, it is best to built the driver modules within the virtual machine OS, and then copy the <code>.o</code> file to the normal file system so it is available to <code>gdb</code>. Here's how a sample session might look like (assuming 192.168.1.15 is the 'real' IP address of the host OS):  
+
Finally, since the driver modules we're working with here are not built as part of the kernel, <code>gdb</code> will need their symbol files as well. As such, it is best to built the driver modules within the virtual machine OS, and then copy the <code>.o</code> file to the normal file system so it is available to <code>gdb</code>. Here's how a sample session might look like (assuming 192.168.1.15 is the 'real' IP address of the host OS):
 
<small><pre>
 
<small><pre>
 
VM# cd /path/to/minivosc-src
 
VM# cd /path/to/minivosc-src
Line 902: Line 927:
 
VM# cat /sys/module/snd_minivosc/sections/.text
 
VM# cat /sys/module/snd_minivosc/sections/.text
 
0xd8b51000
 
0xd8b51000
VM# echo g | sudo tee /proc/sysrq-trigger  
+
VM# echo g | sudo tee /proc/sysrq-trigger
  
 
(gdb) add-symbol-file ~/snd-minivosc.o 0xd8b51000
 
(gdb) add-symbol-file ~/snd-minivosc.o 0xd8b51000
 
(gdb) continue
 
(gdb) continue
  
VM# arecord ...  
+
VM# arecord ...
 
</pre></small>
 
</pre></small>
  
Note that for correct stepping within <code>gdb</code>, the source files should be at ''the same path'' in both the virtual image OS and the host OS - so, if the source files for the kernel module are in <code>/path/to/minivosc-src</code> in the VM filesystem, the same directory should exist (and have the same source files) in the host filesystem as well. Also, a debug build should be enabled in the <code>Makefile</code> for the driver module (and it is so already for <code>minivosc</code>).  
+
Note that for correct stepping within <code>gdb</code>, the source files should be at ''the same path'' in both the virtual image OS and the host OS - so, if the source files for the kernel module are in <code>/path/to/minivosc-src</code> in the VM filesystem, the same directory should exist (and have the same source files) in the host filesystem as well. Also, a debug build should be enabled in the <code>Makefile</code> for the driver module (and it is so already for <code>minivosc</code>).
  
Finally, once freezes are not an issue anymore, one can simply use <code>printk</code> command throughout the driver module code - no VM image needed; the output of <code>printk</code> can be found in <code>/var/log/syslog</code> or <code>/var/log/messages</code> under Ubuntu 10.04. The <code>minivosc</code> driver code is by default set with these messages enabled, and they can be followed by running, say,  
+
Finally, once freezes are not an issue anymore, one can simply use <code>printk</code> command throughout the driver module code - no VM image needed; the output of <code>printk</code> can be found in <code>/var/log/syslog</code> or <code>/var/log/messages</code> under Ubuntu 10.04. The <code>minivosc</code> driver code is by default set with these messages enabled, and they can be followed by running, say,
 
<pre>
 
<pre>
 
tail -f /var/log/syslog
 
tail -f /var/log/syslog
 
</pre>
 
</pre>
in a terminal.  
+
in a terminal.
  
 
Here are some more resources dealing with debugging the kernel:
 
Here are some more resources dealing with debugging the kernel:

Latest revision as of 11:11, 24 April 2012

Contents

[edit] minivosc ALSA driver

 

For comments and discussion, see: Minivosc entry on Wiki - discuss. Copy of this page also found on author webpage.

 

[edit] Paper

  • Linux Audio Conference LAC2012 (full paper, 8pg) version here:
    • Minivosc.pdf (Minivosc - a minimal virtual oscillator driver for ALSA (Advanced Linux Sound Architecture))
    • Slides and video of presentation also available from the LAC2012 page (in addition to complete proceedings); please allow JavaScript for linuxaudio.org


[edit] Introduction

This is a brief documentation/tutorial on creation of snd-minivosc ALSA ( Advanced Linux Sound Architecture ) driver. The name minivosc should stand for minimal virtual oscillator, and aims to be an example of a minimal ALSA driver, that simply represents a soundcard with a single capture interface, which streams a predefined waveform (and thus behaves as an oscillator in music technology terms). Note that playback is not handled in this driver (nor any sort of realtime control of the oscillator, such as pitch).

At the time of writing, there are but a few documents dealing with writing ALSA drivers (some of these are listed under Tutorials and Presentations - AlsaProject):

Edit: see also following links:


While all these documents certainly provide valuable introductory points, they aren't excesivelly verbose about basic problems inherent in programming soundcard drivers; and they do not provide a full working code example of a driver. Iwai's document discusses an example of a hypothetical PCI device, whereas Collins' tutorial works with a real, though undisclosed device.

Minivosc, on the other hand, is a 'virtual' device driver, in the sense that it does not communicate with real external hardware - and therefore can be used to illustrate problems in soundcard device writing, that exist entirely on the PC side.

Browse the minivosc source here, or check it out from svn:

 svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-minivosc-src

This tutorial/write-up aims to serve as documentation of the development of minivosc, and in doing that, to introduce basic problems in soundcard drivers in as simple terms as possible; and as such, to serve as an addition to already existing ALSA driver resources.

The development machine has the following specs at time of writing:

user@mypc:/$ uname -a
Linux mypc 2.6.32-23-generic #37-Ubuntu SMP Fri Jun 11 07:54:58 UTC 2010 i686 GNU/Linux

user@mypc:/$ cat /etc/issue
Ubuntu 10.04 LTS \n \l

user@mypc:/$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.3-4ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)

user@mypc:/$ gdb -v
GNU gdb (GDB) 7.1-ubuntu

user@mypc:/$ cat /proc/cpuinfo
model name	: Intel(R) Atom(TM) CPU N450   @ 1.66GHz

user@mypc:/$ sudo lshw -class multimedia
  *-multimedia
       description: Audio device
       product: N10/ICH 7 Family High Definition Audio Controller
       configuration: driver=HDA Intel latency=0

user@mypc:/$ aplay -l && arecord -l
**** List of PLAYBACK Hardware Devices ****
card 0: Intel [HDA Intel], device 0: ALC662 rev1 Analog [ALC662 rev1 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
**** List of CAPTURE Hardware Devices ****
card 0: Intel [HDA Intel], device 0: ALC662 rev1 Analog [ALC662 rev1 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0


 

[edit] Starting points

Initially, the search for source code suitable as a starting point, began with looking in the ALSA source files integrated as part of the current linux kernel source (2.6.32 on the development machine at the time). In those, the most obvious place to start is sound/drivers/dummy.c, which produces the snd-dummy driver. It should be a good place to start, because snd-dummy is also a virtual driver (in the sense that it doesn't need external hardware); however, in spite of the name, this example is not trivial at all for a beginner to understand (see below for further discussion on dummy.c).

Additionally, one can go along Ben Collins: Writing an ALSA driver, and produce a minimal driver code that will compile and load. However, such a driver will not do anything in particular when it is 'captured' (read from) or 'played' (written to), and as such it is difficult to use it as an example for gaining further insight into internals of ALSA. In spite of this, minivosc copies its snd_pcm_hardware structure (and some other code portions) from it.

The alsa-devel mailing list helpfully supplied a pointer in the post: "(alsa-devel) Help with dummy.c (where/how to write?)" to a file present in ALSA sources (but not kernel sources), drivers/aloop-kernel.c, representing a virtual 'loopback soundcard' device. It is this file that is taken as a base for minivosc - in fact, it can be said that minivosc.c is a somewhat simplified version of aloop-kernel.c.


 

[edit] Source files

As mentioned above, the minivosc source can be browsed here, or checked out from svn through:

 svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-minivosc-src

It simply consists of a Makefile and minivosc.c source file. Follow the instructions in 'Building and running' in order to work with it; note that by default it has debug build, and debug statements (viewable in /var/log/syslog) enabled - see the 'Debugging' section for more.

In addition, ALSA driver beginners may want to take a look at the bencol source, that can be browsed here, or checked out from svn through:

 svn co https://sdaaubckp.svn.sourceforge.net/svnroot/sdaaubckp/alsa-bencol-src

This driver is simply a copy/paste of Ben Collins' tutorial, with minimal changes to make it build. The folder contains a Makefile, and the following files:

The Makefile contains entries to build any of these source files as snd-bencol.ko; (un)comment relevant lines before building. Also, note that from the three, only bencol-alsa-timer.c can produce some sort of a waveform (the others build and can be insmod-ded, but fail at capturing).


 

[edit] Building and running

One does not need to rebuild the entire linux kernel (which can take up to several hours) in order to build the kernel module for the minivosc driver. Simply, the build dependencies needed for building the linux kernel need to be installed (see Kernel/Compile - Community Ubuntu Documentation); after this, the files minivosc.c and a Makefile can be placed in a folder; and then in a terminal, after cd-ing to the folder, the following can be issued:

make clean && make

which should result with a kernel module file in the same folder, snd-minivosc.c (which follows the ALSA naming convention, where related kernel modules are prefixed with 'snd-')

If this module was built as part of the linux kernel, when one could have used 'modprobe snd_minivosc' to load the module, and 'modprobe -r snd_minivosc' to unload it. However, since in the above example the module is built separately, then we should, instead, use:

sudo insmod ./snd-minivosc.ko	# to load the module
sudo rmmod snd_minivosc 	# to inload the module

Note that the insmod command (unlike modprobe) demands a relative or absolute path to the .ko kernel module object.

To check whether the driver has been loaded after insmod (or unloaded after rmmod), issue

$ lsmod | grep minivosc
snd_minivosc            9028  0
snd_pcm                70662  4 snd_minivosc,snd_hda_intel,snd_hda_codec,snd_pcm_oss
snd                    54148  14 snd_minivosc,snd_hda_codec_realtek,snd_hda_intel,snd_hda_codec,snd_hwdep,snd_pcm_oss,snd_mixer_oss,snd_pcm,snd_seq_oss,snd_rawmidi,snd_seq,snd_timer,snd_seq_device

where lsmod lists the currently loaded modules in memory.

Finally, once the driver has been loaded in memory, we can try to use it. First, we need to check whether it is registered as a soundcard by ALSA, so we issue:

$ aplay -l && arecord -l
**** List of PLAYBACK Hardware Devices ****
card 0: Intel [HDA Intel], device 0: ALC662 rev1 Analog [ALC662 rev1 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
**** List of CAPTURE Hardware Devices ****
card 0: Intel [HDA Intel], device 0: ALC662 rev1 Analog [ALC662 rev1 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: sndminivosc [MySoundCard Audio snd_minivosc], device 0: my_driver-snd_miMySoundCard Audio snd_minivosc [MySoundCard Audio snd_minivosc]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

Note that snd_minivosc does not show up as a playback device at all - it is shown strictly as a capture device. Also, note that if you run alsamixer, and try to select the minivosc card, the program will respond with "This sound device does not have any controls.", which it indeed doesn't (however, the corresponding mixer controls code sections could be easily copied from aloop-kernel.c). Note that another great way to inspect audio devices is by using the alsa-info.sh script.

In the device list above, snd_minivosc is the second soundcard (card 1), and it has one capture interface (subdevice #0). Therefore, in order to capture from it, we can issue:

arecord -D hw:1,0 -d 2 foo.wav

where -D hw:1,0 would refer to choice of second card, first capture interface. Note that if we do not specify any format parameters, man arecord states that:

The default is one channel.
...
If no format is given U8 is used.
...
The default rate is 8000 Hertz.

which means the above command will ask for 2 seconds of 8 KHz mono stream with 8-bit resolution (that is, each sample will be represented by 8 bits - a byte, or an unsigned char) - and that is pretty much the only format that snd-minivosc will accept, as well. If the arecord command executes succesfully, then we can also use an audio editor like Audacity to record (capture) from the minivosc soundcard.


[edit] Note about pulseaudio

Also, note that in Ubuntu 10.04, PulseAudio is started by default; when it's running, it allows access to the Sound Preferences mixer in Ubuntu. While pulseaudio is running, one can insert minivosc driver module without a problem; however, trying to rmmod the module afterwards will fail. In that case, one can try to shutdown pulseaudio with

pulseaudio --kill

and then try to remove the module afterwards. To get back access to the Gnome mixer, start pulseaudio again by using:

pulseaudio --start

However, there is a workaround for the above. Note first, that in Ubuntu 11.04 (Natty), the main volume mixer is gnome-volume-control:

gnome-volume-control with minivosc + pulseaudio.

Upon running pulseaudio --kill, this volume control will get muted (see image above right) - however, after a couple of mouseovers, the applet will "recover" - and then the "Sound Preferences" mixer can be raised again; the minivosc driver will still be present in "Sound Preferences" under "Input".

However, there is also an additional volume control, which is associated with the pulseaudio package - this one is known as pavucontrol (it can be separately installed through apt-get):

pavucontrol with minivosc + pulseaudio.

In this pavucontrol "Volume Control", note that the minivosc driver should show up in the "Configuration" tab, where by default it is set on the "Analog Mono Input" profile. If you change this profile to "Off", then pulseaudio will no longer claim the minivosc driver; and so the driver can be easily insmod'ded and rmmod'ded multiple times without a problem, even while pulseaudio is running.

 

[edit] Understanding ALSA driver architecture

Understanding the ALSA driver architecture can be quite a mouthful, as there are plenty of functions and structs that need to be present in order for a driver to function. Before we review those, let's first revisit the context of use of an ALSA driver, and consider the following diagram:

Soundcard driver context

The diagram represents a simplified abstraction of a mono in, mono out soundcard, connected to a PC through some sort of a bus (PCI, USB, ISA...). Obviously, for each input or output, we need an ADC and DAC device (respectively) present on the soundcard; all the rest of the digital circuitry needed to interface these convertors to the bus (and the rest of the PC) is abstracted in the diagram as "Controller". As the CPU is, ultimately, in control of the bus, we can consider the soundcard driver to be a piece of software running on the CPU, which handles the transfer of data, in each direction (playback or capture), between the soundcard and the rest of the PC (meaning CPU and memory).

In this case, the minivosc driver will - without actual hardware - present a soundcard with a single mono input to the rest of the system (and thus, the whole playback direction as on the diagram above would not exist for it).

At this point, it is important to mention a few words about the Linux driver model (see Documentation/driver-model/). We can inspect /sys/devices in a bash shell:

$ ls -la /sys/devices/
total 0
drwxr-xr-x 10 root root 0 2010-08-09 17:32 .
drwxr-xr-x 12 root root 0 2010-08-09 17:32 ..
drwxr-xr-x  3 root root 0 2010-08-10 09:42 isa
drwxr-xr-x 15 root root 0 2010-08-09 17:32 LNXSYSTM:00
drwxr-xr-x 19 root root 0 2010-08-09 17:32 pci0000:00
drwxr-xr-x 10 root root 0 2010-08-09 17:32 platform
drwxr-xr-x 15 root root 0 2010-08-09 17:32 pnp0
drwxr-xr-x  3 root root 0 2010-08-10 09:42 pnp1
drwxr-xr-x 11 root root 0 2010-08-09 17:32 system
drwxr-xr-x 20 root root 0 2010-08-09 17:32 virtual

$ for ix in /sys/devices/pci*/0* ; do echo $ix ; ls -la $ix | grep -i usb  ; done
/sys/devices/pci0000:00/0000:00:00.0
/sys/devices/pci0000:00/0000:00:02.0
...
/sys/devices/pci0000:00/0000:00:1d.0
drwxr-xr-x  6 root root    0 2010-08-09 17:32 usb2
drwxr-xr-x  3 root root    0 2010-08-10 09:11 usbmon
/sys/devices/pci0000:00/0000:00:1d.1
drwxr-xr-x  5 root root    0 2010-08-09 17:32 usb3
drwxr-xr-x  3 root root    0 2010-08-10 09:11 usbmon
...

This tells us that the system recognizes 'isa', 'pci', 'platform' etc. devices (with a note, that USB devices show under the 'pci' bus). Now, this is important, because an ALSA driver must receive a pointer to a corresponding driver structure in the _init and _exit functions:

  • In Writing an ALSA Driver, a PCI ALSA driver is discussed;
    • hence struct pci_driver driver is used, along with pci_register_driver(&driver) in _init
  • In usbaudio.c, a USB ALSA driver is given;
    • hence struct usb_driver usb_audio_driver is used, along with usb_register(&usb_audio_driver) in _init

However, since dummy.c and aloop-kernel.c (as well as minivosc) do not represent any real hardware - they will instead utilize the platform driver model (see /driver-model/platform.txt); that is:

  • struct platform_driver XYZ_driver is used, along with platform_driver_register(&XYZ_driver) in _init


 

[edit] Driver / device initialization

Assume now, that the diagram above represents a soundcard connected to the USB bus. Since USB devices are meant to support hot-plugging, the driver should be able to handle the situations where the device is plugged or unplugged while the computer is still on. Hence, the driver must differentiate between the moments when the driver is loaded or unloaded (in our case, that is when insmod and rmmod commands are executed); and the moments when the device itself is connected to, or disconnected from, the bus. The Linux kernel (see Anatomy of a kernel module object) and ALSA driver architectures provide several such predefined functions, which in the case of minivosc are:

// * declare driver functions - linux kernel
static int __init alsa_card_minivosc_init(void);
static void __exit alsa_card_minivosc_exit(void);

// * declare driver device handling functions - ALSA
static int __devinit minivosc_probe(struct platform_device *devptr);
static int __devexit minivosc_remove(struct platform_device *devptr);

// * declare driver pcm operations functions - ALSA
static int minivosc_hw_params(struct snd_pcm_substream *ss,
                        struct snd_pcm_hw_params *hw_params);
static int minivosc_hw_free(struct snd_pcm_substream *ss);
static int minivosc_pcm_open(struct snd_pcm_substream *ss);
static int minivosc_pcm_close(struct snd_pcm_substream *ss);
static int minivosc_pcm_prepare(struct snd_pcm_substream *ss);
static int minivosc_pcm_trigger(struct snd_pcm_substream *ss,
                          int cmd);
static snd_pcm_uframes_t minivosc_pcm_pointer(struct snd_pcm_substream *ss);

static int minivosc_pcm_dev_free(struct snd_device *device);

Note that this is not the full scope of predefined functions (for more, see Iwai's documents); however they are the necesarry minimum needed for minivosc to perform. Here is a brief rundown of these functions - first the driver and device initialization functions:

  • _init - runs when the driver module is loaded (i.e. w/ insmod)
  • _exit - runs when the driver module is unloaded (i.e. w/ rmmod)
  • _probe - runs when the device is attached to bus if hotpluggable (see Re: (linux-usb-devel) use of __devinit in st5481)
  • _remove - runs when the device is removed from bus if hotpluggable

In this case, once the minivosc driver is loaded via insmod, it always runs the alsa_card_minivosc_init and minivosc_probe functions one after another; and these two functions are enough to get the ALSA system to recognize and list a soundcard.

In addition, the following structures should be defined for the driver and device initialization functions - in minivosc.c:

// specifies what func is called @ snd_card_free
// used in snd_device_new
static struct snd_device_ops dev_ops =
{
	.dev_free = minivosc_pcm_dev_free,
};

// * we need a struct describing the driver:
static struct platform_driver minivosc_driver =
{
	.probe		= minivosc_probe,
	.remove		= __devexit_p(minivosc_remove),
	.driver		= {
		.name	= SND_minivosc_DRIVER,
		.owner = THIS_MODULE
	},
};


 

[edit] Hardware parameters and PCM Interface functions

Now, it needs to be defined what happens when the driver gets used by userland audio software (such as arecord or audacity). Typically, audio software will request the driver to play back (or capture) at a given format (number of streams as in mono or stereo, choice of sampling rate and sampling resolution); the driver then should transfer data from userspace memory to the soundcard (in case of playback) or transfer data from the soundcard to userspace memory (in case of capture) at the requested format. These types of operations are handled by so called PCM operations ALSA functions. Note that in the case of ALSA, the 'PCM' doesn't mean specifically pulse-code modulation, as noted in "ALSA project - the C library reference: PCM (digital audio) interface":

Although abbreviation PCM stands for Pulse Code Modulation, we are understanding it as general digital audio processing with volume samples generated in continuous time periods.

The allowed audio formats that the driver will accept, as well as the PCM operations functions, should be defined as structures, which in the case of minivosc are:

#define MAX_BUFFER (32 * 48)
static struct snd_pcm_hardware minivosc_pcm_hw =
{
	.info = (SNDRV_PCM_INFO_MMAP |
	SNDRV_PCM_INFO_INTERLEAVED |
	SNDRV_PCM_INFO_BLOCK_TRANSFER |
	SNDRV_PCM_INFO_MMAP_VALID),
	.formats          = SNDRV_PCM_FMTBIT_U8,
	.rates            = SNDRV_PCM_RATE_8000,
	.rate_min         = 8000,
	.rate_max         = 8000,
	.channels_min     = 1,
	.channels_max     = 1,
	.buffer_bytes_max = MAX_BUFFER, //(32 * 48) = 1536,
	.period_bytes_min = 48,
	.period_bytes_max = 48,
	.periods_min      = 1,
	.periods_max      = 32,
};

static struct snd_pcm_ops minivosc_pcm_ops =
{
	.open      = minivosc_pcm_open,
	.close     = minivosc_pcm_close,
	.ioctl     = snd_pcm_lib_ioctl,
	.hw_params = minivosc_hw_params,
	.hw_free   = minivosc_hw_free,
	.prepare   = minivosc_pcm_prepare,
	.trigger   = minivosc_pcm_trigger,
	.pointer   = minivosc_pcm_pointer,
};

Note that snd_pcm_ops can usually be separate *_playback_ops and *_capture_ops structs; however since minivosc presents only a single capture interface, only the above minivosc_pcm_ops struct exists.

Next, note that in the snd_pcm_hardware structure (copied from Ben Collins' tutorial): rate_max = rate_min = 8000 and .formats = SNDRV_PCM_FMTBIT_U8, .rates = SNDRV_PCM_RATE_8000,; this means that the driver will accept only 8KHz as a sampling rate, and only 8 bit as sampling resolution - and requesting, say, CD quality (44.1KHz, 16bit) from minivosc will therefore fail:

$ arecord -f cd -D hw:1,0 -d 2 foo.wav
Recording WAVE 'foo.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
arecord: set_params:990: Sample format non available
Available formats:
- U8

Let us now provide a brief rundown of the PCM operations:

  • _pcm_open - runs each time a (sub)stream is opened (i.e. when you execute arecord; or press 'Record' in audacity).
  • _pcm_close - runs each time (sub)stream is closed (i.e. a couple of seconds after: arecord finishes executing; or pressing 'Stop' in audacity during active recording).
  • ioctl - special communication with hardware - since we have no actual hardware, we simply specify ALSA's snd_pcm_lib_ioctl
  • _hw_params - allocates kernel memory for a substream, according to requested format, through use of snd_pcm_lib_malloc_pages
  • _hw_free - frees kernel memory for a substream
  • _pcm_prepare - "This callback is called when the pcm is 'prepared'. You can set the format type, sample rate, etc. here. The difference from hw_params is that the prepare callback will be called each time snd_pcm_prepare() is called, i.e. when recovering after underruns, etc." (writing-an-alsa-driver.pdf)
  • _pcm_trigger - "This is called when the pcm is started, stopped or paused. ... At least, the START and STOP commands must be defined in this callback." (writing-an-alsa-driver.pdf)
  • _pcm_pointer - "This callback is called when the PCM middle layer inquires the current hardware position on the buffer. The position must be returned in frames, ranging from 0 to buffer_size - 1. This is called usually from the buffer-update routine in the pcm middle layer, which is invoked when snd_pcm_period_elapsed() is called in the interrupt routine. Then the pcm middle layer updates the position and calculates the available space, and wakes up the sleeping poll threads, etc." (writing-an-alsa-driver.pdf)

A typical sequence of the basic PCM operations steps, that can be seen in minivosc debug messages, is:

# at insmod:
[48803.808593] ./minivosc.c: alsa_card_mini
vosc_init
[48803.808821] ./minivosc.c: minivosc_probe
: probe
[48803.808860] : -- mydev f431a60c

# at arecord:
[48810.487603] ./minivosc.c: minivosc_pcm_open
[48810.488110] ./minivosc.c: minivosc_hw_params
[48810.488162] ./minivosc.c: minivosc_pcm_prepare
[48810.488170] :        bps: 8000; runtime->buffer_size: 1536; mydev->pcm_buffer_size: 1536
[48810.488478] ./minivosc.c: minivosc_pcm_trigger - trig 1
...
[48811.489504] ./minivosc.c: minivosc_pcm_trigger - trig 0
[48811.489527] ./minivosc.c: minivosc_hw_free
[48811.489588] ./minivosc.c: minivosc_hw_free
[48811.489596] ./minivosc.c: minivosc_pcm_close

# at rmmod:
[49005.736089] ./minivosc.c: alsa_card_minivosc_exit
[49005.736097] ./minivosc.c: minivosc_unregister_all
[49005.736146] ./minivosc.c: minivosc_remove
[49005.755433] ./minivosc.c: minivosc_pcm_dev_free
[49005.755445] ./minivosc.c: minivosc_pcm_free


Note that if one goes along Ben Collins' tutorial, and builds an example out of it, _pcm_prepare, _pcm_trigger and _pcm_pointer can be left essentially empty:

static int my_pcm_prepare(struct snd_pcm_substream *ss)
{
	return 0;
}


static int my_pcm_trigger(struct snd_pcm_substream *ss,
                          int cmd)
{
	int ret = 0;

	switch (cmd)
	{
		case SNDRV_PCM_TRIGGER_START:
			// Start the hardware capture
			break;
		case SNDRV_PCM_TRIGGER_STOP:
			// Stop the hardware capture
			break;
		default:
			ret = -EINVAL;
	}

	return ret;
}


static snd_pcm_uframes_t my_pcm_pointer(struct snd_pcm_substream *ss)
{
	struct my_device *my_dev = snd_pcm_substream_chip(ss);
	return my_dev->hw_idx;
}

and while the code will compile and the module will load without problems, attempting to use such a driver will result with:

$ arecord -D hw:1,0 -d 2 foo.wav
Recording WAVE 'foo.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono
arecord: pcm_read:1629: read error: Input/output error

... which means, _pcm_prepare, _pcm_trigger and _pcm_pointer cannot be left empty, if we expect the driver to work :)


 

[edit] Device structure

As different functions of the driver may need access to information at different times, we must provide a structure that can be accessed and modified by these functions. In the case of minivosc, we use a single structure to represent both the device and the only available substream:

struct minivosc_device
{
	struct snd_card *card;
	struct snd_pcm *pcm;
	const struct minivosc_pcm_ops *timer_ops;
	/*
	* we have only one substream, so all data in this struct
	*/
	/* copied from struct loopback: */
	struct mutex cable_lock;
	/* copied from struct loopback_cable: */
	/* PCM parameters */
	unsigned int pcm_period_size;
	unsigned int pcm_bps;		/* bytes per second */
	/* flags */
	unsigned int valid;
	unsigned int running;
	unsigned int period_update_pending :1;
	/* timer stuff */
	unsigned int irq_pos;		/* fractional IRQ position */
	unsigned int period_size_frac;
	unsigned long last_jiffies;
	struct timer_list timer;
	/* copied from struct loopback_pcm: */
	struct snd_pcm_substream *substream;
	unsigned int pcm_buffer_size;
	unsigned int buf_pos;	/* position in buffer */
	unsigned int silent_size;
	/* added for waveform: */
	unsigned int wvf_pos;	/* position in waveform array */
	unsigned int wvf_lift;	/* lift of waveform array */
};

Note that in most part, these variables are taken from aloop-kernel.c; however both aloop-kernel.c and dummy.c are capable of handling multiple capture and playback substreams - and thus in those drivers, several structs (instead of a single) are used, because arrays of structs must be implemented so as to represent multiple substreams.

Note also, that the "_open" PCM operation is the first time when the ALSA system makes a real pointer to a snd_pcm_substream available; hence, it is in this callback where we need to make sure that we set ourselves the pointer minivosc_device->substream to the real pointer passed by the system; otherwise, the rest of the PCM functions will not have the right pointer to work with (and hence kernel oops and crashes can be expected).

More detailed information about the substream pointer and its use can be found in writing-an-alsa-driver.pdf, under Runtime Pointer - The Chest of PCM Information.


 

[edit] Timing and memory (buffer) management

We now arrive at a slightly more complex part of an ALSA driver. We have already mentioned that minivosc corresponds to (or simulates the context in) the diagram above - except with only a single DAC (and no ADC); and thus with only the 'Capture' data transfer direction present. Even though this driver can thus, by definition, only support a single direction of data transfer (from the card to the PC), there could be several strategies involved with this:

  • The PC repeatedly keeps on asking the card if it has data to supply (polling); if it does it handles the data transfer (copies data from the card to PC memory).
  • The soundcard generates a signal when it has data ready for the PC; upon this signal, the PC stops whatever its doing, and it handles the data transfer (copies data from the card to PC memory) (interrupt)

In principle, either of these approaches could be used so that the PC would receive data of one sample (which in minivosc case is 8 bit, or a byte) at a time - however, that would be inefficient use of computer resources. That is why within ALSA, data transfer encompasses multiple samples - chunks - at a time.

Going back to the Ben Collins tutorial, where capture is discussed, we can already start guessing why the minimal example built from that tutorial will not do anything: "The buffer I've shown we assume to have been filled during interrupt." (Ben Collins: Writing an ALSA driver: PCM handler callbacks) - seemingly, an interrupt generated by a device; however the interrupt function is in any case not provided.

So we can take a look again at aloop-kernel.c and dummy.c, where we can find that:

In other words: if there isn't an actual hardware to generate interrupts; then we must set up some sort of a timer, that will repeatedly trigger a function (that would correspond to a polling function) in our virtual soundcard driver. In this case, minivosc copies the timer API approach from aloop-kernel.c.

At this point, let's take a look at which PCM functions get called in minivosc after it had been triggered for start:

.

[48810.488478] ./minivosc.c: minivosc_pcm_trigger - trig 1
[48810.488486] : minivosc_timer_start: mydev->period_size_frac: 12000; mydev->ir
q_pos: 0 jiffies: 12127621 pcm_bps 8000
[48810.488497] : +minivosc_pointer
[48810.488500] : *minivosc_pos_update: running
[48810.488505] : *      : jiffies 12127621, ->last_jiffies 12127621, delta 0
[48810.488510] : +      bytes_to_frames(: 0, mydev->buf_pos: 0
[48810.493135] : minivosc_timer_function: running
[48810.493141] : *minivosc_pos_update: running
[48810.493147] : *      : jiffies 12127623, ->last_jiffies 12127621, delta 2
[48810.493152] : *      : last_pos 0, c->irq_pos 16000, count 64
[48810.493157] : >minivosc_xfer_buf: count: 64
[48810.493163] : _ minivosc_fill_capture_buf ss 1536 bs 1536 bytes 64 buf_pos 0
sizeof 1 jiffies 12127623
[48810.493182] : *      : mydev->irq_pos >= mydev->period_size_frac 12000
[48810.493188] : minivosc_timer_start: mydev->period_size_frac: 12000; mydev->ir
q_pos: 4000 jiffies: 12127623 pcm_bps 8000
[48810.493194] :        : calling snd_pcm_period_elapsed
[48810.493199] : +minivosc_pointer
[48810.493202] : *minivosc_pos_update: running
[48810.493207] : *      : jiffies 12127623, ->last_jiffies 12127623, delta 0
[48810.493212] : +      bytes_to_frames(: 64, mydev->buf_pos: 64
[48810.493324] : +minivosc_pointer

Let's briefly discuss the snippet above:

  • The timer is repeatedly activated (set off) by calling _timer_start.
  • When the time set for the timer expires, the _timer_function callback function runs, which calls _pos_update .
  • If _pos_update detects a difference in jiffies, it calls _xfer_buf which in turn calls _fill_capture_buf
    • in this case, when all is finished, _timer_start is called again
  • _pos_update can also be called independently by _pcm_pointer.
    • in this case, usually there is no difference in jiffies (delta is 0), in which case _pos_update quickly exits, not calling any other function.

At this point, lets include an excerpt from Jiffy (time):
In computing, a jiffy is the duration of one tick of the system timer interrupt. It is not an absolute time interval unit, since its duration depends on the clock interrupt frequency of the particular hardware platform.
...
Within the Linux 2.6 operating system kernel, since release 2.6.13, on the Intel i386 platform a jiffy is by default 4 ms, or 1/250 of a second. The jiffy values for other Linux versions and platforms have typically varied between about 1 ms and 10 ms.

So, to be more precise - _pos_update actually measures time (in jiffies), elapsed since the last call to _timer_start, as the variable delta; only if delta is more than 0 jiffies, a call to _xfer_buf is made, requesting a transfer of ammount of samples (data) that corresponds to the elapsed time in jiffies, according to the requested sampling rate, resolution and number of streams. Let's use this for the log above:

  • A sampling rate of 8000 KHz, means we have to transfer data for 8000 samples each second for a single (mono) stream.
  • A sampling resolution of 8 bits = byte, means we have to transfer 8000 bytes each second.
  • And 8000 Bps will be equivalent to (8000 / 250) = 32 bytes per jiffy (given a jiffy is 4ms for a 2.6 kernel)
  • Thus, when delta==2, then 2*32 = 64 bytes will be requested for transfer; when delta==1, then 32 bytes will be requested.

On the development machine, a typical pattern of changes of delta looked like:

*	: jiffies 12127837, ->last_jiffies 12127836, delta 1
*	: jiffies 12127837, ->last_jiffies 12127837, delta 0
*	: jiffies 12127839, ->last_jiffies 12127837, delta 2
*	: jiffies 12127839, ->last_jiffies 12127839, delta 0
*	: jiffies 12127839, ->last_jiffies 12127839, delta 0
*	: jiffies 12127839, ->last_jiffies 12127839, delta 0
*	: jiffies 12127839, ->last_jiffies 12127839, delta 0
*	: jiffies 12127840, ->last_jiffies 12127839, delta 1
*	: jiffies 12127840, ->last_jiffies 12127840, delta 0
*	: jiffies 12127842, ->last_jiffies 12127840, delta 2
*	: jiffies 12127842, ->last_jiffies 12127842, delta 0
*	: jiffies 12127842, ->last_jiffies 12127842, delta 0

which means the requests for byte transfers will repeatedly change between 32 and 64 bytes.

Now, how does the rest of ALSA know that such a requested transfer has been executed succesfully? It does so by asking the driver, what is its current position in the buffer, by calling its _pcm_pointer function; _pcm_pointer should return the buffer position in frames (and in our minivosc case, since we use a mono 8 bit stream, a frame will be equivalent to a size of a single sample, which is a byte). The important thing to remember is that here 'buffer' does not refer to the sizes of these 'individual' transfers of 32 and 64 bytes - it refers to the size of the PCM buffer of the substream, which is determined in _prepare!

This is why we need to keep a variable for the position within this PCM buffer, minivosc_device->buf_pos, within our device struct; we can then update this variable for each 'individual' transfer, and return it back whenever the ALSA middle layer asks for it through _pcm_pointer. (this becomes obvious, if we comment all commands that update minivosc_device->buf_pos - in that case, running a capture from Audacity will visibly show the record cursor being unable to move, and the process will eventually fail.) Note that in aloop-kernel.c, the main calculation of buf_pos occurs in _xfer_buf (in minivosc, it depends on the choice of copying algorithm).

Also note that, after how much time after _start does the timer expire and _timer_function runs, is calculated in _timer_start as:

tick = (mydev->period_size_frac - mydev->irq_pos + mydev->pcm_bps - 1) / mydev->pcm_bps;
mydev->timer.expires = jiffies + tick;

While here, let's also mention snd_pcm_period_elapsed. The _timer_function, after calling _pos_update and _timer_start, checks if mydev->period_update_pending is 1 - if so, then it calls snd_pcm_period_elapsed. The condition of setting period_update_pending to active is if (mydev->irq_pos >= mydev->period_size_frac) in _pos_update, where:

// in include/asm-generic/param.h
# define HZ             CONFIG_HZ       /* Internal kernel timer frequency */

// minivosc
#define byte_pos(x)	((x) / HZ)
#define frac_pos(x)	((x) * HZ)
// _prepare:
mydev->pcm_bps = runtime->rate * runtime->channels
		* snd_pcm_format_width(runtime->format)
		/ 8;
mydev->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size);
mydev->pcm_period_size =
			frames_to_bytes(runtime, runtime->period_size);
mydev->period_size_frac = frac_pos(mydev->pcm_period_size);
// _pos_update:
mydev->irq_pos += delta * mydev->pcm_bps;

// typical values - arecord:
bps: 8000; runtime->buffer_size: 1536; mydev->pcm_buffer_size: 1536
mydev->pcm_period_size: 48; mydev->period_size_frac: 12000

This tells us that we must differentiate between size of PCM buffer, pcm_buffer_size (1536 bytes) - and pcm_period_size (48 bytes). In simple terms, we could understand this as: as soon as a new batch of 48 bytes have been written in the PCM buffer, the ALSA middle layer should be informed by calling snd_pcm_period_elapsed; and it is this call that finally, after all the buffer operations performed within the driver, makes the data available to audio software like arecord that can proceed with, say, recording this data to disk.

writing-an-alsa-driver.pdf, in regards to snd_pcm_period_elapsed, mentions:

Interrupt Handler
The rest of pcm stuff is the PCM interrupt handler. The role of PCM interrupt handler in the sound driver
is to update the buffer position and to tell the PCM middle layer when the buffer position goes across the
prescribed period size. To inform this, call the snd_pcm_period_elapsed() function.
...
Interrupts at the period (fragment) boundary
This is the most frequently found type: the hardware generates an interrupt at each period boundary. In
this case, you can call snd_pcm_period_elapsed() at each interrupt.
...
High frequency timer interrupts
This happense when the hardware doesn't generate interrupts at the period boundary but issues timer
interrupts at a fixed timer rate (e.g. es1968 or ymfpci drivers). In this case, you need to check the current
hardware position and accumulate the processed sample length at each interrupt. When the accumulated
size exceeds the period size, call snd_pcm_period_elapsed() and reset the accumulator.
...
On calling snd_pcm_period_elapsed()
In both cases, even if more than one period are elapsed, you don't have to call
snd_pcm_period_elapsed() many times. Call only once. And the pcm layer will check the current
hardware pointer and update to the latest status.


 

[edit] More on memory (buffer) management

At this point, let us recall that minivosc simply repeats a short waveform, in order to generate a continuous tone. This waveform is specified as the array wvfdat within the driver code. Additionally, at each repetition, the waveform can be 'lifted' - that is, a constant value can be added to it - which is controlled by the minivosc_device->wvf_lift variable.

In the case of actual capture hardware, the driver would have to first collect the data from the card in some intermediate buffer (array) - and in the case of minivosc, that intermediate buffer is in fact wvfdat; the only difference from the hardware case being, that it is pre-filled with data (and in a real soundcard, it would have to be continuosly updated with data from the soundcard).

Thus, we can state the following: regardless if we talk about a virtual or a real hardware driver, a key part of the driver job, is to transfer data from an intermediate buffer/array (here wvfdat) to the PCM buffer for that substream (here minivosc_device->substream->runtime->dma_area) - in 'individual' transfers of chunks, whose size is determined by the time elapsed since the last 'individual' transfer (or in other words, the time between two consecutive _timer_functions).

In other words, in the case of minivosc we can distinguish between:

  • intermediate (waveform) buffer/array - wvfdat - size 21 bytes
    • size preset by driver programmer
  • 'individual' transfer chunk size - given by bytes / count - size 32 (or 64) bytes
    • size dependent on timing between consecutive executions of _timer_function & stream(s) format
  • PCM substream buffer/array - dev->substream->runtime->dma_area - size 816 (or 1536) bytes
    • size chosen by software (?): audacity usually claims 816 bytes, arecord 1536 bytes
  • pcm_period_size - size 48 bytes,
    • for calling snd_pcm_period_elapsed, size set by stream(s) format & kernel timer frequency

And, since it turns out that, in this case, the intermediate buffer size (21) is less than the 'individual' transfer chunk size (32 or 64), we come to an interesting situation, not accounted for in the original aloop-kernel.c - displayed on the diagram below (the colors on the diagram match the colors used in the list above).

Buffer visualisation diagram.


As shown in the buffer visualisation diagram, due to intermediate (waveform) buffer/array size (wvfsz) being smaller than 'individual' transfer chunk size (count), we need to loop through the waveform buffer/array in order to fill a chunk request - and the waveform piece will not end at the end of the chunk request. In other words, the data will not be aligned at boundary.

That is why, although 'individual' transfer chunk size is not a real array, we have to treat it as such, because we need to keep a pointer for it (here dpos). In other words, if we want seamless looping of the waveform buffer, we need to keep three buffer pointers:

  • dev->wvfpos - where are we in the wvfdat array
  • dpos - where are we in the current chunk request
  • dev->buf_pos - where are we in the PCM substream buffer (...->dma_area)

To illustrate this, there are three copying algorithms one can choose from in minivosc's function _fill_capture_buf - simply by uncommenting the corresponding #define (and commenting the others):

  • COPYALG_V1 - here, bytes are copied in chunks using memcpy (which slightly complicates buffer pointer calculation)
  • COPYALG_V2 - here, bytes are copied one by one from wvfdat to dma_area through assignment in a loop
  • COPYALG_V3 - a copy of copy_play_buf function's algorithm from aloop-kernel.c

Note that V1 and V2 calculate their own buf_pos in _fill_capture_buf, and they can both demonstrate seamless looping of the waveform:

Seamless loop example (V1 or V2) - the large spikes represent different buffer sizes of audacity and arecord.

Seamless loop V1.

Seamless loop V2.

V3 uses the same calculation of buf_pos as originally in aloop-kernel.c, and it does show that the waveform looping in that case is not seamless:

Problems in looping between audacity and arecord, due to differing buffer sizes, when the buf_pos is calculated in _xfer_buf as in aloop-kernel.c.

Problems in looping...

Also note that by uncommenting the #define BUFFERMARKS, we can insert bytes marking specific 'edges' of the buffer, which visibly illustrates the differences in PCM buffer sizes between arecord and audacity (note, you can use these buffer marks, even if no algorithm for copying is used, that is, without a waveform - however, buf_pos still has to be handled):

Illustration of buffer marks - and difference of buffer sizes between arecord and audacity.

buffer marks - 'zoomed' out.

buffer marks - 'zoomed' in.

Note that while audacity seems to 'speed up' its buffers after some periods, it will still report the same 32 and 64 bytes requests in the logs as arecord (?!)

Finally, let's include these snippets related to buffers:

From ALSA project - the C library reference: PCM (digital audio) interface:

One digital value is called sample. More samples are collected to frames (frame is terminology for ALSA) depending on count of converters used at one specific time. One frame might contain one sample (when only one converter is used - mono) or more samples (for example: stereo has signals from two converters recorded at same time). Digital audio stream contains collection of frames recorded at boundaries of continuous time periods.

General overview
ALSA uses the ring buffer to store outgoing (playback) and incoming (capture, record) samples. There are two pointers being maintained to allow a precise communication between application and device pointing to current processed sample by hardware and last processed sample by application. The modern audio chips allow to program the transfer time periods. It means that the stream of samples is divided to small chunks. Device acknowledges to application when the transfer of a chunk is complete.

Transfer methods in UNIX environments
In the UNIX environment, data chunk acknowledges are received via standard I/O calls or event waiting routines (poll or select function). To accomplish this list, the asynchronous notification of acknowledges should be listed here. The ALSA implementation for these methods is described in the ALSA transfers section.
...
ALSA transfers
There are two methods to transfer samples in application. The first method is the standard read / write one. The second method, uses the direct audio buffer to communicate with the device while ALSA library manages this space itself. You can find examples of all communication schemes for playback in Sine-wave generator example. To complete the list, we should note that snd_pcm_wait() function contains embedded poll waiting implementation.

And from HowTo Asynchronous Playback - ALSA wiki:

snd_pcm_hw_params_set_access is used to set the transfer mode I've been talking about at the start of this document. There are two types of transfer modes:

    * Regular - using the snd_pcm_write* functions
    * Mmap'd - writing directly to a memory pointer

Besides this, there are also two ways to represent the data transfered, interleaved and non-interleaved. If the stream you're playing is mono, this won't make a difference. In all other cases, interleaved means the data is transfered in individual frames, where each frame is composed of a single sample from each channel. Non-interleaved means data is transfered in periods, where each period is composed of a chunk of samples from each channel.

To visualize the case above, where we have a 16-bit stereo sound stream:

    * interleaved would look like:	LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR LL RR ...
    * non-interleaved might look like:	LL LL LL LL LL RR RR RR RR RR LL LL LL LL LL RR RR RR RR RR ...

where each character represents a byte in the buffer, and padding should of course be ignored (it's just for clarity).

Note that I emphasized 'might' in the non-interleaved case. The size of the chunks depends on the period size hardware parameter, which you can adjust using snd_pcm_hw_params_set_period_size. But in most cases, you want interleaved access.


So, given that we have SNDRV_PCM_INFO_MMAP_VALID in our _pcm_hw struct, and we never use snd_pcm_write* functions (but instead we use say memcpy to transfer data) - it would be safe to say that in minivosc, the mmap transfer mode is being used.


 

[edit] dummy.c revisited

Some documentation for it can be found on Matrix:Module-dummy - AlsaProject, where it is described as:

This driver provides up to 4 devices with up to 16 substreams. It uses a timer to sink and generate data. Useful for initial testing of an ALSA installation.

Some more information on (alsa-devel) Problem in dummy driver:

> I am working on the dummy driver provided with the ALSA. I took it from
> the linux kernel 2.6.20.1
> I build the module and load it. The XMMS seems to play ok (doesn't hang
> and all) but none of
> the recording application seem to record from the driver.
>
> Can the driver in current state work as the loop back cable between
> applications?

No, it's really dummy driver which eats playback samples and returns zero
samples for capture. Try use the snd-aloop driver.

And in Alsa-sound-mini-HOWTO: How to install ALSA sound drivers:

The great thing is: you don't need a supported sound card anymore, as ALSA now has a dummy driver that does nothing! (No, it really does nothing, but some programs will work now that they believe there is a sound card available).

If we look again at the dummy.c driver, and we try to apply the same approach as in minivosc - that is, we simply try to copy bytes into substream->runtime->dma_area right before snd_pcm_period_elapsed is called - we will experience SEVERE crashes/freezes. The reason for this is a variable fake_buffers being set to 1 - in which case, the dma_area is, in fact, not allocated at all!

Therefore, the patch below shows some minimal changes that need to be implemented on dummy.c; so that a few bytes at the beginning of PCM buffer are written during capturing (which results with pulses at PCM buffer boundaries in the captured audio):

--- dummy-orig.c	2010-07-20 22:15:36.086589147 +0200
+++ dummy.c	2010-07-20 22:21:23.469990765 +0200
@@ -18,6 +18,14 @@
  *
  */

+
+static int debug = 1;
+/* Use our own dbg macro */
+#undef dbg
+#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg); } while (0)
+
+
+
 #include <linux/init.h>
 #include <linux/err.h>
 #include <linux/platform_device.h>
@@ -154,7 +162,12 @@
 #ifdef CONFIG_HIGH_RES_TIMERS
 static int hrtimer = 1;
 #endif
-static int fake_buffer = 1;
+
+//static int fake_buffer = 1;
+// NOTE: IF WE INTEND TO WRITE TO
+// DMA_AREA, fake_buffer CANNOT BE 1
+// ELSE VERY SERIOUS CRASHES HAPPEN
+static int fake_buffer = 0;

 module_param_array(index, int, NULL, 0444);
 MODULE_PARM_DESC(index, "Index value for dummy soundcard.");
@@ -355,14 +372,26 @@
 static void dummy_hrtimer_pcm_elapsed(unsigned long priv)
 {
 	struct dummy_hrtimer_pcm *dpcm = (struct dummy_hrtimer_pcm *)priv;
-	if (atomic_read(&dpcm->running))
+
+	if (atomic_read(&dpcm->running)) {
+		// we should write into the buffer right before snd_pcm_period_elapsed? probably yes... and probably only if dpcm->running...
+		// trying to write the value 230 (e6) four times
+		// note that this works in arecord without specifying format (which defaults to 8KHz, 8bit);
+		// but arecord completely messes the buffers up, for say, stereo 16 bit 44.1 KHz (as in audacity - although sometimes even audacity gives OK pulses, as long as one makes sure to reclick to select correct input).
+		if (dpcm->substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			memset(dpcm->substream->runtime->dma_area, 230, 4);
+
+
 		snd_pcm_period_elapsed(dpcm->substream);
+	}
 }

 static enum hrtimer_restart dummy_hrtimer_callback(struct hrtimer *timer)
 {
 	struct dummy_hrtimer_pcm *dpcm;

+	dbg("%s: dummy_hrtimer_callback", __func__);
+
 	dpcm = container_of(timer, struct dummy_hrtimer_pcm, timer);
 	if (!atomic_read(&dpcm->running))
 		return HRTIMER_NORESTART;
@@ -547,11 +580,12 @@
 		dummy->timer_ops = &dummy_hrtimer_ops;
 #endif

-	err = dummy->timer_ops->create(substream);
+	err = dummy->timer_ops->create(substream); // this calls dummy_hrtimer_create, where dpcm->substream is set to substream
 	if (err < 0)
 		return err;

 	runtime->hw = dummy_pcm_hardware;
+	//dpcm->substream = substream; // already done in _create
 	if (substream->pcm->device & 1) {
 		runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
 		runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
@@ -1024,7 +1061,8 @@
 	.resume		= snd_dummy_resume,
 #endif
 	.driver		= {
-		.name	= SND_DUMMY_DRIVER
+		.name	= SND_DUMMY_DRIVER,
+		.owner = THIS_MODULE
 	},
 };

Let's just note that in this case, we use memset to write 4 bytes at the beginning of the PCM buffers; if we request (say via arecord) a 8-bit mono stream, then we will see four samples in the captured audio; if we asked for a floating point (32 bit) mono stream, then we will see a single sample in the captured audio (which makes sense, since a float is usually encoded using 4 bytes, that is - sizeof(float) is 4).


 

[edit] Debugging

One of the most problematic things in driver development is their debugging as kernel modules - and especially problematic are errors such as memsetting a null pointer (which is what happens, if we try to write to dma_area in dummy.c, while fake_buffers = 1). In such a case, the computer freezes, without time to generate printk kernel debug messages in /var/log/syslog - and the only way out from such a freeze is a hard reboot (power off and on).

In such a case, pretty much the first thing that pops to mind is to step the code in a debugger and identify the offending line. However, since in case of drivers we are talking about kernel modules (not userspace programs), the procedure for debugging them is not trivial.

Fortunately, there is a kernel debugger built into the Linux kernel since version 2.6.26, known as kgdb light. What this means is that, while for earlier kernels this functionality required recompiling the kernel - for kernels newer than 2.6.26, we can simply add arguments like

kgdboc=ttyS0 kgdbwait

to the GRUB boot entry for the operating system - and then when the OS boots, instead of loading the desktop etc., the boot process will in fact halt, and wait for a signal from the GNU debugger gdb. This signal needs to be delivered through a serial connection - and so, debugging a kernel using kgdb assumes having a second machine that will run gdb for debugging, connected to the machine that runs the kernel / module to be debugged via serial cable.

Notably, since newer PCs don't even have a real RS-232 serial port, the only remaining approach to debugging with kgdb is on a single PC, through usage of a virtual machine. Here VirtualBox OSE was used (although in principle also Qemu or KVM could be used, since the Intel Atom processor used here does not support hardware virtualization, VirtualBox is as good as any), a virtual hard drive created from it, and Ubuntu 10.04 command line version was installed using the Ubuntu minimal CD image. VirtualBox can then be set up in its Settings / Serial Ports to: 'Enable Serial Port', and 'Create Pipe', where 'Port/File Path' would be a file like /tmp/vboxpipe.

Then, after adding 'kgdboc=ttyS0 kgdbwait' as GRUB2 boot options to the virtual image OS installation, we can boot the virtual image; after a while the booting process starts, and should show:
kgdb: Waiting for connection from remote gdb.

Then, in the host OS environment, in one terminal we can run
$ socat UNIX-CONNECT:/tmp/vboxpipe TCP-LISTEN:8040

and in another:

$ gdb /path/to/linux-2.6.32/vmlinux
..Reading symbols from /path/to/linux-2.6.32/vmlinux...done.
(gdb) target remote 127.0.0.1:8040

Remote debugging using 127.0.0.1:8040
kgdb_breakpoint (new_kgdb_io_ops=0xc0747f18) at kernel/kgdb.c:1721
1721		wmb(); /* Sync point after breakpoint */
(gdb) continue

after which control is passed from gdb to the kernel running in the virtual image, and the virtual kernel image completes booting. After that, breakpoints can be made by running:

$ echo g | sudo tee /proc/sysrq-trigger

at the virtual image bash prompt; or by using

#define BREAKPOINT() asm("   int $3");

in the driver kernel module code, and then calling BREAKPOINT(); wherever in the driver code we want. Obviously, if we have a breakpoint in, say, "_prepare" function, we first insmod the driver module in the VM image OS, and then should call arecord in the VM so that the driver is activated there.

Note that the vmlinux file used in the gdb call above is, in fact, the symbol file for the kernel; and the only way to obtain it is to rebuild the kernel. So although you don't need to rebuild the kernel simply to be able to break into gdb, you must rebuild the kernel in order to obtain the symbol file, and be able to step through source - without a symbol file, the gdb session above would look like:

$ gdb
(gdb) target remote 127.0.0.1:8040
Remote debugging using 127.0.0.1:8040
0xc019de6e in ?? ()
(gdb) continue

Of course, after you rebuild your kernel (it should automatically be set to generate debug symbols), you should also install your new debug kernel in the VM OS - and set it to boot by default, with the kgdboc=ttyS0 kgdbwait options appended to its boot entry.

Finally, since the driver modules we're working with here are not built as part of the kernel, gdb will need their symbol files as well. As such, it is best to built the driver modules within the virtual machine OS, and then copy the .o file to the normal file system so it is available to gdb. Here's how a sample session might look like (assuming 192.168.1.15 is the 'real' IP address of the host OS):

VM# cd /path/to/minivosc-src
VM# make
VM# scp snd-minivosc.o 192.168.1.15:~
VM# sudo insmod ./snd-minivosc.ko
VM# cat /sys/module/snd_minivosc/sections/.text
0xd8b51000
VM# echo g | sudo tee /proc/sysrq-trigger

(gdb) add-symbol-file ~/snd-minivosc.o 0xd8b51000
(gdb) continue

VM# arecord ...

Note that for correct stepping within gdb, the source files should be at the same path in both the virtual image OS and the host OS - so, if the source files for the kernel module are in /path/to/minivosc-src in the VM filesystem, the same directory should exist (and have the same source files) in the host filesystem as well. Also, a debug build should be enabled in the Makefile for the driver module (and it is so already for minivosc).

Finally, once freezes are not an issue anymore, one can simply use printk command throughout the driver module code - no VM image needed; the output of printk can be found in /var/log/syslog or /var/log/messages under Ubuntu 10.04. The minivosc driver code is by default set with these messages enabled, and they can be followed by running, say,

tail -f /var/log/syslog

in a terminal.

Here are some more resources dealing with debugging the kernel:



»»

Custom Search
Personal tools
Namespaces

Variants
Actions
Navigation
wiki
Toolbox