2005-04-16 15:20:36 -07:00
/*
* Berkshire USB-PC Watchdog Card Driver
*
2007-01-08 22:45:30 +01:00
* (c) Copyright 2004-2007 Wim Van Sebroeck <wim@iguana.be>.
2005-04-16 15:20:36 -07:00
*
* Based on source code of the following authors:
* Ken Hollis <kenji@bitgate.com>,
2008-10-27 15:17:56 +00:00
* Alan Cox <alan@lxorguk.ukuu.org.uk>,
2005-04-16 15:20:36 -07:00
* Matt Domsch <Matt_Domsch@dell.com>,
* Rob Radez <rob@osinvestor.com>,
* Greg Kroah-Hartman <greg@kroah.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
* provide warranty for any of this software. This material is
* provided "AS-IS" and at no charge.
*
* Thanks also to Simon Machell at Berkshire Products Inc. for
* providing the test hardware. More info is available at
* http://www.berkprod.com/ or http://www.pcwatchdog.com/
*/
2007-01-08 22:40:33 +01:00
# include <linux/module.h> /* For module specific items */
# include <linux/moduleparam.h> /* For new moduleparam's */
# include <linux/types.h> /* For standard types (like size_t) */
# include <linux/errno.h> /* For the -ENODEV/... values */
# include <linux/kernel.h> /* For printk/panic/... */
# include <linux/delay.h> /* For mdelay function */
# include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */
# include <linux/watchdog.h> /* For the watchdog specific items */
# include <linux/notifier.h> /* For notifier support */
# include <linux/reboot.h> /* For reboot_notifier stuff */
# include <linux/init.h> /* For __init/__exit/... */
# include <linux/fs.h> /* For file operations */
# include <linux/usb.h> /* For USB functions */
# include <linux/slab.h> /* For kmalloc, ... */
# include <linux/mutex.h> /* For mutex locking */
2006-12-12 23:46:47 +01:00
# include <linux/hid.h> /* For HID_REQ_SET_REPORT & HID_DT_REPORT */
2008-07-15 11:46:11 +00:00
# include <linux/uaccess.h> /* For copy_to_user/put_user/... */
2007-01-08 22:40:33 +01:00
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_USB_DEBUG
static int debug = 1 ;
# else
static int debug ;
# endif
/* Use our own dbg macro */
# undef dbg
# define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG PFX format "\n" , ## arg); } while (0)
/* Module and Version Information */
2007-01-08 22:45:30 +01:00
# define DRIVER_VERSION "1.02"
2007-01-27 20:54:24 +01:00
# define DRIVER_DATE "21 Jan 2007"
2005-04-16 15:20:36 -07:00
# define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>"
# define DRIVER_DESC "Berkshire USB-PC Watchdog driver"
# define DRIVER_LICENSE "GPL"
# define DRIVER_NAME "pcwd_usb"
# define PFX DRIVER_NAME ": "
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( DRIVER_LICENSE ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
MODULE_ALIAS_MISCDEV ( TEMP_MINOR ) ;
/* Module Parameters */
module_param ( debug , int , 0 ) ;
MODULE_PARM_DESC ( debug , " Debug enabled or not " ) ;
2007-01-08 22:45:30 +01:00
# define WATCHDOG_HEARTBEAT 0 /* default heartbeat = delay-time from dip-switches */
2005-04-16 15:20:36 -07:00
static int heartbeat = WATCHDOG_HEARTBEAT ;
module_param ( heartbeat , int , 0 ) ;
2007-01-08 22:45:30 +01:00
MODULE_PARM_DESC ( heartbeat , " Watchdog heartbeat in seconds. (0<heartbeat<65536 or 0=delay-time from dip-switches, default= " __MODULE_STRING ( WATCHDOG_HEARTBEAT ) " ) " ) ;
2005-04-16 15:20:36 -07:00
2005-07-27 11:43:58 -07:00
static int nowayout = WATCHDOG_NOWAYOUT ;
2005-04-16 15:20:36 -07:00
module_param ( nowayout , int , 0 ) ;
2007-01-27 20:54:24 +01:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2005-04-16 15:20:36 -07:00
/* The vendor and product id's for the USB-PC Watchdog card */
# define USB_PCWD_VENDOR_ID 0x0c98
# define USB_PCWD_PRODUCT_ID 0x1140
/* table of devices that work with this driver */
2008-08-06 20:19:41 +00:00
static struct usb_device_id usb_pcwd_table [ ] = {
2005-04-16 15:20:36 -07:00
{ USB_DEVICE ( USB_PCWD_VENDOR_ID , USB_PCWD_PRODUCT_ID ) } ,
{ } /* Terminating entry */
} ;
MODULE_DEVICE_TABLE ( usb , usb_pcwd_table ) ;
/* according to documentation max. time to process a command for the USB
* watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */
# define USB_COMMAND_TIMEOUT 250
/* Watchdog's internal commands */
# define CMD_READ_TEMP 0x02 /* Read Temperature; Re-trigger Watchdog */
# define CMD_TRIGGER CMD_READ_TEMP
# define CMD_GET_STATUS 0x04 /* Get Status Information */
# define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */
# define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */
# define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */
# define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current Watchdog Time */
# define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */
# define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG
2007-01-08 22:45:30 +01:00
/* Watchdog's Dip Switch heartbeat values */
2008-08-06 20:19:41 +00:00
static const int heartbeat_tbl [ ] = {
2007-01-08 22:45:30 +01:00
5 , /* OFF-OFF-OFF = 5 Sec */
10 , /* OFF-OFF-ON = 10 Sec */
30 , /* OFF-ON-OFF = 30 Sec */
60 , /* OFF-ON-ON = 1 Min */
300 , /* ON-OFF-OFF = 5 Min */
600 , /* ON-OFF-ON = 10 Min */
1800 , /* ON-ON-OFF = 30 Min */
3600 , /* ON-ON-ON = 1 hour */
} ;
2005-04-16 15:20:36 -07:00
/* We can only use 1 card due to the /dev/watchdog restriction */
static int cards_found ;
/* some internal variables */
static unsigned long is_active ;
static char expect_release ;
/* Structure to hold all of our device specific stuff */
struct usb_pcwd_private {
2008-08-06 20:19:41 +00:00
struct usb_device * udev ; /* save off the usb device pointer */
struct usb_interface * interface ; /* the interface for this device */
2005-04-16 15:20:36 -07:00
unsigned int interface_number ; /* the interface number used for cmd's */
2008-08-06 20:19:41 +00:00
unsigned char * intr_buffer ; /* the buffer to intr data */
2005-04-16 15:20:36 -07:00
dma_addr_t intr_dma ; /* the dma address for the intr buffer */
size_t intr_size ; /* the size of the intr buffer */
2008-08-06 20:19:41 +00:00
struct urb * intr_urb ; /* the urb used for the intr pipe */
2005-04-16 15:20:36 -07:00
unsigned char cmd_command ; /* The command that is reported back */
unsigned char cmd_data_msb ; /* The data MSB that is reported back */
unsigned char cmd_data_lsb ; /* The data LSB that is reported back */
atomic_t cmd_received ; /* true if we received a report after a command */
int exists ; /* Wether or not the device exists */
2007-05-03 20:15:14 -07:00
struct mutex mtx ; /* locks this structure */
2005-04-16 15:20:36 -07:00
} ;
static struct usb_pcwd_private * usb_pcwd_device ;
/* prevent races between open() and disconnect() */
2006-03-23 03:00:27 -08:00
static DEFINE_MUTEX ( disconnect_mutex ) ;
2005-04-16 15:20:36 -07:00
/* local function prototypes */
2008-08-06 20:19:41 +00:00
static int usb_pcwd_probe ( struct usb_interface * interface , const struct usb_device_id * id ) ;
static void usb_pcwd_disconnect ( struct usb_interface * interface ) ;
2005-04-16 15:20:36 -07:00
/* usb specific object needed to register this driver with the usb subsystem */
static struct usb_driver usb_pcwd_driver = {
. name = DRIVER_NAME ,
. probe = usb_pcwd_probe ,
. disconnect = usb_pcwd_disconnect ,
. id_table = usb_pcwd_table ,
} ;
2006-10-05 14:55:46 +01:00
static void usb_pcwd_intr_done ( struct urb * urb )
2005-04-16 15:20:36 -07:00
{
struct usb_pcwd_private * usb_pcwd = ( struct usb_pcwd_private * ) urb - > context ;
unsigned char * data = usb_pcwd - > intr_buffer ;
int retval ;
switch ( urb - > status ) {
case 0 : /* success */
break ;
case - ECONNRESET : /* unlink */
case - ENOENT :
case - ESHUTDOWN :
/* this urb is terminated, clean up */
2008-03-05 18:24:58 -08:00
dbg ( " %s - urb shutting down with status: %d " , __func__ , urb - > status ) ;
2005-04-16 15:20:36 -07:00
return ;
/* -EPIPE: should clear the halt */
default : /* error */
2008-03-05 18:24:58 -08:00
dbg ( " %s - nonzero urb status received: %d " , __func__ , urb - > status ) ;
2005-04-16 15:20:36 -07:00
goto resubmit ;
}
dbg ( " received following data cmd=0x%02x msb=0x%02x lsb=0x%02x " ,
data [ 0 ] , data [ 1 ] , data [ 2 ] ) ;
usb_pcwd - > cmd_command = data [ 0 ] ;
usb_pcwd - > cmd_data_msb = data [ 1 ] ;
usb_pcwd - > cmd_data_lsb = data [ 2 ] ;
/* notify anyone waiting that the cmd has finished */
2008-08-06 20:19:41 +00:00
atomic_set ( & usb_pcwd - > cmd_received , 1 ) ;
2005-04-16 15:20:36 -07:00
resubmit :
2008-08-06 20:19:41 +00:00
retval = usb_submit_urb ( urb , GFP_ATOMIC ) ;
2005-04-16 15:20:36 -07:00
if ( retval )
printk ( KERN_ERR PFX " can't resubmit intr, usb_submit_urb failed with result %d \n " ,
retval ) ;
}
static int usb_pcwd_send_command ( struct usb_pcwd_private * usb_pcwd , unsigned char cmd ,
unsigned char * msb , unsigned char * lsb )
{
int got_response , count ;
unsigned char buf [ 6 ] ;
/* We will not send any commands if the USB PCWD device does not exist */
if ( ( ! usb_pcwd ) | | ( ! usb_pcwd - > exists ) )
return - 1 ;
/* The USB PC Watchdog uses a 6 byte report format. The board currently uses
* only 3 of the six bytes of the report. */
buf [ 0 ] = cmd ; /* Byte 0 = CMD */
buf [ 1 ] = * msb ; /* Byte 1 = Data MSB */
buf [ 2 ] = * lsb ; /* Byte 2 = Data LSB */
buf [ 3 ] = buf [ 4 ] = buf [ 5 ] = 0 ; /* All other bytes not used */
dbg ( " sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x " ,
buf [ 0 ] , buf [ 1 ] , buf [ 2 ] ) ;
2008-08-06 20:19:41 +00:00
atomic_set ( & usb_pcwd - > cmd_received , 0 ) ;
2005-04-16 15:20:36 -07:00
if ( usb_control_msg ( usb_pcwd - > udev , usb_sndctrlpipe ( usb_pcwd - > udev , 0 ) ,
HID_REQ_SET_REPORT , HID_DT_REPORT ,
0x0200 , usb_pcwd - > interface_number , buf , sizeof ( buf ) ,
USB_COMMAND_TIMEOUT ) ! = sizeof ( buf ) ) {
dbg ( " usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x \n " , cmd , * msb , * lsb ) ;
}
/* wait till the usb card processed the command,
* with a max. timeout of USB_COMMAND_TIMEOUT */
got_response = 0 ;
for ( count = 0 ; ( count < USB_COMMAND_TIMEOUT ) & & ( ! got_response ) ; count + + ) {
mdelay ( 1 ) ;
2008-08-06 20:19:41 +00:00
if ( atomic_read ( & usb_pcwd - > cmd_received ) )
2005-04-16 15:20:36 -07:00
got_response = 1 ;
}
if ( ( got_response ) & & ( cmd = = usb_pcwd - > cmd_command ) ) {
/* read back response */
* msb = usb_pcwd - > cmd_data_msb ;
* lsb = usb_pcwd - > cmd_data_lsb ;
}
return got_response ;
}
static int usb_pcwd_start ( struct usb_pcwd_private * usb_pcwd )
{
unsigned char msb = 0x00 ;
unsigned char lsb = 0x00 ;
int retval ;
/* Enable Watchdog */
retval = usb_pcwd_send_command ( usb_pcwd , CMD_ENABLE_WATCHDOG , & msb , & lsb ) ;
if ( ( retval = = 0 ) | | ( lsb = = 0 ) ) {
printk ( KERN_ERR PFX " Card did not acknowledge enable attempt \n " ) ;
return - 1 ;
}
return 0 ;
}
static int usb_pcwd_stop ( struct usb_pcwd_private * usb_pcwd )
{
unsigned char msb = 0xA5 ;
unsigned char lsb = 0xC3 ;
int retval ;
/* Disable Watchdog */
retval = usb_pcwd_send_command ( usb_pcwd , CMD_DISABLE_WATCHDOG , & msb , & lsb ) ;
if ( ( retval = = 0 ) | | ( lsb ! = 0 ) ) {
printk ( KERN_ERR PFX " Card did not acknowledge disable attempt \n " ) ;
return - 1 ;
}
return 0 ;
}
static int usb_pcwd_keepalive ( struct usb_pcwd_private * usb_pcwd )
{
unsigned char dummy ;
/* Re-trigger Watchdog */
usb_pcwd_send_command ( usb_pcwd , CMD_TRIGGER , & dummy , & dummy ) ;
return 0 ;
}
static int usb_pcwd_set_heartbeat ( struct usb_pcwd_private * usb_pcwd , int t )
{
unsigned char msb = t / 256 ;
unsigned char lsb = t % 256 ;
if ( ( t < 0x0001 ) | | ( t > 0xFFFF ) )
return - EINVAL ;
/* Write new heartbeat to watchdog */
usb_pcwd_send_command ( usb_pcwd , CMD_WRITE_WATCHDOG_TIMEOUT , & msb , & lsb ) ;
heartbeat = t ;
return 0 ;
}
static int usb_pcwd_get_temperature ( struct usb_pcwd_private * usb_pcwd , int * temperature )
{
unsigned char msb , lsb ;
usb_pcwd_send_command ( usb_pcwd , CMD_READ_TEMP , & msb , & lsb ) ;
/*
* Convert celsius to fahrenheit, since this was
* the decided 'standard' for this return value.
*/
* temperature = ( lsb * 9 / 5 ) + 32 ;
return 0 ;
}
2006-05-21 12:48:44 +02:00
static int usb_pcwd_get_timeleft ( struct usb_pcwd_private * usb_pcwd , int * time_left )
{
unsigned char msb , lsb ;
/* Read the time that's left before rebooting */
/* Note: if the board is not yet armed then we will read 0xFFFF */
usb_pcwd_send_command ( usb_pcwd , CMD_READ_WATCHDOG_TIMEOUT , & msb , & lsb ) ;
* time_left = ( msb < < 8 ) + lsb ;
return 0 ;
}
2005-04-16 15:20:36 -07:00
/*
* /dev/watchdog handling
*/
static ssize_t usb_pcwd_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
{
/* See if we got the magic character 'V' and reload the timer */
if ( len ) {
if ( ! nowayout ) {
size_t i ;
/* note: just in case someone wrote the magic character
* five months ago... */
expect_release = 0 ;
/* scan to see whether or not we got the magic character */
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
2008-08-06 20:19:41 +00:00
if ( get_user ( c , data + i ) )
2005-04-16 15:20:36 -07:00
return - EFAULT ;
if ( c = = ' V ' )
expect_release = 42 ;
}
}
/* someone wrote to us, we should reload the timer */
usb_pcwd_keepalive ( usb_pcwd_device ) ;
}
return len ;
}
2008-07-03 23:51:32 -07:00
static long usb_pcwd_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2005-04-16 15:20:36 -07:00
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
static struct watchdog_info ident = {
. options = WDIOF_KEEPALIVEPING |
WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE ,
. firmware_version = 1 ,
. identity = DRIVER_NAME ,
} ;
switch ( cmd ) {
2008-07-17 18:08:47 +00:00
case WDIOC_GETSUPPORT :
2008-08-06 20:19:41 +00:00
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
2005-04-16 15:20:36 -07:00
2008-07-17 18:08:47 +00:00
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
2005-04-16 15:20:36 -07:00
2008-07-17 18:08:47 +00:00
case WDIOC_GETTEMP :
{
int temperature ;
2005-04-16 15:20:36 -07:00
2008-07-17 18:08:47 +00:00
if ( usb_pcwd_get_temperature ( usb_pcwd_device , & temperature ) )
return - EFAULT ;
2005-04-16 15:20:36 -07:00
2008-07-17 18:08:47 +00:00
return put_user ( temperature , p ) ;
}
case WDIOC_SETOPTIONS :
{
int new_options , retval = - EINVAL ;
2008-08-06 20:19:41 +00:00
if ( get_user ( new_options , p ) )
2008-07-17 18:08:47 +00:00
return - EFAULT ;
if ( new_options & WDIOS_DISABLECARD ) {
usb_pcwd_stop ( usb_pcwd_device ) ;
retval = 0 ;
2005-04-16 15:20:36 -07:00
}
2008-07-17 18:08:47 +00:00
if ( new_options & WDIOS_ENABLECARD ) {
usb_pcwd_start ( usb_pcwd_device ) ;
retval = 0 ;
2005-04-16 15:20:36 -07:00
}
2008-07-17 18:08:47 +00:00
return retval ;
}
2005-04-16 15:20:36 -07:00
2008-07-18 11:41:17 +00:00
case WDIOC_KEEPALIVE :
usb_pcwd_keepalive ( usb_pcwd_device ) ;
return 0 ;
2008-07-17 18:08:47 +00:00
case WDIOC_SETTIMEOUT :
{
int new_heartbeat ;
2005-04-16 15:20:36 -07:00
2008-07-17 18:08:47 +00:00
if ( get_user ( new_heartbeat , p ) )
return - EFAULT ;
2005-04-16 15:20:36 -07:00
2008-07-17 18:08:47 +00:00
if ( usb_pcwd_set_heartbeat ( usb_pcwd_device , new_heartbeat ) )
return - EINVAL ;
2005-04-16 15:20:36 -07:00
2008-07-17 18:08:47 +00:00
usb_pcwd_keepalive ( usb_pcwd_device ) ;
/* Fall */
}
2005-04-16 15:20:36 -07:00
2008-07-17 18:08:47 +00:00
case WDIOC_GETTIMEOUT :
return put_user ( heartbeat , p ) ;
2006-05-21 12:48:44 +02:00
2008-07-17 18:08:47 +00:00
case WDIOC_GETTIMELEFT :
{
int time_left ;
2006-05-21 12:48:44 +02:00
2008-07-17 18:08:47 +00:00
if ( usb_pcwd_get_timeleft ( usb_pcwd_device , & time_left ) )
return - EFAULT ;
2006-05-21 12:48:44 +02:00
2008-07-17 18:08:47 +00:00
return put_user ( time_left , p ) ;
}
default :
return - ENOTTY ;
2005-04-16 15:20:36 -07:00
}
}
static int usb_pcwd_open ( struct inode * inode , struct file * file )
{
/* /dev/watchdog can only be opened once */
if ( test_and_set_bit ( 0 , & is_active ) )
return - EBUSY ;
/* Activate */
usb_pcwd_start ( usb_pcwd_device ) ;
usb_pcwd_keepalive ( usb_pcwd_device ) ;
return nonseekable_open ( inode , file ) ;
}
static int usb_pcwd_release ( struct inode * inode , struct file * file )
{
/*
* Shut off the timer.
*/
if ( expect_release = = 42 ) {
usb_pcwd_stop ( usb_pcwd_device ) ;
} else {
printk ( KERN_CRIT PFX " Unexpected close, not stopping watchdog! \n " ) ;
usb_pcwd_keepalive ( usb_pcwd_device ) ;
}
expect_release = 0 ;
clear_bit ( 0 , & is_active ) ;
return 0 ;
}
/*
* /dev/temperature handling
*/
static ssize_t usb_pcwd_temperature_read ( struct file * file , char __user * data ,
size_t len , loff_t * ppos )
{
int temperature ;
if ( usb_pcwd_get_temperature ( usb_pcwd_device , & temperature ) )
return - EFAULT ;
if ( copy_to_user ( data , & temperature , 1 ) )
return - EFAULT ;
return 1 ;
}
static int usb_pcwd_temperature_open ( struct inode * inode , struct file * file )
{
return nonseekable_open ( inode , file ) ;
}
static int usb_pcwd_temperature_release ( struct inode * inode , struct file * file )
{
return 0 ;
}
/*
* Notify system
*/
static int usb_pcwd_notify_sys ( struct notifier_block * this , unsigned long code , void * unused )
{
2008-08-06 20:19:41 +00:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
usb_pcwd_stop ( usb_pcwd_device ) ; /* Turn the WDT off */
2005-04-16 15:20:36 -07:00
return NOTIFY_DONE ;
}
/*
* Kernel Interfaces
*/
2006-07-03 00:24:21 -07:00
static const struct file_operations usb_pcwd_fops = {
2005-04-16 15:20:36 -07:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = usb_pcwd_write ,
2008-07-03 23:51:32 -07:00
. unlocked_ioctl = usb_pcwd_ioctl ,
2005-04-16 15:20:36 -07:00
. open = usb_pcwd_open ,
. release = usb_pcwd_release ,
} ;
static struct miscdevice usb_pcwd_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & usb_pcwd_fops ,
} ;
2006-07-03 00:24:21 -07:00
static const struct file_operations usb_pcwd_temperature_fops = {
2005-04-16 15:20:36 -07:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. read = usb_pcwd_temperature_read ,
. open = usb_pcwd_temperature_open ,
. release = usb_pcwd_temperature_release ,
} ;
static struct miscdevice usb_pcwd_temperature_miscdev = {
. minor = TEMP_MINOR ,
. name = " temperature " ,
. fops = & usb_pcwd_temperature_fops ,
} ;
static struct notifier_block usb_pcwd_notifier = {
. notifier_call = usb_pcwd_notify_sys ,
} ;
/**
* usb_pcwd_delete
*/
2008-08-06 20:19:41 +00:00
static inline void usb_pcwd_delete ( struct usb_pcwd_private * usb_pcwd )
2005-04-16 15:20:36 -07:00
{
2006-11-08 15:34:02 +01:00
usb_free_urb ( usb_pcwd - > intr_urb ) ;
2005-04-16 15:20:36 -07:00
if ( usb_pcwd - > intr_buffer ! = NULL )
usb_buffer_free ( usb_pcwd - > udev , usb_pcwd - > intr_size ,
usb_pcwd - > intr_buffer , usb_pcwd - > intr_dma ) ;
2008-08-06 20:19:41 +00:00
kfree ( usb_pcwd ) ;
2005-04-16 15:20:36 -07:00
}
/**
* usb_pcwd_probe
*
* Called by the usb core when a new device is connected that it thinks
* this driver might be interested in.
*/
static int usb_pcwd_probe ( struct usb_interface * interface , const struct usb_device_id * id )
{
struct usb_device * udev = interface_to_usbdev ( interface ) ;
struct usb_host_interface * iface_desc ;
struct usb_endpoint_descriptor * endpoint ;
struct usb_pcwd_private * usb_pcwd = NULL ;
int pipe , maxp ;
int retval = - ENOMEM ;
int got_fw_rev ;
unsigned char fw_rev_major , fw_rev_minor ;
char fw_ver_str [ 20 ] ;
unsigned char option_switches , dummy ;
cards_found + + ;
if ( cards_found > 1 ) {
printk ( KERN_ERR PFX " This driver only supports 1 device \n " ) ;
return - ENODEV ;
}
/* get the active interface descriptor */
iface_desc = interface - > cur_altsetting ;
/* check out that we have a HID device */
if ( ! ( iface_desc - > desc . bInterfaceClass = = USB_CLASS_HID ) ) {
printk ( KERN_ERR PFX " The device isn't a Human Interface Device \n " ) ;
return - ENODEV ;
}
/* check out the endpoint: it has to be Interrupt & IN */
endpoint = & iface_desc - > endpoint [ 0 ] . desc ;
if ( ! ( ( endpoint - > bEndpointAddress & USB_DIR_IN ) & &
( ( endpoint - > bmAttributes & USB_ENDPOINT_XFERTYPE_MASK )
= = USB_ENDPOINT_XFER_INT ) ) ) {
/* we didn't find a Interrupt endpoint with direction IN */
printk ( KERN_ERR PFX " Couldn't find an INTR & IN endpoint \n " ) ;
return - ENODEV ;
}
/* get a handle to the interrupt data pipe */
pipe = usb_rcvintpipe ( udev , endpoint - > bEndpointAddress ) ;
maxp = usb_maxpacket ( udev , pipe , usb_pipeout ( pipe ) ) ;
/* allocate memory for our device and initialize it */
2008-08-06 20:19:41 +00:00
usb_pcwd = kzalloc ( sizeof ( struct usb_pcwd_private ) , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( usb_pcwd = = NULL ) {
printk ( KERN_ERR PFX " Out of memory \n " ) ;
goto error ;
}
usb_pcwd_device = usb_pcwd ;
2007-05-03 20:15:14 -07:00
mutex_init ( & usb_pcwd - > mtx ) ;
2005-04-16 15:20:36 -07:00
usb_pcwd - > udev = udev ;
usb_pcwd - > interface = interface ;
usb_pcwd - > interface_number = iface_desc - > desc . bInterfaceNumber ;
usb_pcwd - > intr_size = ( le16_to_cpu ( endpoint - > wMaxPacketSize ) > 8 ? le16_to_cpu ( endpoint - > wMaxPacketSize ) : 8 ) ;
/* set up the memory buffer's */
2008-08-06 20:19:41 +00:00
usb_pcwd - > intr_buffer = usb_buffer_alloc ( udev , usb_pcwd - > intr_size , GFP_ATOMIC , & usb_pcwd - > intr_dma ) ;
if ( ! usb_pcwd - > intr_buffer ) {
2005-04-16 15:20:36 -07:00
printk ( KERN_ERR PFX " Out of memory \n " ) ;
goto error ;
}
/* allocate the urb's */
usb_pcwd - > intr_urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! usb_pcwd - > intr_urb ) {
printk ( KERN_ERR PFX " Out of memory \n " ) ;
goto error ;
}
/* initialise the intr urb's */
usb_fill_int_urb ( usb_pcwd - > intr_urb , udev , pipe ,
usb_pcwd - > intr_buffer , usb_pcwd - > intr_size ,
usb_pcwd_intr_done , usb_pcwd , endpoint - > bInterval ) ;
usb_pcwd - > intr_urb - > transfer_dma = usb_pcwd - > intr_dma ;
usb_pcwd - > intr_urb - > transfer_flags | = URB_NO_TRANSFER_DMA_MAP ;
/* register our interrupt URB with the USB system */
if ( usb_submit_urb ( usb_pcwd - > intr_urb , GFP_KERNEL ) ) {
printk ( KERN_ERR PFX " Problem registering interrupt URB \n " ) ;
retval = - EIO ; /* failure */
goto error ;
}
/* The device exists and can be communicated with */
usb_pcwd - > exists = 1 ;
/* disable card */
usb_pcwd_stop ( usb_pcwd ) ;
/* Get the Firmware Version */
got_fw_rev = usb_pcwd_send_command ( usb_pcwd , CMD_GET_FIRMWARE_VERSION , & fw_rev_major , & fw_rev_minor ) ;
2008-08-06 20:19:41 +00:00
if ( got_fw_rev )
2005-04-16 15:20:36 -07:00
sprintf ( fw_ver_str , " %u.%02u " , fw_rev_major , fw_rev_minor ) ;
2008-08-06 20:19:41 +00:00
else
2005-04-16 15:20:36 -07:00
sprintf ( fw_ver_str , " <card no answer> " ) ;
printk ( KERN_INFO PFX " Found card (Firmware: %s) with temp option \n " ,
fw_ver_str ) ;
/* Get switch settings */
usb_pcwd_send_command ( usb_pcwd , CMD_GET_DIP_SWITCH_SETTINGS , & dummy , & option_switches ) ;
printk ( KERN_INFO PFX " Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s \n " ,
option_switches ,
( ( option_switches & 0x10 ) ? " ON " : " OFF " ) ,
( ( option_switches & 0x08 ) ? " ON " : " OFF " ) ) ;
2007-01-08 22:45:30 +01:00
/* If heartbeat = 0 then we use the heartbeat from the dip-switches */
if ( heartbeat = = 0 )
heartbeat = heartbeat_tbl [ ( option_switches & 0x07 ) ] ;
2005-04-16 15:20:36 -07:00
/* Check that the heartbeat value is within it's range ; if not reset to the default */
if ( usb_pcwd_set_heartbeat ( usb_pcwd , heartbeat ) ) {
usb_pcwd_set_heartbeat ( usb_pcwd , WATCHDOG_HEARTBEAT ) ;
printk ( KERN_INFO PFX " heartbeat value must be 0<heartbeat<65536, using %d \n " ,
WATCHDOG_HEARTBEAT ) ;
}
retval = register_reboot_notifier ( & usb_pcwd_notifier ) ;
if ( retval ! = 0 ) {
printk ( KERN_ERR PFX " cannot register reboot notifier (err=%d) \n " ,
retval ) ;
goto error ;
}
retval = misc_register ( & usb_pcwd_temperature_miscdev ) ;
if ( retval ! = 0 ) {
printk ( KERN_ERR PFX " cannot register miscdev on minor=%d (err=%d) \n " ,
TEMP_MINOR , retval ) ;
goto err_out_unregister_reboot ;
}
retval = misc_register ( & usb_pcwd_miscdev ) ;
if ( retval ! = 0 ) {
printk ( KERN_ERR PFX " cannot register miscdev on minor=%d (err=%d) \n " ,
WATCHDOG_MINOR , retval ) ;
goto err_out_misc_deregister ;
}
/* we can register the device now, as it is ready */
2008-08-06 20:19:41 +00:00
usb_set_intfdata ( interface , usb_pcwd ) ;
2005-04-16 15:20:36 -07:00
printk ( KERN_INFO PFX " initialized. heartbeat=%d sec (nowayout=%d) \n " ,
heartbeat , nowayout ) ;
return 0 ;
err_out_misc_deregister :
misc_deregister ( & usb_pcwd_temperature_miscdev ) ;
err_out_unregister_reboot :
unregister_reboot_notifier ( & usb_pcwd_notifier ) ;
error :
2006-03-10 19:04:38 +01:00
if ( usb_pcwd )
usb_pcwd_delete ( usb_pcwd ) ;
2005-04-16 15:20:36 -07:00
usb_pcwd_device = NULL ;
return retval ;
}
/**
* usb_pcwd_disconnect
*
* Called by the usb core when the device is removed from the system.
*
* This routine guarantees that the driver will not submit any more urbs
* by clearing dev->udev.
*/
static void usb_pcwd_disconnect ( struct usb_interface * interface )
{
struct usb_pcwd_private * usb_pcwd ;
/* prevent races with open() */
2006-03-23 03:00:27 -08:00
mutex_lock ( & disconnect_mutex ) ;
2005-04-16 15:20:36 -07:00
2008-08-06 20:19:41 +00:00
usb_pcwd = usb_get_intfdata ( interface ) ;
usb_set_intfdata ( interface , NULL ) ;
2005-04-16 15:20:36 -07:00
2007-05-03 20:15:14 -07:00
mutex_lock ( & usb_pcwd - > mtx ) ;
2005-04-16 15:20:36 -07:00
/* Stop the timer before we leave */
if ( ! nowayout )
usb_pcwd_stop ( usb_pcwd ) ;
/* We should now stop communicating with the USB PCWD device */
usb_pcwd - > exists = 0 ;
/* Deregister */
misc_deregister ( & usb_pcwd_miscdev ) ;
misc_deregister ( & usb_pcwd_temperature_miscdev ) ;
unregister_reboot_notifier ( & usb_pcwd_notifier ) ;
2007-05-03 20:15:14 -07:00
mutex_unlock ( & usb_pcwd - > mtx ) ;
2005-04-16 15:20:36 -07:00
/* Delete the USB PCWD device */
usb_pcwd_delete ( usb_pcwd ) ;
cards_found - - ;
2006-03-23 03:00:27 -08:00
mutex_unlock ( & disconnect_mutex ) ;
2005-04-16 15:20:36 -07:00
printk ( KERN_INFO PFX " USB PC Watchdog disconnected \n " ) ;
}
/**
* usb_pcwd_init
*/
static int __init usb_pcwd_init ( void )
{
int result ;
/* register this driver with the USB subsystem */
result = usb_register ( & usb_pcwd_driver ) ;
if ( result ) {
printk ( KERN_ERR PFX " usb_register failed. Error number %d \n " ,
result ) ;
return result ;
}
printk ( KERN_INFO PFX DRIVER_DESC " v " DRIVER_VERSION " ( " DRIVER_DATE " ) \n " ) ;
return 0 ;
}
/**
* usb_pcwd_exit
*/
static void __exit usb_pcwd_exit ( void )
{
/* deregister this driver with the USB subsystem */
usb_deregister ( & usb_pcwd_driver ) ;
}
2008-08-06 20:19:41 +00:00
module_init ( usb_pcwd_init ) ;
module_exit ( usb_pcwd_exit ) ;