[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 |