miércoles, abril 26, 2006

Driver serial para Linux

#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/slab.h>

#include <linux/ioport.h>
#include <linux/fcntl.h>
#include <linux/delay.h>
#include <asm/io.h>

#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <asm/system.h>
#include <asm/irq.h>

#include <linux/delay.h>
#include <linux/string.h>
#include "serial.h"

MODULE_AUTHOR("Felixls");
MODULE_LICENSE("Dual BSD/GPL");

int ser_busy = SER_FREE;

static int ser_open(struct inode * inode, struct file * file)
{
int codigo;

unsigned int minor = MINOR(inode->i_rdev);

printk(KERN_ALERT " SER: opening...\n");

if (minor != 0)
return -ENODEV;

if (ser_busy==SER_BUSY)
return -EBUSY;

printk (KERN_ALERT "SER: serial abierto.\n");

outb(0x80, SER_BASE + 3); // Set DLAB ON
outb(0x0C, SER_BASE + 0); // Set Baud rate DL low (9600) (DLL) 0x0C
/* 0x01 = 115.200 BPS */
/* 0x02 = 57.600 BPS */
/* 0x03 = 38.400 BPS */
/* 0x06 = 19.200 BPS */
/* 0x0C = 9.600 BPS */
/* 0x18 = 2.400 BPS */
outb(0x00, SER_BASE + 1); // Set Baud rate DL high (DLM)
outb(0x03, SER_BASE + 3); // 8bits, No Parity, 1 Stop bit (LCR)
outb(0xC7, SER_BASE + 2); // Enable FIFO Control Register (FCR)
outb(0x0B, SER_BASE + 4); // Turn on DTR, RTS (MCR)

ser_busy = SER_BUSY;

return 0;
}

int serial_recieved()
{
return inb(SER_IN + 5) & 1;
}

int is_transmit_empty()
{
return inb(SER_IN + 5) & 0x20;
}

static ssize_t ser_read (struct file *file, char *buf, size_t count, loff_t *nose)
{
int i, c;
int readed = 0;
int timeout = 100;
char *readbuffer;

readbuffer = kmalloc(count, GFP_KERNEL);

for(i = 0; i < count; i++)
{
c = 0;
while (serial_recieved() == 0 && c < timeout)
{
msleep(1);
c++;
continue;
}
if (c >= timeout) break;

readbuffer[i] = inb(SER_IN);

readed++;
}

if (readed > 0)
__copy_to_user(buf, readbuffer, readed);

kfree(readbuffer);

if (readed == 0 && c >= timeout)
return -EINTR;

return readed;
}

static ssize_t ser_write(struct file *file, const char *buf,size_t count, loff_t *nose)
{
char *writebuffer;
int i;

writebuffer = kmalloc(count, GFP_KERNEL);

__copy_from_user(writebuffer, buf, count);

int written = 0;

for(i=0; i<count; i++)
{
while (is_transmit_empty()==0)
{
msleep(1);
continue;
}

outb(writebuffer[i], SER_OUT);

written++;
}
kfree(writebuffer);

return written;
}


static int ser_release (struct inode *inode, struct file *file)
{
ser_busy = SER_FREE;
printk (KERN_ALERT "SER: serial cerrado.\n");
return 0;
}

struct file_operations ser_fops=
{
.owner = THIS_MODULE,
.read = ser_read,
.write = ser_write,
.open = ser_open,
.release = ser_release,
};

static int serial_init(void)
{
if (register_chrdev(SER_MAYOR, "ser", &ser_fops))
{
printk(KERN_ALERT "ERROR: init_module ha fallado instalando SER driver...\n");
return -EIO;
}
printk(KERN_ALERT "modulo instalado!\n");
return 0;
}

static void serial_exit(void)
{
if (ser_busy)
{
printk(KERN_ALERT "WARNG: SER driver ocupado, no se pudo remover....\n");
return;
}
if (unregister_chrdev(SER_MAYOR,"ser") != 0)
printk(KERN_ALERT "ERROR: cleanup_module ha fallado, no se puede desregistrar SER deiver...\n");
else
printk(KERN_ALERT "modulo desinstalado!\n");
}

module_init(serial_init);
module_exit(serial_exit);