Index: head/sys/compat/linux/check_internal_locks.d =================================================================== --- head/sys/compat/linux/check_internal_locks.d (revision 298828) +++ head/sys/compat/linux/check_internal_locks.d (revision 298829) @@ -1,97 +1,97 @@ #!/usr/sbin/dtrace -qs /*- * Copyright (c) 2008-2012 Alexander Leidinger * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ /** * Check if the internal locks are correctly acquired/released: * - no recursive locking (mtx locks, write locks) * - no unlocking of already unlocked one * * Print stacktrace if a lock is longer locked than about 10sec or more. */ #pragma D option dynvarsize=32m #pragma D option specsize=32m BEGIN { check["futex_mtx"] = 0; } linuxulator*:locks:futex_mtx:locked /check[probefunc] > 0/ { printf("ERROR: recursive lock of %s (%p),", probefunc, arg0); printf(" or missing SDT probe in kernel. Stack trace follows:"); stack(); } linuxulator*:locks:futex_mtx:locked { ++check[probefunc]; @stats[probefunc] = count(); ts[probefunc] = timestamp; spec[probefunc] = speculation(); } linuxulator*:locks:futex_mtx:unlock /check[probefunc] == 0/ { - printf("ERROR: unlock attemt of unlocked %s (%p),", probefunc, arg0); + printf("ERROR: unlock attempt of unlocked %s (%p),", probefunc, arg0); printf(" missing SDT probe in kernel, or dtrace program started"); printf(" while the %s was already held (race condition).", probefunc); printf(" Stack trace follows:"); stack(); } linuxulator*:locks:futex_mtx:unlock { discard(spec[probefunc]); spec[probefunc] = 0; --check[probefunc]; } /* Timeout handling */ tick-10s /spec["futex_mtx"] != 0 && timestamp - ts["futex_mtx"] >= 9999999000/ { commit(spec["futex_mtx"]); spec["futex_mtx"] = 0; } /* Statistics */ END { printf("Number of locks per type:"); printa(@stats); } Index: head/sys/compat/linux/linux_fork.c =================================================================== --- head/sys/compat/linux/linux_fork.c (revision 298828) +++ head/sys/compat/linux/linux_fork.c (revision 298829) @@ -1,482 +1,482 @@ /*- * Copyright (c) 2004 Tim J. Robbins * Copyright (c) 2002 Doug Rabson * Copyright (c) 2000 Marcel Moolenaar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef COMPAT_LINUX32 #include #include #else #include #include #endif #include #include #include #include int linux_fork(struct thread *td, struct linux_fork_args *args) { struct fork_req fr; int error; struct proc *p2; struct thread *td2; #ifdef DEBUG if (ldebug(fork)) printf(ARGS(fork, "")); #endif bzero(&fr, sizeof(fr)); fr.fr_flags = RFFDG | RFPROC | RFSTOPPED; fr.fr_procp = &p2; if ((error = fork1(td, &fr)) != 0) return (error); td2 = FIRST_THREAD_IN_PROC(p2); linux_proc_init(td, td2, 0); td->td_retval[0] = p2->p_pid; /* * Make this runnable after we are finished with it. */ thread_lock(td2); TD_SET_CAN_RUN(td2); sched_add(td2, SRQ_BORING); thread_unlock(td2); return (0); } int linux_vfork(struct thread *td, struct linux_vfork_args *args) { struct fork_req fr; int error; struct proc *p2; struct thread *td2; #ifdef DEBUG if (ldebug(vfork)) printf(ARGS(vfork, "")); #endif bzero(&fr, sizeof(fr)); fr.fr_flags = RFFDG | RFPROC | RFMEM | RFPPWAIT | RFSTOPPED; fr.fr_procp = &p2; if ((error = fork1(td, &fr)) != 0) return (error); td2 = FIRST_THREAD_IN_PROC(p2); linux_proc_init(td, td2, 0); td->td_retval[0] = p2->p_pid; /* * Make this runnable after we are finished with it. */ thread_lock(td2); TD_SET_CAN_RUN(td2); sched_add(td2, SRQ_BORING); thread_unlock(td2); return (0); } static int linux_clone_proc(struct thread *td, struct linux_clone_args *args) { struct fork_req fr; int error, ff = RFPROC | RFSTOPPED; struct proc *p2; struct thread *td2; int exit_signal; struct linux_emuldata *em; #ifdef DEBUG if (ldebug(clone)) { printf(ARGS(clone, "flags %x, stack %p, parent tid: %p, " "child tid: %p"), (unsigned)args->flags, args->stack, args->parent_tidptr, args->child_tidptr); } #endif exit_signal = args->flags & 0x000000ff; if (LINUX_SIG_VALID(exit_signal)) { exit_signal = linux_to_bsd_signal(exit_signal); } else if (exit_signal != 0) return (EINVAL); if (args->flags & LINUX_CLONE_VM) ff |= RFMEM; if (args->flags & LINUX_CLONE_SIGHAND) ff |= RFSIGSHARE; /* * XXX: In Linux, sharing of fs info (chroot/cwd/umask) - * and open files is independant. In FreeBSD, its in one + * and open files is independent. In FreeBSD, its in one * structure but in reality it does not cause any problems * because both of these flags are usually set together. */ if (!(args->flags & (LINUX_CLONE_FILES | LINUX_CLONE_FS))) ff |= RFFDG; if (args->flags & LINUX_CLONE_PARENT_SETTID) if (args->parent_tidptr == NULL) return (EINVAL); if (args->flags & LINUX_CLONE_VFORK) ff |= RFPPWAIT; bzero(&fr, sizeof(fr)); fr.fr_flags = ff; fr.fr_procp = &p2; error = fork1(td, &fr); if (error) return (error); td2 = FIRST_THREAD_IN_PROC(p2); /* create the emuldata */ linux_proc_init(td, td2, args->flags); em = em_find(td2); KASSERT(em != NULL, ("clone_proc: emuldata not found.\n")); if (args->flags & LINUX_CLONE_CHILD_SETTID) em->child_set_tid = args->child_tidptr; else em->child_set_tid = NULL; if (args->flags & LINUX_CLONE_CHILD_CLEARTID) em->child_clear_tid = args->child_tidptr; else em->child_clear_tid = NULL; if (args->flags & LINUX_CLONE_PARENT_SETTID) { error = copyout(&p2->p_pid, args->parent_tidptr, sizeof(p2->p_pid)); if (error) printf(LMSG("copyout failed!")); } PROC_LOCK(p2); p2->p_sigparent = exit_signal; PROC_UNLOCK(p2); /* * In a case of stack = NULL, we are supposed to COW calling process * stack. This is what normal fork() does, so we just keep tf_rsp arg * intact. */ linux_set_upcall_kse(td2, PTROUT(args->stack)); if (args->flags & LINUX_CLONE_SETTLS) linux_set_cloned_tls(td2, args->tls); /* * If CLONE_PARENT is set, then the parent of the new process will be * the same as that of the calling process. */ if (args->flags & LINUX_CLONE_PARENT) { sx_xlock(&proctree_lock); PROC_LOCK(p2); proc_reparent(p2, td->td_proc->p_pptr); PROC_UNLOCK(p2); sx_xunlock(&proctree_lock); } #ifdef DEBUG if (ldebug(clone)) printf(LMSG("clone: successful rfork to %d, " "stack %p sig = %d"), (int)p2->p_pid, args->stack, exit_signal); #endif /* * Make this runnable after we are finished with it. */ thread_lock(td2); TD_SET_CAN_RUN(td2); sched_add(td2, SRQ_BORING); thread_unlock(td2); td->td_retval[0] = p2->p_pid; return (0); } static int linux_clone_thread(struct thread *td, struct linux_clone_args *args) { struct linux_emuldata *em; struct thread *newtd; struct proc *p; int error; #ifdef DEBUG if (ldebug(clone)) { printf(ARGS(clone, "thread: flags %x, stack %p, parent tid: %p, " "child tid: %p"), (unsigned)args->flags, args->stack, args->parent_tidptr, args->child_tidptr); } #endif LINUX_CTR4(clone_thread, "thread(%d) flags %x ptid %p ctid %p", td->td_tid, (unsigned)args->flags, args->parent_tidptr, args->child_tidptr); if (args->flags & LINUX_CLONE_PARENT_SETTID) if (args->parent_tidptr == NULL) return (EINVAL); /* Threads should be created with own stack */ if (args->stack == NULL) return (EINVAL); p = td->td_proc; #ifdef RACCT if (racct_enable) { PROC_LOCK(p); error = racct_add(p, RACCT_NTHR, 1); PROC_UNLOCK(p); if (error != 0) return (EPROCLIM); } #endif /* Initialize our td */ error = kern_thr_alloc(p, 0, &newtd); if (error) goto fail; cpu_set_upcall(newtd, td); bzero(&newtd->td_startzero, __rangeof(struct thread, td_startzero, td_endzero)); bcopy(&td->td_startcopy, &newtd->td_startcopy, __rangeof(struct thread, td_startcopy, td_endcopy)); newtd->td_proc = p; thread_cow_get(newtd, td); /* create the emuldata */ linux_proc_init(td, newtd, args->flags); em = em_find(newtd); KASSERT(em != NULL, ("clone_thread: emuldata not found.\n")); if (args->flags & LINUX_CLONE_SETTLS) linux_set_cloned_tls(newtd, args->tls); if (args->flags & LINUX_CLONE_CHILD_SETTID) em->child_set_tid = args->child_tidptr; else em->child_set_tid = NULL; if (args->flags & LINUX_CLONE_CHILD_CLEARTID) em->child_clear_tid = args->child_tidptr; else em->child_clear_tid = NULL; cpu_thread_clean(newtd); linux_set_upcall_kse(newtd, PTROUT(args->stack)); PROC_LOCK(p); p->p_flag |= P_HADTHREADS; bcopy(p->p_comm, newtd->td_name, sizeof(newtd->td_name)); if (args->flags & LINUX_CLONE_PARENT) thread_link(newtd, p->p_pptr); else thread_link(newtd, p); thread_lock(td); /* let the scheduler know about these things. */ sched_fork_thread(td, newtd); thread_unlock(td); if (P_SHOULDSTOP(p)) newtd->td_flags |= TDF_ASTPENDING | TDF_NEEDSUSPCHK; PROC_UNLOCK(p); tidhash_add(newtd); #ifdef DEBUG if (ldebug(clone)) printf(ARGS(clone, "successful clone to %d, stack %p"), (int)newtd->td_tid, args->stack); #endif LINUX_CTR2(clone_thread, "thread(%d) successful clone to %d", td->td_tid, newtd->td_tid); if (args->flags & LINUX_CLONE_PARENT_SETTID) { error = copyout(&newtd->td_tid, args->parent_tidptr, sizeof(newtd->td_tid)); if (error) printf(LMSG("clone_thread: copyout failed!")); } /* * Make this runnable after we are finished with it. */ thread_lock(newtd); TD_SET_CAN_RUN(newtd); sched_add(newtd, SRQ_BORING); thread_unlock(newtd); td->td_retval[0] = newtd->td_tid; return (0); fail: #ifdef RACCT if (racct_enable) { PROC_LOCK(p); racct_sub(p, RACCT_NTHR, 1); PROC_UNLOCK(p); } #endif return (error); } int linux_clone(struct thread *td, struct linux_clone_args *args) { if (args->flags & LINUX_CLONE_THREAD) return (linux_clone_thread(td, args)); else return (linux_clone_proc(td, args)); } int linux_exit(struct thread *td, struct linux_exit_args *args) { struct linux_emuldata *em; em = em_find(td); KASSERT(em != NULL, ("exit: emuldata not found.\n")); LINUX_CTR2(exit, "thread(%d) (%d)", em->em_tid, args->rval); linux_thread_detach(td); /* * XXX. When the last two threads of a process * exit via pthread_exit() try thr_exit() first. */ kern_thr_exit(td); exit1(td, args->rval, 0); /* NOTREACHED */ } int linux_set_tid_address(struct thread *td, struct linux_set_tid_address_args *args) { struct linux_emuldata *em; em = em_find(td); KASSERT(em != NULL, ("set_tid_address: emuldata not found.\n")); em->child_clear_tid = args->tidptr; td->td_retval[0] = em->em_tid; LINUX_CTR3(set_tid_address, "tidptr(%d) %p, returns %d", em->em_tid, args->tidptr, td->td_retval[0]); return (0); } void linux_thread_detach(struct thread *td) { struct linux_sys_futex_args cup; struct linux_emuldata *em; int *child_clear_tid; int error; em = em_find(td); KASSERT(em != NULL, ("thread_detach: emuldata not found.\n")); LINUX_CTR1(thread_detach, "thread(%d)", em->em_tid); release_futexes(td, em); child_clear_tid = em->child_clear_tid; if (child_clear_tid != NULL) { LINUX_CTR2(thread_detach, "thread(%d) %p", em->em_tid, child_clear_tid); error = suword32(child_clear_tid, 0); if (error != 0) return; cup.uaddr = child_clear_tid; cup.op = LINUX_FUTEX_WAKE; cup.val = 1; /* wake one */ cup.timeout = NULL; cup.uaddr2 = NULL; cup.val3 = 0; error = linux_sys_futex(td, &cup); /* * this cannot happen at the moment and if this happens it * probably means there is a user space bug */ if (error != 0) linux_msg(td, "futex stuff in thread_detach failed."); } } Index: head/sys/compat/linux/linux_misc.c =================================================================== --- head/sys/compat/linux/linux_misc.c (revision 298828) +++ head/sys/compat/linux/linux_misc.c (revision 298829) @@ -1,2515 +1,2515 @@ /*- * Copyright (c) 2002 Doug Rabson * Copyright (c) 1994-1995 Søren Schmidt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_compat.h" #include #include #include #if defined(__i386__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef COMPAT_LINUX32 #include #include #else #include #include #endif #include #include #include #include #include #include #include #include #include /** * Special DTrace provider for the linuxulator. * * In this file we define the provider for the entire linuxulator. All * modules (= files of the linuxulator) use it. * * We define a different name depending on the emulated bitsize, see * ../..//linux{,32}/linux.h, e.g.: * native bitsize = linuxulator * amd64, 32bit emulation = linuxulator32 */ LIN_SDT_PROVIDER_DEFINE(LINUX_DTRACE); int stclohz; /* Statistics clock frequency */ static unsigned int linux_to_bsd_resource[LINUX_RLIM_NLIMITS] = { RLIMIT_CPU, RLIMIT_FSIZE, RLIMIT_DATA, RLIMIT_STACK, RLIMIT_CORE, RLIMIT_RSS, RLIMIT_NPROC, RLIMIT_NOFILE, RLIMIT_MEMLOCK, RLIMIT_AS }; struct l_sysinfo { l_long uptime; /* Seconds since boot */ l_ulong loads[3]; /* 1, 5, and 15 minute load averages */ #define LINUX_SYSINFO_LOADS_SCALE 65536 l_ulong totalram; /* Total usable main memory size */ l_ulong freeram; /* Available memory size */ l_ulong sharedram; /* Amount of shared memory */ l_ulong bufferram; /* Memory used by buffers */ l_ulong totalswap; /* Total swap space size */ l_ulong freeswap; /* swap space still available */ l_ushort procs; /* Number of current processes */ l_ushort pads; l_ulong totalbig; l_ulong freebig; l_uint mem_unit; char _f[20-2*sizeof(l_long)-sizeof(l_int)]; /* padding */ }; struct l_pselect6arg { l_uintptr_t ss; l_size_t ss_len; }; static int linux_utimensat_nsec_valid(l_long); int linux_sysinfo(struct thread *td, struct linux_sysinfo_args *args) { struct l_sysinfo sysinfo; vm_object_t object; int i, j; struct timespec ts; getnanouptime(&ts); if (ts.tv_nsec != 0) ts.tv_sec++; sysinfo.uptime = ts.tv_sec; /* Use the information from the mib to get our load averages */ for (i = 0; i < 3; i++) sysinfo.loads[i] = averunnable.ldavg[i] * LINUX_SYSINFO_LOADS_SCALE / averunnable.fscale; sysinfo.totalram = physmem * PAGE_SIZE; sysinfo.freeram = sysinfo.totalram - vm_cnt.v_wire_count * PAGE_SIZE; sysinfo.sharedram = 0; mtx_lock(&vm_object_list_mtx); TAILQ_FOREACH(object, &vm_object_list, object_list) if (object->shadow_count > 1) sysinfo.sharedram += object->resident_page_count; mtx_unlock(&vm_object_list_mtx); sysinfo.sharedram *= PAGE_SIZE; sysinfo.bufferram = 0; swap_pager_status(&i, &j); sysinfo.totalswap = i * PAGE_SIZE; sysinfo.freeswap = (i - j) * PAGE_SIZE; sysinfo.procs = nprocs; /* The following are only present in newer Linux kernels. */ sysinfo.totalbig = 0; sysinfo.freebig = 0; sysinfo.mem_unit = 1; return (copyout(&sysinfo, args->info, sizeof(sysinfo))); } int linux_alarm(struct thread *td, struct linux_alarm_args *args) { struct itimerval it, old_it; u_int secs; int error; #ifdef DEBUG if (ldebug(alarm)) printf(ARGS(alarm, "%u"), args->secs); #endif secs = args->secs; /* - * Linux alarm() is always successfull. Limit secs to INT32_MAX / 2 + * Linux alarm() is always successful. Limit secs to INT32_MAX / 2 * to match kern_setitimer()'s limit to avoid error from it. * * XXX. Linux limit secs to INT_MAX on 32 and does not limit on 64-bit * platforms. */ if (secs > INT32_MAX / 2) secs = INT32_MAX / 2; it.it_value.tv_sec = secs; it.it_value.tv_usec = 0; timevalclear(&it.it_interval); error = kern_setitimer(td, ITIMER_REAL, &it, &old_it); KASSERT(error == 0, ("kern_setitimer returns %d", error)); if ((old_it.it_value.tv_sec == 0 && old_it.it_value.tv_usec > 0) || old_it.it_value.tv_usec >= 500000) old_it.it_value.tv_sec++; td->td_retval[0] = old_it.it_value.tv_sec; return (0); } int linux_brk(struct thread *td, struct linux_brk_args *args) { struct vmspace *vm = td->td_proc->p_vmspace; vm_offset_t new, old; struct obreak_args /* { char * nsize; } */ tmp; #ifdef DEBUG if (ldebug(brk)) printf(ARGS(brk, "%p"), (void *)(uintptr_t)args->dsend); #endif old = (vm_offset_t)vm->vm_daddr + ctob(vm->vm_dsize); new = (vm_offset_t)args->dsend; tmp.nsize = (char *)new; if (((caddr_t)new > vm->vm_daddr) && !sys_obreak(td, &tmp)) td->td_retval[0] = (long)new; else td->td_retval[0] = (long)old; return (0); } #if defined(__i386__) /* XXX: what about amd64/linux32? */ int linux_uselib(struct thread *td, struct linux_uselib_args *args) { struct nameidata ni; struct vnode *vp; struct exec *a_out; struct vattr attr; vm_offset_t vmaddr; unsigned long file_offset; unsigned long bss_size; char *library; ssize_t aresid; int error, locked, writecount; LCONVPATHEXIST(td, args->library, &library); #ifdef DEBUG if (ldebug(uselib)) printf(ARGS(uselib, "%s"), library); #endif a_out = NULL; locked = 0; vp = NULL; NDINIT(&ni, LOOKUP, ISOPEN | FOLLOW | LOCKLEAF | AUDITVNODE1, UIO_SYSSPACE, library, td); error = namei(&ni); LFREEPATH(library); if (error) goto cleanup; vp = ni.ni_vp; NDFREE(&ni, NDF_ONLY_PNBUF); /* * From here on down, we have a locked vnode that must be unlocked. * XXX: The code below largely duplicates exec_check_permissions(). */ locked = 1; /* Writable? */ error = VOP_GET_WRITECOUNT(vp, &writecount); if (error != 0) goto cleanup; if (writecount != 0) { error = ETXTBSY; goto cleanup; } /* Executable? */ error = VOP_GETATTR(vp, &attr, td->td_ucred); if (error) goto cleanup; if ((vp->v_mount->mnt_flag & MNT_NOEXEC) || ((attr.va_mode & 0111) == 0) || (attr.va_type != VREG)) { /* EACCESS is what exec(2) returns. */ error = ENOEXEC; goto cleanup; } /* Sensible size? */ if (attr.va_size == 0) { error = ENOEXEC; goto cleanup; } /* Can we access it? */ error = VOP_ACCESS(vp, VEXEC, td->td_ucred, td); if (error) goto cleanup; /* * XXX: This should use vn_open() so that it is properly authorized, * and to reduce code redundancy all over the place here. * XXX: Not really, it duplicates far more of exec_check_permissions() * than vn_open(). */ #ifdef MAC error = mac_vnode_check_open(td->td_ucred, vp, VREAD); if (error) goto cleanup; #endif error = VOP_OPEN(vp, FREAD, td->td_ucred, td, NULL); if (error) goto cleanup; /* Pull in executable header into exec_map */ error = vm_mmap(exec_map, (vm_offset_t *)&a_out, PAGE_SIZE, VM_PROT_READ, VM_PROT_READ, 0, OBJT_VNODE, vp, 0); if (error) goto cleanup; /* Is it a Linux binary ? */ if (((a_out->a_magic >> 16) & 0xff) != 0x64) { error = ENOEXEC; goto cleanup; } /* * While we are here, we should REALLY do some more checks */ /* Set file/virtual offset based on a.out variant. */ switch ((int)(a_out->a_magic & 0xffff)) { case 0413: /* ZMAGIC */ file_offset = 1024; break; case 0314: /* QMAGIC */ file_offset = 0; break; default: error = ENOEXEC; goto cleanup; } bss_size = round_page(a_out->a_bss); /* Check various fields in header for validity/bounds. */ if (a_out->a_text & PAGE_MASK || a_out->a_data & PAGE_MASK) { error = ENOEXEC; goto cleanup; } /* text + data can't exceed file size */ if (a_out->a_data + a_out->a_text > attr.va_size) { error = EFAULT; goto cleanup; } /* * text/data/bss must not exceed limits * XXX - this is not complete. it should check current usage PLUS * the resources needed by this library. */ PROC_LOCK(td->td_proc); if (a_out->a_text > maxtsiz || a_out->a_data + bss_size > lim_cur_proc(td->td_proc, RLIMIT_DATA) || racct_set(td->td_proc, RACCT_DATA, a_out->a_data + bss_size) != 0) { PROC_UNLOCK(td->td_proc); error = ENOMEM; goto cleanup; } PROC_UNLOCK(td->td_proc); /* * Prevent more writers. * XXX: Note that if any of the VM operations fail below we don't * clear this flag. */ VOP_SET_TEXT(vp); /* * Lock no longer needed */ locked = 0; VOP_UNLOCK(vp, 0); /* * Check if file_offset page aligned. Currently we cannot handle * misalinged file offsets, and so we read in the entire image * (what a waste). */ if (file_offset & PAGE_MASK) { #ifdef DEBUG printf("uselib: Non page aligned binary %lu\n", file_offset); #endif /* Map text+data read/write/execute */ /* a_entry is the load address and is page aligned */ vmaddr = trunc_page(a_out->a_entry); /* get anon user mapping, read+write+execute */ error = vm_map_find(&td->td_proc->p_vmspace->vm_map, NULL, 0, &vmaddr, a_out->a_text + a_out->a_data, 0, VMFS_NO_SPACE, VM_PROT_ALL, VM_PROT_ALL, 0); if (error) goto cleanup; error = vn_rdwr(UIO_READ, vp, (void *)vmaddr, file_offset, a_out->a_text + a_out->a_data, UIO_USERSPACE, 0, td->td_ucred, NOCRED, &aresid, td); if (error != 0) goto cleanup; if (aresid != 0) { error = ENOEXEC; goto cleanup; } } else { #ifdef DEBUG printf("uselib: Page aligned binary %lu\n", file_offset); #endif /* * for QMAGIC, a_entry is 20 bytes beyond the load address * to skip the executable header */ vmaddr = trunc_page(a_out->a_entry); /* * Map it all into the process's space as a single * copy-on-write "data" segment. */ error = vm_mmap(&td->td_proc->p_vmspace->vm_map, &vmaddr, a_out->a_text + a_out->a_data, VM_PROT_ALL, VM_PROT_ALL, MAP_PRIVATE | MAP_FIXED, OBJT_VNODE, vp, file_offset); if (error) goto cleanup; } #ifdef DEBUG printf("mem=%08lx = %08lx %08lx\n", (long)vmaddr, ((long *)vmaddr)[0], ((long *)vmaddr)[1]); #endif if (bss_size != 0) { /* Calculate BSS start address */ vmaddr = trunc_page(a_out->a_entry) + a_out->a_text + a_out->a_data; /* allocate some 'anon' space */ error = vm_map_find(&td->td_proc->p_vmspace->vm_map, NULL, 0, &vmaddr, bss_size, 0, VMFS_NO_SPACE, VM_PROT_ALL, VM_PROT_ALL, 0); if (error) goto cleanup; } cleanup: /* Unlock vnode if needed */ if (locked) VOP_UNLOCK(vp, 0); /* Release the temporary mapping. */ if (a_out) kmap_free_wakeup(exec_map, (vm_offset_t)a_out, PAGE_SIZE); return (error); } #endif /* __i386__ */ int linux_select(struct thread *td, struct linux_select_args *args) { l_timeval ltv; struct timeval tv0, tv1, utv, *tvp; int error; #ifdef DEBUG if (ldebug(select)) printf(ARGS(select, "%d, %p, %p, %p, %p"), args->nfds, (void *)args->readfds, (void *)args->writefds, (void *)args->exceptfds, (void *)args->timeout); #endif /* * Store current time for computation of the amount of * time left. */ if (args->timeout) { if ((error = copyin(args->timeout, <v, sizeof(ltv)))) goto select_out; utv.tv_sec = ltv.tv_sec; utv.tv_usec = ltv.tv_usec; #ifdef DEBUG if (ldebug(select)) printf(LMSG("incoming timeout (%jd/%ld)"), (intmax_t)utv.tv_sec, utv.tv_usec); #endif if (itimerfix(&utv)) { /* * The timeval was invalid. Convert it to something * valid that will act as it does under Linux. */ utv.tv_sec += utv.tv_usec / 1000000; utv.tv_usec %= 1000000; if (utv.tv_usec < 0) { utv.tv_sec -= 1; utv.tv_usec += 1000000; } if (utv.tv_sec < 0) timevalclear(&utv); } microtime(&tv0); tvp = &utv; } else tvp = NULL; error = kern_select(td, args->nfds, args->readfds, args->writefds, args->exceptfds, tvp, LINUX_NFDBITS); #ifdef DEBUG if (ldebug(select)) printf(LMSG("real select returns %d"), error); #endif if (error) goto select_out; if (args->timeout) { if (td->td_retval[0]) { /* * Compute how much time was left of the timeout, * by subtracting the current time and the time * before we started the call, and subtracting * that result from the user-supplied value. */ microtime(&tv1); timevalsub(&tv1, &tv0); timevalsub(&utv, &tv1); if (utv.tv_sec < 0) timevalclear(&utv); } else timevalclear(&utv); #ifdef DEBUG if (ldebug(select)) printf(LMSG("outgoing timeout (%jd/%ld)"), (intmax_t)utv.tv_sec, utv.tv_usec); #endif ltv.tv_sec = utv.tv_sec; ltv.tv_usec = utv.tv_usec; if ((error = copyout(<v, args->timeout, sizeof(ltv)))) goto select_out; } select_out: #ifdef DEBUG if (ldebug(select)) printf(LMSG("select_out -> %d"), error); #endif return (error); } int linux_mremap(struct thread *td, struct linux_mremap_args *args) { struct munmap_args /* { void *addr; size_t len; } */ bsd_args; int error = 0; #ifdef DEBUG if (ldebug(mremap)) printf(ARGS(mremap, "%p, %08lx, %08lx, %08lx"), (void *)(uintptr_t)args->addr, (unsigned long)args->old_len, (unsigned long)args->new_len, (unsigned long)args->flags); #endif if (args->flags & ~(LINUX_MREMAP_FIXED | LINUX_MREMAP_MAYMOVE)) { td->td_retval[0] = 0; return (EINVAL); } /* * Check for the page alignment. * Linux defines PAGE_MASK to be FreeBSD ~PAGE_MASK. */ if (args->addr & PAGE_MASK) { td->td_retval[0] = 0; return (EINVAL); } args->new_len = round_page(args->new_len); args->old_len = round_page(args->old_len); if (args->new_len > args->old_len) { td->td_retval[0] = 0; return (ENOMEM); } if (args->new_len < args->old_len) { bsd_args.addr = (caddr_t)((uintptr_t)args->addr + args->new_len); bsd_args.len = args->old_len - args->new_len; error = sys_munmap(td, &bsd_args); } td->td_retval[0] = error ? 0 : (uintptr_t)args->addr; return (error); } #define LINUX_MS_ASYNC 0x0001 #define LINUX_MS_INVALIDATE 0x0002 #define LINUX_MS_SYNC 0x0004 int linux_msync(struct thread *td, struct linux_msync_args *args) { struct msync_args bsd_args; bsd_args.addr = (caddr_t)(uintptr_t)args->addr; bsd_args.len = (uintptr_t)args->len; bsd_args.flags = args->fl & ~LINUX_MS_SYNC; return (sys_msync(td, &bsd_args)); } int linux_time(struct thread *td, struct linux_time_args *args) { struct timeval tv; l_time_t tm; int error; #ifdef DEBUG if (ldebug(time)) printf(ARGS(time, "*")); #endif microtime(&tv); tm = tv.tv_sec; if (args->tm && (error = copyout(&tm, args->tm, sizeof(tm)))) return (error); td->td_retval[0] = tm; return (0); } struct l_times_argv { l_clock_t tms_utime; l_clock_t tms_stime; l_clock_t tms_cutime; l_clock_t tms_cstime; }; /* * Glibc versions prior to 2.2.1 always use hard-coded CLK_TCK value. * Since 2.2.1 Glibc uses value exported from kernel via AT_CLKTCK * auxiliary vector entry. */ #define CLK_TCK 100 #define CONVOTCK(r) (r.tv_sec * CLK_TCK + r.tv_usec / (1000000 / CLK_TCK)) #define CONVNTCK(r) (r.tv_sec * stclohz + r.tv_usec / (1000000 / stclohz)) #define CONVTCK(r) (linux_kernver(td) >= LINUX_KERNVER_2004000 ? \ CONVNTCK(r) : CONVOTCK(r)) int linux_times(struct thread *td, struct linux_times_args *args) { struct timeval tv, utime, stime, cutime, cstime; struct l_times_argv tms; struct proc *p; int error; #ifdef DEBUG if (ldebug(times)) printf(ARGS(times, "*")); #endif if (args->buf != NULL) { p = td->td_proc; PROC_LOCK(p); PROC_STATLOCK(p); calcru(p, &utime, &stime); PROC_STATUNLOCK(p); calccru(p, &cutime, &cstime); PROC_UNLOCK(p); tms.tms_utime = CONVTCK(utime); tms.tms_stime = CONVTCK(stime); tms.tms_cutime = CONVTCK(cutime); tms.tms_cstime = CONVTCK(cstime); if ((error = copyout(&tms, args->buf, sizeof(tms)))) return (error); } microuptime(&tv); td->td_retval[0] = (int)CONVTCK(tv); return (0); } int linux_newuname(struct thread *td, struct linux_newuname_args *args) { struct l_new_utsname utsname; char osname[LINUX_MAX_UTSNAME]; char osrelease[LINUX_MAX_UTSNAME]; char *p; #ifdef DEBUG if (ldebug(newuname)) printf(ARGS(newuname, "*")); #endif linux_get_osname(td, osname); linux_get_osrelease(td, osrelease); bzero(&utsname, sizeof(utsname)); strlcpy(utsname.sysname, osname, LINUX_MAX_UTSNAME); getcredhostname(td->td_ucred, utsname.nodename, LINUX_MAX_UTSNAME); getcreddomainname(td->td_ucred, utsname.domainname, LINUX_MAX_UTSNAME); strlcpy(utsname.release, osrelease, LINUX_MAX_UTSNAME); strlcpy(utsname.version, version, LINUX_MAX_UTSNAME); for (p = utsname.version; *p != '\0'; ++p) if (*p == '\n') { *p = '\0'; break; } strlcpy(utsname.machine, linux_kplatform, LINUX_MAX_UTSNAME); return (copyout(&utsname, args->buf, sizeof(utsname))); } struct l_utimbuf { l_time_t l_actime; l_time_t l_modtime; }; int linux_utime(struct thread *td, struct linux_utime_args *args) { struct timeval tv[2], *tvp; struct l_utimbuf lut; char *fname; int error; LCONVPATHEXIST(td, args->fname, &fname); #ifdef DEBUG if (ldebug(utime)) printf(ARGS(utime, "%s, *"), fname); #endif if (args->times) { if ((error = copyin(args->times, &lut, sizeof lut))) { LFREEPATH(fname); return (error); } tv[0].tv_sec = lut.l_actime; tv[0].tv_usec = 0; tv[1].tv_sec = lut.l_modtime; tv[1].tv_usec = 0; tvp = tv; } else tvp = NULL; error = kern_utimesat(td, AT_FDCWD, fname, UIO_SYSSPACE, tvp, UIO_SYSSPACE); LFREEPATH(fname); return (error); } int linux_utimes(struct thread *td, struct linux_utimes_args *args) { l_timeval ltv[2]; struct timeval tv[2], *tvp = NULL; char *fname; int error; LCONVPATHEXIST(td, args->fname, &fname); #ifdef DEBUG if (ldebug(utimes)) printf(ARGS(utimes, "%s, *"), fname); #endif if (args->tptr != NULL) { if ((error = copyin(args->tptr, ltv, sizeof ltv))) { LFREEPATH(fname); return (error); } tv[0].tv_sec = ltv[0].tv_sec; tv[0].tv_usec = ltv[0].tv_usec; tv[1].tv_sec = ltv[1].tv_sec; tv[1].tv_usec = ltv[1].tv_usec; tvp = tv; } error = kern_utimesat(td, AT_FDCWD, fname, UIO_SYSSPACE, tvp, UIO_SYSSPACE); LFREEPATH(fname); return (error); } static int linux_utimensat_nsec_valid(l_long nsec) { if (nsec == LINUX_UTIME_OMIT || nsec == LINUX_UTIME_NOW) return (0); if (nsec >= 0 && nsec <= 999999999) return (0); return (1); } int linux_utimensat(struct thread *td, struct linux_utimensat_args *args) { struct l_timespec l_times[2]; struct timespec times[2], *timesp = NULL; char *path = NULL; int error, dfd, flags = 0; dfd = (args->dfd == LINUX_AT_FDCWD) ? AT_FDCWD : args->dfd; #ifdef DEBUG if (ldebug(utimensat)) printf(ARGS(utimensat, "%d, *"), dfd); #endif if (args->flags & ~LINUX_AT_SYMLINK_NOFOLLOW) return (EINVAL); if (args->times != NULL) { error = copyin(args->times, l_times, sizeof(l_times)); if (error != 0) return (error); if (linux_utimensat_nsec_valid(l_times[0].tv_nsec) != 0 || linux_utimensat_nsec_valid(l_times[1].tv_nsec) != 0) return (EINVAL); times[0].tv_sec = l_times[0].tv_sec; switch (l_times[0].tv_nsec) { case LINUX_UTIME_OMIT: times[0].tv_nsec = UTIME_OMIT; break; case LINUX_UTIME_NOW: times[0].tv_nsec = UTIME_NOW; break; default: times[0].tv_nsec = l_times[0].tv_nsec; } times[1].tv_sec = l_times[1].tv_sec; switch (l_times[1].tv_nsec) { case LINUX_UTIME_OMIT: times[1].tv_nsec = UTIME_OMIT; break; case LINUX_UTIME_NOW: times[1].tv_nsec = UTIME_NOW; break; default: times[1].tv_nsec = l_times[1].tv_nsec; break; } timesp = times; /* This breaks POSIX, but is what the Linux kernel does * _on purpose_ (documented in the man page for utimensat(2)), * so we must follow that behaviour. */ if (times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT) return (0); } if (args->pathname != NULL) LCONVPATHEXIST_AT(td, args->pathname, &path, dfd); else if (args->flags != 0) return (EINVAL); if (args->flags & LINUX_AT_SYMLINK_NOFOLLOW) flags |= AT_SYMLINK_NOFOLLOW; if (path == NULL) error = kern_futimens(td, dfd, timesp, UIO_SYSSPACE); else { error = kern_utimensat(td, dfd, path, UIO_SYSSPACE, timesp, UIO_SYSSPACE, flags); LFREEPATH(path); } return (error); } int linux_futimesat(struct thread *td, struct linux_futimesat_args *args) { l_timeval ltv[2]; struct timeval tv[2], *tvp = NULL; char *fname; int error, dfd; dfd = (args->dfd == LINUX_AT_FDCWD) ? AT_FDCWD : args->dfd; LCONVPATHEXIST_AT(td, args->filename, &fname, dfd); #ifdef DEBUG if (ldebug(futimesat)) printf(ARGS(futimesat, "%s, *"), fname); #endif if (args->utimes != NULL) { if ((error = copyin(args->utimes, ltv, sizeof ltv))) { LFREEPATH(fname); return (error); } tv[0].tv_sec = ltv[0].tv_sec; tv[0].tv_usec = ltv[0].tv_usec; tv[1].tv_sec = ltv[1].tv_sec; tv[1].tv_usec = ltv[1].tv_usec; tvp = tv; } error = kern_utimesat(td, dfd, fname, UIO_SYSSPACE, tvp, UIO_SYSSPACE); LFREEPATH(fname); return (error); } int linux_common_wait(struct thread *td, int pid, int *status, int options, struct rusage *ru) { int error, tmpstat; error = kern_wait(td, pid, &tmpstat, options, ru); if (error) return (error); if (status) { tmpstat &= 0xffff; if (WIFSIGNALED(tmpstat)) tmpstat = (tmpstat & 0xffffff80) | bsd_to_linux_signal(WTERMSIG(tmpstat)); else if (WIFSTOPPED(tmpstat)) tmpstat = (tmpstat & 0xffff00ff) | (bsd_to_linux_signal(WSTOPSIG(tmpstat)) << 8); else if (WIFCONTINUED(tmpstat)) tmpstat = 0xffff; error = copyout(&tmpstat, status, sizeof(int)); } return (error); } #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) int linux_waitpid(struct thread *td, struct linux_waitpid_args *args) { struct linux_wait4_args wait4_args; #ifdef DEBUG if (ldebug(waitpid)) printf(ARGS(waitpid, "%d, %p, %d"), args->pid, (void *)args->status, args->options); #endif wait4_args.pid = args->pid; wait4_args.status = args->status; wait4_args.options = args->options; wait4_args.rusage = NULL; return (linux_wait4(td, &wait4_args)); } #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ int linux_wait4(struct thread *td, struct linux_wait4_args *args) { int error, options; struct rusage ru, *rup; #ifdef DEBUG if (ldebug(wait4)) printf(ARGS(wait4, "%d, %p, %d, %p"), args->pid, (void *)args->status, args->options, (void *)args->rusage); #endif if (args->options & ~(LINUX_WUNTRACED | LINUX_WNOHANG | LINUX_WCONTINUED | __WCLONE | __WNOTHREAD | __WALL)) return (EINVAL); options = WEXITED; linux_to_bsd_waitopts(args->options, &options); if (args->rusage != NULL) rup = &ru; else rup = NULL; error = linux_common_wait(td, args->pid, args->status, options, rup); if (error != 0) return (error); if (args->rusage != NULL) error = linux_copyout_rusage(&ru, args->rusage); return (error); } int linux_waitid(struct thread *td, struct linux_waitid_args *args) { int status, options, sig; struct __wrusage wru; siginfo_t siginfo; l_siginfo_t lsi; idtype_t idtype; struct proc *p; int error; options = 0; linux_to_bsd_waitopts(args->options, &options); if (options & ~(WNOHANG | WNOWAIT | WEXITED | WUNTRACED | WCONTINUED)) return (EINVAL); if (!(options & (WEXITED | WUNTRACED | WCONTINUED))) return (EINVAL); switch (args->idtype) { case LINUX_P_ALL: idtype = P_ALL; break; case LINUX_P_PID: if (args->id <= 0) return (EINVAL); idtype = P_PID; break; case LINUX_P_PGID: if (args->id <= 0) return (EINVAL); idtype = P_PGID; break; default: return (EINVAL); } error = kern_wait6(td, idtype, args->id, &status, options, &wru, &siginfo); if (error != 0) return (error); if (args->rusage != NULL) { error = linux_copyout_rusage(&wru.wru_children, args->rusage); if (error != 0) return (error); } if (args->info != NULL) { p = td->td_proc; if (td->td_retval[0] == 0) bzero(&lsi, sizeof(lsi)); else { sig = bsd_to_linux_signal(siginfo.si_signo); siginfo_to_lsiginfo(&siginfo, &lsi, sig); } error = copyout(&lsi, args->info, sizeof(lsi)); } td->td_retval[0] = 0; return (error); } int linux_mknod(struct thread *td, struct linux_mknod_args *args) { char *path; int error; LCONVPATHCREAT(td, args->path, &path); #ifdef DEBUG if (ldebug(mknod)) printf(ARGS(mknod, "%s, %d, %ju"), path, args->mode, (uintmax_t)args->dev); #endif switch (args->mode & S_IFMT) { case S_IFIFO: case S_IFSOCK: error = kern_mkfifoat(td, AT_FDCWD, path, UIO_SYSSPACE, args->mode); break; case S_IFCHR: case S_IFBLK: error = kern_mknodat(td, AT_FDCWD, path, UIO_SYSSPACE, args->mode, args->dev); break; case S_IFDIR: error = EPERM; break; case 0: args->mode |= S_IFREG; /* FALLTHROUGH */ case S_IFREG: error = kern_openat(td, AT_FDCWD, path, UIO_SYSSPACE, O_WRONLY | O_CREAT | O_TRUNC, args->mode); if (error == 0) kern_close(td, td->td_retval[0]); break; default: error = EINVAL; break; } LFREEPATH(path); return (error); } int linux_mknodat(struct thread *td, struct linux_mknodat_args *args) { char *path; int error, dfd; dfd = (args->dfd == LINUX_AT_FDCWD) ? AT_FDCWD : args->dfd; LCONVPATHCREAT_AT(td, args->filename, &path, dfd); #ifdef DEBUG if (ldebug(mknodat)) printf(ARGS(mknodat, "%s, %d, %d"), path, args->mode, args->dev); #endif switch (args->mode & S_IFMT) { case S_IFIFO: case S_IFSOCK: error = kern_mkfifoat(td, dfd, path, UIO_SYSSPACE, args->mode); break; case S_IFCHR: case S_IFBLK: error = kern_mknodat(td, dfd, path, UIO_SYSSPACE, args->mode, args->dev); break; case S_IFDIR: error = EPERM; break; case 0: args->mode |= S_IFREG; /* FALLTHROUGH */ case S_IFREG: error = kern_openat(td, dfd, path, UIO_SYSSPACE, O_WRONLY | O_CREAT | O_TRUNC, args->mode); if (error == 0) kern_close(td, td->td_retval[0]); break; default: error = EINVAL; break; } LFREEPATH(path); return (error); } /* * UGH! This is just about the dumbest idea I've ever heard!! */ int linux_personality(struct thread *td, struct linux_personality_args *args) { #ifdef DEBUG if (ldebug(personality)) printf(ARGS(personality, "%lu"), (unsigned long)args->per); #endif if (args->per != 0) return (EINVAL); /* Yes Jim, it's still a Linux... */ td->td_retval[0] = 0; return (0); } struct l_itimerval { l_timeval it_interval; l_timeval it_value; }; #define B2L_ITIMERVAL(bip, lip) \ (bip)->it_interval.tv_sec = (lip)->it_interval.tv_sec; \ (bip)->it_interval.tv_usec = (lip)->it_interval.tv_usec; \ (bip)->it_value.tv_sec = (lip)->it_value.tv_sec; \ (bip)->it_value.tv_usec = (lip)->it_value.tv_usec; int linux_setitimer(struct thread *td, struct linux_setitimer_args *uap) { int error; struct l_itimerval ls; struct itimerval aitv, oitv; #ifdef DEBUG if (ldebug(setitimer)) printf(ARGS(setitimer, "%p, %p"), (void *)uap->itv, (void *)uap->oitv); #endif if (uap->itv == NULL) { uap->itv = uap->oitv; return (linux_getitimer(td, (struct linux_getitimer_args *)uap)); } error = copyin(uap->itv, &ls, sizeof(ls)); if (error != 0) return (error); B2L_ITIMERVAL(&aitv, &ls); #ifdef DEBUG if (ldebug(setitimer)) { printf("setitimer: value: sec: %jd, usec: %ld\n", (intmax_t)aitv.it_value.tv_sec, aitv.it_value.tv_usec); printf("setitimer: interval: sec: %jd, usec: %ld\n", (intmax_t)aitv.it_interval.tv_sec, aitv.it_interval.tv_usec); } #endif error = kern_setitimer(td, uap->which, &aitv, &oitv); if (error != 0 || uap->oitv == NULL) return (error); B2L_ITIMERVAL(&ls, &oitv); return (copyout(&ls, uap->oitv, sizeof(ls))); } int linux_getitimer(struct thread *td, struct linux_getitimer_args *uap) { int error; struct l_itimerval ls; struct itimerval aitv; #ifdef DEBUG if (ldebug(getitimer)) printf(ARGS(getitimer, "%p"), (void *)uap->itv); #endif error = kern_getitimer(td, uap->which, &aitv); if (error != 0) return (error); B2L_ITIMERVAL(&ls, &aitv); return (copyout(&ls, uap->itv, sizeof(ls))); } #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) int linux_nice(struct thread *td, struct linux_nice_args *args) { struct setpriority_args bsd_args; bsd_args.which = PRIO_PROCESS; bsd_args.who = 0; /* current process */ bsd_args.prio = args->inc; return (sys_setpriority(td, &bsd_args)); } #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ int linux_setgroups(struct thread *td, struct linux_setgroups_args *args) { struct ucred *newcred, *oldcred; l_gid_t *linux_gidset; gid_t *bsd_gidset; int ngrp, error; struct proc *p; ngrp = args->gidsetsize; if (ngrp < 0 || ngrp >= ngroups_max + 1) return (EINVAL); linux_gidset = malloc(ngrp * sizeof(*linux_gidset), M_LINUX, M_WAITOK); error = copyin(args->grouplist, linux_gidset, ngrp * sizeof(l_gid_t)); if (error) goto out; newcred = crget(); crextend(newcred, ngrp + 1); p = td->td_proc; PROC_LOCK(p); oldcred = p->p_ucred; crcopy(newcred, oldcred); /* * cr_groups[0] holds egid. Setting the whole set from * the supplied set will cause egid to be changed too. * Keep cr_groups[0] unchanged to prevent that. */ if ((error = priv_check_cred(oldcred, PRIV_CRED_SETGROUPS, 0)) != 0) { PROC_UNLOCK(p); crfree(newcred); goto out; } if (ngrp > 0) { newcred->cr_ngroups = ngrp + 1; bsd_gidset = newcred->cr_groups; ngrp--; while (ngrp >= 0) { bsd_gidset[ngrp + 1] = linux_gidset[ngrp]; ngrp--; } } else newcred->cr_ngroups = 1; setsugid(p); proc_set_cred(p, newcred); PROC_UNLOCK(p); crfree(oldcred); error = 0; out: free(linux_gidset, M_LINUX); return (error); } int linux_getgroups(struct thread *td, struct linux_getgroups_args *args) { struct ucred *cred; l_gid_t *linux_gidset; gid_t *bsd_gidset; int bsd_gidsetsz, ngrp, error; cred = td->td_ucred; bsd_gidset = cred->cr_groups; bsd_gidsetsz = cred->cr_ngroups - 1; /* * cr_groups[0] holds egid. Returning the whole set * here will cause a duplicate. Exclude cr_groups[0] * to prevent that. */ if ((ngrp = args->gidsetsize) == 0) { td->td_retval[0] = bsd_gidsetsz; return (0); } if (ngrp < bsd_gidsetsz) return (EINVAL); ngrp = 0; linux_gidset = malloc(bsd_gidsetsz * sizeof(*linux_gidset), M_LINUX, M_WAITOK); while (ngrp < bsd_gidsetsz) { linux_gidset[ngrp] = bsd_gidset[ngrp + 1]; ngrp++; } error = copyout(linux_gidset, args->grouplist, ngrp * sizeof(l_gid_t)); free(linux_gidset, M_LINUX); if (error) return (error); td->td_retval[0] = ngrp; return (0); } int linux_setrlimit(struct thread *td, struct linux_setrlimit_args *args) { struct rlimit bsd_rlim; struct l_rlimit rlim; u_int which; int error; #ifdef DEBUG if (ldebug(setrlimit)) printf(ARGS(setrlimit, "%d, %p"), args->resource, (void *)args->rlim); #endif if (args->resource >= LINUX_RLIM_NLIMITS) return (EINVAL); which = linux_to_bsd_resource[args->resource]; if (which == -1) return (EINVAL); error = copyin(args->rlim, &rlim, sizeof(rlim)); if (error) return (error); bsd_rlim.rlim_cur = (rlim_t)rlim.rlim_cur; bsd_rlim.rlim_max = (rlim_t)rlim.rlim_max; return (kern_setrlimit(td, which, &bsd_rlim)); } #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) int linux_old_getrlimit(struct thread *td, struct linux_old_getrlimit_args *args) { struct l_rlimit rlim; struct rlimit bsd_rlim; u_int which; #ifdef DEBUG if (ldebug(old_getrlimit)) printf(ARGS(old_getrlimit, "%d, %p"), args->resource, (void *)args->rlim); #endif if (args->resource >= LINUX_RLIM_NLIMITS) return (EINVAL); which = linux_to_bsd_resource[args->resource]; if (which == -1) return (EINVAL); lim_rlimit(td, which, &bsd_rlim); #ifdef COMPAT_LINUX32 rlim.rlim_cur = (unsigned int)bsd_rlim.rlim_cur; if (rlim.rlim_cur == UINT_MAX) rlim.rlim_cur = INT_MAX; rlim.rlim_max = (unsigned int)bsd_rlim.rlim_max; if (rlim.rlim_max == UINT_MAX) rlim.rlim_max = INT_MAX; #else rlim.rlim_cur = (unsigned long)bsd_rlim.rlim_cur; if (rlim.rlim_cur == ULONG_MAX) rlim.rlim_cur = LONG_MAX; rlim.rlim_max = (unsigned long)bsd_rlim.rlim_max; if (rlim.rlim_max == ULONG_MAX) rlim.rlim_max = LONG_MAX; #endif return (copyout(&rlim, args->rlim, sizeof(rlim))); } #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ int linux_getrlimit(struct thread *td, struct linux_getrlimit_args *args) { struct l_rlimit rlim; struct rlimit bsd_rlim; u_int which; #ifdef DEBUG if (ldebug(getrlimit)) printf(ARGS(getrlimit, "%d, %p"), args->resource, (void *)args->rlim); #endif if (args->resource >= LINUX_RLIM_NLIMITS) return (EINVAL); which = linux_to_bsd_resource[args->resource]; if (which == -1) return (EINVAL); lim_rlimit(td, which, &bsd_rlim); rlim.rlim_cur = (l_ulong)bsd_rlim.rlim_cur; rlim.rlim_max = (l_ulong)bsd_rlim.rlim_max; return (copyout(&rlim, args->rlim, sizeof(rlim))); } int linux_sched_setscheduler(struct thread *td, struct linux_sched_setscheduler_args *args) { struct sched_param sched_param; struct thread *tdt; int error, policy; #ifdef DEBUG if (ldebug(sched_setscheduler)) printf(ARGS(sched_setscheduler, "%d, %d, %p"), args->pid, args->policy, (const void *)args->param); #endif switch (args->policy) { case LINUX_SCHED_OTHER: policy = SCHED_OTHER; break; case LINUX_SCHED_FIFO: policy = SCHED_FIFO; break; case LINUX_SCHED_RR: policy = SCHED_RR; break; default: return (EINVAL); } error = copyin(args->param, &sched_param, sizeof(sched_param)); if (error) return (error); tdt = linux_tdfind(td, args->pid, -1); if (tdt == NULL) return (ESRCH); error = kern_sched_setscheduler(td, tdt, policy, &sched_param); PROC_UNLOCK(tdt->td_proc); return (error); } int linux_sched_getscheduler(struct thread *td, struct linux_sched_getscheduler_args *args) { struct thread *tdt; int error, policy; #ifdef DEBUG if (ldebug(sched_getscheduler)) printf(ARGS(sched_getscheduler, "%d"), args->pid); #endif tdt = linux_tdfind(td, args->pid, -1); if (tdt == NULL) return (ESRCH); error = kern_sched_getscheduler(td, tdt, &policy); PROC_UNLOCK(tdt->td_proc); switch (policy) { case SCHED_OTHER: td->td_retval[0] = LINUX_SCHED_OTHER; break; case SCHED_FIFO: td->td_retval[0] = LINUX_SCHED_FIFO; break; case SCHED_RR: td->td_retval[0] = LINUX_SCHED_RR; break; } return (error); } int linux_sched_get_priority_max(struct thread *td, struct linux_sched_get_priority_max_args *args) { struct sched_get_priority_max_args bsd; #ifdef DEBUG if (ldebug(sched_get_priority_max)) printf(ARGS(sched_get_priority_max, "%d"), args->policy); #endif switch (args->policy) { case LINUX_SCHED_OTHER: bsd.policy = SCHED_OTHER; break; case LINUX_SCHED_FIFO: bsd.policy = SCHED_FIFO; break; case LINUX_SCHED_RR: bsd.policy = SCHED_RR; break; default: return (EINVAL); } return (sys_sched_get_priority_max(td, &bsd)); } int linux_sched_get_priority_min(struct thread *td, struct linux_sched_get_priority_min_args *args) { struct sched_get_priority_min_args bsd; #ifdef DEBUG if (ldebug(sched_get_priority_min)) printf(ARGS(sched_get_priority_min, "%d"), args->policy); #endif switch (args->policy) { case LINUX_SCHED_OTHER: bsd.policy = SCHED_OTHER; break; case LINUX_SCHED_FIFO: bsd.policy = SCHED_FIFO; break; case LINUX_SCHED_RR: bsd.policy = SCHED_RR; break; default: return (EINVAL); } return (sys_sched_get_priority_min(td, &bsd)); } #define REBOOT_CAD_ON 0x89abcdef #define REBOOT_CAD_OFF 0 #define REBOOT_HALT 0xcdef0123 #define REBOOT_RESTART 0x01234567 #define REBOOT_RESTART2 0xA1B2C3D4 #define REBOOT_POWEROFF 0x4321FEDC #define REBOOT_MAGIC1 0xfee1dead #define REBOOT_MAGIC2 0x28121969 #define REBOOT_MAGIC2A 0x05121996 #define REBOOT_MAGIC2B 0x16041998 int linux_reboot(struct thread *td, struct linux_reboot_args *args) { struct reboot_args bsd_args; #ifdef DEBUG if (ldebug(reboot)) printf(ARGS(reboot, "0x%x"), args->cmd); #endif if (args->magic1 != REBOOT_MAGIC1) return (EINVAL); switch (args->magic2) { case REBOOT_MAGIC2: case REBOOT_MAGIC2A: case REBOOT_MAGIC2B: break; default: return (EINVAL); } switch (args->cmd) { case REBOOT_CAD_ON: case REBOOT_CAD_OFF: return (priv_check(td, PRIV_REBOOT)); case REBOOT_HALT: bsd_args.opt = RB_HALT; break; case REBOOT_RESTART: case REBOOT_RESTART2: bsd_args.opt = 0; break; case REBOOT_POWEROFF: bsd_args.opt = RB_POWEROFF; break; default: return (EINVAL); } return (sys_reboot(td, &bsd_args)); } /* * The FreeBSD native getpid(2), getgid(2) and getuid(2) also modify * td->td_retval[1] when COMPAT_43 is defined. This clobbers registers that * are assumed to be preserved. The following lightweight syscalls fixes * this. See also linux_getgid16() and linux_getuid16() in linux_uid16.c * * linux_getpid() - MP SAFE * linux_getgid() - MP SAFE * linux_getuid() - MP SAFE */ int linux_getpid(struct thread *td, struct linux_getpid_args *args) { #ifdef DEBUG if (ldebug(getpid)) printf(ARGS(getpid, "")); #endif td->td_retval[0] = td->td_proc->p_pid; return (0); } int linux_gettid(struct thread *td, struct linux_gettid_args *args) { struct linux_emuldata *em; #ifdef DEBUG if (ldebug(gettid)) printf(ARGS(gettid, "")); #endif em = em_find(td); KASSERT(em != NULL, ("gettid: emuldata not found.\n")); td->td_retval[0] = em->em_tid; return (0); } int linux_getppid(struct thread *td, struct linux_getppid_args *args) { #ifdef DEBUG if (ldebug(getppid)) printf(ARGS(getppid, "")); #endif PROC_LOCK(td->td_proc); td->td_retval[0] = td->td_proc->p_pptr->p_pid; PROC_UNLOCK(td->td_proc); return (0); } int linux_getgid(struct thread *td, struct linux_getgid_args *args) { #ifdef DEBUG if (ldebug(getgid)) printf(ARGS(getgid, "")); #endif td->td_retval[0] = td->td_ucred->cr_rgid; return (0); } int linux_getuid(struct thread *td, struct linux_getuid_args *args) { #ifdef DEBUG if (ldebug(getuid)) printf(ARGS(getuid, "")); #endif td->td_retval[0] = td->td_ucred->cr_ruid; return (0); } int linux_getsid(struct thread *td, struct linux_getsid_args *args) { struct getsid_args bsd; #ifdef DEBUG if (ldebug(getsid)) printf(ARGS(getsid, "%i"), args->pid); #endif bsd.pid = args->pid; return (sys_getsid(td, &bsd)); } int linux_nosys(struct thread *td, struct nosys_args *ignore) { return (ENOSYS); } int linux_getpriority(struct thread *td, struct linux_getpriority_args *args) { struct getpriority_args bsd_args; int error; #ifdef DEBUG if (ldebug(getpriority)) printf(ARGS(getpriority, "%i, %i"), args->which, args->who); #endif bsd_args.which = args->which; bsd_args.who = args->who; error = sys_getpriority(td, &bsd_args); td->td_retval[0] = 20 - td->td_retval[0]; return (error); } int linux_sethostname(struct thread *td, struct linux_sethostname_args *args) { int name[2]; #ifdef DEBUG if (ldebug(sethostname)) printf(ARGS(sethostname, "*, %i"), args->len); #endif name[0] = CTL_KERN; name[1] = KERN_HOSTNAME; return (userland_sysctl(td, name, 2, 0, 0, 0, args->hostname, args->len, 0, 0)); } int linux_setdomainname(struct thread *td, struct linux_setdomainname_args *args) { int name[2]; #ifdef DEBUG if (ldebug(setdomainname)) printf(ARGS(setdomainname, "*, %i"), args->len); #endif name[0] = CTL_KERN; name[1] = KERN_NISDOMAINNAME; return (userland_sysctl(td, name, 2, 0, 0, 0, args->name, args->len, 0, 0)); } int linux_exit_group(struct thread *td, struct linux_exit_group_args *args) { #ifdef DEBUG if (ldebug(exit_group)) printf(ARGS(exit_group, "%i"), args->error_code); #endif LINUX_CTR2(exit_group, "thread(%d) (%d)", td->td_tid, args->error_code); /* * XXX: we should send a signal to the parent if * SIGNAL_EXIT_GROUP is set. We ignore that (temporarily?) * as it doesnt occur often. */ exit1(td, args->error_code, 0); /* NOTREACHED */ } #define _LINUX_CAPABILITY_VERSION 0x19980330 struct l_user_cap_header { l_int version; l_int pid; }; struct l_user_cap_data { l_int effective; l_int permitted; l_int inheritable; }; int linux_capget(struct thread *td, struct linux_capget_args *args) { struct l_user_cap_header luch; struct l_user_cap_data lucd; int error; if (args->hdrp == NULL) return (EFAULT); error = copyin(args->hdrp, &luch, sizeof(luch)); if (error != 0) return (error); if (luch.version != _LINUX_CAPABILITY_VERSION) { luch.version = _LINUX_CAPABILITY_VERSION; error = copyout(&luch, args->hdrp, sizeof(luch)); if (error) return (error); return (EINVAL); } if (luch.pid) return (EPERM); if (args->datap) { /* * The current implementation doesn't support setting * a capability (it's essentially a stub) so indicate * that no capabilities are currently set or available * to request. */ bzero (&lucd, sizeof(lucd)); error = copyout(&lucd, args->datap, sizeof(lucd)); } return (error); } int linux_capset(struct thread *td, struct linux_capset_args *args) { struct l_user_cap_header luch; struct l_user_cap_data lucd; int error; if (args->hdrp == NULL || args->datap == NULL) return (EFAULT); error = copyin(args->hdrp, &luch, sizeof(luch)); if (error != 0) return (error); if (luch.version != _LINUX_CAPABILITY_VERSION) { luch.version = _LINUX_CAPABILITY_VERSION; error = copyout(&luch, args->hdrp, sizeof(luch)); if (error) return (error); return (EINVAL); } if (luch.pid) return (EPERM); error = copyin(args->datap, &lucd, sizeof(lucd)); if (error != 0) return (error); /* We currently don't support setting any capabilities. */ if (lucd.effective || lucd.permitted || lucd.inheritable) { linux_msg(td, "capset effective=0x%x, permitted=0x%x, " "inheritable=0x%x is not implemented", (int)lucd.effective, (int)lucd.permitted, (int)lucd.inheritable); return (EPERM); } return (0); } int linux_prctl(struct thread *td, struct linux_prctl_args *args) { int error = 0, max_size; struct proc *p = td->td_proc; char comm[LINUX_MAX_COMM_LEN]; struct linux_emuldata *em; int pdeath_signal; #ifdef DEBUG if (ldebug(prctl)) printf(ARGS(prctl, "%d, %ju, %ju, %ju, %ju"), args->option, (uintmax_t)args->arg2, (uintmax_t)args->arg3, (uintmax_t)args->arg4, (uintmax_t)args->arg5); #endif switch (args->option) { case LINUX_PR_SET_PDEATHSIG: if (!LINUX_SIG_VALID(args->arg2)) return (EINVAL); em = em_find(td); KASSERT(em != NULL, ("prctl: emuldata not found.\n")); em->pdeath_signal = args->arg2; break; case LINUX_PR_GET_PDEATHSIG: em = em_find(td); KASSERT(em != NULL, ("prctl: emuldata not found.\n")); pdeath_signal = em->pdeath_signal; error = copyout(&pdeath_signal, (void *)(register_t)args->arg2, sizeof(pdeath_signal)); break; case LINUX_PR_GET_KEEPCAPS: /* * Indicate that we always clear the effective and * permitted capability sets when the user id becomes * non-zero (actually the capability sets are simply * always zero in the current implementation). */ td->td_retval[0] = 0; break; case LINUX_PR_SET_KEEPCAPS: /* * Ignore requests to keep the effective and permitted * capability sets when the user id becomes non-zero. */ break; case LINUX_PR_SET_NAME: /* * To be on the safe side we need to make sure to not * overflow the size a linux program expects. We already * do this here in the copyin, so that we don't need to * check on copyout. */ max_size = MIN(sizeof(comm), sizeof(p->p_comm)); error = copyinstr((void *)(register_t)args->arg2, comm, max_size, NULL); /* Linux silently truncates the name if it is too long. */ if (error == ENAMETOOLONG) { /* * XXX: copyinstr() isn't documented to populate the * array completely, so do a copyin() to be on the * safe side. This should be changed in case * copyinstr() is changed to guarantee this. */ error = copyin((void *)(register_t)args->arg2, comm, max_size - 1); comm[max_size - 1] = '\0'; } if (error) return (error); PROC_LOCK(p); strlcpy(p->p_comm, comm, sizeof(p->p_comm)); PROC_UNLOCK(p); break; case LINUX_PR_GET_NAME: PROC_LOCK(p); strlcpy(comm, p->p_comm, sizeof(comm)); PROC_UNLOCK(p); error = copyout(comm, (void *)(register_t)args->arg2, strlen(comm) + 1); break; default: error = EINVAL; break; } return (error); } int linux_sched_setparam(struct thread *td, struct linux_sched_setparam_args *uap) { struct sched_param sched_param; struct thread *tdt; int error; #ifdef DEBUG if (ldebug(sched_setparam)) printf(ARGS(sched_setparam, "%d, *"), uap->pid); #endif error = copyin(uap->param, &sched_param, sizeof(sched_param)); if (error) return (error); tdt = linux_tdfind(td, uap->pid, -1); if (tdt == NULL) return (ESRCH); error = kern_sched_setparam(td, tdt, &sched_param); PROC_UNLOCK(tdt->td_proc); return (error); } int linux_sched_getparam(struct thread *td, struct linux_sched_getparam_args *uap) { struct sched_param sched_param; struct thread *tdt; int error; #ifdef DEBUG if (ldebug(sched_getparam)) printf(ARGS(sched_getparam, "%d, *"), uap->pid); #endif tdt = linux_tdfind(td, uap->pid, -1); if (tdt == NULL) return (ESRCH); error = kern_sched_getparam(td, tdt, &sched_param); PROC_UNLOCK(tdt->td_proc); if (error == 0) error = copyout(&sched_param, uap->param, sizeof(sched_param)); return (error); } /* * Get affinity of a process. */ int linux_sched_getaffinity(struct thread *td, struct linux_sched_getaffinity_args *args) { int error; struct thread *tdt; struct cpuset_getaffinity_args cga; #ifdef DEBUG if (ldebug(sched_getaffinity)) printf(ARGS(sched_getaffinity, "%d, %d, *"), args->pid, args->len); #endif if (args->len < sizeof(cpuset_t)) return (EINVAL); tdt = linux_tdfind(td, args->pid, -1); if (tdt == NULL) return (ESRCH); PROC_UNLOCK(tdt->td_proc); cga.level = CPU_LEVEL_WHICH; cga.which = CPU_WHICH_TID; cga.id = tdt->td_tid; cga.cpusetsize = sizeof(cpuset_t); cga.mask = (cpuset_t *) args->user_mask_ptr; if ((error = sys_cpuset_getaffinity(td, &cga)) == 0) td->td_retval[0] = sizeof(cpuset_t); return (error); } /* * Set affinity of a process. */ int linux_sched_setaffinity(struct thread *td, struct linux_sched_setaffinity_args *args) { struct cpuset_setaffinity_args csa; struct thread *tdt; #ifdef DEBUG if (ldebug(sched_setaffinity)) printf(ARGS(sched_setaffinity, "%d, %d, *"), args->pid, args->len); #endif if (args->len < sizeof(cpuset_t)) return (EINVAL); tdt = linux_tdfind(td, args->pid, -1); if (tdt == NULL) return (ESRCH); PROC_UNLOCK(tdt->td_proc); csa.level = CPU_LEVEL_WHICH; csa.which = CPU_WHICH_TID; csa.id = tdt->td_tid; csa.cpusetsize = sizeof(cpuset_t); csa.mask = (cpuset_t *) args->user_mask_ptr; return (sys_cpuset_setaffinity(td, &csa)); } struct linux_rlimit64 { uint64_t rlim_cur; uint64_t rlim_max; }; int linux_prlimit64(struct thread *td, struct linux_prlimit64_args *args) { struct rlimit rlim, nrlim; struct linux_rlimit64 lrlim; struct proc *p; u_int which; int flags; int error; #ifdef DEBUG if (ldebug(prlimit64)) printf(ARGS(prlimit64, "%d, %d, %p, %p"), args->pid, args->resource, (void *)args->new, (void *)args->old); #endif if (args->resource >= LINUX_RLIM_NLIMITS) return (EINVAL); which = linux_to_bsd_resource[args->resource]; if (which == -1) return (EINVAL); if (args->new != NULL) { /* * Note. Unlike FreeBSD where rlim is signed 64-bit Linux * rlim is unsigned 64-bit. FreeBSD treats negative limits * as INFINITY so we do not need a conversion even. */ error = copyin(args->new, &nrlim, sizeof(nrlim)); if (error != 0) return (error); } flags = PGET_HOLD | PGET_NOTWEXIT; if (args->new != NULL) flags |= PGET_CANDEBUG; else flags |= PGET_CANSEE; error = pget(args->pid, flags, &p); if (error != 0) return (error); if (args->old != NULL) { PROC_LOCK(p); lim_rlimit_proc(p, which, &rlim); PROC_UNLOCK(p); if (rlim.rlim_cur == RLIM_INFINITY) lrlim.rlim_cur = LINUX_RLIM_INFINITY; else lrlim.rlim_cur = rlim.rlim_cur; if (rlim.rlim_max == RLIM_INFINITY) lrlim.rlim_max = LINUX_RLIM_INFINITY; else lrlim.rlim_max = rlim.rlim_max; error = copyout(&lrlim, args->old, sizeof(lrlim)); if (error != 0) goto out; } if (args->new != NULL) error = kern_proc_setrlimit(td, p, which, &nrlim); out: PRELE(p); return (error); } int linux_pselect6(struct thread *td, struct linux_pselect6_args *args) { struct timeval utv, tv0, tv1, *tvp; struct l_pselect6arg lpse6; struct l_timespec lts; struct timespec uts; l_sigset_t l_ss; sigset_t *ssp; sigset_t ss; int error; ssp = NULL; if (args->sig != NULL) { error = copyin(args->sig, &lpse6, sizeof(lpse6)); if (error != 0) return (error); if (lpse6.ss_len != sizeof(l_ss)) return (EINVAL); if (lpse6.ss != 0) { error = copyin(PTRIN(lpse6.ss), &l_ss, sizeof(l_ss)); if (error != 0) return (error); linux_to_bsd_sigset(&l_ss, &ss); ssp = &ss; } } /* * Currently glibc changes nanosecond number to microsecond. * This mean losing precision but for now it is hardly seen. */ if (args->tsp != NULL) { error = copyin(args->tsp, <s, sizeof(lts)); if (error != 0) return (error); error = linux_to_native_timespec(&uts, <s); if (error != 0) return (error); TIMESPEC_TO_TIMEVAL(&utv, &uts); if (itimerfix(&utv)) return (EINVAL); microtime(&tv0); tvp = &utv; } else tvp = NULL; error = kern_pselect(td, args->nfds, args->readfds, args->writefds, args->exceptfds, tvp, ssp, LINUX_NFDBITS); if (error == 0 && args->tsp != NULL) { if (td->td_retval[0] != 0) { /* * Compute how much time was left of the timeout, * by subtracting the current time and the time * before we started the call, and subtracting * that result from the user-supplied value. */ microtime(&tv1); timevalsub(&tv1, &tv0); timevalsub(&utv, &tv1); if (utv.tv_sec < 0) timevalclear(&utv); } else timevalclear(&utv); TIMEVAL_TO_TIMESPEC(&utv, &uts); native_to_linux_timespec(<s, &uts); error = copyout(<s, args->tsp, sizeof(lts)); } return (error); } int linux_ppoll(struct thread *td, struct linux_ppoll_args *args) { struct timespec ts0, ts1; struct l_timespec lts; struct timespec uts, *tsp; l_sigset_t l_ss; sigset_t *ssp; sigset_t ss; int error; if (args->sset != NULL) { if (args->ssize != sizeof(l_ss)) return (EINVAL); error = copyin(args->sset, &l_ss, sizeof(l_ss)); if (error) return (error); linux_to_bsd_sigset(&l_ss, &ss); ssp = &ss; } else ssp = NULL; if (args->tsp != NULL) { error = copyin(args->tsp, <s, sizeof(lts)); if (error) return (error); error = linux_to_native_timespec(&uts, <s); if (error != 0) return (error); nanotime(&ts0); tsp = &uts; } else tsp = NULL; error = kern_poll(td, args->fds, args->nfds, tsp, ssp); if (error == 0 && args->tsp != NULL) { if (td->td_retval[0]) { nanotime(&ts1); timespecsub(&ts1, &ts0); timespecsub(&uts, &ts1); if (uts.tv_sec < 0) timespecclear(&uts); } else timespecclear(&uts); native_to_linux_timespec(<s, &uts); error = copyout(<s, args->tsp, sizeof(lts)); } return (error); } #if defined(DEBUG) || defined(KTR) /* XXX: can be removed when every ldebug(...) and KTR stuff are removed. */ #ifdef COMPAT_LINUX32 #define L_MAXSYSCALL LINUX32_SYS_MAXSYSCALL #else #define L_MAXSYSCALL LINUX_SYS_MAXSYSCALL #endif u_char linux_debug_map[howmany(L_MAXSYSCALL, sizeof(u_char))]; static int linux_debug(int syscall, int toggle, int global) { if (global) { char c = toggle ? 0 : 0xff; memset(linux_debug_map, c, sizeof(linux_debug_map)); return (0); } if (syscall < 0 || syscall >= L_MAXSYSCALL) return (EINVAL); if (toggle) clrbit(linux_debug_map, syscall); else setbit(linux_debug_map, syscall); return (0); } #undef L_MAXSYSCALL /* * Usage: sysctl linux.debug=.<0/1> * * E.g.: sysctl linux.debug=21.0 * * As a special case, syscall "all" will apply to all syscalls globally. */ #define LINUX_MAX_DEBUGSTR 16 int linux_sysctl_debug(SYSCTL_HANDLER_ARGS) { char value[LINUX_MAX_DEBUGSTR], *p; int error, sysc, toggle; int global = 0; value[0] = '\0'; error = sysctl_handle_string(oidp, value, LINUX_MAX_DEBUGSTR, req); if (error || req->newptr == NULL) return (error); for (p = value; *p != '\0' && *p != '.'; p++); if (*p == '\0') return (EINVAL); *p++ = '\0'; sysc = strtol(value, NULL, 0); toggle = strtol(p, NULL, 0); if (strcmp(value, "all") == 0) global = 1; error = linux_debug(sysc, toggle, global); return (error); } #endif /* DEBUG || KTR */ int linux_sched_rr_get_interval(struct thread *td, struct linux_sched_rr_get_interval_args *uap) { struct timespec ts; struct l_timespec lts; struct thread *tdt; int error; /* * According to man in case the invalid pid specified * EINVAL should be returned. */ if (uap->pid < 0) return (EINVAL); tdt = linux_tdfind(td, uap->pid, -1); if (tdt == NULL) return (ESRCH); error = kern_sched_rr_get_interval_td(td, tdt, &ts); PROC_UNLOCK(tdt->td_proc); if (error != 0) return (error); native_to_linux_timespec(<s, &ts); return (copyout(<s, uap->interval, sizeof(lts))); } /* * In case when the Linux thread is the initial thread in * the thread group thread id is equal to the process id. * Glibc depends on this magic (assert in pthread_getattr_np.c). */ struct thread * linux_tdfind(struct thread *td, lwpid_t tid, pid_t pid) { struct linux_emuldata *em; struct thread *tdt; struct proc *p; tdt = NULL; if (tid == 0 || tid == td->td_tid) { tdt = td; PROC_LOCK(tdt->td_proc); } else if (tid > PID_MAX) tdt = tdfind(tid, pid); else { /* * Initial thread where the tid equal to the pid. */ p = pfind(tid); if (p != NULL) { if (SV_PROC_ABI(p) != SV_ABI_LINUX) { /* * p is not a Linuxulator process. */ PROC_UNLOCK(p); return (NULL); } FOREACH_THREAD_IN_PROC(p, tdt) { em = em_find(tdt); if (tid == em->em_tid) return (tdt); } PROC_UNLOCK(p); } return (NULL); } return (tdt); } void linux_to_bsd_waitopts(int options, int *bsdopts) { if (options & LINUX_WNOHANG) *bsdopts |= WNOHANG; if (options & LINUX_WUNTRACED) *bsdopts |= WUNTRACED; if (options & LINUX_WEXITED) *bsdopts |= WEXITED; if (options & LINUX_WCONTINUED) *bsdopts |= WCONTINUED; if (options & LINUX_WNOWAIT) *bsdopts |= WNOWAIT; if (options & __WCLONE) *bsdopts |= WLINUXCLONE; } Index: head/sys/compat/linux/linux_socket.h =================================================================== --- head/sys/compat/linux/linux_socket.h (revision 298828) +++ head/sys/compat/linux/linux_socket.h (revision 298829) @@ -1,337 +1,337 @@ /*- * Copyright (c) 2000 Assar Westerlund * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _LINUX_SOCKET_H_ #define _LINUX_SOCKET_H_ /* msg flags in recvfrom/recvmsg */ #define LINUX_MSG_OOB 0x01 #define LINUX_MSG_PEEK 0x02 #define LINUX_MSG_DONTROUTE 0x04 #define LINUX_MSG_CTRUNC 0x08 #define LINUX_MSG_PROXY 0x10 #define LINUX_MSG_TRUNC 0x20 #define LINUX_MSG_DONTWAIT 0x40 #define LINUX_MSG_EOR 0x80 #define LINUX_MSG_WAITALL 0x100 #define LINUX_MSG_FIN 0x200 #define LINUX_MSG_SYN 0x400 #define LINUX_MSG_CONFIRM 0x800 #define LINUX_MSG_RST 0x1000 #define LINUX_MSG_ERRQUEUE 0x2000 #define LINUX_MSG_NOSIGNAL 0x4000 #define LINUX_MSG_WAITFORONE 0x10000 #define LINUX_MSG_CMSG_CLOEXEC 0x40000000 /* Socket-level control message types */ #define LINUX_SCM_RIGHTS 0x01 #define LINUX_SCM_CREDENTIALS 0x02 #define LINUX_SCM_TIMESTAMP 0x1D struct l_msghdr { l_uintptr_t msg_name; l_int msg_namelen; l_uintptr_t msg_iov; l_size_t msg_iovlen; l_uintptr_t msg_control; l_size_t msg_controllen; l_uint msg_flags; }; struct l_mmsghdr { struct l_msghdr msg_hdr; l_uint msg_len; }; struct l_cmsghdr { l_size_t cmsg_len; l_int cmsg_level; l_int cmsg_type; }; -/* Ancilliary data object information macros */ +/* Ancillary data object information macros */ #define LINUX_CMSG_ALIGN(len) roundup2(len, sizeof(l_ulong)) #define LINUX_CMSG_DATA(cmsg) ((void *)((char *)(cmsg) + \ LINUX_CMSG_ALIGN(sizeof(struct l_cmsghdr)))) #define LINUX_CMSG_SPACE(len) (LINUX_CMSG_ALIGN(sizeof(struct l_cmsghdr)) + \ LINUX_CMSG_ALIGN(len)) #define LINUX_CMSG_LEN(len) (LINUX_CMSG_ALIGN(sizeof(struct l_cmsghdr)) + \ (len)) #define LINUX_CMSG_FIRSTHDR(msg) \ ((msg)->msg_controllen >= \ sizeof(struct l_cmsghdr) ? \ (struct l_cmsghdr *) \ PTRIN((msg)->msg_control) : \ (struct l_cmsghdr *)(NULL)) #define LINUX_CMSG_NXTHDR(msg, cmsg) \ ((((char *)(cmsg) + \ LINUX_CMSG_ALIGN((cmsg)->cmsg_len) + \ sizeof(*(cmsg))) > \ (((char *)PTRIN((msg)->msg_control)) + \ (msg)->msg_controllen)) ? \ (struct l_cmsghdr *) NULL : \ (struct l_cmsghdr *)((char *)(cmsg) + \ LINUX_CMSG_ALIGN((cmsg)->cmsg_len))) #define CMSG_HDRSZ CMSG_LEN(0) #define L_CMSG_HDRSZ LINUX_CMSG_LEN(0) /* Supported address families */ #define LINUX_AF_UNSPEC 0 #define LINUX_AF_UNIX 1 #define LINUX_AF_INET 2 #define LINUX_AF_AX25 3 #define LINUX_AF_IPX 4 #define LINUX_AF_APPLETALK 5 #define LINUX_AF_INET6 10 /* Supported socket types */ #define LINUX_SOCK_STREAM 1 #define LINUX_SOCK_DGRAM 2 #define LINUX_SOCK_RAW 3 #define LINUX_SOCK_RDM 4 #define LINUX_SOCK_SEQPACKET 5 #define LINUX_SOCK_MAX LINUX_SOCK_SEQPACKET #define LINUX_SOCK_TYPE_MASK 0xf /* Flags for socket, socketpair, accept4 */ #define LINUX_SOCK_CLOEXEC LINUX_O_CLOEXEC #define LINUX_SOCK_NONBLOCK LINUX_O_NONBLOCK struct l_ucred { uint32_t pid; uint32_t uid; uint32_t gid; }; #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) struct linux_sendto_args { int s; l_uintptr_t msg; int len; int flags; l_uintptr_t to; int tolen; }; struct linux_socket_args { int domain; int type; int protocol; }; struct linux_bind_args { int s; l_uintptr_t name; int namelen; }; struct linux_connect_args { int s; l_uintptr_t name; int namelen; }; struct linux_listen_args { int s; int backlog; }; struct linux_accept_args { int s; l_uintptr_t addr; l_uintptr_t namelen; }; struct linux_accept4_args { int s; l_uintptr_t addr; l_uintptr_t namelen; int flags; }; struct linux_getsockname_args { int s; l_uintptr_t addr; l_uintptr_t namelen; }; struct linux_getpeername_args { int s; l_uintptr_t addr; l_uintptr_t namelen; }; struct linux_socketpair_args { int domain; int type; int protocol; l_uintptr_t rsv; }; struct linux_recvfrom_args { int s; l_uintptr_t buf; int len; int flags; l_uintptr_t from; l_uintptr_t fromlen; }; struct linux_sendmsg_args { int s; l_uintptr_t msg; int flags; }; struct linux_recvmsg_args { int s; l_uintptr_t msg; int flags; }; struct linux_shutdown_args { int s; int how; }; struct linux_setsockopt_args { int s; int level; int optname; l_uintptr_t optval; int optlen; }; struct linux_getsockopt_args { int s; int level; int optname; l_uintptr_t optval; l_uintptr_t optlen; }; int linux_socket(struct thread *td, struct linux_socket_args *args); int linux_bind(struct thread *td, struct linux_bind_args *args); int linux_connect(struct thread *, struct linux_connect_args *); int linux_listen(struct thread *td, struct linux_listen_args *args); int linux_accept(struct thread *td, struct linux_accept_args *args); int linux_accept4(struct thread *td, struct linux_accept4_args *args); int linux_getsockname(struct thread *td, struct linux_getsockname_args *args); int linux_getpeername(struct thread *td, struct linux_getpeername_args *args); int linux_socketpair(struct thread *td, struct linux_socketpair_args *args); int linux_sendto(struct thread *td, struct linux_sendto_args *args); int linux_recvfrom(struct thread *td, struct linux_recvfrom_args *args); int linux_sendmsg(struct thread *td, struct linux_sendmsg_args *args); int linux_recvmsg(struct thread *td, struct linux_recvmsg_args *args); int linux_shutdown(struct thread *td, struct linux_shutdown_args *args); int linux_setsockopt(struct thread *td, struct linux_setsockopt_args *args); int linux_getsockopt(struct thread *td, struct linux_getsockopt_args *args); #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ /* Operations for socketcall */ #define LINUX_SOCKET 1 #define LINUX_BIND 2 #define LINUX_CONNECT 3 #define LINUX_LISTEN 4 #define LINUX_ACCEPT 5 #define LINUX_GETSOCKNAME 6 #define LINUX_GETPEERNAME 7 #define LINUX_SOCKETPAIR 8 #define LINUX_SEND 9 #define LINUX_RECV 10 #define LINUX_SENDTO 11 #define LINUX_RECVFROM 12 #define LINUX_SHUTDOWN 13 #define LINUX_SETSOCKOPT 14 #define LINUX_GETSOCKOPT 15 #define LINUX_SENDMSG 16 #define LINUX_RECVMSG 17 #define LINUX_ACCEPT4 18 #define LINUX_RECVMMSG 19 #define LINUX_SENDMMSG 20 /* Socket options */ #define LINUX_IP_TOS 1 #define LINUX_IP_TTL 2 #define LINUX_IP_HDRINCL 3 #define LINUX_IP_OPTIONS 4 #define LINUX_IP_MULTICAST_IF 32 #define LINUX_IP_MULTICAST_TTL 33 #define LINUX_IP_MULTICAST_LOOP 34 #define LINUX_IP_ADD_MEMBERSHIP 35 #define LINUX_IP_DROP_MEMBERSHIP 36 #define LINUX_IPV6_CHECKSUM 7 #define LINUX_IPV6_NEXTHOP 9 #define LINUX_IPV6_UNICAST_HOPS 16 #define LINUX_IPV6_MULTICAST_IF 17 #define LINUX_IPV6_MULTICAST_HOPS 18 #define LINUX_IPV6_MULTICAST_LOOP 19 #define LINUX_IPV6_ADD_MEMBERSHIP 20 #define LINUX_IPV6_DROP_MEMBERSHIP 21 #define LINUX_IPV6_V6ONLY 26 #define LINUX_IPV6_RECVPKTINFO 49 #define LINUX_IPV6_PKTINFO 50 #define LINUX_IPV6_RECVHOPLIMIT 51 #define LINUX_IPV6_HOPLIMIT 52 #define LINUX_IPV6_RECVHOPOPTS 53 #define LINUX_IPV6_HOPOPTS 54 #define LINUX_IPV6_RTHDRDSTOPTS 55 #define LINUX_IPV6_RECVRTHDR 56 #define LINUX_IPV6_RTHDR 57 #define LINUX_IPV6_RECVDSTOPTS 58 #define LINUX_IPV6_DSTOPTS 59 #define LINUX_IPV6_RECVPATHMTU 60 #define LINUX_IPV6_PATHMTU 61 #define LINUX_IPV6_DONTFRAG 62 #define LINUX_TCP_NODELAY 1 #define LINUX_TCP_MAXSEG 2 #define LINUX_TCP_KEEPIDLE 4 #define LINUX_TCP_KEEPINTVL 5 #define LINUX_TCP_KEEPCNT 6 #define LINUX_TCP_MD5SIG 14 #endif /* _LINUX_SOCKET_H_ */ Index: head/sys/compat/linux/trace_futexes.d =================================================================== --- head/sys/compat/linux/trace_futexes.d (revision 298828) +++ head/sys/compat/linux/trace_futexes.d (revision 298829) @@ -1,182 +1,182 @@ #!/usr/sbin/dtrace -qs /*- * Copyright (c) 2011-2012 Alexander Leidinger * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ /** * Trace futex operations: * - internal locks * - size of the futex list * - report error conditions (emulation errors, kernel errors, * programming errors) * - execution time (wallclock) of futex related functions */ #pragma D option specsize=32m /* Error conditions */ linuxulator*:futex:futex_get:error, linuxulator*:futex:futex_sleep:requeue_error, linuxulator*:futex:futex_sleep:sleep_error, linuxulator*:futex:futex_wait:copyin_error, linuxulator*:futex:futex_wait:itimerfix_error, linuxulator*:futex:futex_wait:sleep_error, linuxulator*:futex:futex_atomic_op:missing_access_check, linuxulator*:futex:futex_atomic_op:unimplemented_op, linuxulator*:futex:futex_atomic_op:unimplemented_cmp, linuxulator*:futex:linux_sys_futex:unimplemented_clockswitch, linuxulator*:futex:linux_sys_futex:copyin_error, linuxulator*:futex:linux_sys_futex:unhandled_efault, linuxulator*:futex:linux_sys_futex:unimplemented_lock_pi, linuxulator*:futex:linux_sys_futex:unimplemented_unlock_pi, linuxulator*:futex:linux_sys_futex:unimplemented_trylock_pi, linuxulator*:futex:linux_sys_futex:unimplemented_wait_requeue_pi, linuxulator*:futex:linux_sys_futex:unimplemented_cmp_requeue_pi, linuxulator*:futex:linux_sys_futex:unknown_operation, linuxulator*:futex:linux_get_robust_list:copyout_error, linuxulator*:futex:handle_futex_death:copyin_error, linuxulator*:futex:fetch_robust_entry:copyin_error, linuxulator*:futex:release_futexes:copyin_error { printf("ERROR: %s in %s:%s:%s\n", probename, probeprov, probemod, probefunc); stack(); ustack(); } linuxulator*:futex:linux_sys_futex:invalid_cmp_requeue_use, linuxulator*:futex:linux_sys_futex:deprecated_requeue, linuxulator*:futex:linux_set_robust_list:size_error { printf("WARNING: %s:%s:%s:%s in application %s, maybe an application error?\n", probename, probeprov, probemod, probefunc, execname); stack(); ustack(); } /* Per futex checks/statistics */ linuxulator*:futex:futex:create { ++futex_count; @max_futexes = max(futex_count); } linuxulator*:futex:futex:destroy /futex_count == 0/ { printf("ERROR: Request to destroy a futex which was not created,\n"); printf(" or this script was started after some futexes where\n"); printf(" created. Stack trace:\n"); stack(); ustack(); } linuxulator*:futex:futex:destroy { --futex_count; } /* Internal locks */ linuxulator*:locks:futex_mtx:locked { ++check[probefunc, arg0]; @stats[probefunc] = count(); ts[probefunc] = timestamp; spec[probefunc] = speculation(); printf("Stacktrace of last lock operation of the %s:\n", probefunc); stack(); } linuxulator*:locks:futex_mtx:unlock /check[probefunc, arg0] == 0/ { - printf("ERROR: unlock attemt of unlocked %s (%p),", probefunc, arg0); + printf("ERROR: unlock attempt of unlocked %s (%p),", probefunc, arg0); printf(" missing SDT probe in kernel, or dtrace program started"); printf(" while the %s was already held (race condition).", probefunc); printf(" Stack trace follows:"); stack(); } linuxulator*:locks:futex_mtx:unlock { discard(spec[probefunc]); spec[probefunc] = 0; --check[probefunc, arg0]; } /* Timeout handling for internal locks */ tick-10s /spec["futex_mtx"] != 0 && timestamp - ts["futex_mtx"] >= 9999999000/ { commit(spec["futex_mtx"]); spec["futex_mtx"] = 0; } /* Timing statistings */ linuxulator*:futex::entry { self->time[probefunc] = timestamp; @calls[probeprov, execname, probefunc] = count(); } linuxulator*:futex::return /self->time[probefunc] != 0/ { this->timediff = self->time[probefunc] - timestamp; @timestats[probeprov, execname, probefunc] = quantize(this->timediff); @longest[probeprov, probefunc] = max(this->timediff); self->time[probefunc] = 0; } /* Statistics */ END { printf("Number of locks per type:"); printa(@stats); printf("Number of maximum number of futexes in the futex list:"); printa(@max_futexes); printf("Number of futexes still existing: %d", futex_count); printf("Number of calls per provider/application/kernel function:"); printa(@calls); printf("Wallclock-timing statistics per provider/application/kernel function (in ns):"); printa(@timestats); printf("Longest running (wallclock!) functions per provider (in ns):"); printa(@longest); } Index: head/sys/compat/linuxkpi/common/include/linux/dma-mapping.h =================================================================== --- head/sys/compat/linuxkpi/common/include/linux/dma-mapping.h (revision 298828) +++ head/sys/compat/linuxkpi/common/include/linux/dma-mapping.h (revision 298829) @@ -1,280 +1,280 @@ /*- * Copyright (c) 2010 Isilon Systems, Inc. * Copyright (c) 2010 iX Systems, Inc. * Copyright (c) 2010 Panasas, Inc. * Copyright (c) 2013, 2014 Mellanox Technologies, Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _LINUX_DMA_MAPPING_H_ #define _LINUX_DMA_MAPPING_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include enum dma_data_direction { DMA_BIDIRECTIONAL = 0, DMA_TO_DEVICE = 1, DMA_FROM_DEVICE = 2, DMA_NONE = 3, }; struct dma_map_ops { void* (*alloc_coherent)(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp); void (*free_coherent)(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle); dma_addr_t (*map_page)(struct device *dev, struct page *page, unsigned long offset, size_t size, enum dma_data_direction dir, struct dma_attrs *attrs); void (*unmap_page)(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction dir, struct dma_attrs *attrs); int (*map_sg)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, struct dma_attrs *attrs); void (*unmap_sg)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, struct dma_attrs *attrs); void (*sync_single_for_cpu)(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction dir); void (*sync_single_for_device)(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction dir); void (*sync_single_range_for_cpu)(struct device *dev, dma_addr_t dma_handle, unsigned long offset, size_t size, enum dma_data_direction dir); void (*sync_single_range_for_device)(struct device *dev, dma_addr_t dma_handle, unsigned long offset, size_t size, enum dma_data_direction dir); void (*sync_sg_for_cpu)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir); void (*sync_sg_for_device)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir); int (*mapping_error)(struct device *dev, dma_addr_t dma_addr); int (*dma_supported)(struct device *dev, u64 mask); int is_phys; }; #define DMA_BIT_MASK(n) ((2ULL << ((n) - 1)) - 1ULL) static inline int dma_supported(struct device *dev, u64 mask) { /* XXX busdma takes care of this elsewhere. */ return (1); } static inline int dma_set_mask(struct device *dev, u64 dma_mask) { if (!dev->dma_mask || !dma_supported(dev, dma_mask)) return -EIO; *dev->dma_mask = dma_mask; return (0); } static inline int dma_set_coherent_mask(struct device *dev, u64 mask) { if (!dma_supported(dev, mask)) return -EIO; - /* XXX Currently we don't support a seperate coherent mask. */ + /* XXX Currently we don't support a separate coherent mask. */ return 0; } static inline void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag) { vm_paddr_t high; size_t align; void *mem; if (dev->dma_mask) high = *dev->dma_mask; else high = BUS_SPACE_MAXADDR_32BIT; align = PAGE_SIZE << get_order(size); mem = (void *)kmem_alloc_contig(kmem_arena, size, flag, 0, high, align, 0, VM_MEMATTR_DEFAULT); if (mem) *dma_handle = vtophys(mem); else *dma_handle = 0; return (mem); } static inline void * dma_zalloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag) { return (dma_alloc_coherent(dev, size, dma_handle, flag | __GFP_ZERO)); } static inline void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle) { kmem_free(kmem_arena, (vm_offset_t)cpu_addr, size); } /* XXX This only works with no iommu. */ static inline dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr, size_t size, enum dma_data_direction dir, struct dma_attrs *attrs) { return vtophys(ptr); } static inline void dma_unmap_single_attrs(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir, struct dma_attrs *attrs) { } static inline int dma_map_sg_attrs(struct device *dev, struct scatterlist *sgl, int nents, enum dma_data_direction dir, struct dma_attrs *attrs) { struct scatterlist *sg; int i; for_each_sg(sgl, sg, nents, i) sg_dma_address(sg) = sg_phys(sg); return (nents); } static inline void dma_unmap_sg_attrs(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, struct dma_attrs *attrs) { } static inline dma_addr_t dma_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size, enum dma_data_direction direction) { return VM_PAGE_TO_PHYS(page) + offset; } static inline void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size, enum dma_data_direction direction) { } static inline void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction) { } static inline void dma_sync_single(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir) { dma_sync_single_for_cpu(dev, addr, size, dir); } static inline void dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction) { } static inline void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nelems, enum dma_data_direction direction) { } static inline void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nelems, enum dma_data_direction direction) { } static inline void dma_sync_single_range_for_cpu(struct device *dev, dma_addr_t dma_handle, unsigned long offset, size_t size, int direction) { } static inline void dma_sync_single_range_for_device(struct device *dev, dma_addr_t dma_handle, unsigned long offset, size_t size, int direction) { } static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr) { return (0); } static inline unsigned int dma_set_max_seg_size(struct device *dev, unsigned int size) { return (0); } #define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, NULL) #define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, NULL) #define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, NULL) #define dma_unmap_sg(d, s, n, r) dma_unmap_sg_attrs(d, s, n, r, NULL) #define DEFINE_DMA_UNMAP_ADDR(name) dma_addr_t name #define DEFINE_DMA_UNMAP_LEN(name) __u32 name #define dma_unmap_addr(p, name) ((p)->name) #define dma_unmap_addr_set(p, name, v) (((p)->name) = (v)) #define dma_unmap_len(p, name) ((p)->name) #define dma_unmap_len_set(p, name, v) (((p)->name) = (v)) extern int uma_align_cache; #define dma_get_cache_alignment() uma_align_cache #endif /* _LINUX_DMA_MAPPING_H_ */ Index: head/sys/compat/linuxkpi/common/include/linux/dmapool.h =================================================================== --- head/sys/compat/linuxkpi/common/include/linux/dmapool.h (revision 298828) +++ head/sys/compat/linuxkpi/common/include/linux/dmapool.h (revision 298829) @@ -1,87 +1,87 @@ /*- * Copyright (c) 2010 Isilon Systems, Inc. * Copyright (c) 2010 iX Systems, Inc. * Copyright (c) 2010 Panasas, Inc. * Copyright (c) 2013, 2014 Mellanox Technologies, Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _LINUX_DMAPOOL_H_ #define _LINUX_DMAPOOL_H_ #include #include #include #include #include struct dma_pool { uma_zone_t pool_zone; }; static inline struct dma_pool * dma_pool_create(char *name, struct device *dev, size_t size, size_t align, size_t boundary) { struct dma_pool *pool; pool = kmalloc(sizeof(*pool), GFP_KERNEL); align--; /* - * XXX Eventually this could use a seperate allocf to honor boundary + * XXX Eventually this could use a separate allocf to honor boundary * and physical address requirements of the device. */ pool->pool_zone = uma_zcreate(name, size, NULL, NULL, NULL, NULL, align, UMA_ZONE_OFFPAGE|UMA_ZONE_HASH); return (pool); } static inline void dma_pool_destroy(struct dma_pool *pool) { uma_zdestroy(pool->pool_zone); kfree(pool); } static inline void * dma_pool_alloc(struct dma_pool *pool, gfp_t mem_flags, dma_addr_t *handle) { void *vaddr; vaddr = uma_zalloc(pool->pool_zone, mem_flags); if (vaddr) *handle = vtophys(vaddr); return (vaddr); } static inline void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr) { uma_zfree(pool->pool_zone, vaddr); } #endif /* _LINUX_DMAPOOL_H_ */ Index: head/sys/compat/linuxkpi/common/src/linux_radix.c =================================================================== --- head/sys/compat/linuxkpi/common/src/linux_radix.c (revision 298828) +++ head/sys/compat/linuxkpi/common/src/linux_radix.c (revision 298829) @@ -1,218 +1,218 @@ /*- * Copyright (c) 2010 Isilon Systems, Inc. * Copyright (c) 2010 iX Systems, Inc. * Copyright (c) 2010 Panasas, Inc. * Copyright (c) 2013, 2014 Mellanox Technologies, Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_RADIX, "radix", "Linux radix compat"); static inline int radix_max(struct radix_tree_root *root) { return (1 << (root->height * RADIX_TREE_MAP_SHIFT)) - 1; } static inline int radix_pos(long id, int height) { return (id >> (RADIX_TREE_MAP_SHIFT * height)) & RADIX_TREE_MAP_MASK; } void * radix_tree_lookup(struct radix_tree_root *root, unsigned long index) { struct radix_tree_node *node; void *item; int height; item = NULL; node = root->rnode; height = root->height - 1; if (index > radix_max(root)) goto out; while (height && node) node = node->slots[radix_pos(index, height--)]; if (node) item = node->slots[radix_pos(index, 0)]; out: return (item); } void * radix_tree_delete(struct radix_tree_root *root, unsigned long index) { struct radix_tree_node *stack[RADIX_TREE_MAX_HEIGHT]; struct radix_tree_node *node; void *item; int height; int idx; item = NULL; node = root->rnode; height = root->height - 1; if (index > radix_max(root)) goto out; /* * Find the node and record the path in stack. */ while (height && node) { stack[height] = node; node = node->slots[radix_pos(index, height--)]; } idx = radix_pos(index, 0); if (node) item = node->slots[idx]; /* * If we removed something reduce the height of the tree. */ if (item) for (;;) { node->slots[idx] = NULL; node->count--; if (node->count > 0) break; free(node, M_RADIX); if (node == root->rnode) { root->rnode = NULL; root->height = 0; break; } height++; node = stack[height]; idx = radix_pos(index, height); } out: return (item); } int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item) { struct radix_tree_node *node; struct radix_tree_node *temp[RADIX_TREE_MAX_HEIGHT - 1]; int height; int idx; /* bail out upon insertion of a NULL item */ if (item == NULL) return (-EINVAL); /* get root node, if any */ node = root->rnode; /* allocate root node, if any */ if (node == NULL) { node = malloc(sizeof(*node), M_RADIX, root->gfp_mask | M_ZERO); if (node == NULL) return (-ENOMEM); root->rnode = node; root->height++; } /* expand radix tree as needed */ while (radix_max(root) < index) { /* check if the radix tree is getting too big */ if (root->height == RADIX_TREE_MAX_HEIGHT) return (-E2BIG); /* * If the root radix level is not empty, we need to * allocate a new radix level: */ if (node->count != 0) { node = malloc(sizeof(*node), M_RADIX, root->gfp_mask | M_ZERO); if (node == NULL) return (-ENOMEM); node->slots[0] = root->rnode; node->count++; root->rnode = node; } root->height++; } /* get radix tree height index */ height = root->height - 1; /* walk down the tree until the first missing node, if any */ for ( ; height != 0; height--) { idx = radix_pos(index, height); if (node->slots[idx] == NULL) break; node = node->slots[idx]; } /* allocate the missing radix levels, if any */ for (idx = 0; idx != height; idx++) { temp[idx] = malloc(sizeof(*node), M_RADIX, root->gfp_mask | M_ZERO); if (temp[idx] == NULL) { while(idx--) free(temp[idx], M_RADIX); - /* check if we should free the root node aswell */ + /* Check if we should free the root node as well. */ if (root->rnode->count == 0) { free(root->rnode, M_RADIX); root->rnode = NULL; root->height = 0; } return (-ENOMEM); } } /* setup new radix levels, if any */ for ( ; height != 0; height--) { idx = radix_pos(index, height); node->slots[idx] = temp[height - 1]; node->count++; node = node->slots[idx]; } /* * Insert and adjust count if the item does not already exist. */ idx = radix_pos(index, 0); if (node->slots[idx]) return (-EEXIST); node->slots[idx] = item; node->count++; return (0); } Index: head/sys/compat/linuxkpi/common/src/linux_usb.c =================================================================== --- head/sys/compat/linuxkpi/common/src/linux_usb.c (revision 298828) +++ head/sys/compat/linuxkpi/common/src/linux_usb.c (revision 298829) @@ -1,1746 +1,1746 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2007 Luigi Rizzo - Universita` di Pisa. All rights reserved. * Copyright (c) 2007 Hans Petter Selasky. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usb_debug #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ struct usb_linux_softc { LIST_ENTRY(usb_linux_softc) sc_attached_list; device_t sc_fbsd_dev; struct usb_device *sc_fbsd_udev; struct usb_interface *sc_ui; struct usb_driver *sc_udrv; }; /* prototypes */ static device_probe_t usb_linux_probe; static device_attach_t usb_linux_attach; static device_detach_t usb_linux_detach; static device_suspend_t usb_linux_suspend; static device_resume_t usb_linux_resume; static usb_callback_t usb_linux_isoc_callback; static usb_callback_t usb_linux_non_isoc_callback; static usb_complete_t usb_linux_wait_complete; static uint16_t usb_max_isoc_frames(struct usb_device *); static int usb_start_wait_urb(struct urb *, usb_timeout_t, uint16_t *); static const struct usb_device_id *usb_linux_lookup_id( const struct usb_device_id *, struct usb_attach_arg *); static struct usb_driver *usb_linux_get_usb_driver(struct usb_linux_softc *); static int usb_linux_create_usb_device(struct usb_device *, device_t); static void usb_linux_cleanup_interface(struct usb_device *, struct usb_interface *); static void usb_linux_complete(struct usb_xfer *); static int usb_unlink_urb_sub(struct urb *, uint8_t); /*------------------------------------------------------------------------* * FreeBSD USB interface *------------------------------------------------------------------------*/ static LIST_HEAD(, usb_linux_softc) usb_linux_attached_list; static LIST_HEAD(, usb_driver) usb_linux_driver_list; static device_method_t usb_linux_methods[] = { /* Device interface */ DEVMETHOD(device_probe, usb_linux_probe), DEVMETHOD(device_attach, usb_linux_attach), DEVMETHOD(device_detach, usb_linux_detach), DEVMETHOD(device_suspend, usb_linux_suspend), DEVMETHOD(device_resume, usb_linux_resume), DEVMETHOD_END }; static driver_t usb_linux_driver = { .name = "usb_linux", .methods = usb_linux_methods, .size = sizeof(struct usb_linux_softc), }; static devclass_t usb_linux_devclass; DRIVER_MODULE(usb_linux, uhub, usb_linux_driver, usb_linux_devclass, NULL, 0); MODULE_VERSION(usb_linux, 1); /*------------------------------------------------------------------------* * usb_linux_lookup_id * * This functions takes an array of "struct usb_device_id" and tries * to match the entries with the information in "struct usb_attach_arg". * If it finds a match the matching entry will be returned. * Else "NULL" will be returned. *------------------------------------------------------------------------*/ static const struct usb_device_id * usb_linux_lookup_id(const struct usb_device_id *id, struct usb_attach_arg *uaa) { if (id == NULL) { goto done; } /* * Keep on matching array entries until we find one with * "match_flags" equal to zero, which indicates the end of the * array: */ for (; id->match_flags; id++) { if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && (id->idVendor != uaa->info.idVendor)) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && (id->idProduct != uaa->info.idProduct)) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) && (id->bcdDevice_lo > uaa->info.bcdDevice)) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) && (id->bcdDevice_hi < uaa->info.bcdDevice)) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && (id->bDeviceClass != uaa->info.bDeviceClass)) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) && (id->bDeviceSubClass != uaa->info.bDeviceSubClass)) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && (id->bDeviceProtocol != uaa->info.bDeviceProtocol)) { continue; } if ((uaa->info.bDeviceClass == 0xFF) && !(id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && (id->match_flags & (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS | USB_DEVICE_ID_MATCH_INT_PROTOCOL))) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) && (id->bInterfaceClass != uaa->info.bInterfaceClass)) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) && (id->bInterfaceSubClass != uaa->info.bInterfaceSubClass)) { continue; } if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) && (id->bInterfaceProtocol != uaa->info.bInterfaceProtocol)) { continue; } /* we found a match! */ return (id); } done: return (NULL); } /*------------------------------------------------------------------------* * usb_linux_probe * * This function is the FreeBSD probe callback. It is called from the * FreeBSD USB stack through the "device_probe_and_attach()" function. *------------------------------------------------------------------------*/ static int usb_linux_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_driver *udrv; int err = ENXIO; if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } mtx_lock(&Giant); LIST_FOREACH(udrv, &usb_linux_driver_list, linux_driver_list) { if (usb_linux_lookup_id(udrv->id_table, uaa)) { err = 0; break; } } mtx_unlock(&Giant); return (err); } /*------------------------------------------------------------------------* * usb_linux_get_usb_driver * * This function returns the pointer to the "struct usb_driver" where * the Linux USB device driver "struct usb_device_id" match was found. * We apply a lock before reading out the pointer to avoid races. *------------------------------------------------------------------------*/ static struct usb_driver * usb_linux_get_usb_driver(struct usb_linux_softc *sc) { struct usb_driver *udrv; mtx_lock(&Giant); udrv = sc->sc_udrv; mtx_unlock(&Giant); return (udrv); } /*------------------------------------------------------------------------* * usb_linux_attach * * This function is the FreeBSD attach callback. It is called from the * FreeBSD USB stack through the "device_probe_and_attach()" function. * This function is called when "usb_linux_probe()" returns zero. *------------------------------------------------------------------------*/ static int usb_linux_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_linux_softc *sc = device_get_softc(dev); struct usb_driver *udrv; const struct usb_device_id *id = NULL; mtx_lock(&Giant); LIST_FOREACH(udrv, &usb_linux_driver_list, linux_driver_list) { id = usb_linux_lookup_id(udrv->id_table, uaa); if (id) break; } mtx_unlock(&Giant); if (id == NULL) { return (ENXIO); } if (usb_linux_create_usb_device(uaa->device, dev) != 0) return (ENOMEM); device_set_usb_desc(dev); sc->sc_fbsd_udev = uaa->device; sc->sc_fbsd_dev = dev; sc->sc_udrv = udrv; sc->sc_ui = usb_ifnum_to_if(uaa->device, uaa->info.bIfaceNum); if (sc->sc_ui == NULL) { return (EINVAL); } if (udrv->probe) { if ((udrv->probe) (sc->sc_ui, id)) { return (ENXIO); } } mtx_lock(&Giant); LIST_INSERT_HEAD(&usb_linux_attached_list, sc, sc_attached_list); mtx_unlock(&Giant); /* success */ return (0); } /*------------------------------------------------------------------------* * usb_linux_detach * * This function is the FreeBSD detach callback. It is called from the * FreeBSD USB stack through the "device_detach()" function. *------------------------------------------------------------------------*/ static int usb_linux_detach(device_t dev) { struct usb_linux_softc *sc = device_get_softc(dev); struct usb_driver *udrv = NULL; mtx_lock(&Giant); if (sc->sc_attached_list.le_prev) { LIST_REMOVE(sc, sc_attached_list); sc->sc_attached_list.le_prev = NULL; udrv = sc->sc_udrv; sc->sc_udrv = NULL; } mtx_unlock(&Giant); if (udrv && udrv->disconnect) { (udrv->disconnect) (sc->sc_ui); } /* * Make sure that we free all FreeBSD USB transfers belonging to * this Linux "usb_interface", hence they will most likely not be * needed any more. */ usb_linux_cleanup_interface(sc->sc_fbsd_udev, sc->sc_ui); return (0); } /*------------------------------------------------------------------------* * usb_linux_suspend * * This function is the FreeBSD suspend callback. Usually it does nothing. *------------------------------------------------------------------------*/ static int usb_linux_suspend(device_t dev) { struct usb_linux_softc *sc = device_get_softc(dev); struct usb_driver *udrv = usb_linux_get_usb_driver(sc); int err; if (udrv && udrv->suspend) { err = (udrv->suspend) (sc->sc_ui, 0); } return (0); } /*------------------------------------------------------------------------* * usb_linux_resume * * This function is the FreeBSD resume callback. Usually it does nothing. *------------------------------------------------------------------------*/ static int usb_linux_resume(device_t dev) { struct usb_linux_softc *sc = device_get_softc(dev); struct usb_driver *udrv = usb_linux_get_usb_driver(sc); int err; if (udrv && udrv->resume) { err = (udrv->resume) (sc->sc_ui); } return (0); } /*------------------------------------------------------------------------* * Linux emulation layer *------------------------------------------------------------------------*/ /*------------------------------------------------------------------------* * usb_max_isoc_frames * * The following function returns the maximum number of isochronous * frames that we support per URB. It is not part of the Linux USB API. *------------------------------------------------------------------------*/ static uint16_t usb_max_isoc_frames(struct usb_device *dev) { ; /* indent fix */ switch (usbd_get_speed(dev)) { case USB_SPEED_LOW: case USB_SPEED_FULL: return (USB_MAX_FULL_SPEED_ISOC_FRAMES); default: return (USB_MAX_HIGH_SPEED_ISOC_FRAMES); } } /*------------------------------------------------------------------------* * usb_submit_urb * * This function is used to queue an URB after that it has been * initialized. If it returns non-zero, it means that the URB was not * queued. *------------------------------------------------------------------------*/ int usb_submit_urb(struct urb *urb, uint16_t mem_flags) { struct usb_host_endpoint *uhe; uint8_t do_unlock; int err; if (urb == NULL) return (-EINVAL); do_unlock = mtx_owned(&Giant) ? 0 : 1; if (do_unlock) mtx_lock(&Giant); if (urb->endpoint == NULL) { err = -EINVAL; goto done; } /* * Check to see if the urb is in the process of being killed * and stop a urb that is in the process of being killed from * being re-submitted (e.g. from its completion callback * function). */ if (urb->kill_count != 0) { err = -EPERM; goto done; } uhe = urb->endpoint; /* * Check that we have got a FreeBSD USB transfer that will dequeue * the URB structure and do the real transfer. If there are no USB * transfers, then we return an error. */ if (uhe->bsd_xfer[0] || uhe->bsd_xfer[1]) { /* we are ready! */ TAILQ_INSERT_TAIL(&uhe->bsd_urb_list, urb, bsd_urb_list); urb->status = -EINPROGRESS; usbd_transfer_start(uhe->bsd_xfer[0]); usbd_transfer_start(uhe->bsd_xfer[1]); err = 0; } else { /* no pipes have been setup yet! */ urb->status = -EINVAL; err = -EINVAL; } done: if (do_unlock) mtx_unlock(&Giant); return (err); } /*------------------------------------------------------------------------* * usb_unlink_urb * * This function is used to stop an URB after that it is been * submitted, but before the "complete" callback has been called. On *------------------------------------------------------------------------*/ int usb_unlink_urb(struct urb *urb) { return (usb_unlink_urb_sub(urb, 0)); } static void usb_unlink_bsd(struct usb_xfer *xfer, struct urb *urb, uint8_t drain) { if (xfer == NULL) return; if (!usbd_transfer_pending(xfer)) return; if (xfer->priv_fifo == (void *)urb) { if (drain) { mtx_unlock(&Giant); usbd_transfer_drain(xfer); mtx_lock(&Giant); } else { usbd_transfer_stop(xfer); } usbd_transfer_start(xfer); } } static int usb_unlink_urb_sub(struct urb *urb, uint8_t drain) { struct usb_host_endpoint *uhe; uint16_t x; uint8_t do_unlock; int err; if (urb == NULL) return (-EINVAL); do_unlock = mtx_owned(&Giant) ? 0 : 1; if (do_unlock) mtx_lock(&Giant); if (drain) urb->kill_count++; if (urb->endpoint == NULL) { err = -EINVAL; goto done; } uhe = urb->endpoint; if (urb->bsd_urb_list.tqe_prev) { /* not started yet, just remove it from the queue */ TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); urb->bsd_urb_list.tqe_prev = NULL; urb->status = -ECONNRESET; urb->actual_length = 0; for (x = 0; x < urb->number_of_packets; x++) { urb->iso_frame_desc[x].actual_length = 0; } if (urb->complete) { (urb->complete) (urb); } } else { /* * If the URB is not on the URB list, then check if one of * the FreeBSD USB transfer are processing the current URB. * If so, re-start that transfer, which will lead to the * termination of that URB: */ usb_unlink_bsd(uhe->bsd_xfer[0], urb, drain); usb_unlink_bsd(uhe->bsd_xfer[1], urb, drain); } err = 0; done: if (drain) urb->kill_count--; if (do_unlock) mtx_unlock(&Giant); return (err); } /*------------------------------------------------------------------------* * usb_clear_halt * * This function must always be used to clear the stall. Stall is when * an USB endpoint returns a stall message to the USB host controller. * Until the stall is cleared, no data can be transferred. *------------------------------------------------------------------------*/ int usb_clear_halt(struct usb_device *dev, struct usb_host_endpoint *uhe) { struct usb_config cfg[1]; struct usb_endpoint *ep; uint8_t type; uint8_t addr; if (uhe == NULL) return (-EINVAL); type = uhe->desc.bmAttributes & UE_XFERTYPE; addr = uhe->desc.bEndpointAddress; memset(cfg, 0, sizeof(cfg)); cfg[0].type = type; cfg[0].endpoint = addr & UE_ADDR; cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); ep = usbd_get_endpoint(dev, uhe->bsd_iface_index, cfg); if (ep == NULL) return (-EINVAL); usbd_clear_data_toggle(dev, ep); return (usb_control_msg(dev, &dev->ep0, UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT, UF_ENDPOINT_HALT, addr, NULL, 0, 1000)); } /*------------------------------------------------------------------------* * usb_start_wait_urb * * This is an internal function that is used to perform synchronous * Linux USB transfers. *------------------------------------------------------------------------*/ static int usb_start_wait_urb(struct urb *urb, usb_timeout_t timeout, uint16_t *p_actlen) { int err; uint8_t do_unlock; /* you must have a timeout! */ if (timeout == 0) { timeout = 1; } urb->complete = &usb_linux_wait_complete; urb->timeout = timeout; urb->transfer_flags |= URB_WAIT_WAKEUP; urb->transfer_flags &= ~URB_IS_SLEEPING; do_unlock = mtx_owned(&Giant) ? 0 : 1; if (do_unlock) mtx_lock(&Giant); err = usb_submit_urb(urb, 0); if (err) goto done; /* * the URB might have completed before we get here, so check that by * using some flags! */ while (urb->transfer_flags & URB_WAIT_WAKEUP) { urb->transfer_flags |= URB_IS_SLEEPING; cv_wait(&urb->cv_wait, &Giant); urb->transfer_flags &= ~URB_IS_SLEEPING; } err = urb->status; done: if (do_unlock) mtx_unlock(&Giant); if (p_actlen != NULL) { if (err) *p_actlen = 0; else *p_actlen = urb->actual_length; } return (err); } /*------------------------------------------------------------------------* * usb_control_msg * * The following function performs a control transfer sequence one any * control, bulk or interrupt endpoint, specified by "uhe". A control * transfer means that you transfer an 8-byte header first followed by * a data-phase as indicated by the 8-byte header. The "timeout" is * given in milliseconds. * * Return values: * 0: Success * < 0: Failure - * > 0: Acutal length + * > 0: Actual length *------------------------------------------------------------------------*/ int usb_control_msg(struct usb_device *dev, struct usb_host_endpoint *uhe, uint8_t request, uint8_t requesttype, uint16_t value, uint16_t index, void *data, uint16_t size, usb_timeout_t timeout) { struct usb_device_request req; struct urb *urb; int err; uint16_t actlen; uint8_t type; uint8_t addr; req.bmRequestType = requesttype; req.bRequest = request; USETW(req.wValue, value); USETW(req.wIndex, index); USETW(req.wLength, size); if (uhe == NULL) { return (-EINVAL); } type = (uhe->desc.bmAttributes & UE_XFERTYPE); addr = (uhe->desc.bEndpointAddress & UE_ADDR); if (type != UE_CONTROL) { return (-EINVAL); } if (addr == 0) { /* * The FreeBSD USB stack supports standard control * transfers on control endpoint zero: */ err = usbd_do_request_flags(dev, NULL, &req, data, USB_SHORT_XFER_OK, &actlen, timeout); if (err) { err = -EPIPE; } else { err = actlen; } return (err); } if (dev->flags.usb_mode != USB_MODE_HOST) { /* not supported */ return (-EINVAL); } err = usb_setup_endpoint(dev, uhe, 1 /* dummy */ ); /* * NOTE: we need to allocate real memory here so that we don't * transfer data to/from the stack! * * 0xFFFF is a FreeBSD specific magic value. */ urb = usb_alloc_urb(0xFFFF, size); if (urb == NULL) return (-ENOMEM); urb->dev = dev; urb->endpoint = uhe; memcpy(urb->setup_packet, &req, sizeof(req)); if (size && (!(req.bmRequestType & UT_READ))) { /* move the data to a real buffer */ memcpy(USB_ADD_BYTES(urb->setup_packet, sizeof(req)), data, size); } err = usb_start_wait_urb(urb, timeout, &actlen); if (req.bmRequestType & UT_READ) { if (actlen) { bcopy(USB_ADD_BYTES(urb->setup_packet, sizeof(req)), data, actlen); } } usb_free_urb(urb); if (err == 0) { err = actlen; } return (err); } /*------------------------------------------------------------------------* * usb_set_interface * * The following function will select which alternate setting of an * USB interface you plan to use. By default alternate setting with * index zero is selected. Note that "iface_no" is not the interface * index, but rather the value of "bInterfaceNumber". *------------------------------------------------------------------------*/ int usb_set_interface(struct usb_device *dev, uint8_t iface_no, uint8_t alt_index) { struct usb_interface *p_ui = usb_ifnum_to_if(dev, iface_no); int err; if (p_ui == NULL) return (-EINVAL); if (alt_index >= p_ui->num_altsetting) return (-EINVAL); usb_linux_cleanup_interface(dev, p_ui); err = -usbd_set_alt_interface_index(dev, p_ui->bsd_iface_index, alt_index); if (err == 0) { p_ui->cur_altsetting = p_ui->altsetting + alt_index; } return (err); } /*------------------------------------------------------------------------* * usb_setup_endpoint * * The following function is an extension to the Linux USB API that * allows you to set a maximum buffer size for a given USB endpoint. * The maximum buffer size is per URB. If you don't call this function * to set a maximum buffer size, the endpoint will not be functional. * Note that for isochronous endpoints the maximum buffer size must be * a non-zero dummy, hence this function will base the maximum buffer * size on "wMaxPacketSize". *------------------------------------------------------------------------*/ int usb_setup_endpoint(struct usb_device *dev, struct usb_host_endpoint *uhe, usb_size_t bufsize) { struct usb_config cfg[2]; uint8_t type = uhe->desc.bmAttributes & UE_XFERTYPE; uint8_t addr = uhe->desc.bEndpointAddress; if (uhe->fbsd_buf_size == bufsize) { /* optimize */ return (0); } usbd_transfer_unsetup(uhe->bsd_xfer, 2); uhe->fbsd_buf_size = bufsize; if (bufsize == 0) { return (0); } memset(cfg, 0, sizeof(cfg)); if (type == UE_ISOCHRONOUS) { /* * Isochronous transfers are special in that they don't fit * into the BULK/INTR/CONTROL transfer model. */ cfg[0].type = type; cfg[0].endpoint = addr & UE_ADDR; cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); cfg[0].callback = &usb_linux_isoc_callback; cfg[0].bufsize = 0; /* use wMaxPacketSize */ cfg[0].frames = usb_max_isoc_frames(dev); cfg[0].flags.proxy_buffer = 1; #if 0 /* * The Linux USB API allows non back-to-back * isochronous frames which we do not support. If the * isochronous frames are not back-to-back we need to * do a copy, and then we need a buffer for * that. Enable this at your own risk. */ cfg[0].flags.ext_buffer = 1; #endif cfg[0].flags.short_xfer_ok = 1; bcopy(cfg, cfg + 1, sizeof(*cfg)); /* Allocate and setup two generic FreeBSD USB transfers */ if (usbd_transfer_setup(dev, &uhe->bsd_iface_index, uhe->bsd_xfer, cfg, 2, uhe, &Giant)) { return (-EINVAL); } } else { if (bufsize > (1 << 22)) { /* limit buffer size */ bufsize = (1 << 22); } /* Allocate and setup one generic FreeBSD USB transfer */ cfg[0].type = type; cfg[0].endpoint = addr & UE_ADDR; cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); cfg[0].callback = &usb_linux_non_isoc_callback; cfg[0].bufsize = bufsize; cfg[0].flags.ext_buffer = 1; /* enable zero-copy */ cfg[0].flags.proxy_buffer = 1; cfg[0].flags.short_xfer_ok = 1; if (usbd_transfer_setup(dev, &uhe->bsd_iface_index, uhe->bsd_xfer, cfg, 1, uhe, &Giant)) { return (-EINVAL); } } return (0); } /*------------------------------------------------------------------------* * usb_linux_create_usb_device * * The following function is used to build up a per USB device * structure tree, that mimics the Linux one. The root structure * is returned by this function. *------------------------------------------------------------------------*/ static int usb_linux_create_usb_device(struct usb_device *udev, device_t dev) { struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); struct usb_descriptor *desc; struct usb_interface_descriptor *id; struct usb_endpoint_descriptor *ed; struct usb_interface *p_ui = NULL; struct usb_host_interface *p_uhi = NULL; struct usb_host_endpoint *p_uhe = NULL; usb_size_t size; uint16_t niface_total; uint16_t nedesc; uint16_t iface_no_curr; uint16_t iface_index; uint8_t pass; uint8_t iface_no; /* * We do two passes. One pass for computing necessary memory size * and one pass to initialize all the allocated memory structures. */ for (pass = 0; pass < 2; pass++) { iface_no_curr = 0xFFFF; niface_total = 0; iface_index = 0; nedesc = 0; desc = NULL; /* * Iterate over all the USB descriptors. Use the USB config * descriptor pointer provided by the FreeBSD USB stack. */ while ((desc = usb_desc_foreach(cd, desc))) { /* * Build up a tree according to the descriptors we * find: */ switch (desc->bDescriptorType) { case UDESC_DEVICE: break; case UDESC_ENDPOINT: ed = (void *)desc; if ((ed->bLength < sizeof(*ed)) || (iface_index == 0)) break; if (p_uhe) { bcopy(ed, &p_uhe->desc, sizeof(p_uhe->desc)); p_uhe->bsd_iface_index = iface_index - 1; TAILQ_INIT(&p_uhe->bsd_urb_list); p_uhe++; } if (p_uhi) { (p_uhi - 1)->desc.bNumEndpoints++; } nedesc++; break; case UDESC_INTERFACE: id = (void *)desc; if (id->bLength < sizeof(*id)) break; if (p_uhi) { bcopy(id, &p_uhi->desc, sizeof(p_uhi->desc)); p_uhi->desc.bNumEndpoints = 0; p_uhi->endpoint = p_uhe; p_uhi->string = ""; p_uhi->bsd_iface_index = iface_index; p_uhi++; } iface_no = id->bInterfaceNumber; niface_total++; if (iface_no_curr != iface_no) { if (p_ui) { p_ui->altsetting = p_uhi - 1; p_ui->cur_altsetting = p_uhi - 1; p_ui->num_altsetting = 1; p_ui->bsd_iface_index = iface_index; p_ui->linux_udev = udev; p_ui++; } iface_no_curr = iface_no; iface_index++; } else { if (p_ui) { (p_ui - 1)->num_altsetting++; } } break; default: break; } } if (pass == 0) { size = (sizeof(*p_uhe) * nedesc) + (sizeof(*p_ui) * iface_index) + (sizeof(*p_uhi) * niface_total); p_uhe = malloc(size, M_USBDEV, M_WAITOK | M_ZERO); p_ui = (void *)(p_uhe + nedesc); p_uhi = (void *)(p_ui + iface_index); udev->linux_iface_start = p_ui; udev->linux_iface_end = p_ui + iface_index; udev->linux_endpoint_start = p_uhe; udev->linux_endpoint_end = p_uhe + nedesc; udev->devnum = device_get_unit(dev); bcopy(&udev->ddesc, &udev->descriptor, sizeof(udev->descriptor)); bcopy(udev->ctrl_ep.edesc, &udev->ep0.desc, sizeof(udev->ep0.desc)); } } return (0); } /*------------------------------------------------------------------------* * usb_alloc_urb * * This function should always be used when you allocate an URB for * use with the USB Linux stack. In case of an isochronous transfer * you must specifiy the maximum number of "iso_packets" which you * plan to transfer per URB. This function is always blocking, and * "mem_flags" are not regarded like on Linux. *------------------------------------------------------------------------*/ struct urb * usb_alloc_urb(uint16_t iso_packets, uint16_t mem_flags) { struct urb *urb; usb_size_t size; if (iso_packets == 0xFFFF) { /* * FreeBSD specific magic value to ask for control transfer * memory allocation: */ size = sizeof(*urb) + sizeof(struct usb_device_request) + mem_flags; } else { size = sizeof(*urb) + (iso_packets * sizeof(urb->iso_frame_desc[0])); } urb = malloc(size, M_USBDEV, M_WAITOK | M_ZERO); if (urb) { cv_init(&urb->cv_wait, "URBWAIT"); if (iso_packets == 0xFFFF) { urb->setup_packet = (void *)(urb + 1); urb->transfer_buffer = (void *)(urb->setup_packet + sizeof(struct usb_device_request)); } else { urb->number_of_packets = iso_packets; } } return (urb); } /*------------------------------------------------------------------------* * usb_find_host_endpoint * * The following function will return the Linux USB host endpoint * structure that matches the given endpoint type and endpoint * value. If no match is found, NULL is returned. This function is not * part of the Linux USB API and is only used internally. *------------------------------------------------------------------------*/ struct usb_host_endpoint * usb_find_host_endpoint(struct usb_device *dev, uint8_t type, uint8_t ep) { struct usb_host_endpoint *uhe; struct usb_host_endpoint *uhe_end; struct usb_host_interface *uhi; struct usb_interface *ui; uint8_t ea; uint8_t at; uint8_t mask; if (dev == NULL) { return (NULL); } if (type == UE_CONTROL) { mask = UE_ADDR; } else { mask = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR); } ep &= mask; /* * Iterate over all the interfaces searching the selected alternate * setting only, and all belonging endpoints. */ for (ui = dev->linux_iface_start; ui != dev->linux_iface_end; ui++) { uhi = ui->cur_altsetting; if (uhi) { uhe_end = uhi->endpoint + uhi->desc.bNumEndpoints; for (uhe = uhi->endpoint; uhe != uhe_end; uhe++) { ea = uhe->desc.bEndpointAddress; at = uhe->desc.bmAttributes; if (((ea & mask) == ep) && ((at & UE_XFERTYPE) == type)) { return (uhe); } } } } if ((type == UE_CONTROL) && ((ep & UE_ADDR) == 0)) { return (&dev->ep0); } return (NULL); } /*------------------------------------------------------------------------* * usb_altnum_to_altsetting * * The following function returns a pointer to an alternate setting by * index given a "usb_interface" pointer. If the alternate setting by * index does not exist, NULL is returned. And alternate setting is a * variant of an interface, but usually with slightly different * characteristics. *------------------------------------------------------------------------*/ struct usb_host_interface * usb_altnum_to_altsetting(const struct usb_interface *intf, uint8_t alt_index) { if (alt_index >= intf->num_altsetting) { return (NULL); } return (intf->altsetting + alt_index); } /*------------------------------------------------------------------------* * usb_ifnum_to_if * * The following function searches up an USB interface by * "bInterfaceNumber". If no match is found, NULL is returned. *------------------------------------------------------------------------*/ struct usb_interface * usb_ifnum_to_if(struct usb_device *dev, uint8_t iface_no) { struct usb_interface *p_ui; for (p_ui = dev->linux_iface_start; p_ui != dev->linux_iface_end; p_ui++) { if ((p_ui->num_altsetting > 0) && (p_ui->altsetting->desc.bInterfaceNumber == iface_no)) { return (p_ui); } } return (NULL); } /*------------------------------------------------------------------------* * usb_buffer_alloc *------------------------------------------------------------------------*/ void * usb_buffer_alloc(struct usb_device *dev, usb_size_t size, uint16_t mem_flags, uint8_t *dma_addr) { return (malloc(size, M_USBDEV, M_WAITOK | M_ZERO)); } /*------------------------------------------------------------------------* * usbd_get_intfdata *------------------------------------------------------------------------*/ void * usbd_get_intfdata(struct usb_interface *intf) { return (intf->bsd_priv_sc); } /*------------------------------------------------------------------------* * usb_linux_register * * The following function is used by the "USB_DRIVER_EXPORT()" macro, * and is used to register a Linux USB driver, so that its * "usb_device_id" structures gets searched a probe time. This * function is not part of the Linux USB API, and is for internal use * only. *------------------------------------------------------------------------*/ void usb_linux_register(void *arg) { struct usb_driver *drv = arg; mtx_lock(&Giant); LIST_INSERT_HEAD(&usb_linux_driver_list, drv, linux_driver_list); mtx_unlock(&Giant); usb_needs_explore_all(); } /*------------------------------------------------------------------------* * usb_linux_deregister * * The following function is used by the "USB_DRIVER_EXPORT()" macro, * and is used to deregister a Linux USB driver. This function will * ensure that all driver instances belonging to the Linux USB device * driver in question, gets detached before the driver is * unloaded. This function is not part of the Linux USB API, and is * for internal use only. *------------------------------------------------------------------------*/ void usb_linux_deregister(void *arg) { struct usb_driver *drv = arg; struct usb_linux_softc *sc; repeat: mtx_lock(&Giant); LIST_FOREACH(sc, &usb_linux_attached_list, sc_attached_list) { if (sc->sc_udrv == drv) { mtx_unlock(&Giant); device_detach(sc->sc_fbsd_dev); goto repeat; } } LIST_REMOVE(drv, linux_driver_list); mtx_unlock(&Giant); } /*------------------------------------------------------------------------* * usb_linux_free_device * * The following function is only used by the FreeBSD USB stack, to * cleanup and free memory after that a Linux USB device was attached. *------------------------------------------------------------------------*/ void usb_linux_free_device(struct usb_device *dev) { struct usb_host_endpoint *uhe; struct usb_host_endpoint *uhe_end; int err; uhe = dev->linux_endpoint_start; uhe_end = dev->linux_endpoint_end; while (uhe != uhe_end) { err = usb_setup_endpoint(dev, uhe, 0); uhe++; } err = usb_setup_endpoint(dev, &dev->ep0, 0); free(dev->linux_endpoint_start, M_USBDEV); } /*------------------------------------------------------------------------* * usb_buffer_free *------------------------------------------------------------------------*/ void usb_buffer_free(struct usb_device *dev, usb_size_t size, void *addr, uint8_t dma_addr) { free(addr, M_USBDEV); } /*------------------------------------------------------------------------* * usb_free_urb *------------------------------------------------------------------------*/ void usb_free_urb(struct urb *urb) { if (urb == NULL) { return; } /* make sure that the current URB is not active */ usb_kill_urb(urb); /* destroy condition variable */ cv_destroy(&urb->cv_wait); /* just free it */ free(urb, M_USBDEV); } /*------------------------------------------------------------------------* * usb_init_urb * * The following function can be used to initialize a custom URB. It * is not recommended to use this function. Use "usb_alloc_urb()" * instead. *------------------------------------------------------------------------*/ void usb_init_urb(struct urb *urb) { if (urb == NULL) { return; } memset(urb, 0, sizeof(*urb)); } /*------------------------------------------------------------------------* * usb_kill_urb *------------------------------------------------------------------------*/ void usb_kill_urb(struct urb *urb) { usb_unlink_urb_sub(urb, 1); } /*------------------------------------------------------------------------* * usb_set_intfdata * * The following function sets the per Linux USB interface private * data pointer. It is used by most Linux USB device drivers. *------------------------------------------------------------------------*/ void usb_set_intfdata(struct usb_interface *intf, void *data) { intf->bsd_priv_sc = data; } /*------------------------------------------------------------------------* * usb_linux_cleanup_interface * * The following function will release all FreeBSD USB transfers * associated with a Linux USB interface. It is for internal use only. *------------------------------------------------------------------------*/ static void usb_linux_cleanup_interface(struct usb_device *dev, struct usb_interface *iface) { struct usb_host_interface *uhi; struct usb_host_interface *uhi_end; struct usb_host_endpoint *uhe; struct usb_host_endpoint *uhe_end; int err; uhi = iface->altsetting; uhi_end = iface->altsetting + iface->num_altsetting; while (uhi != uhi_end) { uhe = uhi->endpoint; uhe_end = uhi->endpoint + uhi->desc.bNumEndpoints; while (uhe != uhe_end) { err = usb_setup_endpoint(dev, uhe, 0); uhe++; } uhi++; } } /*------------------------------------------------------------------------* * usb_linux_wait_complete * * The following function is used by "usb_start_wait_urb()" to wake it * up, when an USB transfer has finished. *------------------------------------------------------------------------*/ static void usb_linux_wait_complete(struct urb *urb) { if (urb->transfer_flags & URB_IS_SLEEPING) { cv_signal(&urb->cv_wait); } urb->transfer_flags &= ~URB_WAIT_WAKEUP; } /*------------------------------------------------------------------------* * usb_linux_complete *------------------------------------------------------------------------*/ static void usb_linux_complete(struct usb_xfer *xfer) { struct urb *urb; urb = usbd_xfer_get_priv(xfer); usbd_xfer_set_priv(xfer, NULL); if (urb->complete) { (urb->complete) (urb); } } /*------------------------------------------------------------------------* * usb_linux_isoc_callback * * The following is the FreeBSD isochronous USB callback. Isochronous * frames are USB packets transferred 1000 or 8000 times per second, * depending on whether a full- or high- speed USB transfer is * used. *------------------------------------------------------------------------*/ static void usb_linux_isoc_callback(struct usb_xfer *xfer, usb_error_t error) { usb_frlength_t max_frame = xfer->max_frame_size; usb_frlength_t offset; usb_frcount_t x; struct urb *urb = usbd_xfer_get_priv(xfer); struct usb_host_endpoint *uhe = usbd_xfer_softc(xfer); struct usb_iso_packet_descriptor *uipd; DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (urb->bsd_isread) { /* copy in data with regard to the URB */ offset = 0; for (x = 0; x < urb->number_of_packets; x++) { uipd = urb->iso_frame_desc + x; if (uipd->length > xfer->frlengths[x]) { if (urb->transfer_flags & URB_SHORT_NOT_OK) { /* XXX should be EREMOTEIO */ uipd->status = -EPIPE; } else { uipd->status = 0; } } else { uipd->status = 0; } uipd->actual_length = xfer->frlengths[x]; if (!xfer->flags.ext_buffer) { usbd_copy_out(xfer->frbuffers, offset, USB_ADD_BYTES(urb->transfer_buffer, uipd->offset), uipd->actual_length); } offset += max_frame; } } else { for (x = 0; x < urb->number_of_packets; x++) { uipd = urb->iso_frame_desc + x; uipd->actual_length = xfer->frlengths[x]; uipd->status = 0; } } urb->actual_length = xfer->actlen; /* check for short transfer */ if (xfer->actlen < xfer->sumlen) { /* short transfer */ if (urb->transfer_flags & URB_SHORT_NOT_OK) { /* XXX should be EREMOTEIO */ urb->status = -EPIPE; } else { urb->status = 0; } } else { /* success */ urb->status = 0; } /* call callback */ usb_linux_complete(xfer); case USB_ST_SETUP: tr_setup: if (xfer->priv_fifo == NULL) { /* get next transfer */ urb = TAILQ_FIRST(&uhe->bsd_urb_list); if (urb == NULL) { /* nothing to do */ return; } TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); urb->bsd_urb_list.tqe_prev = NULL; x = xfer->max_frame_count; if (urb->number_of_packets > x) { /* XXX simply truncate the transfer */ urb->number_of_packets = x; } } else { DPRINTF("Already got a transfer\n"); /* already got a transfer (should not happen) */ urb = usbd_xfer_get_priv(xfer); } urb->bsd_isread = (uhe->desc.bEndpointAddress & UE_DIR_IN) ? 1 : 0; if (xfer->flags.ext_buffer) { /* set virtual address to load */ usbd_xfer_set_frame_data(xfer, 0, urb->transfer_buffer, 0); } if (!(urb->bsd_isread)) { /* copy out data with regard to the URB */ offset = 0; for (x = 0; x < urb->number_of_packets; x++) { uipd = urb->iso_frame_desc + x; usbd_xfer_set_frame_len(xfer, x, uipd->length); if (!xfer->flags.ext_buffer) { usbd_copy_in(xfer->frbuffers, offset, USB_ADD_BYTES(urb->transfer_buffer, uipd->offset), uipd->length); } offset += uipd->length; } } else { /* * compute the transfer length into the "offset" * variable */ offset = urb->number_of_packets * max_frame; /* setup "frlengths" array */ for (x = 0; x < urb->number_of_packets; x++) { uipd = urb->iso_frame_desc + x; usbd_xfer_set_frame_len(xfer, x, max_frame); } } usbd_xfer_set_priv(xfer, urb); xfer->flags.force_short_xfer = 0; xfer->timeout = urb->timeout; xfer->nframes = urb->number_of_packets; usbd_transfer_submit(xfer); return; default: /* Error */ if (xfer->error == USB_ERR_CANCELLED) { urb->status = -ECONNRESET; } else { urb->status = -EPIPE; /* stalled */ } /* Set zero for "actual_length" */ urb->actual_length = 0; /* Set zero for "actual_length" */ for (x = 0; x < urb->number_of_packets; x++) { urb->iso_frame_desc[x].actual_length = 0; urb->iso_frame_desc[x].status = urb->status; } /* call callback */ usb_linux_complete(xfer); if (xfer->error == USB_ERR_CANCELLED) { /* we need to return in this case */ return; } goto tr_setup; } } /*------------------------------------------------------------------------* * usb_linux_non_isoc_callback * * The following is the FreeBSD BULK/INTERRUPT and CONTROL USB * callback. It dequeues Linux USB stack compatible URB's, transforms * the URB fields into a FreeBSD USB transfer, and defragments the USB * transfer as required. When the transfer is complete the "complete" * callback is called. *------------------------------------------------------------------------*/ static void usb_linux_non_isoc_callback(struct usb_xfer *xfer, usb_error_t error) { enum { REQ_SIZE = sizeof(struct usb_device_request) }; struct urb *urb = usbd_xfer_get_priv(xfer); struct usb_host_endpoint *uhe = usbd_xfer_softc(xfer); uint8_t *ptr; usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); uint8_t data_frame = xfer->flags_int.control_xfr ? 1 : 0; DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (xfer->flags_int.control_xfr) { /* don't transfer the setup packet again: */ usbd_xfer_set_frame_len(xfer, 0, 0); } if (urb->bsd_isread && (!xfer->flags.ext_buffer)) { /* copy in data with regard to the URB */ usbd_copy_out(xfer->frbuffers + data_frame, 0, urb->bsd_data_ptr, xfer->frlengths[data_frame]); } urb->bsd_length_rem -= xfer->frlengths[data_frame]; urb->bsd_data_ptr += xfer->frlengths[data_frame]; urb->actual_length += xfer->frlengths[data_frame]; /* check for short transfer */ if (xfer->actlen < xfer->sumlen) { urb->bsd_length_rem = 0; /* short transfer */ if (urb->transfer_flags & URB_SHORT_NOT_OK) { urb->status = -EPIPE; } else { urb->status = 0; } } else { /* check remainder */ if (urb->bsd_length_rem > 0) { goto setup_bulk; } /* success */ urb->status = 0; } /* call callback */ usb_linux_complete(xfer); case USB_ST_SETUP: tr_setup: /* get next transfer */ urb = TAILQ_FIRST(&uhe->bsd_urb_list); if (urb == NULL) { /* nothing to do */ return; } TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); urb->bsd_urb_list.tqe_prev = NULL; usbd_xfer_set_priv(xfer, urb); xfer->flags.force_short_xfer = 0; xfer->timeout = urb->timeout; if (xfer->flags_int.control_xfr) { /* * USB control transfers need special handling. * First copy in the header, then copy in data! */ if (!xfer->flags.ext_buffer) { usbd_copy_in(xfer->frbuffers, 0, urb->setup_packet, REQ_SIZE); usbd_xfer_set_frame_len(xfer, 0, REQ_SIZE); } else { /* set virtual address to load */ usbd_xfer_set_frame_data(xfer, 0, urb->setup_packet, REQ_SIZE); } ptr = urb->setup_packet; /* setup data transfer direction and length */ urb->bsd_isread = (ptr[0] & UT_READ) ? 1 : 0; urb->bsd_length_rem = ptr[6] | (ptr[7] << 8); } else { /* setup data transfer direction */ urb->bsd_length_rem = urb->transfer_buffer_length; urb->bsd_isread = (uhe->desc.bEndpointAddress & UE_DIR_IN) ? 1 : 0; } urb->bsd_data_ptr = urb->transfer_buffer; urb->actual_length = 0; setup_bulk: if (max_bulk > urb->bsd_length_rem) { max_bulk = urb->bsd_length_rem; } /* check if we need to force a short transfer */ if ((max_bulk == urb->bsd_length_rem) && (urb->transfer_flags & URB_ZERO_PACKET) && (!xfer->flags_int.control_xfr)) { xfer->flags.force_short_xfer = 1; } /* check if we need to copy in data */ if (xfer->flags.ext_buffer) { /* set virtual address to load */ usbd_xfer_set_frame_data(xfer, data_frame, urb->bsd_data_ptr, max_bulk); } else if (!urb->bsd_isread) { /* copy out data with regard to the URB */ usbd_copy_in(xfer->frbuffers + data_frame, 0, urb->bsd_data_ptr, max_bulk); usbd_xfer_set_frame_len(xfer, data_frame, max_bulk); } if (xfer->flags_int.control_xfr) { if (max_bulk > 0) { xfer->nframes = 2; } else { xfer->nframes = 1; } } else { xfer->nframes = 1; } usbd_transfer_submit(xfer); return; default: if (xfer->error == USB_ERR_CANCELLED) { urb->status = -ECONNRESET; } else { urb->status = -EPIPE; } /* Set zero for "actual_length" */ urb->actual_length = 0; /* call callback */ usb_linux_complete(xfer); if (xfer->error == USB_ERR_CANCELLED) { /* we need to return in this case */ return; } goto tr_setup; } } /*------------------------------------------------------------------------* * usb_fill_bulk_urb *------------------------------------------------------------------------*/ void usb_fill_bulk_urb(struct urb *urb, struct usb_device *udev, struct usb_host_endpoint *uhe, void *buf, int length, usb_complete_t callback, void *arg) { urb->dev = udev; urb->endpoint = uhe; urb->transfer_buffer = buf; urb->transfer_buffer_length = length; urb->complete = callback; urb->context = arg; } /*------------------------------------------------------------------------* * usb_bulk_msg * * NOTE: This function can also be used for interrupt endpoints! * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ int usb_bulk_msg(struct usb_device *udev, struct usb_host_endpoint *uhe, void *data, int len, uint16_t *pactlen, usb_timeout_t timeout) { struct urb *urb; int err; if (uhe == NULL) return (-EINVAL); if (len < 0) return (-EINVAL); err = usb_setup_endpoint(udev, uhe, 4096 /* bytes */); if (err) return (err); urb = usb_alloc_urb(0, 0); if (urb == NULL) return (-ENOMEM); usb_fill_bulk_urb(urb, udev, uhe, data, len, usb_linux_wait_complete, NULL); err = usb_start_wait_urb(urb, timeout, pactlen); usb_free_urb(urb); return (err); } MODULE_DEPEND(linuxkpi, usb, 1, 1, 1); static void usb_linux_init(void *arg) { /* register our function */ usb_linux_free_device_p = &usb_linux_free_device; } SYSINIT(usb_linux_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb_linux_init, NULL); SYSUNINIT(usb_linux_unload, SI_SUB_LOCK, SI_ORDER_ANY, usb_linux_unload, NULL);