This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / arch / mips / kernel / kspd.c
diff --git a/arch/mips/kernel/kspd.c b/arch/mips/kernel/kspd.c
new file mode 100644 (file)
index 0000000..f06a144
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * 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/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)
+
+#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
+
+#define SP_VPE 1
+
+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 }
+};
+
+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 < (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;
+       struct timeval tv;
+       struct timezone tz;
+       int cmd;
+
+       char *vcwd;
+       mm_segment_t old_fs;
+       int size;
+
+       ret.retval = -1;
+
+       if (!rtlx_read(RTLX_CHANNEL_SYSIO, &sc, sizeof(struct mtsp_syscall), 0)) {
+               printk(KERN_ERR "Expected request but nothing to read\n");
+               return;
+       }
+
+       size = sc.size;
+
+       if (size) {
+               if (!rtlx_read(RTLX_CHANNEL_SYSIO, &generic, size, 0)) {
+                       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 */
+
+       if (vpe_getuid(SP_VPE))
+               sp_setfsuidgid( vpe_getuid(SP_VPE), vpe_getgid(SP_VPE));
+
+       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;
+
+               ret.errno = errno;
+               break;
+
+       case MTSP_SYSCALL_EXIT:
+               list_for_each_entry(n, &kspd_notifylist, list)
+                       n->kspd_sp_exit(SP_VPE);
+               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(SP_VPE);
+
+               /* 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);
+                       ret.errno = errno;
+               } else
+                       printk(KERN_WARNING
+                              "KSPD: Unknown SP syscall number %d\n", sc.cmd);
+               break;
+       } /* switch */
+
+       if (vpe_getuid(SP_VPE))
+               sp_setfsuidgid( 0, 0);
+
+       if ((rtlx_write(RTLX_CHANNEL_SYSIO, &ret, sizeof(struct mtsp_syscall_ret), 0))
+           < sizeof(struct mtsp_syscall_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_fdset || 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;
+               }
+       }
+}
+
+static int channel_open = 0;
+
+/* the work handler */
+static void sp_work(void *data)
+{
+       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, NULL);
+               queue_work(workqueue, &work);
+       } else
+               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(&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;
+       vpe_notify(SP_VPE, &notify);
+
+       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");