...making Linux just a little more fun!
By Maxin B. John and Rajith R.
This article is dedicated to those who wish to be unconventional. Linux supports most of the popular sound cards. Even if you don't have a sound card, you can still get sound support (perhaps not all that ear soothing!) from the Parallel port in your PC. In this article, we will discuss one of the many ways to obtain sound output without a sound card.
We are using a 1.6GHZ Pentium 4. The Linux distribution on this box is PCQLinux 2004, which is based on Fedora. In the hardware part we have used some resistors, a Parallel port connector and wires to interconnect all this hardware.
The sound driver is a character device usually denoted as "/dev/dsp". All sound applications such as mpg123, Mplayer, etc., direct their digital output to this device. The DAC (Digital to Analog Converter) used here is the Parallel port. This port is the gadget that converts the 1s and 0s that make up the binary numbers into real analog time-varying voltage which will be connected to the speakers of our Computer.
There are two main types of devices under all Linux systems: character and block devices. Character devices are those for which no buffering is performed, and block devices are those which are accessed through a cache. Block devices must be random access, but character devices are not required to be, though some are. Filesystems can only be mounted if they are on block devices.
The sources for character devices are kept in drivers/char/, and the sources for block devices are kept in drivers/block/. They have similar interfaces, and are very much alike, except for reading and writing. Because of the difference in reading and writing, initialization is different, as block devices have to register a strategy routine, which is registered in a different way than the parsp_read and parsp_write routines of a character device driver.
Now if you have a .wav file which is of a specific format, say 16-bit,
stereo, raw pcm, to make it play on the system sound device you might open
the /dev/dsp node using the open()
system call, and
open your .wav file, read a block of data from the .wav file, and write it
to the /dev/dsp node using read()
and
write()
system calls respectively.
The ability to unload a module is one of the most useful features of modularization because it helps cut down development time; you can test successive versions of your new driver without going through the lengthy shutdown/reboot cycle each time.
The compilation of a kernel module is performed by the gcc compiler using the make file, which contains:
gcc -c &1 -O2 -Wall -DMODULE -D__KERNEL__ -I /usr/src/linux(your kernel version)
The output of the compilation is a module which is to be inserted into the kernel to produce the sound. Our driver, called parsp.o, can be loaded into the running kernel using:
insmod -f parsp.o
and removed from the kernel using:
rmmod parsp
The install.sh file contains some magic to redirect all the sound applications to our driver. Usually the applications use the /dev/dsp device, which has a major number 14, to produce the sound output. What we are going to do first is delete that special file. Then we replace the same file with a major number that is specified in our driver.
./install.sh 203
Note that 203 is the major number specified in our driver - so all the applications that use /dev/dsp, such as mpg123, Mplayer, XMMS, etc. will now use our driver to produce output.
But we know that we cannot forget our past - so, in order to restore our previous state, we would simply run uninstall.sh. The uninstall.sh script restores the previous state by deleting the /dev/dsp created by the install.sh script and recreating the original /dev/dsp which had the major number 14 and minor number 3.
mknod /dev/dsp c 14 3
Don't forget to reconnect the speaker plug to the output jack of the sound card of your system. (We had this experience three or four times and wondered why there was no sound from the original soundcard of our system!!)
The ioctl()
(short for input/output control) system call
is used on /dev/dsp to talk to the device driver. There are
recognized conventions in Linux, the most popular of which is the OSS or
Open Sound System. This is the standard interface implemented in Linux and
followed by thousands of device drivers on the kernel side.
The ioctl()
implementation was the most tedious thing in this
project. The list of all ioctls for the soundcard can obtain from
souncard.h or by using the command:
man ioctl_list
We tried our level best to include the ioctls required to play the music files through various music players. Of these various music players, Mplayer was the most demanding one. Although we can play music using Mplayer, support for it is not perfect at the moment.
The Standard Parallel Port of your system is a 25 pin port. The signals available at the parallel port are 0v, which represents logical 0, and +5v, which represents logical 1. This port is the simplest port in your system by which you can control a large number of devices.
Although the parallel port contains 25 pins, we'll focus our attention mainly on the 8 data out pins (pin numbers 2-9) and a ground pin (any pin from 18-25) that we'll use for our purpose.
The usual way of using the parallel port is by using the good old
outb()
instruction. In our driver, the bytes are written to
the parallel port using
outb(b, 0x378);
So don't go anywhere, just sit in front of the computer and play something nice with the parallel port of your computer. But don't blame us if you end up frying up your computer - just be sensible and things will go smoothly...
We have explained earlier, the bit 1 is represented by 5v and bit 0 is represented by 0v at the parallel port. The magic performed to create these intermediate voltage levels is done by the resistor circuitry connected to the parallel port output. These varying voltage levels in turn generate the audible output when connected to the input of your speaker system. When we experimented with the circuit using the code given below with the help of a multimeter, we got the following results:
(Text Version: test.c.txt)
#include<asm/io.h> main() { iopl(3); int b; printf("Enter the value of b:\n"); scanf("%d",&b); outb(b,0x378); }
Don't forget to compile it using the optimization option, i.e.
cc filename.c -O2
For b=0 Output voltage = 0v For b=255 Ourput voltage = 3.8v For b=240 Output voltage = 2.1v
The values are obtained across the ground of the port and output of the resistor circuit (1 and 2 connected together). This may vary from system to system, so don't worry if you don't get those exact values. Quality of the output can be increased by using other complex circuits such as amplifiers and filters. We are not going to explain that here as it will increase the cost of the hardware.
The code that performs the core function is present in the
pcsp_write()
.
count= (count < 44100 ? count : 44100); if(copy_from_user((void *)data_buffer,buf,count)) return -EFAULT; canplay =1; for(v=0;v<count;v++) //loops till the end of the buffer { b=buf[v]; outb(b,0x378); // Writes those bytes to the parallel port for(i=0;i<loop;i++); //loops to adjust the speed of playback }
At first, a music player such as mpg123 opens the /dev/dsp
device via pcsp_open()
and gathers information on its
characteristics (i.e., buffer size, mono or stereo, number of channels,
etc.) using parsp_ioctl()
. After that, it writes
manageable-sized chunks of information into the buffer of the device
driver. The bytes in the buffer are then transferred to the parallel port:
outb(b, 0x378);
The next loop is significant, since it controls the playback speed.
for(i=0;i<loop;i++);
This value of this loop must be changed to suit the processor speed of
each system. This process continues until the end of the music file. At that
point the count becomes zero and the music player software closes the
device by calling parsp_close()
.
In the init_module()
section, we initialize the variables
used in the code. The module is registered by:
major=register_chrdev(major,name,&parsp_fops);
The memory for the buffer used in the driver is allocated by:
data_buffer= (long) kmalloc(44100, GFP_KERNEL);
In cleanup_module()
, the allocated memory for the driver
is freed by:
kfree((void *) data_buffer);
The module is removed from the kernel by:
unregister_chrdev(major,name);
parsp.c make install.sh uninstall.sh
The connection to the speaker plug is as shown in the figure. The resistors used here are 1k ohm, 1/4 watt resistors. (If these exact values are not available, try something that's reasonably close; the values are not critical).
After all this painful and time consuming assembly of our device, let's enjoy the fruit that our hard work has brought us. The Linux way of testing the device driver is by using the 'cat' command:
cat ding.wav > /dev/dsp
Do you hear something? Yeah... You will... Now, let us go to the next level. We will try to play a mp3 file by using mpg123
mpg123 dingdong.mp3
and enjoy that heavenly music! Try some other players such as Mplayer, XMMS, etc.
Our adventures with the parallel port will not stop here. A brave new idea is to create and control a mini-fountain right in the living room which will dance with the music played on the computer!
Please let us know your crazy ideas using the parallel port.
We have tried to present a few things about using the parallel port of your PC in an interesting way; this module is software that makes good use of it. Be warned, though -- you may get addicted to the "digital noise" produced by this software.
We will be grateful to readers who point out errors/inconsistencies, if any. We look forward to hearing from you soon!
A "must read" book for kernel development is Linux Kernel Development by Robert Love.
The article that inspired this adventure was "Creating a Kernel Driver for the PC Speaker" by Cherry George Mathew.
Maxin B. John works for HCL Infosystems Ltd, Pondicherry and is an MCA
graduate from Govt. Engg. College, Thrissur. He likes to experiment with Python
and Bash. He thanks his guru Mr. Pramode C.E for introducing him to the
wonderful world of Linux.
I am a Linux user from India. I love the freedom that Linux gives to its
users. I must thank my mentor Mr. Pramode C.E for introducing me to the
the wonderful world of Linux.