dcsimg

Writing Radio Card Drivers for Linux

Video4Linux is the Linux 2.2 interface to TV- and radio- related cards. Prior kernels had a jumble of incompatible add-on modules. This made it very hard to write a general-purpose video application for Linux. A wide variety of radio interfaces are available for PCs, and these are generally very simple to program. Usually, the biggest problem with supporting such devices is extracting documentation from the vendor.

Video4Linux is the Linux 2.2 interface to TV- and radio- related cards. Prior kernels had a jumble of incompatible add-on modules. This made it very hard to write a general-purpose video application for Linux. A wide variety of radio interfaces are available for PCs, and these are generally very simple to program. Usually, the biggest problem with supporting such devices is extracting documentation from the vendor.

Radio Devices

A radio interface supports a simple set of control ioctl() system calls standardized across all radio and TV interfaces. It does not support read or write, however, which are used for video streams.

The reason radio cards do not allow you to read the audio stream into an application is that, without exception, these cards provide a connection to a sound card, and sound cards can be used to read the radio data just fine. Therefore, you only need the ioctl system calls to control the radio tuner itself. The Video4Linux core provides an interface for registering devices. The first step in writing our radio card driver is to register it.


static struct video_device my_radio
{
“My radio”,
VID_TYPE_TUNER,
VID_HARDWARE_MYRADIO,
radio_open.
radio_close,
NULL /* no read */
NULL, /* no write */
NULL, /* no poll */
radio_ioctl,
NULL, /* no special init function */
NULL /* no private data */
};

This declares our Video4Linux device-driver interface. The VID_TYPE_ value defines what kind of an interface we are, which specifies the basic capabilities of our driver.

The only defined value that is relevant for a radio card is VID_TYPE_TUNER, which indicates that the device can be tuned. Clearly our radio is going to be tunable.




Video4Linux Driver Types


VFL_TYPE_RADIO /dev/radio{n}t


Radio devices are assigned in this block. As with all of these values, the actual device number assignment is done by the Video4Linux layer according to what is free.


VFL_TYPE_GRABBER /dev/video{n}


Video-capture devices and also — counterintuitively for the name — hardware video-playback devices such as MPEG2 cards.


VFL_TYPE_VBI /dev/vbi{n}


The VBI devices capture the hidden lines on a television picture that carry further information like closed-caption data, Teletext (primarily in Europe), and now Intercast and the ATVEC Internet television encodings.


VFL_TYPE_VTX /dev/vtx{n}


VTX is “Videotext,” also known as “Teletext.” This is a system for sending numbered, 40×25, mostly textual page images over the hidden lines. Unlike the /dev/vbi interfaces, this is for “smart” decoder chips. (The use of the word smart here has to be taken in context; the smartest teletext chips are fairly dumb pieces of technology).

The VID_HARDWARE_ types are unique to each device. Numbers are assigned by sending e-mail to alan@redhat.com when device drivers are going to be released. While you are initially developing your driver, however, you can pull a suitably large number out of your hat and use it. 10,000 should be safe for a very long time, even allowing for the huge number of vendors making new and different radio cards.

We declare an open and close routine, but we don’t need read or write, as explained above. Because of this, poll is not needed either.

The private initialize function, if supplied, is run when the device is registered. In this driver, we’ve already done all the work needed, so no special initialization routine is necessary. The last entry of this structure is a pointer to a private data structure that can be used by the device driver for internal use. We set this field to NULL for the moment.

Having the structure defined is all very well but, as you can see in Listing One, we now need to register it with the kernel. The first stage of the initialization is to check that the I/O space we are about to fiddle with doesn’t belong to some other driver. If it does, we leave it alone. If the user gives the address of the wrong device then we will spot this. These policies will generally avoid crashing the machine.




Listing One: Initializing the Device


static int io = 0×320;

int __init myradio_init(struct video_init *v)
{
if(check_region(io, MY_IO_SIZE))
{
printk(KERN_ERR “myradio: port 0x%03X is in use.\n”, io);
return -EBUSY;
}

if(video_device_register(&my_radio, VFL_TYPE_RADIO)==-1)
return -EINVAL;
request_region(io, MY_IO_SIZE, “myradio”);
return 0;
}

Now we ask the Video4Linux layer to register the device for us. We hand it our carefully designed video_device structure and tell it which group of devices we want our driver registered with. In this case VFL_TYPE_RADIO is the appropriate value. You can have a look at the other available driver types in the Video4Linux Driver Types sidebar. Finally we allocate our I/O space so that nobody treads on us and return 0 to signify general happiness.

