/************************************************************************\
## ##
## Creation Date: 17 Mar 2007 ##
## Last Update: 18 Mar 2007 ##
## Author: XInstruments ##
## ##
\************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <asm/uaccess.h>
#include "usmcdrv-driver.h"
#include "usmctypes.h"
#include "usmcpkt.h"
#include <linux/slab.h>
#if HAVE_UNLOCKED_IOCTL
#include <linux/mutex.h>
#else
#include <linux/smp_lock.h>
#endif
#define info( format, arg...) printk(KERN_INFO USMC_DEV_NAME ": " format "\n", ## arg)
#define err( format, arg...) printk(KERN_ERR USMC_DEV_NAME ": " format "\n", ## arg)
MODULE_DESCRIPTION ( "MicroSMC driver for Linux" );
MODULE_AUTHOR ( "XInstruments" );
MODULE_LICENSE ( "GPL" );
MODULE_VERSION ( "1.0" );
static int num_devices = 0; // Number of currently connected devices.
/*-----------------------------------------------------
Init/Deinit section
-----------------------------------------------------*/
static int usmcdrv_init_module ( void )
{
int res;
printk ( KERN_DEBUG "Module usmcdrv init\n" );
res = usb_register ( &usmc_driver );
if ( res )
err ( "usb_register failed. Error number %d", res );
return res;
}
static void usmcdrv_exit_module ( void )
{
printk ( KERN_DEBUG "Module usmcdrv exit\n" );
usb_deregister ( &usmc_driver );
}
module_init ( usmcdrv_init_module );
module_exit ( usmcdrv_exit_module );
/*-----------------------------------------------------
Connect/Disconnect section
-----------------------------------------------------*/
static int usmc_probe ( struct usb_interface * interface,
const struct usb_device_id * id )
{
struct usb_usmc * dev = NULL;
int retval = -ENOMEM;
dev = kzalloc ( sizeof ( *dev ), GFP_KERNEL );
if ( dev == NULL ) {
err ( "Out of memory" );
goto error;
}
kref_init ( &dev -> kref );
sema_init ( &dev -> limit_sem, WRITES_IN_FLIGHT );
dev -> udev = usb_get_dev ( interface_to_usbdev ( interface ) );
dev -> interface = interface;
usb_set_intfdata ( interface, dev );
retval = usb_register_dev ( interface, &usmc_class );
if ( retval )
{
err ( "Not able to get a minor for this device." );
usb_set_intfdata ( interface, NULL );
goto error;
}
info ( USMC_DEV_NAME " device now attached to USBusmc-%d", interface -> minor );
num_devices++;
return 0;
error:
if ( dev )
kref_put ( &dev -> kref, usmc_delete );
return retval;
}
static void usmc_disconnect ( struct usb_interface * interface )
{
struct usb_usmc * dev;
int minor = interface -> minor;
#if HAVE_UNLOCKED_IOCTL
struct mutex fs_mutex;
mutex_init(&fs_mutex);
mutex_lock(&fs_mutex);
#else
lock_kernel();
#endif
dev = usb_get_intfdata ( interface );
usb_set_intfdata ( interface, NULL );
/* give back our minor */
usb_deregister_dev ( interface, &usmc_class );
num_devices--;
#if HAVE_UNLOCKED_IOCTL
mutex_unlock(&fs_mutex);
#else
unlock_kernel();
#endif
/* decrement our usage count */
kref_put ( &dev -> kref, usmc_delete );
info ( USMC_DEV_NAME " #%d now disconnected", minor );
}
/*-----------------------------------------------------
File operations section
-----------------------------------------------------*/
static int usmc_open ( struct inode * inode, struct file * file )
{
struct usb_usmc * dev;
struct usb_interface * interface;
int subminor;
int retval = 0;
info ( "usmc_open" );
subminor = iminor ( inode );
interface = usb_find_interface ( &usmc_driver, subminor );
if ( !interface )
{
err ( "%s - error, can't find device for minor %d",
__FUNCTION__, subminor );
retval = -ENODEV;
}
dev = usb_get_intfdata ( interface );
if ( !dev )
{
retval = -ENODEV;
}
/* increment our usage count for the device */
kref_get ( &dev -> kref );
/* save our object in the file's private structure */
file -> private_data = dev;
return retval;
}
static int usmc_release ( struct inode * inode, struct file * file )
{
struct usb_usmc * dev;
info ( "usmc_release" );
dev = ( struct usb_usmc * ) file -> private_data;
if ( dev == NULL )
return -ENODEV;
/* decrement the count on our device */
kref_put ( &dev -> kref, usmc_delete );
return 0;
}
static long usmc_ioctl ( struct file * file, unsigned int ioctl_num, unsigned long ioctl_param )
{
struct usb_usmc * dev;
char * user_buf;
char * kern_buf;
char * kern_data_buf;
__u32 dwTimeOut;
long dwRes;
int i;
unsigned int pipe0;
__u8 bRequestType;
__u8 bRequest;
__u16 wValue;
__u16 wIndex;
__u16 wLength;
dev = ( struct usb_usmc * ) file -> private_data;
user_buf = ( char * ) ioctl_param;
kern_buf = NULL;
dwTimeOut = 30000;
dwRes = USMC_SUCCESS;
switch ( ioctl_num )
{
case IOCTL_GET_DESCRIPTOR_CONFIGURATION:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_DESCRIPTOR_CONFIGURATION" );
bsp_GetDescriptor ( GET_DESCRIPTOR_CONFIGURATION,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_DESCRIPTOR_DEVICE:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_DESCRIPTOR_DEVICE" );
bsp_GetDescriptor ( IOCTL_GET_DESCRIPTOR_DEVICE,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_DESCRIPTOR_STRING:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_DESCRIPTOR_STRING" );
bsp_GetDescriptor ( IOCTL_GET_DESCRIPTOR_STRING,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_STATUS_DEVICE:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_STATUS_DEVICE" );
bsp_GetStatus ( IOCTL_GET_STATUS_DEVICE,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_STATUS_ENDPOINT:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_STATUS_ENDPOINT" );
bsp_GetStatus ( IOCTL_GET_STATUS_ENDPOINT,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_STATUS_INTERFACE:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_STATUS_INTERFACE" );
bsp_GetStatus ( IOCTL_GET_STATUS_INTERFACE,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_VERSION:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_VERSION" );
bsp_GetVersion ( &bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_SERIAL:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_SERIAL" );
bsp_GetSerial ( &bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_ENCODER_STATE:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_ENCODER_STATE" );
bsp_GetEncoderState ( &bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_STATE:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_STATE" );
bsp_GetState ( &bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GO_TO:
info ( "usmc_ioctl. ioctl_num: IOCTL_GO_TO" );
kern_buf = bsp_GoTo ( user_buf,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_SET_MODE:
info ( "usmc_ioctl. ioctl_num: IOCTL_SET_MODE" );
kern_buf = bsp_SetMode ( user_buf,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_SET_PARAMETERS:
info ( "usmc_ioctl. ioctl_num: IOCTL_SET_PARAMETERS" );
kern_buf = bsp_SetParameters ( user_buf,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_DOWNLOAD:
info ( "usmc_ioctl. ioctl_num: IOCTL_DOWNLOAD" );
kern_buf = bsp_Download ( user_buf,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_SET_SERIAL:
info ( "usmc_ioctl. ioctl_num: IOCTL_SET_SERIAL" );
kern_buf = bsp_SetSerial ( user_buf,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_SET_CURRENT_POSITION:
info ( "usmc_ioctl. ioctl_num: IOCTL_SET_CURRENT_POSITION" );
bsp_SetCurrentPosition ( user_buf,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_STOP_STEP_MOTOR:
info ( "usmc_ioctl. ioctl_num: IOCTL_STOP_STEP_MOTOR" );
bsp_StopStepMotor ( &bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_EMULATE_BUTTONS:
info ( "usmc_ioctl. ioctl_num: IOCTL_EMULATE_BUTTONS" );
bsp_EmulateButtons ( user_buf,
&bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_SAVE_PARAMETERS:
info ( "usmc_ioctl. ioctl_num: IOCTL_SAVE_PARAMETERS" );
bsp_SaveParameters ( &bRequestType,
&bRequest,
&wValue,
&wIndex,
&wLength );
break;
case IOCTL_GET_NOD:
info ( "usmc_ioctl. ioctl_num: IOCTL_GET_NOD" );
dwRes = 1;
kern_buf = user_to_kernel ( user_buf, 1 );
kern_buf [0] = num_devices;
break;
default:
err ( "usmc_ioctl. unknown ioctl_num: %d", ioctl_num );
dwRes = -1;
break;
}
if ( dwRes == USMC_SUCCESS )
{
if ( ( bRequestType & USB_DIR_IN ) != USB_DIR_IN )
{
kern_data_buf = wLength > 0 ? kern_buf + 4 : NULL;
pipe0 = usb_sndctrlpipe ( dev -> udev, 0 );
} else {
kern_buf = ( char * ) kzalloc ( ( ssize_t ) wLength, GFP_KERNEL );
kern_data_buf = kern_buf;
pipe0 = usb_rcvctrlpipe ( dev -> udev, 0 );
}
dwRes = usb_control_msg ( dev -> udev,
pipe0,
bRequest,
bRequestType,
wValue,
wIndex,
kern_data_buf,
wLength,
dwTimeOut );
if ( ( bRequestType & USB_DIR_IN ) == USB_DIR_IN )
{
for ( i = 0 ; i < wLength ; i++ )
put_user ( kern_data_buf [i], user_buf + i );
}
} else if ( dwRes > 0 ) {
for ( i = 0 ; i < dwRes ; i++ )
put_user ( kern_buf [i], user_buf + i );
}
kfree ( kern_buf );
if ( dwRes < 0 ) {
err ( "usmc_ioctl failed: dwRes: %d", (int) dwRes );
} else {
info ( "transmitted %d bytes over control pipe0", (int) dwRes );
}
return dwRes;
}
/*-----------------------------------------------------
Util functions section
-----------------------------------------------------*/
static void usmc_delete ( struct kref * kref )
{
struct usb_usmc * dev = to_usmc_dev ( kref );
usb_put_dev ( dev -> udev );
kfree ( dev );
}