linux/arch/mips/kernel/kspd.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2005 MIPS Technologies, Inc.  All rights reserved.
   3 *
   4 *  This program is free software; you can distribute it and/or modify it
   5 *  under the terms of the GNU General Public License (Version 2) as
   6 *  published by the Free Software Foundation.
   7 *
   8 *  This program is distributed in the hope it will be useful, but WITHOUT
   9 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  11 *  for more details.
  12 *
  13 *  You should have received a copy of the GNU General Public License along
  14 *  with this program; if not, write to the Free Software Foundation, Inc.,
  15 *  59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
  16 *
  17 */
  18#include <linux/kernel.h>
  19#include <linux/module.h>
  20#include <linux/sched.h>
  21#include <linux/unistd.h>
  22#include <linux/file.h>
  23#include <linux/fdtable.h>
  24#include <linux/fs.h>
  25#include <linux/syscalls.h>
  26#include <linux/workqueue.h>
  27#include <linux/errno.h>
  28#include <linux/list.h>
  29
  30#include <asm/vpe.h>
  31#include <asm/rtlx.h>
  32#include <asm/kspd.h>
  33
  34static struct workqueue_struct *workqueue;
  35static struct work_struct work;
  36
  37extern unsigned long cpu_khz;
  38
  39struct mtsp_syscall {
  40        int cmd;
  41        unsigned char abi;
  42        unsigned char size;
  43};
  44
  45struct mtsp_syscall_ret {
  46        int retval;
  47        int errno;
  48};
  49
  50struct mtsp_syscall_generic {
  51        int arg0;
  52        int arg1;
  53        int arg2;
  54        int arg3;
  55        int arg4;
  56        int arg5;
  57        int arg6;
  58};
  59
  60static struct list_head kspd_notifylist;
  61static int sp_stopping;
  62
  63/* these should match with those in the SDE kit */
  64#define MTSP_SYSCALL_BASE       0
  65#define MTSP_SYSCALL_EXIT       (MTSP_SYSCALL_BASE + 0)
  66#define MTSP_SYSCALL_OPEN       (MTSP_SYSCALL_BASE + 1)
  67#define MTSP_SYSCALL_READ       (MTSP_SYSCALL_BASE + 2)
  68#define MTSP_SYSCALL_WRITE      (MTSP_SYSCALL_BASE + 3)
  69#define MTSP_SYSCALL_CLOSE      (MTSP_SYSCALL_BASE + 4)
  70#define MTSP_SYSCALL_LSEEK32    (MTSP_SYSCALL_BASE + 5)
  71#define MTSP_SYSCALL_ISATTY     (MTSP_SYSCALL_BASE + 6)
  72#define MTSP_SYSCALL_GETTIME    (MTSP_SYSCALL_BASE + 7)
  73#define MTSP_SYSCALL_PIPEFREQ   (MTSP_SYSCALL_BASE + 8)
  74#define MTSP_SYSCALL_GETTOD     (MTSP_SYSCALL_BASE + 9)
  75#define MTSP_SYSCALL_IOCTL     (MTSP_SYSCALL_BASE + 10)
  76
  77#define MTSP_O_RDONLY           0x0000
  78#define MTSP_O_WRONLY           0x0001
  79#define MTSP_O_RDWR             0x0002
  80#define MTSP_O_NONBLOCK         0x0004
  81#define MTSP_O_APPEND           0x0008
  82#define MTSP_O_SHLOCK           0x0010
  83#define MTSP_O_EXLOCK           0x0020
  84#define MTSP_O_ASYNC            0x0040
  85#define MTSP_O_FSYNC            O_SYNC
  86#define MTSP_O_NOFOLLOW         0x0100
  87#define MTSP_O_SYNC             0x0080
  88#define MTSP_O_CREAT            0x0200
  89#define MTSP_O_TRUNC            0x0400
  90#define MTSP_O_EXCL             0x0800
  91#define MTSP_O_BINARY           0x8000
  92
  93extern int tclimit;
  94
  95struct apsp_table  {
  96        int sp;
  97        int ap;
  98};
  99
 100/* we might want to do the mode flags too */
 101struct apsp_table open_flags_table[] = {
 102        { MTSP_O_RDWR, O_RDWR },
 103        { MTSP_O_WRONLY, O_WRONLY },
 104        { MTSP_O_CREAT, O_CREAT },
 105        { MTSP_O_TRUNC, O_TRUNC },
 106        { MTSP_O_NONBLOCK, O_NONBLOCK },
 107        { MTSP_O_APPEND, O_APPEND },
 108        { MTSP_O_NOFOLLOW, O_NOFOLLOW }
 109};
 110
 111struct apsp_table syscall_command_table[] = {
 112        { MTSP_SYSCALL_OPEN, __NR_open },
 113        { MTSP_SYSCALL_CLOSE, __NR_close },
 114        { MTSP_SYSCALL_READ, __NR_read },
 115        { MTSP_SYSCALL_WRITE, __NR_write },
 116        { MTSP_SYSCALL_LSEEK32, __NR_lseek },
 117        { MTSP_SYSCALL_IOCTL, __NR_ioctl }
 118};
 119
 120static int sp_syscall(int num, int arg0, int arg1, int arg2, int arg3)
 121{
 122        register long int _num  __asm__("$2") = num;
 123        register long int _arg0  __asm__("$4") = arg0;
 124        register long int _arg1  __asm__("$5") = arg1;
 125        register long int _arg2  __asm__("$6") = arg2;
 126        register long int _arg3  __asm__("$7") = arg3;
 127
 128        mm_segment_t old_fs;
 129
 130        old_fs = get_fs();
 131        set_fs(KERNEL_DS);
 132
 133        __asm__ __volatile__ (
 134        "       syscall                                 \n"
 135        : "=r" (_num), "=r" (_arg3)
 136        : "r" (_num), "r" (_arg0), "r" (_arg1), "r" (_arg2), "r" (_arg3));
 137
 138        set_fs(old_fs);
 139
 140        /* $a3 is error flag */
 141        if (_arg3)
 142                return -_num;
 143
 144        return _num;
 145}
 146
 147static int translate_syscall_command(int cmd)
 148{
 149        int i;
 150        int ret = -1;
 151
 152        for (i = 0; i < ARRAY_SIZE(syscall_command_table); i++) {
 153                if ((cmd == syscall_command_table[i].sp))
 154                        return syscall_command_table[i].ap;
 155        }
 156
 157        return ret;
 158}
 159
 160static unsigned int translate_open_flags(int flags)
 161{
 162        int i;
 163        unsigned int ret = 0;
 164
 165        for (i = 0; i < ARRAY_SIZE(open_flags_table); i++) {
 166                if( (flags & open_flags_table[i].sp) ) {
 167                        ret |= open_flags_table[i].ap;
 168                }
 169        }
 170
 171        return ret;
 172}
 173
 174
 175static int sp_setfsuidgid(uid_t uid, gid_t gid)
 176{
 177        struct cred *new;
 178
 179        new = prepare_creds();
 180        if (!new)
 181                return -ENOMEM;
 182
 183        new->fsuid = uid;
 184        new->fsgid = gid;
 185
 186        commit_creds(new);
 187
 188        return 0;
 189}
 190
 191/*
 192 * Expects a request to be on the sysio channel. Reads it.  Decides whether
 193 * its a linux syscall and runs it, or whatever.  Puts the return code back
 194 * into the request and sends the whole thing back.
 195 */
 196void sp_work_handle_request(void)
 197{
 198        struct mtsp_syscall sc;
 199        struct mtsp_syscall_generic generic;
 200        struct mtsp_syscall_ret ret;
 201        struct kspd_notifications *n;
 202        unsigned long written;
 203        mm_segment_t old_fs;
 204        struct timeval tv;
 205        struct timezone tz;
 206        int err, cmd;
 207
 208        char *vcwd;
 209        int size;
 210
 211        ret.retval = -1;
 212
 213        old_fs = get_fs();
 214        set_fs(KERNEL_DS);
 215
 216        if (!rtlx_read(RTLX_CHANNEL_SYSIO, &sc, sizeof(struct mtsp_syscall))) {
 217                set_fs(old_fs);
 218                printk(KERN_ERR "Expected request but nothing to read\n");
 219                return;
 220        }
 221
 222        size = sc.size;
 223
 224        if (size) {
 225                if (!rtlx_read(RTLX_CHANNEL_SYSIO, &generic, size)) {
 226                        set_fs(old_fs);
 227                        printk(KERN_ERR "Expected request but nothing to read\n");
 228                        return;
 229                }
 230        }
 231
 232        /* Run the syscall at the privilege of the user who loaded the
 233           SP program */
 234
 235        if (vpe_getuid(tclimit)) {
 236                err = sp_setfsuidgid(vpe_getuid(tclimit), vpe_getgid(tclimit));
 237                if (!err)
 238                        pr_err("Change of creds failed\n");
 239        }
 240
 241        switch (sc.cmd) {
 242        /* needs the flags argument translating from SDE kit to
 243           linux */
 244        case MTSP_SYSCALL_PIPEFREQ:
 245                ret.retval = cpu_khz * 1000;
 246                ret.errno = 0;
 247                break;
 248
 249        case MTSP_SYSCALL_GETTOD:
 250                memset(&tz, 0, sizeof(tz));
 251                if ((ret.retval = sp_syscall(__NR_gettimeofday, (int)&tv,
 252                                             (int)&tz, 0, 0)) == 0)
 253                ret.retval = tv.tv_sec;
 254                break;
 255
 256        case MTSP_SYSCALL_EXIT:
 257                list_for_each_entry(n, &kspd_notifylist, list)
 258                        n->kspd_sp_exit(tclimit);
 259                sp_stopping = 1;
 260
 261                printk(KERN_DEBUG "KSPD got exit syscall from SP exitcode %d\n",
 262                       generic.arg0);
 263                break;
 264
 265        case MTSP_SYSCALL_OPEN:
 266                generic.arg1 = translate_open_flags(generic.arg1);
 267
 268                vcwd = vpe_getcwd(tclimit);
 269
 270                /* change to cwd of the process that loaded the SP program */
 271                old_fs = get_fs();
 272                set_fs(KERNEL_DS);
 273                sys_chdir(vcwd);
 274                set_fs(old_fs);
 275
 276                sc.cmd = __NR_open;
 277
 278                /* fall through */
 279
 280        default:
 281                if ((sc.cmd >= __NR_Linux) &&
 282                    (sc.cmd <= (__NR_Linux +  __NR_Linux_syscalls)) )
 283                        cmd = sc.cmd;
 284                else
 285                        cmd = translate_syscall_command(sc.cmd);
 286
 287                if (cmd >= 0) {
 288                        ret.retval = sp_syscall(cmd, generic.arg0, generic.arg1,
 289                                                generic.arg2, generic.arg3);
 290                } else
 291                        printk(KERN_WARNING
 292                               "KSPD: Unknown SP syscall number %d\n", sc.cmd);
 293                break;
 294        } /* switch */
 295
 296        if (vpe_getuid(tclimit)) {
 297                err = sp_setfsuidgid(0, 0);
 298                if (!err)
 299                        pr_err("restoring old creds failed\n");
 300        }
 301
 302        old_fs = get_fs();
 303        set_fs(KERNEL_DS);
 304        written = rtlx_write(RTLX_CHANNEL_SYSIO, &ret, sizeof(ret));
 305        set_fs(old_fs);
 306        if (written < sizeof(ret))
 307                printk("KSPD: sp_work_handle_request failed to send to SP\n");
 308}
 309
 310static void sp_cleanup(void)
 311{
 312        struct files_struct *files = current->files;
 313        int i, j;
 314        struct fdtable *fdt;
 315
 316        j = 0;
 317
 318        /*
 319         * It is safe to dereference the fd table without RCU or
 320         * ->file_lock
 321         */
 322        fdt = files_fdtable(files);
 323        for (;;) {
 324                unsigned long set;
 325                i = j * __NFDBITS;
 326                if (i >= fdt->max_fds)
 327                        break;
 328                set = fdt->open_fds->fds_bits[j++];
 329                while (set) {
 330                        if (set & 1) {
 331                                struct file * file = xchg(&fdt->fd[i], NULL);
 332                                if (file)
 333                                        filp_close(file, files);
 334                        }
 335                        i++;
 336                        set >>= 1;
 337                }
 338        }
 339
 340        /* Put daemon cwd back to root to avoid umount problems */
 341        sys_chdir("/");
 342}
 343
 344static int channel_open;
 345
 346/* the work handler */
 347static void sp_work(struct work_struct *unused)
 348{
 349        if (!channel_open) {
 350                if( rtlx_open(RTLX_CHANNEL_SYSIO, 1) != 0) {
 351                        printk("KSPD: unable to open sp channel\n");
 352                        sp_stopping = 1;
 353                } else {
 354                        channel_open++;
 355                        printk(KERN_DEBUG "KSPD: SP channel opened\n");
 356                }
 357        } else {
 358                /* wait for some data, allow it to sleep */
 359                rtlx_read_poll(RTLX_CHANNEL_SYSIO, 1);
 360
 361                /* Check we haven't been woken because we are stopping */
 362                if (!sp_stopping)
 363                        sp_work_handle_request();
 364        }
 365
 366        if (!sp_stopping)
 367                queue_work(workqueue, &work);
 368        else
 369                sp_cleanup();
 370}
 371
 372static void startwork(int vpe)
 373{
 374        sp_stopping = channel_open = 0;
 375
 376        if (workqueue == NULL) {
 377                if ((workqueue = create_singlethread_workqueue("kspd")) == NULL) {
 378                        printk(KERN_ERR "unable to start kspd\n");
 379                        return;
 380                }
 381
 382                INIT_WORK(&work, sp_work);
 383        }
 384
 385        queue_work(workqueue, &work);
 386}
 387
 388static void stopwork(int vpe)
 389{
 390        sp_stopping = 1;
 391
 392        printk(KERN_DEBUG "KSPD: SP stopping\n");
 393}
 394
 395void kspd_notify(struct kspd_notifications *notify)
 396{
 397        list_add(&notify->list, &kspd_notifylist);
 398}
 399
 400static struct vpe_notifications notify;
 401static int kspd_module_init(void)
 402{
 403        INIT_LIST_HEAD(&kspd_notifylist);
 404
 405        notify.start = startwork;
 406        notify.stop = stopwork;
 407        vpe_notify(tclimit, &notify);
 408
 409        return 0;
 410}
 411
 412static void kspd_module_exit(void)
 413{
 414
 415}
 416
 417module_init(kspd_module_init);
 418module_exit(kspd_module_exit);
 419
 420MODULE_DESCRIPTION("MIPS KSPD");
 421MODULE_AUTHOR("Elizabeth Oldham, MIPS Technologies, Inc.");
 422MODULE_LICENSE("GPL");
 423