/**
Beckhoff CX1000 Kernel level driver
Copyright (C) 2005 Thermo Electron Corporation
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
http://www.gnu.org/copyleft/lesser.html
\file
This driver maps the dual port I/O ram to user space. It is not that clever
it just grabs the whole block every update. This is a reasonable performance
hit and it would be better to modify the driver to deal with only the bits
of memory you are interested in. The better solution would be to allow reads
of the data directly from the user code with a good spinlock on the update.
\author Richard Lemon
\date 12/08/04
*/
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/reboot.h>
#include <asm/io.h rel='nofollow' onclick='return false;'>
#include <asm/errno.h rel='nofollow' onclick='return false;'>
#include <asm/uaccess.h rel='nofollow' onclick='return false;'>
#include <asm/page.h rel='nofollow' onclick='return false;'>
#include <asm/atomic.h rel='nofollow' onclick='return false;'>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include "bhkldrv.h"
// module level defines
MODULE_AUTHOR("Richard Lemon");
static int bhkldrv_major = 250;
module_param(bhkldrv_major, int, 0);
MODULE_DESCRIPTION("Support for Beckhoff CX1000 embedded PC");
MODULE_SUPPORTED_DEVICE("/dev/bhio");
MODULE_LICENSE("Dual BSD/GPL");
// global variables
/**
Used as a bitmask to signal between kernel
timer interrupt and read / write operations
bit0 = signals to stop the driver (stop scheduling the tasklet)
bit1 = signals that the driver is stopped
bit2 = mutex locks the IO memory between tasklet and user IO
bit3 = Set if the watchdog is active
bit4 = signals new data after write (dirty bit)
*/
unsigned long io_poll = 0;
const unsigned long io_used = 0x1000; //!< Used block length for IO
unsigned char* io_new = NULL; //!< User space buffer for Beckhoff data
unsigned char* io_old = NULL; //!< Temporary buffer for Beckhoff data
void* io_base = NULL; //!< Base address of remapped memory area
int bh_interrupt_count = 0; //!< Interrupt count of missed interrupts for tasklet
atomic_t bh_watchdog_count; //!< Counter for watchdog timeouts
atomic_t bh_watchdog_timeout; //!< Counter for watchdog timeouts
//! board map variable initialised with KBus available
BH_IO_BOARD_MAP bh_boardmap = {0x01,0x200,0x200,0x00,0x00,0x00};
//! declare work structure
void bhkldrv_timedout(void *ptr);
DECLARE_WORK(bh_work, bhkldrv_timedout , NULL);
//! Tasklet function to handle bottom half processing for timer interrupt
void bh_do_tasklet(unsigned long);
DECLARE_TASKLET(bh_tasklet, bh_do_tasklet, 0);
//! Wait queue to signal when data is ready to be read.
DECLARE_WAIT_QUEUE_HEAD(read_wait_queue);
// forward function declarations
int bhkldrv_open(struct inode *inode, struct file *filp);
int bhkldrv_release(struct inode *inode, struct file *filp);
int bhkldrv_ioctl(struct inode *node, struct file *filep, unsigned int cmd, unsigned long arg);
ssize_t bhkldrv_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
ssize_t bhkldrv_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos);
//! Structure to map the available interfaces to the kernel
struct file_operations bhkldrv_remap_ops = {
open: bhkldrv_open,
release: bhkldrv_release,
ioctl: bhkldrv_ioctl,
read: bhkldrv_read,
write: bhkldrv_write,
};
/**
\brief Runs the device IO loop, called every jiffy
\author RDL
\date 17/08/04
*/
void bhkldrv_timedout(void *ptr)
{
if (test_bit(0,&io_poll)) // check if we are meant to end
{
tasklet_schedule(&bh_tasklet);
bh_interrupt_count++; // record the interrupt
schedule_delayed_work(&bh_work, 2);// add ourselves back into the timer queue
}
else
clear_bit(1, &io_poll); // signal back that we are finished
}
/**
\brief Does the actual read, bottom half style processing so we don't lockup
the kernel.
\author RDL
\date 23/08/04
\bug Need to copy like this
*/
void bh_do_tasklet(unsigned long something)
{
if (test_bit(3,&io_poll))
{
// watchdog is active
atomic_inc( &bh_watchdog_count );
if ( atomic_read(&bh_watchdog_count) > atomic_read(&bh_watchdog_timeout ) )
{
//printk("watchdog timed out - reboot in progress\n");
kernel_restart(NULL);
return;
}
}
// if the read request is finished then lets get the data
if (readb(io_base + GCB_OFFSET + 0x0D) == readb(io_base + GCB_OFFSET + 0x0E))
{
if (test_and_set_bit(2,&io_poll) != 0)
return;
// copy data from IO
memcpy_fromio(io_old + GCB_OFFSET, io_base + GCB_OFFSET, sizeof(GCB)); // from io to swap buffer
if (bh_boardmap.bUseKBus)
{
memcpy_fromio(io_old + KBCB_OFFSET, io_base + KBCB_OFFSET, sizeof(BUSCB)); // from io to swap buffer
if ( bh_boardmap.nKBusInputBytes ) memcpy_fromio(io_old + KBIP_OFFSET, io_base + KBIP_OFFSET, bh_boardmap.nKBusInputBytes ); // from io to swap buffer
if ( bh_boardmap.nKBusOutputBytes ) memcpy_fromio(io_old + KBOP_OFFSET, io_base + KBOP_OFFSET, bh_boardmap.nKBusOutputBytes); // from io to swap buffer
}
if (bh_boardmap.bUseIPBus)
{
memcpy_fromio(io_old + IPCB_OFFSET, io_base + IPCB_OFFSET, sizeof(BUSCB)); // from io to swap buffer
if ( bh_boardmap.nIPBusInputBytes ) memcpy_fromio(io_old + IPIP_OFFSET, io_base + IPIP_OFFSET, bh_boardmap.nIPBusInputBytes ); // from io to swap buffer
if ( bh_boardmap.nIPBusOutputBytes ) memcpy_fromio(io_old + IPOP_OFFSET, io_base + IPOP_OFFSET, bh_boardmap.nIPBusOutputBytes); // from io to swap buffer
}
if ( test_bit(4,&io_poll) )// buffer dirty copy data to IO
{
if ((bh_boardmap.bUseKBus) && ( bh_boardmap.nKBusOutputBytes ))
memcpy_toio(io_base + KBOP_OFFSET, io_new + KBOP_OFFSET, bh_boardmap.nKBusOutputBytes); // from user to dp ram
if ((bh_boardmap.bUseIPBus) && ( bh_boardmap.nIPBusOutputBytes ))
memcpy_toio(io_base + IPOP_OFFSET, io_new + IPOP_OFFSET, bh_boardmap.nIPBusOutputBytes); // from user to dp ram
}
else
{
// buffer clean, let's sync the output buffer
if ( (bh_boardmap.bUseKBus) && ( bh_boardmap.nKBusOutputBytes ) )
memcpy(io_new + KBOP_OFFSET, io_old + KBOP_OFFSET, bh_boardmap.nKBusOutputBytes); // from user to dp ram
if ( (bh_boardmap.bUseIPBus) && ( bh_boardmap.nIPBusOutputBytes ) )
memcpy(io_new + IPOP_OFFSET, io_old + IPOP_OFFSET, bh_boardmap.nIPBusOutputBytes); // from user to dp ram
// signal that the buffer is ready
wake_up_interruptible(&read_wait_queue);
}
// signal that the buffer is clean
clear_bit(4,&io_poll);
// ready for user IO again
clear_bit(2,&io_poll);
//start another run
writeb(readb(io_base + GCB_OFFSET + 0x0D) + 1, io_base + GCB_OFFSET + 0x0E);
}
}
/**
\brief Open the device; all we have to do here is to up the usage count and
set the right fops.
\author RDL
\date 13/08/04
*/
int bhkldrv_open(struct inode *inode, struct file *filp)
{
unsigned int dev = MINOR(inode->i_rdev);
if (dev >= 1)
return -ENODEV;
filp->f_op = &bhkldrv_remap_ops;
// MOD_INC_USE_COUNT;
return 0;
}
/**
\brief Close the device; all we have to do here is to decrement the usage
count.
\author RDL
\date 13/08/04
*/
int bhkldrv_release(struct inode *inode, struct fi