2006-09-20 22:36:48 +04:00
/* Copyright (c) 2006 Coraid, Inc. See COPYING for GPL terms. */
2005-04-17 02:20:36 +04:00
/*
* aoechr . c
* AoE character device driver
*/
# include <linux/hdreg.h>
# include <linux/blkdev.h>
# include "aoe.h"
enum {
//MINOR_STAT = 1, (moved to sysfs)
MINOR_ERR = 2 ,
MINOR_DISCOVER ,
MINOR_INTERFACES ,
2006-01-19 21:46:19 +03:00
MINOR_REVALIDATE ,
2005-04-17 02:20:36 +04:00
MSGSZ = 2048 ,
NMSG = 100 , /* message backlog to retain */
} ;
struct aoe_chardev {
ulong minor ;
char name [ 32 ] ;
} ;
enum { EMFL_VALID = 1 } ;
struct ErrMsg {
short flags ;
short len ;
char * msg ;
} ;
static struct ErrMsg emsgs [ NMSG ] ;
static int emsgs_head_idx , emsgs_tail_idx ;
static struct semaphore emsgs_sema ;
static spinlock_t emsgs_lock ;
static int nblocked_emsgs_readers ;
2005-03-23 20:52:10 +03:00
static struct class * aoe_class ;
2005-04-17 02:20:36 +04:00
static struct aoe_chardev chardevs [ ] = {
{ MINOR_ERR , " err " } ,
{ MINOR_DISCOVER , " discover " } ,
{ MINOR_INTERFACES , " interfaces " } ,
2006-01-19 21:46:19 +03:00
{ MINOR_REVALIDATE , " revalidate " } ,
2005-04-17 02:20:36 +04:00
} ;
static int
discover ( void )
{
aoecmd_cfg ( 0xffff , 0xff ) ;
return 0 ;
}
static int
interfaces ( const char __user * str , size_t size )
{
if ( set_aoe_iflist ( str , size ) ) {
2006-09-20 22:36:49 +04:00
eprintk ( " could not set interface list: too many interfaces \n " ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
return 0 ;
}
2006-01-19 21:46:19 +03:00
static int
revalidate ( const char __user * str , size_t size )
{
int major , minor , n ;
ulong flags ;
struct aoedev * d ;
char buf [ 16 ] ;
if ( size > = sizeof buf )
return - EINVAL ;
buf [ sizeof buf - 1 ] = ' \0 ' ;
if ( copy_from_user ( buf , str , size ) )
return - EFAULT ;
/* should be e%d.%d format */
n = sscanf ( buf , " e%d.%d " , & major , & minor ) ;
if ( n ! = 2 ) {
2006-09-20 22:36:49 +04:00
eprintk ( " invalid device specification \n " ) ;
2006-01-19 21:46:19 +03:00
return - EINVAL ;
}
d = aoedev_by_aoeaddr ( major , minor ) ;
if ( ! d )
return - EINVAL ;
spin_lock_irqsave ( & d - > lock , flags ) ;
2006-09-20 22:36:49 +04:00
d - > flags & = ~ DEVFL_MAXBCNT ;
2006-01-19 21:46:19 +03:00
d - > flags | = DEVFL_PAUSE ;
spin_unlock_irqrestore ( & d - > lock , flags ) ;
aoecmd_cfg ( major , minor ) ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
void
aoechr_error ( char * msg )
{
struct ErrMsg * em ;
char * mp ;
ulong flags , n ;
n = strlen ( msg ) ;
spin_lock_irqsave ( & emsgs_lock , flags ) ;
em = emsgs + emsgs_tail_idx ;
if ( ( em - > flags & EMFL_VALID ) ) {
bail : spin_unlock_irqrestore ( & emsgs_lock , flags ) ;
return ;
}
mp = kmalloc ( n , GFP_ATOMIC ) ;
if ( mp = = NULL ) {
2006-09-20 22:36:49 +04:00
eprintk ( " allocation failure, len=%ld \n " , n ) ;
2005-04-17 02:20:36 +04:00
goto bail ;
}
memcpy ( mp , msg , n ) ;
em - > msg = mp ;
em - > flags | = EMFL_VALID ;
em - > len = n ;
emsgs_tail_idx + + ;
emsgs_tail_idx % = ARRAY_SIZE ( emsgs ) ;
spin_unlock_irqrestore ( & emsgs_lock , flags ) ;
if ( nblocked_emsgs_readers )
up ( & emsgs_sema ) ;
}
static ssize_t
aoechr_write ( struct file * filp , const char __user * buf , size_t cnt , loff_t * offp )
{
int ret = - EINVAL ;
switch ( ( unsigned long ) filp - > private_data ) {
default :
2006-09-20 22:36:49 +04:00
iprintk ( " can't write to that file. \n " ) ;
2005-04-17 02:20:36 +04:00
break ;
case MINOR_DISCOVER :
ret = discover ( ) ;
break ;
case MINOR_INTERFACES :
ret = interfaces ( buf , cnt ) ;
break ;
2006-01-19 21:46:19 +03:00
case MINOR_REVALIDATE :
ret = revalidate ( buf , cnt ) ;
2005-04-17 02:20:36 +04:00
}
if ( ret = = 0 )
ret = cnt ;
return ret ;
}
static int
aoechr_open ( struct inode * inode , struct file * filp )
{
int n , i ;
2006-07-10 15:45:32 +04:00
n = iminor ( inode ) ;
2005-04-17 02:20:36 +04:00
filp - > private_data = ( void * ) ( unsigned long ) n ;
for ( i = 0 ; i < ARRAY_SIZE ( chardevs ) ; + + i )
if ( chardevs [ i ] . minor = = n )
return 0 ;
return - EINVAL ;
}
static int
aoechr_rel ( struct inode * inode , struct file * filp )
{
return 0 ;
}
static ssize_t
aoechr_read ( struct file * filp , char __user * buf , size_t cnt , loff_t * off )
{
unsigned long n ;
char * mp ;
struct ErrMsg * em ;
ssize_t len ;
ulong flags ;
n = ( unsigned long ) filp - > private_data ;
switch ( n ) {
case MINOR_ERR :
spin_lock_irqsave ( & emsgs_lock , flags ) ;
loop :
em = emsgs + emsgs_head_idx ;
if ( ( em - > flags & EMFL_VALID ) = = 0 ) {
if ( filp - > f_flags & O_NDELAY ) {
spin_unlock_irqrestore ( & emsgs_lock , flags ) ;
return - EAGAIN ;
}
nblocked_emsgs_readers + + ;
spin_unlock_irqrestore ( & emsgs_lock , flags ) ;
n = down_interruptible ( & emsgs_sema ) ;
spin_lock_irqsave ( & emsgs_lock , flags ) ;
nblocked_emsgs_readers - - ;
if ( n ) {
spin_unlock_irqrestore ( & emsgs_lock , flags ) ;
return - ERESTARTSYS ;
}
goto loop ;
}
if ( em - > len > cnt ) {
spin_unlock_irqrestore ( & emsgs_lock , flags ) ;
return - EAGAIN ;
}
mp = em - > msg ;
len = em - > len ;
em - > msg = NULL ;
em - > flags & = ~ EMFL_VALID ;
emsgs_head_idx + + ;
emsgs_head_idx % = ARRAY_SIZE ( emsgs ) ;
spin_unlock_irqrestore ( & emsgs_lock , flags ) ;
n = copy_to_user ( buf , mp , len ) ;
kfree ( mp ) ;
return n = = 0 ? len : - EFAULT ;
default :
return - EFAULT ;
}
}
static struct file_operations aoe_fops = {
. write = aoechr_write ,
. read = aoechr_read ,
. open = aoechr_open ,
. release = aoechr_rel ,
. owner = THIS_MODULE ,
} ;
int __init
aoechr_init ( void )
{
int n , i ;
n = register_chrdev ( AOE_MAJOR , " aoechr " , & aoe_fops ) ;
if ( n < 0 ) {
2006-09-20 22:36:49 +04:00
eprintk ( " can't register char device \n " ) ;
2005-04-17 02:20:36 +04:00
return n ;
}
sema_init ( & emsgs_sema , 0 ) ;
spin_lock_init ( & emsgs_lock ) ;
2005-03-23 20:52:10 +03:00
aoe_class = class_create ( THIS_MODULE , " aoe " ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( aoe_class ) ) {
unregister_chrdev ( AOE_MAJOR , " aoechr " ) ;
return PTR_ERR ( aoe_class ) ;
}
for ( i = 0 ; i < ARRAY_SIZE ( chardevs ) ; + + i )
2005-10-28 09:25:43 +04:00
class_device_create ( aoe_class , NULL ,
2005-04-17 02:20:36 +04:00
MKDEV ( AOE_MAJOR , chardevs [ i ] . minor ) ,
NULL , chardevs [ i ] . name ) ;
return 0 ;
}
void
aoechr_exit ( void )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( chardevs ) ; + + i )
2005-03-23 20:52:10 +03:00
class_device_destroy ( aoe_class , MKDEV ( AOE_MAJOR , chardevs [ i ] . minor ) ) ;
class_destroy ( aoe_class ) ;
2005-04-17 02:20:36 +04:00
unregister_chrdev ( AOE_MAJOR , " aoechr " ) ;
}