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/* XXX: check which of these is actually O_SYNC vs O_DSYNC */
  86#define MTSP_O_FSYNC            O_SYNC
  87#define MTSP_O_NOFOLLOW         0x0100
  88#define MTSP_O_SYNC             0x0080
  89#define MTSP_O_CREAT            0x0200
  90#define MTSP_O_TRUNC            0x0400
  91#define MTSP_O_EXCL             0x0800
  92#define MTSP_O_BINARY           0x8000
  93
  94extern int tclimit;
  95
  96struct apsp_table  {
  97        int sp;
  98        int ap;
  99};
 100
 101/* we might want to do the mode flags too */
 102struct apsp_table open_flags_table[] = {
 103        { MTSP_O_RDWR, O_RDWR },
 104        { MTSP_O_WRONLY, O_WRONLY },
 105        { MTSP_O_CREAT, O_CREAT },
 106        { MTSP_O_TRUNC, O_TRUNC },
 107        { MTSP_O_NONBLOCK, O_NONBLOCK },
 108        { MTSP_O_APPEND, O_APPEND },
 109        { MTSP_O_NOFOLLOW, O_NOFOLLOW }
 110};
 111
 112struct apsp_table syscall_command_table[] = {
 113        { MTSP_SYSCALL_OPEN, __NR_open },
 114        { MTSP_SYSCALL_CLOSE, __NR_close },
 115        { MTSP_SYSCALL_READ, __NR_read },
 116        { MTSP_SYSCALL_WRITE, __NR_write },
 117        { MTSP_SYSCALL_LSEEK32, __NR_lseek },
 118        { MTSP_SYSCALL_IOCTL, __NR_ioctl }
 119};
 120
 121static int sp_syscall(int num, int arg0, int arg1, int arg2, int arg3)
 122{
 123        register long int _num  __asm__("$2") = num;
 124        register long int _arg0  __asm__("$4") = arg0;
 125        register long int _arg1  __asm__("$5") = arg1;
 126        register long int _arg2  __asm__("$6") = arg2;
 127        register long int _arg3  __asm__("$7") = arg3;
 128
 129        mm_segment_t old_fs;
 130
 131        old_fs = get_fs();
 132        set_fs(KERNEL_DS);
 133
 134        __asm__ __volatile__ (
 135        "       syscall                                 \n"
 136        : "=r" (_num), "=r" (_arg3)
 137        : "r" (_num), "r" (_arg0), "r" (_arg1), "r" (_arg2), "r" (_arg3));
 138
 139        set_fs(old_fs);
 140
 141        /* $a3 is error flag */
 142        if (_arg3)
 143                return -_num;
 144
 145        return _num;
 146}
 147
 148static int translate_syscall_command(int cmd)
 149{
 150        int i;
 151        int ret = -1;
 152
 153        for (i = 0; i < ARRAY_SIZE(syscall_command_table); i++) {
 154                if ((cmd == syscall_command_table[i].sp))
 155                        return syscall_command_table[i].ap;
 156        }
 157
 158        return ret;
 159}
 160
 161static unsigned int translate_open_flags(int flags)
 162{
 163        int i;
 164        unsigned int ret = 0;
 165
 166        for (i = 0; i < ARRAY_SIZE(open_flags_table); i++) {
 167                if( (flags & open_flags_table[i].sp) ) {
 168                        ret |= open_flags_table[i].ap;
 169                }
 170        }
 171
 172        return ret;
 173}
 174
 175
 176static int sp_setfsuidgid(uid_t uid, gid_t gid)
 177{
 178        struct cred *new;
 179
 180        new = prepare_creds();
 181        if (!new)
 182                return -ENOMEM;
 183
 184        new->fsuid = uid;
 185        new->fsgid = gid;
 186
 187        commit_creds(new);
 188
 189        return 0;
 190}
 191
 192/*
 193 * Expects a request to be on the sysio channel. Reads it.  Decides whether
 194 * its a linux syscall and runs it, or whatever.  Puts the return code back
 195 * into the request and sends the whole thing back.
 196 */
 197void sp_work_handle_request(void)
 198{
 199        struct mtsp_syscall sc;
 200        struct mtsp_syscall_generic generic;
 201        struct mtsp_syscall_ret ret;
 202        struct kspd_notifications *n;
 203        unsigned long written;
 204        mm_segment_t old_fs;
 205        struct timeval tv;
 206        struct timezone tz;
 207        int err, cmd;
 208
 209        char *vcwd;
 210        int size;
 211
 212        ret.retval = -1;
 213
 214        old_fs = get_fs();
 215        set_fs(KERNEL_DS);
 216
 217        if (!rtlx_read(RTLX_CHANNEL_SYSIO, &sc, sizeof(struct mtsp_syscall))) {
 218                set_fs(old_fs);
 219                printk(KERN_ERR "Expected request but nothing to read\n");
 220                return;
 221        }
 222
 223        size = sc.size;
 224
 225        if (size) {
 226                if (!rtlx_read(RTLX_CHANNEL_SYSIO, &generic, size)) {
 227                        set_fs(old_fs);
 228                        printk(KERN_ERR "Expected request but nothing to read\n");
 229                        return;
 230                }
 231        }
 232
 233        /* Run the syscall at the privilege of the user who loaded the
 234           SP program */
 235
 236        if (vpe_getuid(tclimit)) {
 237                err = sp_setfsuidgid(vpe_getuid(tclimit), vpe_getgid(tclimit));
 238                if (!err)
 239                        pr_err("Change of creds failed\n");
 240        }
 241
 242        switch (sc.cmd) {
 243        /* needs the flags argument translating from SDE kit to
 244           linux */
 245        case MTSP_SYSCALL_PIPEFREQ:
 246                ret.retval = cpu_khz * 1000;
 247                ret.errno = 0;
 248                break;
 249
 250        case MTSP_SYSCALL_GETTOD:
 251                memset(&tz, 0, sizeof(tz));
 252                if ((ret.retval = sp_syscall(__NR_gettimeofday, (int)&tv,
 253                                             (int)&tz, 0, 0)) == 0)
 254                        ret.retval = tv.tv_sec;
 255                break;
 256
 257        case MTSP_SYSCALL_EXIT:
 258                list_for_each_entry(n, &kspd_notifylist, list)
 259                        n->kspd_sp_exit(tclimit);
 260                sp_stopping = 1;
 261
 262                printk(KERN_DEBUG "KSPD got exit syscall from SP exitcode %d\n",
 263                       generic.arg0);
 264                break;
 265
 266        case MTSP_SYSCALL_OPEN:
 267                generic.arg1 = translate_open_flags(generic.arg1);
 268
 269                vcwd = vpe_getcwd(tclimit);
 270
 271                /* change to cwd of the process that loaded the SP program */
 272                old_fs = get_fs();
 273                set_fs(KERNEL_DS);
 274                sys_chdir(vcwd);
 275                set_fs(old_fs);
 276
 277                sc.cmd = __NR_open;
 278
 279                /* fall through */
 280
 281        default:
 282                if ((sc.cmd >= __NR_Linux) &&
 283                    (sc.cmd <= (__NR_Linux +  __NR_Linux_syscalls)) )
 284                        cmd = sc.cmd;
 285                else
 286                        cmd = translate_syscall_command(sc.cmd);
 287
 288                if (cmd >= 0) {
 289                        ret.retval = sp_syscall(cmd, generic.arg0, generic.arg1,
 290                                                generic.arg2, generic.arg3);
 291                } else
 292                        printk(KERN_WARNING
 293                               "KSPD: Unknown SP syscall number %d\n", sc.cmd);
 294                break;
 295        } /* switch */
 296
 297        if (vpe_getuid(tclimit)) {
 298                err = sp_setfsuidgid(0, 0);
 299                if (!err)
 300                        pr_err("restoring old creds failed\n");
 301        }
 302
 303        old_fs = get_fs();
 304        set_fs(KERNEL_DS);
 305        written = rtlx_write(RTLX_CHANNEL_SYSIO, &ret, sizeof(ret));
 306        set_fs(old_fs);
 307        if (written < sizeof(ret))
 308                printk("KSPD: sp_work_handle_request failed to send to SP\n");
 309}
 310
 311static void sp_cleanup(void)
 312{
 313        struct files_struct *files = current->files;
 314        int i, j;
 315        struct fdtable *fdt;
 316
 317        j = 0;
 318
 319        /*
 320         * It is safe to dereference the fd table without RCU or
 321         * ->file_lock
 322         */
 323        fdt = files_fdtable(files);
 324        for (;;) {
 325                unsigned long set;
 326                i = j * __NFDBITS;
 327                if (i >= fdt->max_fds)
 328                        break;
 329                set = fdt->open_fds->fds_bits[j++];
 330                while (set) {
 331                        if (set & 1) {
 332                                struct file * file = xchg(&fdt->fd[i], NULL);
 333                                if (file)
 334                                        filp_close(file, files);
 335                        }
 336                        i++;
 337                        set >>= 1;
 338                }
 339        }
 340
 341        /* Put daemon cwd back to root to avoid umount problems */
 342        sys_chdir("/");
 343}
 344
 345static int channel_open;
 346
 347/* the work handler */
 348static void sp_work(struct work_struct *unused)
 349{
 350        if (!channel_open) {
 351                if( rtlx_open(RTLX_CHANNEL_SYSIO, 1) != 0) {
 352                        printk("KSPD: unable to open sp channel\n");
 353                        sp_stopping = 1;
 354                } else {
 355                        channel_open++;
 356                        printk(KERN_DEBUG "KSPD: SP channel opened\n");
 357                }
 358        } else {
 359                /* wait for some data, allow it to sleep */
 360                rtlx_read_poll(RTLX_CHANNEL_SYSIO, 1);
 361
 362                /* Check we haven't been woken because we are stopping */
 363                if (!sp_stopping)
 364                        sp_work_handle_request();
 365        }
 366
 367        if (!sp_stopping)
 368                queue_work(workqueue, &work);
 369        else
 370                sp_cleanup();
 371}
 372
 373static void startwork(int vpe)
 374{
 375        sp_stopping = channel_open = 0;
 376
 377        if (workqueue == NULL) {
 378                if ((workqueue = create_singlethread_workqueue("kspd")) == NULL) {
 379                        printk(KERN_ERR "unable to start kspd\n");
 380                        return;
 381                }
 382
 383                INIT_WORK(&work, sp_work);
 384        }
 385
 386        queue_work(workqueue, &work);
 387}
 388
 389static void stopwork(int vpe)
 390{
 391        sp_stopping = 1;
 392
 393        printk(KERN_DEBUG "KSPD: SP stopping\n");
 394}
 395
 396void kspd_notify(struct kspd_notifications *notify)
 397{
 398        list_add(&notify->list, &kspd_notifylist);
 399}
 400
 401static struct vpe_notifications notify;
 402static int kspd_module_init(void)
 403{
 404        INIT_LIST_HEAD(&kspd_notifylist);
 405
 406        notify.start = startwork;
 407        notify.stop = stopwork;
 408        vpe_notify(tclimit, &notify);
 409
 410        return 0;
 411}
 412
 413static void kspd_module_exit(void)
 414{
 415
 416}
 417
 418module_init(kspd_module_init);
 419module_exit(kspd_module_exit);
 420
 421MODULE_DESCRIPTION("MIPS KSPD");
 422MODULE_AUTHOR("Elizabeth Oldham, MIPS Technologies, Inc.");
 423MODULE_LICENSE("GPL");
 424