Makefile
Code:
obj-m += pulser.o
# put this line in a Makefile that will be processed by "make modules" makefile
kit:
make -C /usr/src/linux-`uname -r` SUBDIRS=`pwd` modules
pulser.c
Code:
/* pulser.c 20050328 JRB */
/* 20050401 control all 8 bits */
/* adapted from lkmpg version 2.6 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/workqueue.h> /* schedule tasks */
#include <linux/sched.h> /* put self to sleep */
#include <linux/string.h>
#include <linux/interrupt.h> /* for irqreturn_t */
#include <linux/init.h>
#include <asm/io.h> /* for inb, outb */
#include <asm/uaccess.h> /* for put_user */
#define pulser_DEVICE_NAME "pulser"
#define pulser_NODE_NAME "/dev/pulser" /* Dev name as it appears in /dev */
#define pulser_WORKQUEUE "WQpulser.c" /* name will appear in process table */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("John Bork");
MODULE_DESCRIPTION("Parallel Port Output Pulser");
MODULE_SUPPORTED_DEVICE(pulser_DEVICE_NAME);
/* function prototypes */
static int pulser_device_open(struct inode *, struct file *);
static int pulser_device_release(struct inode *, struct file *);
static ssize_t pulser_device_read(struct file *, char *, size_t, loff_t *);
static ssize_t pulser_device_write(struct file *, const char *, size_t, loff_t *);
static void pulser_process_io(void *);
/* module global variables */
static struct file_operations pulser_fops = {
.read = pulser_device_read,
.write = pulser_device_write,
.open = pulser_device_open,
.release = pulser_device_release
};
static struct workqueue_struct * pulser_workqueue;
static struct work_struct pulser_task;
static DECLARE_WORK(pulser_task, pulser_process_io, NULL); /* what function to execute for workqueue */
/* kernel-defined module and non-user-controllable parameters */
static int pulser_base_port = 0x378; /* standard address of LPT1 (parallel port) */
static int pulser_major; /* Major number assigned to our device driver */
static int pulser_output_counter = 0; /* How many pulses since module installed */
static int pulser_workqueue_delay = 5; /* How often I/O process occurs (milliseconds) */
/* run time configuration and system state */
/* initially OFF (not pulsing parallel port outputs) */
/* need period (frequency) and pulse duration for normal, cyclic operation */
/* e.g., 30Hz 10% duty cycle */
static unsigned int pulser_duty_cycle = 10; /* Percentage of process cycle period HIGH pulse occurs */
/* frequency will be converted to millisecond countdown timers */
static unsigned int pulser_frequency[8] = {30, 0, 0, 0, 0, 0, 0, 0};
static int pulser_open = 0; /* Is device open? (initially closed) */
static int __init pulser_init(void)
{
char status;
/* register character major device number */
pulser_major = register_chrdev(0, pulser_DEVICE_NAME, &pulser_fops);
if (pulser_major < 0)
{
printk(KERN_INFO "Registering the character device failed with %d\n", pulser_major);
return pulser_major;
}
printk(KERN_INFO "Installing PULSER driver; assigned major number %d\n", pulser_major);
/* remind of need to make node if non-existent */
/* e.g., mknod -m 0666 /dev/pulser c 254 0 */
printk(KERN_INFO " manually mknod -m 0666 %s c %d 0\n", pulser_NODE_NAME, pulser_major);
/* set direction of data port to output */
status = inb(pulser_base_port + 2);
status = status & ~0x20;
outb(status, pulser_base_port + 2);
/* create but do not start workqueue (initially starts with first write) */
pulser_workqueue = create_workqueue(pulser_WORKQUEUE);
return 0;
}
static void __exit pulser_cleanup(void)
{
int stat;
printk(KERN_INFO "Removing PULSER Driver after count %d\n", pulser_output_counter/2);
stat = unregister_chrdev(pulser_major, pulser_DEVICE_NAME);
if (stat < 0)
printk(KERN_INFO "Error in unregister_chrdev: %d\n", stat);
cancel_delayed_work(&pulser_task);
flush_workqueue(pulser_workqueue);
/* necesary operations to prevent kernel crash */
destroy_workqueue(pulser_workqueue);
/* (from LKMPG)
* Sleep until intrpt_routine is called one last time. This is
* necessary, because otherwise we'll deallocate the memory holding
* intrpt_routine and Task while work_timer still references them.
* Notice that here we don't allow signals to interrupt us.
*
* Since WaitQ is now not NULL, this automatically tells the interrupt
* routine it's time to die.
*/
}
module_init(pulser_init);
module_exit(pulser_cleanup);
static int pulser_device_open(struct inode *inode, struct file *file)
{
/* allow only one open instance at a time */
/* will be called at beginning of user read or write character device file (/dev/pulser) */
if (pulser_open)
return -EBUSY;
pulser_open++;
/* increment module usage counter */
try_module_get(THIS_MODULE);
return 0;
}
static int pulser_device_release(struct inode *inode, struct file *file)
{
/* will be called whenever at beginning of user read or write character device file (/dev/pulser) */
pulser_open--;
/* decrement usage counter */
module_put(THIS_MODULE);
return 0;
}
static ssize_t pulser_device_read(struct file *filp, char *buf, size_t len, loff_t * offset)
{
/* send data from kernel space to user space (can be via character device file) */
/* initially do nothing; later return information about current state */
int bytes_read=0;
printk(KERN_INFO " pulser_device_read\n");
return bytes_read;
}
static ssize_t pulser_device_write(struct file *filp, const char *buf, size_t len, loff_t * offset)
{
char databuf[25];
char * ptr = databuf;
int bytes_written = 0;
unsigned int i,j;
/* what are you going to get from user-space? */
/* ASCII representations of frequency and duty cycle */
/* 20050401 allow specification for frequency for each bit */
for(bytes_written=0; bytes_written<sizeof(databuf) && len; len--, bytes_written++)
get_user(*(ptr++), buf++);
printk(KERN_INFO " pulser_device_write (length %d)\n", bytes_written);
/* return error if still bytes to write */
if(len)
return -1;
/* initially only accept one or two characters for frequency */
for(ptr=databuf, i=0; i<8 && ptr-databuf < bytes_written-1; i++, ptr++)
{
if(ptr-databuf >= bytes_written-3)
{
if(ptr-databuf == bytes_written-2)
pulser_frequency[i] = *ptr - '0';
else
{
pulser_frequency[i] = (*ptr - '0') * 10 +(*(ptr+1) - '0');
ptr++;
}
}
else
{
if(*(ptr+1) == ' ')
pulser_frequency[i] = *ptr - '0';
else
{
pulser_frequency[i] = (*ptr - '0') * 10 +(*(ptr+1) - '0');
ptr++;
}
ptr++;
}
if(pulser_frequency[i] > 60)
return -1;
}
/* set the remainder bits to zero Hz*/
for(j=i; j<8; j++)
pulser_frequency[j] = 0;
for(j=0; j<8; j++)
if(pulser_frequency[j])
break;
if(j == 8)
{
/* write zeros to all bits of output */
outb(0, pulser_base_port);
}
else
{
/* initially act on command to start process */
queue_delayed_work(pulser_workqueue, &pulser_task, pulser_workqueue_delay);
}
printk(KERN_INFO "pulser frequency set to {%d,%d,%d,%d,%d,%d,%d,%d} Hz at count %d\n",
pulser_frequency[0], pulser_frequency[1],pulser_frequency[2],pulser_frequency[3],
pulser_frequency[4], pulser_frequency[5],pulser_frequency[6],pulser_frequency[7],
pulser_output_counter/2);
return bytes_written;
}
static void pulser_process_io(void * dummy)
{
static char last_output = 0; /* for resource conservation strategy */
static char output_byte = 0; /* byte that will be written to parallel port */
static int pulser_timer[8] = {0,0,0,0,0,0,0,0}; /* main process countdown timer */
static int pulse_timer[8] = {0,0,0,0,0,0,0,0}; /* countdown for HIGH part of pulse */
int i;
/* output parallel port bit(s) based on counter(s) */
/* need period (frequency) and pulse duration for normal, cyclic operation */
/* e.g., 30Hz 10% duty cycle yields */
for(i=output_byte=0; i<8; i++)
{
if(pulser_frequency[i])
{
if(!pulser_timer[i])
{
/* start a new cycle with HIGH pulse */
pulser_timer[i] = 1000 / pulser_frequency[i];
/* useless to use pulse_timer if duty cycle >= 100% */
pulse_timer[i] = 10 * pulser_duty_cycle / pulser_frequency[i];
/* output bit set HIGH */
output_byte |= (1<<i);
}
else
{
if(pulser_duty_cycle < 100)
{
/* decrement pulse timer */
if(pulse_timer[i] - pulser_workqueue_delay > 0)
{
pulse_timer[i] -= pulser_workqueue_delay;
output_byte |= (1<<i);
}
else
{
pulse_timer[i] = 0;
/* output bit set LOW */
output_byte &= ~(1<<i);
}
}
else
output_byte |= (1<<i);
}
}
/* decrement pulser timer */
if(pulser_timer[i] - pulser_workqueue_delay > 0)
pulser_timer[i] -= pulser_workqueue_delay;
else
pulser_timer[i] = 0;
}
/* output byte to parallel port (save resources by only outputting change) */
if(output_byte != last_output)
{
outb(output_byte, pulser_base_port);
last_output = output_byte;
pulser_output_counter++;
}
/* reschedule this task for re-execution after the workqueue_delay timer interrupts */
for(i=0; i<8; i++)
if(pulser_frequency[i])
break;
if(i != 8)
queue_delayed_work(pulser_workqueue, &pulser_task, pulser_workqueue_delay);
}