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(¬ify->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, ¬ify); 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