2006-04-05 12:45:45 +04:00
/*
* Copyright ( C ) 2005 MIPS Technologies , Inc . All rights reserved .
*
* This program is free software ; you can distribute it and / or modify it
* under the terms of the GNU General Public License ( Version 2 ) as
* published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License
* for more details .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 59 Temple Place - Suite 330 , Boston MA 02111 - 1307 , USA .
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
2007-04-25 18:08:57 +04:00
# include <linux/sched.h>
2006-04-05 12:45:45 +04:00
# include <linux/unistd.h>
# include <linux/file.h>
# include <linux/fs.h>
# include <linux/syscalls.h>
# include <linux/workqueue.h>
# include <linux/errno.h>
# include <linux/list.h>
# include <asm/vpe.h>
# include <asm/rtlx.h>
# include <asm/kspd.h>
static struct workqueue_struct * workqueue = NULL ;
static struct work_struct work ;
extern unsigned long cpu_khz ;
struct mtsp_syscall {
int cmd ;
unsigned char abi ;
unsigned char size ;
} ;
struct mtsp_syscall_ret {
int retval ;
int errno ;
} ;
struct mtsp_syscall_generic {
int arg0 ;
int arg1 ;
int arg2 ;
int arg3 ;
int arg4 ;
int arg5 ;
int arg6 ;
} ;
static struct list_head kspd_notifylist ;
static int sp_stopping = 0 ;
/* these should match with those in the SDE kit */
# define MTSP_SYSCALL_BASE 0
# define MTSP_SYSCALL_EXIT (MTSP_SYSCALL_BASE + 0)
# define MTSP_SYSCALL_OPEN (MTSP_SYSCALL_BASE + 1)
# define MTSP_SYSCALL_READ (MTSP_SYSCALL_BASE + 2)
# define MTSP_SYSCALL_WRITE (MTSP_SYSCALL_BASE + 3)
# define MTSP_SYSCALL_CLOSE (MTSP_SYSCALL_BASE + 4)
# define MTSP_SYSCALL_LSEEK32 (MTSP_SYSCALL_BASE + 5)
# define MTSP_SYSCALL_ISATTY (MTSP_SYSCALL_BASE + 6)
# define MTSP_SYSCALL_GETTIME (MTSP_SYSCALL_BASE + 7)
# define MTSP_SYSCALL_PIPEFREQ (MTSP_SYSCALL_BASE + 8)
# define MTSP_SYSCALL_GETTOD (MTSP_SYSCALL_BASE + 9)
2007-03-13 18:10:50 +03:00
# define MTSP_SYSCALL_IOCTL (MTSP_SYSCALL_BASE + 10)
2006-04-05 12:45:45 +04:00
# define MTSP_O_RDONLY 0x0000
# define MTSP_O_WRONLY 0x0001
# define MTSP_O_RDWR 0x0002
# define MTSP_O_NONBLOCK 0x0004
# define MTSP_O_APPEND 0x0008
# define MTSP_O_SHLOCK 0x0010
# define MTSP_O_EXLOCK 0x0020
# define MTSP_O_ASYNC 0x0040
# define MTSP_O_FSYNC O_SYNC
# define MTSP_O_NOFOLLOW 0x0100
# define MTSP_O_SYNC 0x0080
# define MTSP_O_CREAT 0x0200
# define MTSP_O_TRUNC 0x0400
# define MTSP_O_EXCL 0x0800
# define MTSP_O_BINARY 0x8000
2007-07-27 22:31:10 +04:00
extern int tclimit ;
2006-04-05 12:45:45 +04:00
struct apsp_table {
int sp ;
int ap ;
} ;
/* we might want to do the mode flags too */
struct apsp_table open_flags_table [ ] = {
{ MTSP_O_RDWR , O_RDWR } ,
{ MTSP_O_WRONLY , O_WRONLY } ,
{ MTSP_O_CREAT , O_CREAT } ,
{ MTSP_O_TRUNC , O_TRUNC } ,
{ MTSP_O_NONBLOCK , O_NONBLOCK } ,
{ MTSP_O_APPEND , O_APPEND } ,
{ MTSP_O_NOFOLLOW , O_NOFOLLOW }
} ;
struct apsp_table syscall_command_table [ ] = {
{ MTSP_SYSCALL_OPEN , __NR_open } ,
{ MTSP_SYSCALL_CLOSE , __NR_close } ,
{ MTSP_SYSCALL_READ , __NR_read } ,
{ MTSP_SYSCALL_WRITE , __NR_write } ,
2007-03-13 18:10:50 +03:00
{ MTSP_SYSCALL_LSEEK32 , __NR_lseek } ,
{ MTSP_SYSCALL_IOCTL , __NR_ioctl }
2006-04-05 12:45:45 +04:00
} ;
static int sp_syscall ( int num , int arg0 , int arg1 , int arg2 , int arg3 )
{
2007-10-12 02:46:15 +04:00
register long int _num __asm__ ( " $2 " ) = num ;
register long int _arg0 __asm__ ( " $4 " ) = arg0 ;
register long int _arg1 __asm__ ( " $5 " ) = arg1 ;
register long int _arg2 __asm__ ( " $6 " ) = arg2 ;
register long int _arg3 __asm__ ( " $7 " ) = arg3 ;
2006-04-05 12:45:45 +04:00
mm_segment_t old_fs ;
old_fs = get_fs ( ) ;
set_fs ( KERNEL_DS ) ;
__asm__ __volatile__ (
" syscall \n "
: " =r " ( _num ) , " =r " ( _arg3 )
: " r " ( _num ) , " r " ( _arg0 ) , " r " ( _arg1 ) , " r " ( _arg2 ) , " r " ( _arg3 ) ) ;
set_fs ( old_fs ) ;
/* $a3 is error flag */
if ( _arg3 )
return - _num ;
return _num ;
}
static int translate_syscall_command ( int cmd )
{
int i ;
int ret = - 1 ;
for ( i = 0 ; i < ARRAY_SIZE ( syscall_command_table ) ; i + + ) {
if ( ( cmd = = syscall_command_table [ i ] . sp ) )
return syscall_command_table [ i ] . ap ;
}
return ret ;
}
static unsigned int translate_open_flags ( int flags )
{
int i ;
unsigned int ret = 0 ;
for ( i = 0 ; i < ( sizeof ( open_flags_table ) / sizeof ( struct apsp_table ) ) ;
i + + ) {
if ( ( flags & open_flags_table [ i ] . sp ) ) {
ret | = open_flags_table [ i ] . ap ;
}
}
return ret ;
}
static void sp_setfsuidgid ( uid_t uid , gid_t gid )
{
current - > fsuid = uid ;
current - > fsgid = gid ;
key_fsuid_changed ( current ) ;
key_fsgid_changed ( current ) ;
}
/*
* Expects a request to be on the sysio channel . Reads it . Decides whether
* its a linux syscall and runs it , or whatever . Puts the return code back
* into the request and sends the whole thing back .
*/
void sp_work_handle_request ( void )
{
struct mtsp_syscall sc ;
struct mtsp_syscall_generic generic ;
struct mtsp_syscall_ret ret ;
struct kspd_notifications * n ;
2007-03-16 15:16:27 +03:00
unsigned long written ;
mm_segment_t old_fs ;
2006-04-05 12:45:45 +04:00
struct timeval tv ;
struct timezone tz ;
int cmd ;
char * vcwd ;
int size ;
ret . retval = - 1 ;
2007-03-16 15:16:27 +03:00
old_fs = get_fs ( ) ;
set_fs ( KERNEL_DS ) ;
if ( ! rtlx_read ( RTLX_CHANNEL_SYSIO , & sc , sizeof ( struct mtsp_syscall ) ) ) {
set_fs ( old_fs ) ;
2006-04-05 12:45:45 +04:00
printk ( KERN_ERR " Expected request but nothing to read \n " ) ;
return ;
}
size = sc . size ;
if ( size ) {
2007-03-16 15:16:27 +03:00
if ( ! rtlx_read ( RTLX_CHANNEL_SYSIO , & generic , size ) ) {
set_fs ( old_fs ) ;
2006-04-05 12:45:45 +04:00
printk ( KERN_ERR " Expected request but nothing to read \n " ) ;
return ;
}
}
/* Run the syscall at the priviledge of the user who loaded the
SP program */
2007-07-27 22:31:10 +04:00
if ( vpe_getuid ( tclimit ) )
sp_setfsuidgid ( vpe_getuid ( tclimit ) , vpe_getgid ( tclimit ) ) ;
2006-04-05 12:45:45 +04:00
switch ( sc . cmd ) {
/* needs the flags argument translating from SDE kit to
linux */
case MTSP_SYSCALL_PIPEFREQ :
ret . retval = cpu_khz * 1000 ;
ret . errno = 0 ;
break ;
case MTSP_SYSCALL_GETTOD :
memset ( & tz , 0 , sizeof ( tz ) ) ;
if ( ( ret . retval = sp_syscall ( __NR_gettimeofday , ( int ) & tv ,
2007-10-12 02:46:15 +04:00
( int ) & tz , 0 , 0 ) ) = = 0 )
2006-04-05 12:45:45 +04:00
ret . retval = tv . tv_sec ;
break ;
case MTSP_SYSCALL_EXIT :
list_for_each_entry ( n , & kspd_notifylist , list )
2007-07-27 22:31:10 +04:00
n - > kspd_sp_exit ( tclimit ) ;
2006-04-05 12:45:45 +04:00
sp_stopping = 1 ;
printk ( KERN_DEBUG " KSPD got exit syscall from SP exitcode %d \n " ,
generic . arg0 ) ;
break ;
case MTSP_SYSCALL_OPEN :
generic . arg1 = translate_open_flags ( generic . arg1 ) ;
2007-07-27 22:31:10 +04:00
vcwd = vpe_getcwd ( tclimit ) ;
2006-04-05 12:45:45 +04:00
/* change to the cwd of the process that loaded the SP program */
old_fs = get_fs ( ) ;
set_fs ( KERNEL_DS ) ;
sys_chdir ( vcwd ) ;
set_fs ( old_fs ) ;
sc . cmd = __NR_open ;
/* fall through */
default :
if ( ( sc . cmd > = __NR_Linux ) & &
( sc . cmd < = ( __NR_Linux + __NR_Linux_syscalls ) ) )
cmd = sc . cmd ;
else
cmd = translate_syscall_command ( sc . cmd ) ;
if ( cmd > = 0 ) {
ret . retval = sp_syscall ( cmd , generic . arg0 , generic . arg1 ,
generic . arg2 , generic . arg3 ) ;
} else
printk ( KERN_WARNING
" KSPD: Unknown SP syscall number %d \n " , sc . cmd ) ;
break ;
} /* switch */
2007-07-27 22:31:10 +04:00
if ( vpe_getuid ( tclimit ) )
2006-04-05 12:45:45 +04:00
sp_setfsuidgid ( 0 , 0 ) ;
2007-03-16 15:16:27 +03:00
old_fs = get_fs ( ) ;
set_fs ( KERNEL_DS ) ;
written = rtlx_write ( RTLX_CHANNEL_SYSIO , & ret , sizeof ( ret ) ) ;
set_fs ( old_fs ) ;
if ( written < sizeof ( ret ) )
2006-04-05 12:45:45 +04:00
printk ( " KSPD: sp_work_handle_request failed to send to SP \n " ) ;
}
static void sp_cleanup ( void )
{
struct files_struct * files = current - > files ;
int i , j ;
struct fdtable * fdt ;
j = 0 ;
/*
* It is safe to dereference the fd table without RCU or
* - > file_lock
*/
fdt = files_fdtable ( files ) ;
for ( ; ; ) {
unsigned long set ;
i = j * __NFDBITS ;
2006-12-10 13:21:12 +03:00
if ( i > = fdt - > max_fds )
2006-04-05 12:45:45 +04:00
break ;
set = fdt - > open_fds - > fds_bits [ j + + ] ;
while ( set ) {
if ( set & 1 ) {
struct file * file = xchg ( & fdt - > fd [ i ] , NULL ) ;
if ( file )
filp_close ( file , files ) ;
}
i + + ;
set > > = 1 ;
}
}
}
static int channel_open = 0 ;
/* the work handler */
2006-12-05 22:36:26 +03:00
static void sp_work ( struct work_struct * unused )
2006-04-05 12:45:45 +04:00
{
if ( ! channel_open ) {
if ( rtlx_open ( RTLX_CHANNEL_SYSIO , 1 ) ! = 0 ) {
printk ( " KSPD: unable to open sp channel \n " ) ;
sp_stopping = 1 ;
} else {
channel_open + + ;
printk ( KERN_DEBUG " KSPD: SP channel opened \n " ) ;
}
} else {
/* wait for some data, allow it to sleep */
rtlx_read_poll ( RTLX_CHANNEL_SYSIO , 1 ) ;
/* Check we haven't been woken because we are stopping */
if ( ! sp_stopping )
sp_work_handle_request ( ) ;
}
if ( ! sp_stopping )
queue_work ( workqueue , & work ) ;
else
sp_cleanup ( ) ;
}
static void startwork ( int vpe )
{
sp_stopping = channel_open = 0 ;
if ( workqueue = = NULL ) {
if ( ( workqueue = create_singlethread_workqueue ( " kspd " ) ) = = NULL ) {
printk ( KERN_ERR " unable to start kspd \n " ) ;
return ;
}
2006-12-05 22:36:26 +03:00
INIT_WORK ( & work , sp_work ) ;
2007-07-27 22:31:10 +04:00
}
2006-04-05 12:45:45 +04:00
2007-07-27 22:31:10 +04:00
queue_work ( workqueue , & work ) ;
2006-04-05 12:45:45 +04:00
}
static void stopwork ( int vpe )
{
sp_stopping = 1 ;
printk ( KERN_DEBUG " KSPD: SP stopping \n " ) ;
}
void kspd_notify ( struct kspd_notifications * notify )
{
list_add ( & notify - > list , & kspd_notifylist ) ;
}
static struct vpe_notifications notify ;
static int kspd_module_init ( void )
{
INIT_LIST_HEAD ( & kspd_notifylist ) ;
notify . start = startwork ;
notify . stop = stopwork ;
2007-07-27 22:31:10 +04:00
vpe_notify ( tclimit , & notify ) ;
2006-04-05 12:45:45 +04:00
return 0 ;
}
static void kspd_module_exit ( void )
{
}
module_init ( kspd_module_init ) ;
module_exit ( kspd_module_exit ) ;
MODULE_DESCRIPTION ( " MIPS KSPD " ) ;
MODULE_AUTHOR ( " Elizabeth Oldham, MIPS Technologies, Inc. " ) ;
MODULE_LICENSE ( " GPL " ) ;