The functions we declared in our video_device structure are mostly very simple. First, let’s drop in the standard code for open and close.


static int users = 0;

static int radio_open(struct video_device
*dev, int flags)
{
if(users)
return -EBUSY;
users++;
MOD_INC_USE_COUNT;
return 0;
}

At open time we need to do nothing but check if someone else is also using the radio card. If nobody is using it we make a note that we are using it, thereby ensuring that nobody unloads our driver on us.


static int radio_close(struct video_device
*dev)
{
users –;
MOD_DEC_USE_COUNT;
}

At close time we simply need to reduce the user count and allow the module to become unloadable.

The ioctl Routine

If you are sharp you will have noticed that neither the open nor the close routines attempt to reset or change the radio settings. This is done intentionally. It allows an application to set up the radio and exit. It avoids forcing a user to leave an application running all the time just to be able to listen to the radio.

Now let’s write the ioctl routine, without which the driver will not be terribly useful to anyone (see Listing Two).




Listing Two: Beginning the iotcl Routine


static int radio_ioctl(struct video_device *dev,
unsigned int cmd, void *arg)
{
switch(cmd)
{
case VIDIOCGCAP:
{
struct video_capability v;
v.type = VID_TYPE_TUNER;
v.channels = 1;
v.audios = 1;
v.maxwidth = 0;
v.minwidth = 0;
v.maxheight = 0;
v.minheight = 0;
strcpy(v.name, “My Radio”);
if(copy_to_user(arg, &v, sizeof(v)))
return -EFAULT;
return 0;
}

VIDIOCGCAP is the first ioctl all Video4Linux devices must support. It allows applications to find out what sort of a card they have found and to figure out what they want to do about it. The fields in the structure are:

name: The device text name. This is intended for the user.

channels: The number of different channels (or radio bands) you can tune on this card. It could even be 0 for a card that has no tuning capability. For our simple FM radio it is 1. An AM/FM radio would report 2.

audios: The number of audio inputs on this device. For our radio there is only one audio input.

minwidth, minheight: The smallest size the card is capable of capturing images in. We set these to 0, since radios do not capture pictures.

maxwidth, maxheight: The largest image size the card is capable of capturing. For our radio we report 0.

type: This reports the capabilities of the device and matches the field we filled in the struct video_device when registering.

Having filled in the fields, we use copy_to_ user to copy the structure into the user’s buffer. If the copy fails we return an EFAULT to the application so that it knows it tried to feed us a bad pointer.

The next pair of ioctl operations select which tuner is to be used and let the application find the tuner properties. We have only a single FM band tuner in our example device (Listing Three).




Listing Three: Querying the Tuner


case VIDIOCGTUNER:
{
struct video_tuner v;
if(copy_from_user(&v, arg, sizeof(v))!=0)
return -EFAULT;
if(v.tuner)
return -EINVAL;
v.rangelow=(87*16000);
v.rangehigh=(108*16000);
v.flags = VIDEO_TUNER_LOW;
v.mode = VIDEO_MODE_AUTO;
v.signal = 0xFFFF;
strcpy(v.name, “FM”);
if(copy_to_user(&v, arg, sizeof(v))!=0)
return -EFAULT;
return 0;
}

The VIDIOCGTUNER ioctl allows applications to query a tuner. The application sets the tuner field to the tuner number it wishes to query. The query does not change the tuner that is being used; it merely inquires about the tuner in question.

We have exactly one tuner, so after copying the user buffer to our temporary structure we complain if they asked for a tuner other than tuner 0.

The video_tuner structure has the following fields:

int tuner: The number of the tuner in question

char name[32]: A text description of this tuner. Using “FM” will do fine. This is intended for the application.

u32 flags: Information on the tuner’s capabilities, which can take the following values:

* VIDEO_TUNER_PAL: A PAL TV tuner

* VIDEO_TUNER_NTSC: An NTSC (US) TV tuner

* VIDEO_TUNER_SECAM:A SECAM (French) TV tuner

* VIDEO_TUNER_LOW: When this bit is set, the tuner frequency is scaled in 1/16-of-a-KHz steps. If unset, it is scaled in 1/16-of-a-MHz steps.

* VIDEO_TUNER_NORM: Indicates that the tuner can set its video input format.

* VIDEO_TUNER_STEREO_ON: Indicates that the tuner is currently receiving a stereo signal.

u16 mode: The current reception mode, which can take the following values:

* VIDEO_MODE_PAL,

* VIDEO_MODE_NTSC,

* VIDEO_MODE_SECAM: The various TV video formats

* VIDEO_MODE_AUTO: A device that does not need to do TV format switching

u16 signal: The signal strength scaled between 0 and 65535. If a device cannot tell the signal strength it should report 65535 (or 0xFFFF in hex). Many simple cards contain only a signal/no signal bit. Such cards will report either 0 or 65535.

u32 rangelow, rangehigh: The range of frequencies supported by the radio or TV. It is scaled according to the VIDEO_ TUNER_LOW flag.

The settings for the radio card are thus fairly simple. We report that we are a tuner called FM, for FM radio. In order to get the best tuning resolution, we report VIDEO_TUNER_ LOW and select tuning to 1/16 of a KHz. Its unlikely our card can do that resolution, but it is a fair bet the card can do better than 1/16 of a MHz. VIDEO_TUNER_LOW is appropriate for almost all radio tuners.

We report that the tuner automatically handles deciding what format it is receiving — with our card, this is true enough since it handles only FM radio. Our example card is also incapable of detecting stereo or signal strengths, so it reports a strength of 0xFFFF (maximum) and no stereo detected.

To finish off, we set the range that can be tuned to be 87-108 Mhz, the normal FM broadcast radio range. It is important to find out what the card is actually capable of tuning. It is easy enough simply to use the FM broadcast range. Unfortunately if you do this you will discover the FM broadcast ranges in the USA, Europe, and Japan are all subtly different, and some users cannot receive all the stations they wish.

The application also needs to be able to set the tuner it wishes to use. In our case, with a single tuner this is rather simple to arrange:


 case VIDIOCSTUNER:
{
struct video_tuner v;
if(copy_from_user(&v, arg, sizeof(v)))
return -EFAULT;
if(v.tuner != 0)
return -EINVAL;
return 0;
}

We copy the user-supplied structure into kernel memory so that we can examine it. If the user has selected a tuner other than tuner 0 we reject the request. If they wanted tuner 0 then, surprisingly enough, that is the current tuner already.

Querying and Setting the Frequency

The next two ioctls (Listing Four and below) we need to provide are to get and set the frequency of the radio. These both use an unsigned long argument that is the frequency. Remember, I mentioned earlier that the scale of the frequency is determined according to the VIDEO_TUNER_LOW flag.


 static unsigned long current_freq; /*
Defined elsewhere */

case VIDIOCGFREQ:
if(copy_to_user(arg, &current_freq,
sizeof(unsigned long))
return -EFAULT;
return 0;

Querying the frequency in our case is relatively simple. Our radio card is too dumb to let us query the signal strength, so we remember our setting if we know it. All we have to do is copy it to the user.


case VIDIOCSFREQ:
{
u32 freq;
if(copy_from_user(arg, &freq,
sizeof(unsigned long))!=0)
return -EFAULT;
if(hardware_set_freq(freq)<0)
return -EINVAL;
current_freq = freq;
return 0;
}

Setting the frequency is a little more complex. We begin by copying the desired frequency into kernel space. Next we call a hardware-specific routine to set the radio up. This might be as simple as some scaling and a few writes to an I/O port. For most radio cards it turns out to be a good deal more complicated and may involve programming things like a phase locked loop on the card. This is what hardware documentation is for.

The final set of operations we need to provide for our radio are the volume controls. Not all radio cards can even do volume control. After all, there is a perfectly good volume control on the sound card. We will assume our radio card has a simple four-step volume control.

In Listing Four (pg. 62), there are two ioctls with audio that we need to support. Much like with our tuner example, we begin by copying the user structure into kernel space. Again we check if the user has asked for a valid audio input. We have input only 0 and we punt if they ask for another input.

Then we fill in the video_audio structure. This has the following format:

audio: The input the user wishes to query

volume: The volume setting on a scale of 0-65535

bass: The base level on a scale of 0-65535

treble: The treble level on a scale of 0-65535

flags: The features this audio device supports, which can take the following values:

* VIDEO_AUDIO_MUTE: The audio is currently muted. We assume that our hardware does not support muting, so we fake this in our in our driver by remembering the value of the current_muted flag.

* VIDEO_AUDIO_MUTABLE: The input has a mute option.

* VIDEO_AUDIO_TREBLE: The input has a treble control.

* VIDEO_AUDIO_BASS: The input has a base control.

name: A text name to display to the user. We picked “Radio” since it explains things quite nicely.

mode: The current reception mode for the audio, which is one of the following values:

* VIDEO_SOUND_MONO: Mono sound

* VIDEO_SOUND_STEREO: Stereo sound

* VIDEO_SOUND_LANG1: Alternative language 1 (TV specific)

* VIDEO_SOUND_LANG2: Alternative language 2 (TV specific)

We report mono because our card is too stupid to know if it is in mono or stereo.

balance: The stereo balance on a scale of 0-65535. 32768 is the middle value.

step: The step by which the volume control jumps. This is used to help make it easy for applications to set slider behavior.




Listing Four: Setting Volume Controls


 static int current_volume=0; /* Defined elsewhere */
static int current_muted=0; /* Defined elsewhere */

case VIDIOCGAUDIO:
{
struct video_audio v;
if(copy_from_user(&v, arg, sizeof(v)))
return -EFAULT;
if(v.audio != 0)
return -EINVAL;
v.volume = 16384*current_volume;
v.step = 16384;
strcpy(v.name, “Radio”);
v.mode = VIDEO_SOUND_MONO;
v.balance = 0;
v.base = 0;
v.treble = 0;
v.flags = VIDEO_AUDIO_MUTABLE;
if (current_muted)
v.flags |= VIDEO_AUDIO_MUTE;

if(copy_to_user(arg.&v, sizeof(v)))
return -EFAULT;
return 0;
}

Having filled in the structure, we copy it back to user space.

The VIDIOCSAUDIOioctl allows the user to set the audio parameters in the video_audio structure. The driver does its best to honor the request.


case VIDIOCSAUDIO:
{
struct video_audio v;
if(copy_from_user(&v, arg,
sizeof(v)))
return -EFAULT;
if(v.audio)
return -EINVAL;
current_volume = v.volume;
if(v.flags&VIDEO_AUDIO_MUTE) {
hardware_set_volume(0);
current_muted = 1;
} else {
hardware_set_volume
(current_volume);
current_muted = 0;
}
return 0;
}

In our case there is very little that the user can set. The volume is basically all we allow. Note that we pretend to have a mute feature by using hardware_set_volume(0) for muting. It is questionable whether this is a good idea however. User applications can already fake this themselves, and kernel space is precious.

Final Touches

We now have a working radio ioctl handler. So we just wrap up the function with the following code:


    }
return -ENOIOCTLCMD;
}

which returns an error code to the Video4Linux layer in case an unknown ioctl request was attempted.

Finally, we add in the usual module wrapping, and the driver is done.


#ifndef MODULE

static int io = 0×300;

#else
static int io = -1;
MODULE_AUTHOR(“Alan Cox”);
MODULE_DESCRIPTION(“A driver for an
imaginary radio card.”);
MODULE_PARM(io, “i”);
MODULE_PARM_DESC(io, “I/O address of the
card.”);

EXPORT_NO_SYMBOLS;

int init_module(void)
{
if(io==-1)
{
printk(KERN_ERR “You must set
an I/O address with
io=0x???\n”);
return -EINVAL;
}
return myradio_init(NULL);
}

void cleanup_module(void)
{

video_unregister_device(&my_radio);
release_region(io, MY_IO_SIZE);
}

#endif

In this example we set the IO base by default if the driver is compiled into the kernel where you cannot pass a parameter. For the module we require the user to set the parameter (e.g., by using the option io=0xNNNN on the insmod command line). We initialize io to a nonsense port (-1) so that we can tell if the user supplied an io parameter or not.

We use MODULE_ defines to specify an author for the card driver and a description. We also use them to declare that io is an integer and is the address of the card.

The clean-up routine unregisters the video_device
we registered and frees up the I/O space. Note that the unregister call takes the actual video_devicestructure as its argument. Unlike the file operations structure, which can be shared by all instances of a device, a video_device structure is an actual instance of the device. If you are registering multiple radio devices you need to fill in one structure per device (most likely by setting up a template and copying it to each of the actual device structures).





Alan Cox is a well-known Linux hacker currently working on security auditing, Linux/SGI porting, and modular sound. He can be reached at alan@lxorguk.ukuu.org.uk.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62