| [Home] [Credit Search] [Category Browser] [Staff Roll Call] | The LINUX.COM Article Archive |
| Originally Published: Monday, 30 April 2001 | Author: Cherry George Mathew |
| Published to: featured_articles/Featured Articles | Page: 5/5 - [Printable] |
Creating a Kernel Driver for the PC Speaker
Take a jaunty look at the basics of driver development as Cherry
George Mathew guides us through the process of creating a driver for the PC
speaker under Linux.
|
|
| << Page 5 of 5 | |
One Last PointI guess, that's it, but before I run away satisfied that I've shoved Latin up a thousand throats, I want to clear a few things about the sample that I did in myaudio.x . Linux has a formal method to claim interrupts for device drivers. Trouble is, by the time a module is loaded the scheduler has already claimed the timer interrupt. So we have to hack a bit and steal it from the scheduler. That's why we had to discuss IDTs and stuff. I haven't implemented the OSS interface even though I elaborated it on a TODO list earlier. Nevertheless it's possible to listen to MP3 music with the following: As root user, chdir to the directory where you've copied the source files myaudio.c, myaudio.h, and myaudio.mak. make -f myaudio.mak do a mknod c 254 /dev/pcspeaker cat /proc/devices If you see an "Internal Speaker" entry, we're ready for some fun. Else forget about the whole thing. insmod myaudio.o mpg123 -m -r 22000 --8bit -w /dev/pcspeaker x.mp3 #this should playx.mp3 ENJOY!!!! My name is Cherry George Mathew. I'm a third year Electronics Engineering Under graduate student at College of Engineering, Adoor, Kerala, India. Any questions about this article may be sent to berry.plum@mailcity.com, which time permitting, I will try to answer. Note: All these operations need to be done as user root. So I'm assuming that you're using your own machine, and are ready to trash any part of it, maybe permanently. I cannot take any responsibility for what happened to your system because of my code, so I'll insist that you try it only at your own risk. myaudio.h #include <linux/module.h>
#include <linux/version.h>
#include <linux/linkage.h>
#include <linux/malloc.h>
#include <linux/soundcard.h>
#include <asm/io.h>
#include <asm/uaccess.h>
/* #include linux/kernel.h> */
/* #include include/vmalloc.h> */
/* Among many hazy assumptions, we decide that
- gcc compiler specific operand passing assumption for type
'long', calling proc cleans up stack
- Stack is push down, ie, grows to lower addresses
- 32bit far return does popl eip, popl cs(msw neglected) in that order
- 'leave' cleans up the stack...........................???
for a baby of an interrupt handler routine....... */
/*****************helper functions in use***************/
asmlinkage void myhandler(void);
static int fops_sync_output(struct inode *inode, struct file *file);
void sync_output();
long getvect(long vector_number);
long setvect(long vector_number, long new_handler_address);
int helper_get_pit_count(void);
void helper_set_pit_count(int count);
static void speaker_close(struct inode *inode, struct file *file);
static int speaker_open(struct inode *inode, struct file *file);
static ssize_t speaker_read(struct file *file, char *buf,
size_t count, loff_t * ppos);
static ssize_t speaker_write(struct file *file, const char *buf,
size_t count, loff_t * ppos);
static int speaker_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg);
/********************GLOBAL STRUCTURES ********************/
struct file_operations speaker_fops = {
NULL,
speaker_read,
speaker_write,
NULL,
NULL,
NULL, /* we'll support speaker_ioctl later,
*/
NULL,
speaker_open,
speaker_close,
fops_sync_output,
};
static char berio;
static long timer_idt_ptr, data_buffer, data_ptr;
static int sampling_factor, canplay, pit_counter, compensation_count,
temp_pit_counter, old_pit_counter, buffer_length, virgin,
device_major, no_sleep_just_try_again;
struct __stack_pad_tag {
long __stack_padding1, eax, __stack_padding2;
};
long setvect(long vector, long hook)
{
struct __stack_pad_tag stack_pad __attribute__ ((packed));
asm("sidt %3 \n
leal % 3, %%eax \ n
movl 2(%%eax), %%eax \ n leal(%%eax, %1, 8), %%ecx \ n /* ecx
is
the
'pointer
register'
for
int
vector
*/
movl 4(%%ecx), %%eax \ n
xorw %% ax, %%ax \ n movw(%%ecx), %%ax \ n
/* Remember , pointer to int handler is in eax, we've not
pushed it */
cli \ n
movw %% bx, (%%ecx) \ n
shrl $16, %%ebx \ n movw %% bx, 6(%%ecx) \ n
/* I'm under the assumption that eax can safely be returned
to pseudo register 'stackpad.eax' */
sti \ n " : " =
a " (stack_pad.eax) : " c " (vector)," m
" (stack_pad.__stack_padding1), " m " (stack_pad.eax), " m
" (stack_pad.__stack_padding2)," b " (hook));
return stack_pad.eax;}
long getvect(long vector) {
long eax, __stack_padding1, __stack_padding2;
asm("sidt %4 \n
leal % 4, %%eax \ n
movl 2(%%eax), %%eax \ n
leal(%%eax, %1, 8), %%ecx \ n
movl 4(%%ecx), %%eax \ n
xorw %% ax, %%ax \ n
movw(%%ecx), %%ax \ n " : " =
a " (eax) : " c " (vector)," m " (__stack_padding1), " m
" (eax), " m " (__stack_padding2) ); return eax;}
/* A little glitch in the stack padding-not to worry though,
* it works just fine...... (%4 vs %3 in setvect */
asmlinkage void myhandler(void) {
/* pushl %%ebp; movl %%esp,%%ebp */
asm volatile ("
leave
pushl timer_idt_ptr
pushal
pushf
cli
movw canplay, %cx
jcxz skip_isr
inb $0x61, %al orb $3, %al outb % al, $0x61
/* fetch data */
movl data_ptr, %ebx movw(%ebx), %dx
/* load mode register of pit */
movb $0xb0, %al
outb % al, $0x43
movw pit_counter, %ax
xorb % dh, %dh
mulw % dx movb $8, %cl shrw % cl, %ax
/* load counter 2 */
orw $1, %ax outb % al, $0x42
/* dump MSB into counter 2 assuming it to be zero */
xorb % al, %al outb % al, $0x42
/* increment data pointer */
/* movb berio, %cl
xorb %ch, %ch
jcxz data_ptrplus
add $1, data_ptr
*/
data_ptrplus:incl data_ptr
movl data_ptr, %eax
subl data_buffer, %eax
cmpw % ax, buffer_length jnc skip_isr /* We
don't
want
to
reset
count
everytime
*/
movw $0, canplay /* So just switch off
playback to
indicate end of
buffer */
skip_isr:decw temp_pit_counter
jz hop_to_kernel movb $0x20, %al /* EOI
to
PIC
*/
outb % al, $0x20 popf popal
/*subl $4, %esp /* readjust stack for iret */
pop timer_idt_ptr
iret
hop_to_kernel:movw compensation_count, %ax
movw % ax, temp_pit_counter; popf ");
/* return stack has been set up -> oldhandler. End with popa; ret */
asm("popal ret ");}
myaudio.c #include "myaudio.h"
static int
speaker_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int val; /* , *arg; */
struct audio_buf_info _infobuffer = { 1, 1, 32000, 32000 };
/* we're assuming that this stuff is on a local stack. In that
case the above initializations assume the same values on each
call to ioctl....... ie, 1 buffer fragment available, 1 also
max no: of frags, size in bytes of fragment, amount of
available mem. */
switch (cmd) {
case SNDCTL_DSP_SYNC: /* reset buffers */
call sync_output();
goto default;
case SNDCTL_DSP_POST: /* do launch output ->dmap whatever
that means */
case SNDCTL_DSP_RESET: /* reset dsp */
call sync_output();
goto default;
case SNDCTL_DSP_GETFMTS: /* return in val,dsp format mask */
val = AFMT_U8 | AFMT_U16_LE;
break;
case SNDCTL_DSP_SETFMT: /* set dsp format mask and return val
*/
val = get_user((int *) arg);
if (val & AFMT_U8)
berio = 0;
if (val & AFMT_U16_LE) {
berio = 8;
break;
} else
return -EINVAL;
break;
case SNDCTL_DSP_GETISPACE: /* output in audio_buf_info
(include/soundcard.h) amount of
unused internal buffer space -
invalid for o/p only device */
return -EINVAL;
case SNDCTL_DSP_GETOSPACE: /* same as above but valid */
if (canplay) {
_infobuffer.fragments = 0;
_infobuffer.bytes = 0;
}
else {
_infobuffer.fragments = 1;
_infobuffer.bytes = 32000;
}
case SNDCTL_DSP_NONBLOCK: /* set non-blocking flag in the dev
structs */
no_sleep_just_try_again = 1;
goto default;
case SNDCTL_DSP_GETCAPS: /* DON'T SUPPORT THIS SHIT */
return -EINVAL;
case SOUND_PCM_WRITE_RATE: /* returns speed in val in Hz */
val = case SOUND_PCM_READ_RATE: /* returns read speed in val.
Want to return -EINVAL */
return -EINVAL;
case SNDCTL_DSP_STEREO: /* returns and sets the number of
channels aske d for (1,2) */
case SOUND_PCM_WRITE_CHANNELS: /* returns and sets the
number of channels
requested for (0,1) */
case SOUND_PCM_READ_CHANNELS: /* We return -EINVAL here */
case SOUND_PCM_READ_BITS: /* sets and returns bits per sample */
case SNDCTL_DSP_SETDUPLEX: /* no need to support */
case SNDCTL_DSP_PROFILE: /* no need to support */
case SNDCTL_DSP_GETODELAY: /* I think that this is simply the
time it take for the damned thing
to stop playing when told to stop */
/********** from dma_ioctl ****************/
case SNDCTL_DSP_GETOPTR: /* we need to support this URGENTLY
something called a cinfo struct is
set */
default:
return 0;
}
return put_user(val, (int *) arg);
}
static ssize_t
speaker_write(struct file *file, const char *buf,
size_t count, loff_t * ppos)
{
if (count < 0)
return -EINVAL;
if (!count) { /* Flush output */
sync_output();
return 0;
}
if (!canplay) {
count = (count <= 32000 ? count : 32000);
if (copy_from_user((void *) data_buffer, buf, count))
return -EFAULT;
data_ptr = data_buffer;
buffer_length = count;
canplay = 1;
return count;
}
return 0; /* The idea is to return 0 bytes and
leave the problem to the client */
}
static ssize_t
speaker_read(struct file *file, char *buf, size_t count,
loff_t * ppos)
{
return -EINVAL;
}
static int speaker_open(struct inode *inode, struct file *file)
{
/* Check for re-entrancy */
if (virgin >= 1)
return -EBUSY;
virgin = 1;
MOD_INC_USE_COUNT;
return 0;
}
static void speaker_close(struct inode *inode, struct file *file)
{
virgin = 0;
MOD_DEC_USE_COUNT;
}
static int fops_sync_output(struct inode *inode, struct file *file)
{
sync_output();
return 0;
}
void sync_output()
{
canplay = 0;
data_ptr = data_buffer;
buffer_length = 0;
}
int helper_get_pit_count(void)
{
long ntsc = 1193180;
return ntsc / HZ;
}
void helper_set_pit_count(int counter)
{
cli();
outb(0x36, 0x43);
outb((char) (counter & 0xff), 0x40);
outb((char) (counter >> 8), 0x40);
sti();
}
int init_module(void)
{
/* Initailize all global variables */
if (
(device_major =
register_chrdev(0, "Internal Speaker", &speaker_fops)) < 0) {
printk(KERN_WARNING "Cannot get device majornumber");
return device_major;
}
no_sleep_just_try_again = 0; /* we're into the big leagues
now, ioctls, putting
procceses to sleep ......
mmmmmmmmmm......
*/
virgin = 0; /* Just in case you didn't
know....... ;) */
berio = 0; /* set to 8 for 16 bit mono */
canplay = 0;
sampling_factor = 1; /* ie; counter = 22Khz /
sampling_factor; */
pit_counter = 54;
/* allocate room for our 32KB buffer */
data_buffer = (long) kmalloc(32000, GFP_KERNEL); /* this is
assuming
that
kmalloc
NEVER
FAILS */
timer_idt_ptr = setvect(0x20, (long) myhandler);
old_pit_counter = helper_get_pit_count();
temp_pit_counter = compensation_count =
old_pit_counter / pit_counter;
helper_set_pit_count(pit_counter);
printk
("Hopefully, irq0 has been hooked from vector 20 to %p \n",
myhandler);
return 0;
}
void cleanup_module(void)
{
helper_set_pit_count(old_pit_counter);
setvect(0x20, timer_idt_ptr);
unregister_chrdev(device_major, "Internal Speaker");
kfree((void *) data_buffer);
printk("Phew! Graceful exit!!!!! \n irq0 reset to %p \n",
(void *) timer_idt_ptr);
}
Makefile INCLUDEDIR = /usr/src/linux/include
CFLAGS = -c -D__KERNEL__ -DMODULE -O -Wall -I$(INCLUDEDIR)
VER = $(shell awk -F\" '/REL/ {print $$2}' $(INCLUDEDIR)/linux/version.h)
OBJS = myaudio.o
all: $(OBJS)
myaudio.o:
cc $(CFLAGS) myaudio.c
install:
install -c myaudio.o
clean:
rm -f *~ core
| |
| << Page 5 of 5 | |