|
|
ADEOS IPIPE Hard Real Time - 8254 Timer Programming
Here we have an ADEOS IPIPE example that uses and modifies the 8254 timer interrupt
to have a custom timer interrupt frequency for various purposes, e.g. data sampling,
controlling stuff.
We reprogram the 8254 timer to a higher (sampling) frequency. In order to maintain
a 100Hz timer tick to linux, we only pass the timer interrupt to linux
after PROPAGATE_INT_AFTER_COUNTS timer ticks have occured.
This example can be used to sample data with a custom frequency, while the
Linux domain still will see only a 100Hz tick.
Since the 8254 timer can be programmed only to certain frequencies, we pass the
timer ticks to linux a little faster (e.g. if we need to pass the INT to linux every
220.963435 timer interrupts, we pass the INT after 220 timer interrupts).
In order to maintain the precission, we have another value, CANCEL_INT_AFTER_COUNTS,
which cancels some timer interrupt to linux. So basically linux runs with a slightly
faster tick period, but since we cancel one interrupt every now and then, the time in linux
runs correct in the long run.
You can set the timer frequency with SAMPLE_FREQUENCY.
This example also contains a parallel port interrupt handler, but you do not need
it to test the 8254 stuff. If you also want to see the parallel port interrupt, you need to make a
"null-modem" for the parallel port - connect any output pin (pin 2-9) with the IRQ pin (pin 10 = ACK).
Don't increase the SAMPLE_FREQUENCY too much or your machine will freeze.
Developed and tested with kernel 2.4.32 (adeos-ipipe-2.4.32-i386-1.1-02.patch). Also tested on
kernel 2.6.15 with the IPIPE 1.1 patch.
For further details see the comments in the source code.
NOTE: If you test on kernels 2.4 and 2.6, make sure you do a "make clean" and everything should compile well.
muon.c
/*
ADEOS IPIPE kernel module with 8254 timer usage
Module (C) 2006 www.captain.at
*/
#include <linux/version.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/apic.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
// for 2.4 ipipe
#include <asm/hw_irq.h>
#define SAMPLE_FREQUENCY 22050
#define BASEPORT 0x378
#define PAR_INT 7
static struct ipipe_domain this_domain;
long long parcounter = 0;
long long timercounter = 0;
unsigned int timerprop = 0;
unsigned int subtimer = 0;
#define PIT_CH0 0x40
#define PIT_MODE 0x43
#define RTHAL_8254_IRQ 0
#define NEWLATCH ((int)((LATCH * HZ) / SAMPLE_FREQUENCY))
#define PROPAGATE_INT_AFTER_COUNTS ((int)(LATCH / NEWLATCH))
#define SAMPLE_FREQUENCY_NS ((int)(1000000000/SAMPLE_FREQUENCY))
#define CANCEL_INT_AFTER_COUNTS ((int)(1.0/((1.0/HZ)/((NEWLATCH*\
PROPAGATE_INT_AFTER_COUNTS)/1193180.0)-1.0)))
void handler(unsigned irq) {
unsigned long flags;
flags = ipipe_critical_enter(NULL);
parcounter++;
printk(">>>>>>>>> parcounter=%d\n", (int)parcounter);
ipipe_critical_exit (flags);
ipipe_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
ipipe_propagate_irq(irq);
}
void timer_tick (unsigned irq) {
unsigned long flags;
flags = ipipe_critical_enter(NULL);
timercounter++;
timerprop++;
if (timercounter > SAMPLE_FREQUENCY) {
// we generate a parallel port interrupt only after
// SAMPLE_FREQUENCY timer interrupts have passed
// i.e. after one second (just 4 testing)
outb_p(0, BASEPORT);
outb_p(255, BASEPORT);
printk(">>>>>>>>> timer_tick\n");
timercounter = 0;
}
ipipe_critical_exit (flags);
// pass timer interrupt to linux (100Hz)
// if we check with >= we run too fast, but we skip an interrupt later
if ( timerprop >= PROPAGATE_INT_AFTER_COUNTS ) {
subtimer++;
if (subtimer <= CANCEL_INT_AFTER_COUNTS) {
ipipe_propagate_irq(irq);
} else {
subtimer = 0;
}
timerprop = 0;
}
}
static inline void setup_8254 (void) {
// either use the adeos-ipipe function to programm the 8254,
// or do it ourselfs
ipipe_tune_timer(SAMPLE_FREQUENCY_NS, 0);
/*
unsigned long flags;
flags = ipipe_critical_enter(NULL);
period = NEWLATCH;
if (period > LATCH) period = LATCH;
outb(0x34,PIT_MODE);
outb(period & 0xff,PIT_CH0);
outb(period >> 8,PIT_CH0);
ipipe_critical_exit (flags);
*/
}
static void domain_entry (void) {
printk("Domain %s started.\n",ipipe_current_domain->name); //
// setup 8254 timer
setup_8254();
// grab 8254 interrupt
ipipe_virtualize_irq(ipipe_current_domain, RTHAL_8254_IRQ,
(ipipe_irq_handler_t)&timer_tick, NULL, NULL, IPIPE_DYNAMIC_MASK);
// disable interrupt propagation to other domains (e.g. linux)
ipipe_control_irq(RTHAL_8254_IRQ,0,IPIPE_HANDLE_MASK);
// grab parallel port irq
ipipe_virtualize_irq(ipipe_current_domain, PAR_INT,
(ipipe_irq_handler_t)&handler,NULL,NULL,IPIPE_DYNAMIC_MASK);
//set port to interrupt mode; pins are output
outb_p(0x10, BASEPORT + 2);
// parallel port int is passed to other domains (e.g. linux)
ipipe_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
}
static int __init mod_init (void) {
struct ipipe_domain_attr attr;
printk("SAMPLE_FREQUENCY=%d\n", SAMPLE_FREQUENCY);
printk("SAMPLE_FREQUENCY_NS=%d\n", SAMPLE_FREQUENCY_NS);
printk("NEWLATCH=%d\n", NEWLATCH);
printk("PROPAGATE_INT_AFTER_COUNTS=%d\n", PROPAGATE_INT_AFTER_COUNTS);
printk("CANCEL_INT_AFTER_COUNTS=%d\n", CANCEL_INT_AFTER_COUNTS);
// start our real time domain
ipipe_init_attr (&attr);
attr.name = "TestDomain";
attr.priority = IPIPE_ROOT_PRIO + 1;
attr.entry = &domain_entry;
return ipipe_register_domain(&this_domain,&attr);
}
static void __exit mod_exit (void) {
// reset 8254 timer interrupt to default value
ipipe_tune_timer(10000000,IPIPE_RESET_TIMER);
// unregister our domain
ipipe_unregister_domain(&this_domain);
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
Makefile
UNAME := $(shell uname -r)
KERNEL26 := 2.6
KERNELVERSION := $(findstring $(KERNEL26),$(UNAME))
all::
ifeq ($(KERNELVERSION),2.6)
obj-m := muon.o
INCLUDE := -I/usr/include/asm/mach-default/
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all::
$(MAKE) -C $(KDIR) $(INCLUDE) SUBDIRS=$(PWD) modules
else
TARGET := muon
INCLUDE := -I/lib/modules/`uname -r`/build/include -I/usr/include/asm/mach-default/
CFLAGS := -O2 -Wall -DMODULE -D__KERNEL__ -DLINUX
CC := gcc
all:: ${TARGET}.o
${TARGET}.o: ${TARGET}.c
$(CC) $(CFLAGS) ${INCLUDE} -c ${TARGET}.c
endif
clean::
$(RM) .muon* *.cmd *.o *.ko *.mod.c mydata user
$(RM) -R .tmp*
.PHONY: clean
Last-Modified: Sat, 04 Feb 2006 15:44:14 GMT
|
|
|