| /* | |
| * 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> | |
| #include <linux/sched.h> | |
| #include <linux/unistd.h> | |
| #include <linux/file.h> | |
| #include <linux/fdtable.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) | |
| #define MTSP_SYSCALL_IOCTL (MTSP_SYSCALL_BASE + 10) | |
| #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 | |
| extern int tclimit; | |
| 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 }, | |
| { MTSP_SYSCALL_LSEEK32, __NR_lseek }, | |
| { MTSP_SYSCALL_IOCTL, __NR_ioctl } | |
| }; | |
| static int sp_syscall(int num, int arg0, int arg1, int arg2, int arg3) | |
| { | |
| 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; | |
| 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 < ARRAY_SIZE(open_flags_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->cred->fsuid = uid; | |
| current->cred->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; | |
| unsigned long written; | |
| mm_segment_t old_fs; | |
| struct timeval tv; | |
| struct timezone tz; | |
| int cmd; | |
| char *vcwd; | |
| int size; | |
| ret.retval = -1; | |
| old_fs = get_fs(); | |
| set_fs(KERNEL_DS); | |
| if (!rtlx_read(RTLX_CHANNEL_SYSIO, &sc, sizeof(struct mtsp_syscall))) { | |
| set_fs(old_fs); | |
| printk(KERN_ERR "Expected request but nothing to read\n"); | |
| return; | |
| } | |
| size = sc.size; | |
| if (size) { | |
| if (!rtlx_read(RTLX_CHANNEL_SYSIO, &generic, size)) { | |
| set_fs(old_fs); | |
| printk(KERN_ERR "Expected request but nothing to read\n"); | |
| return; | |
| } | |
| } | |
| /* Run the syscall at the privilege of the user who loaded the | |
| SP program */ | |
| if (vpe_getuid(tclimit)) | |
| sp_setfsuidgid(vpe_getuid(tclimit), vpe_getgid(tclimit)); | |
| 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, | |
| (int)&tz, 0, 0)) == 0) | |
| ret.retval = tv.tv_sec; | |
| break; | |
| case MTSP_SYSCALL_EXIT: | |
| list_for_each_entry(n, &kspd_notifylist, list) | |
| n->kspd_sp_exit(tclimit); | |
| 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); | |
| vcwd = vpe_getcwd(tclimit); | |
| /* change to 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 */ | |
| if (vpe_getuid(tclimit)) | |
| sp_setfsuidgid( 0, 0); | |
| 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)) | |
| 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; | |
| if (i >= fdt->max_fds) | |
| 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; | |
| } | |
| } | |
| /* Put daemon cwd back to root to avoid umount problems */ | |
| sys_chdir("/"); | |
| } | |
| static int channel_open = 0; | |
| /* the work handler */ | |
| static void sp_work(struct work_struct *unused) | |
| { | |
| 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; | |
| } | |
| INIT_WORK(&work, sp_work); | |
| } | |
| queue_work(workqueue, &work); | |
| } | |
| static void stopwork(int vpe) | |
| { | |
| sp_stopping = 1; | |
| printk(KERN_DEBUG "KSPD: SP stopping\n"); | |
| } | |
| void kspd_notify(struct kspd_notifications *notify) | |
| { | |
| list_add(¬ify->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; | |
| vpe_notify(tclimit, ¬ify); | |
| 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"); |