Index: user/ngie/detangle-rc/lib/libthr/thread/thr_resume_np.c =================================================================== --- user/ngie/detangle-rc/lib/libthr/thread/thr_resume_np.c (revision 299126) +++ user/ngie/detangle-rc/lib/libthr/thread/thr_resume_np.c (revision 299127) @@ -1,97 +1,97 @@ /* * Copyright (c) 1995 John Birrell . * 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. * 3. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY JOHN BIRRELL 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. */ #include __FBSDID("$FreeBSD$"); #include "namespace.h" #include #include #include #include "un-namespace.h" #include "thr_private.h" __weak_reference(_pthread_resume_np, pthread_resume_np); __weak_reference(_pthread_resume_all_np, pthread_resume_all_np); static void resume_common(struct pthread *thread); /* Resume a thread: */ int _pthread_resume_np(pthread_t thread) { struct pthread *curthread = _get_curthread(); int ret; /* Add a reference to the thread: */ if ((ret = _thr_find_thread(curthread, thread, /*include dead*/0)) == 0) { /* Lock the threads scheduling queue: */ resume_common(thread); THR_THREAD_UNLOCK(curthread, thread); } return (ret); } void _pthread_resume_all_np(void) { struct pthread *curthread = _get_curthread(); struct pthread *thread; int old_nocancel; old_nocancel = curthread->no_cancel; curthread->no_cancel = 1; _thr_suspend_all_lock(curthread); /* Take the thread list lock: */ THREAD_LIST_RDLOCK(curthread); TAILQ_FOREACH(thread, &_thread_list, tle) { if (thread != curthread) { THR_THREAD_LOCK(curthread, thread); resume_common(thread); THR_THREAD_UNLOCK(curthread, thread); } } /* Release the thread list lock: */ THREAD_LIST_UNLOCK(curthread); _thr_suspend_all_unlock(curthread); curthread->no_cancel = old_nocancel; _thr_testcancel(curthread); } static void resume_common(struct pthread *thread) { /* Clear the suspend flag: */ - thread->flags &= ~THR_FLAGS_NEED_SUSPEND; + thread->flags &= ~(THR_FLAGS_NEED_SUSPEND | THR_FLAGS_SUSPENDED); thread->cycle++; _thr_umtx_wake(&thread->cycle, 1, 0); } Index: user/ngie/detangle-rc/lib/libthr/thread/thr_sig.c =================================================================== --- user/ngie/detangle-rc/lib/libthr/thread/thr_sig.c (revision 299126) +++ user/ngie/detangle-rc/lib/libthr/thread/thr_sig.c (revision 299127) @@ -1,763 +1,761 @@ /* * Copyright (c) 2005, David Xu * 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 "namespace.h" #include #include #include #include #include #include #include #include #include #include "un-namespace.h" #include "libc_private.h" #include "libc_private.h" #include "thr_private.h" /* #define DEBUG_SIGNAL */ #ifdef DEBUG_SIGNAL #define DBG_MSG stdout_debug #else #define DBG_MSG(x...) #endif struct usigaction { struct sigaction sigact; struct urwlock lock; }; static struct usigaction _thr_sigact[_SIG_MAXSIG]; static inline struct usigaction * __libc_sigaction_slot(int signo) { return (&_thr_sigact[signo - 1]); } static void thr_sighandler(int, siginfo_t *, void *); static void handle_signal(struct sigaction *, int, siginfo_t *, ucontext_t *); static void check_deferred_signal(struct pthread *); static void check_suspend(struct pthread *); static void check_cancel(struct pthread *curthread, ucontext_t *ucp); int _sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec * timeout); int _sigwaitinfo(const sigset_t *set, siginfo_t *info); int _sigwait(const sigset_t *set, int *sig); int _setcontext(const ucontext_t *); int _swapcontext(ucontext_t *, const ucontext_t *); static const sigset_t _thr_deferset={{ 0xffffffff & ~(_SIG_BIT(SIGBUS)|_SIG_BIT(SIGILL)|_SIG_BIT(SIGFPE)| _SIG_BIT(SIGSEGV)|_SIG_BIT(SIGTRAP)|_SIG_BIT(SIGSYS)), 0xffffffff, 0xffffffff, 0xffffffff}}; static const sigset_t _thr_maskset={{ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}}; void _thr_signal_block(struct pthread *curthread) { if (curthread->sigblock > 0) { curthread->sigblock++; return; } __sys_sigprocmask(SIG_BLOCK, &_thr_maskset, &curthread->sigmask); curthread->sigblock++; } void _thr_signal_unblock(struct pthread *curthread) { if (--curthread->sigblock == 0) __sys_sigprocmask(SIG_SETMASK, &curthread->sigmask, NULL); } int _thr_send_sig(struct pthread *thread, int sig) { return thr_kill(thread->tid, sig); } static inline void remove_thr_signals(sigset_t *set) { if (SIGISMEMBER(*set, SIGCANCEL)) SIGDELSET(*set, SIGCANCEL); } static const sigset_t * thr_remove_thr_signals(const sigset_t *set, sigset_t *newset) { *newset = *set; remove_thr_signals(newset); return (newset); } static void sigcancel_handler(int sig __unused, siginfo_t *info __unused, ucontext_t *ucp) { struct pthread *curthread = _get_curthread(); int err; if (THR_IN_CRITICAL(curthread)) return; err = errno; check_suspend(curthread); check_cancel(curthread, ucp); errno = err; } typedef void (*ohandler)(int sig, int code, struct sigcontext *scp, char *addr, __sighandler_t *catcher); /* * The signal handler wrapper is entered with all signal masked. */ static void thr_sighandler(int sig, siginfo_t *info, void *_ucp) { struct pthread *curthread; ucontext_t *ucp; struct sigaction act; struct usigaction *usa; int err; err = errno; curthread = _get_curthread(); ucp = _ucp; usa = __libc_sigaction_slot(sig); _thr_rwl_rdlock(&usa->lock); act = usa->sigact; _thr_rwl_unlock(&usa->lock); errno = err; curthread->deferred_run = 0; /* * if a thread is in critical region, for example it holds low level locks, * try to defer the signal processing, however if the signal is synchronous * signal, it means a bad thing has happened, this is a programming error, * resuming fault point can not help anything (normally causes deadloop), * so here we let user code handle it immediately. */ if (THR_IN_CRITICAL(curthread) && SIGISMEMBER(_thr_deferset, sig)) { memcpy(&curthread->deferred_sigact, &act, sizeof(struct sigaction)); memcpy(&curthread->deferred_siginfo, info, sizeof(siginfo_t)); curthread->deferred_sigmask = ucp->uc_sigmask; /* mask all signals, we will restore it later. */ ucp->uc_sigmask = _thr_deferset; return; } handle_signal(&act, sig, info, ucp); } static void handle_signal(struct sigaction *actp, int sig, siginfo_t *info, ucontext_t *ucp) { struct pthread *curthread = _get_curthread(); ucontext_t uc2; __siginfohandler_t *sigfunc; int cancel_point; int cancel_async; int cancel_enable; int in_sigsuspend; int err; /* add previous level mask */ SIGSETOR(actp->sa_mask, ucp->uc_sigmask); /* add this signal's mask */ if (!(actp->sa_flags & SA_NODEFER)) SIGADDSET(actp->sa_mask, sig); in_sigsuspend = curthread->in_sigsuspend; curthread->in_sigsuspend = 0; /* * If thread is in deferred cancellation mode, disable cancellation * in signal handler. * If user signal handler calls a cancellation point function, e.g, * it calls write() to write data to file, because write() is a * cancellation point, the thread is immediately cancelled if * cancellation is pending, to avoid this problem while thread is in * deferring mode, cancellation is temporarily disabled. */ cancel_point = curthread->cancel_point; cancel_async = curthread->cancel_async; cancel_enable = curthread->cancel_enable; curthread->cancel_point = 0; if (!cancel_async) curthread->cancel_enable = 0; /* restore correct mask before calling user handler */ __sys_sigprocmask(SIG_SETMASK, &actp->sa_mask, NULL); sigfunc = actp->sa_sigaction; /* * We have already reset cancellation point flags, so if user's code * longjmp()s out of its signal handler, wish its jmpbuf was set * outside of a cancellation point, in most cases, this would be * true. However, there is no way to save cancel_enable in jmpbuf, * so after setjmps() returns once more, the user code may need to * re-set cancel_enable flag by calling pthread_setcancelstate(). */ if ((actp->sa_flags & SA_SIGINFO) != 0) { sigfunc(sig, info, ucp); } else { ((ohandler)sigfunc)(sig, info->si_code, (struct sigcontext *)ucp, info->si_addr, (__sighandler_t *)sigfunc); } err = errno; curthread->in_sigsuspend = in_sigsuspend; curthread->cancel_point = cancel_point; curthread->cancel_enable = cancel_enable; memcpy(&uc2, ucp, sizeof(uc2)); SIGDELSET(uc2.uc_sigmask, SIGCANCEL); /* reschedule cancellation */ check_cancel(curthread, &uc2); errno = err; syscall(SYS_sigreturn, &uc2); } void _thr_ast(struct pthread *curthread) { if (!THR_IN_CRITICAL(curthread)) { check_deferred_signal(curthread); check_suspend(curthread); check_cancel(curthread, NULL); } } /* reschedule cancellation */ static void check_cancel(struct pthread *curthread, ucontext_t *ucp) { if (__predict_true(!curthread->cancel_pending || !curthread->cancel_enable || curthread->no_cancel)) return; /* * Otherwise, we are in defer mode, and we are at * cancel point, tell kernel to not block the current * thread on next cancelable system call. * * There are three cases we should call thr_wake() to * turn on TDP_WAKEUP or send SIGCANCEL in kernel: * 1) we are going to call a cancelable system call, * non-zero cancel_point means we are already in * cancelable state, next system call is cancelable. * 2) because _thr_ast() may be called by * THR_CRITICAL_LEAVE() which is used by rtld rwlock * and any libthr internal locks, when rtld rwlock * is used, it is mostly caused by an unresolved PLT. * Those routines may clear the TDP_WAKEUP flag by * invoking some system calls, in those cases, we * also should reenable the flag. * 3) thread is in sigsuspend(), and the syscall insists * on getting a signal before it agrees to return. */ if (curthread->cancel_point) { if (curthread->in_sigsuspend && ucp) { SIGADDSET(ucp->uc_sigmask, SIGCANCEL); curthread->unblock_sigcancel = 1; _thr_send_sig(curthread, SIGCANCEL); } else thr_wake(curthread->tid); } else if (curthread->cancel_async) { /* * asynchronous cancellation mode, act upon * immediately. */ _pthread_exit_mask(PTHREAD_CANCELED, ucp? &ucp->uc_sigmask : NULL); } } static void check_deferred_signal(struct pthread *curthread) { ucontext_t *uc; struct sigaction act; siginfo_t info; int uc_len; if (__predict_true(curthread->deferred_siginfo.si_signo == 0 || curthread->deferred_run)) return; curthread->deferred_run = 1; uc_len = __getcontextx_size(); uc = alloca(uc_len); getcontext(uc); if (curthread->deferred_siginfo.si_signo == 0) { curthread->deferred_run = 0; return; } __fillcontextx2((char *)uc); act = curthread->deferred_sigact; uc->uc_sigmask = curthread->deferred_sigmask; memcpy(&info, &curthread->deferred_siginfo, sizeof(siginfo_t)); /* remove signal */ curthread->deferred_siginfo.si_signo = 0; handle_signal(&act, info.si_signo, &info, uc); } static void check_suspend(struct pthread *curthread) { uint32_t cycle; if (__predict_true((curthread->flags & (THR_FLAGS_NEED_SUSPEND | THR_FLAGS_SUSPENDED)) != THR_FLAGS_NEED_SUSPEND)) return; if (curthread == _single_thread) return; if (curthread->force_exit) return; /* * Blocks SIGCANCEL which other threads must send. */ _thr_signal_block(curthread); /* * Increase critical_count, here we don't use THR_LOCK/UNLOCK * because we are leaf code, we don't want to recursively call * ourself. */ curthread->critical_count++; THR_UMUTEX_LOCK(curthread, &(curthread)->lock); - while ((curthread->flags & (THR_FLAGS_NEED_SUSPEND | - THR_FLAGS_SUSPENDED)) == THR_FLAGS_NEED_SUSPEND) { + while ((curthread->flags & THR_FLAGS_NEED_SUSPEND) != 0) { curthread->cycle++; cycle = curthread->cycle; /* Wake the thread suspending us. */ _thr_umtx_wake(&curthread->cycle, INT_MAX, 0); /* * if we are from pthread_exit, we don't want to * suspend, just go and die. */ if (curthread->state == PS_DEAD) break; curthread->flags |= THR_FLAGS_SUSPENDED; THR_UMUTEX_UNLOCK(curthread, &(curthread)->lock); _thr_umtx_wait_uint(&curthread->cycle, cycle, NULL, 0); THR_UMUTEX_LOCK(curthread, &(curthread)->lock); - curthread->flags &= ~THR_FLAGS_SUSPENDED; } THR_UMUTEX_UNLOCK(curthread, &(curthread)->lock); curthread->critical_count--; _thr_signal_unblock(curthread); } void _thr_signal_init(int dlopened) { struct sigaction act, nact, oact; struct usigaction *usa; sigset_t oldset; int sig, error; if (dlopened) { __sys_sigprocmask(SIG_SETMASK, &_thr_maskset, &oldset); for (sig = 1; sig <= _SIG_MAXSIG; sig++) { if (sig == SIGCANCEL) continue; error = __sys_sigaction(sig, NULL, &oact); if (error == -1 || oact.sa_handler == SIG_DFL || oact.sa_handler == SIG_IGN) continue; usa = __libc_sigaction_slot(sig); usa->sigact = oact; nact = oact; remove_thr_signals(&usa->sigact.sa_mask); nact.sa_flags &= ~SA_NODEFER; nact.sa_flags |= SA_SIGINFO; nact.sa_sigaction = thr_sighandler; nact.sa_mask = _thr_maskset; (void)__sys_sigaction(sig, &nact, NULL); } __sys_sigprocmask(SIG_SETMASK, &oldset, NULL); } /* Install SIGCANCEL handler. */ SIGFILLSET(act.sa_mask); act.sa_flags = SA_SIGINFO; act.sa_sigaction = (__siginfohandler_t *)&sigcancel_handler; __sys_sigaction(SIGCANCEL, &act, NULL); /* Unblock SIGCANCEL */ SIGEMPTYSET(act.sa_mask); SIGADDSET(act.sa_mask, SIGCANCEL); __sys_sigprocmask(SIG_UNBLOCK, &act.sa_mask, NULL); } void _thr_sigact_unload(struct dl_phdr_info *phdr_info) { #if 0 struct pthread *curthread = _get_curthread(); struct urwlock *rwlp; struct sigaction *actp; struct usigaction *usa; struct sigaction kact; void (*handler)(int); int sig; _thr_signal_block(curthread); for (sig = 1; sig <= _SIG_MAXSIG; sig++) { usa = __libc_sigaction_slot(sig); actp = &usa->sigact; retry: handler = actp->sa_handler; if (handler != SIG_DFL && handler != SIG_IGN && __elf_phdr_match_addr(phdr_info, handler)) { rwlp = &usa->lock; _thr_rwl_wrlock(rwlp); if (handler != actp->sa_handler) { _thr_rwl_unlock(rwlp); goto retry; } actp->sa_handler = SIG_DFL; actp->sa_flags = SA_SIGINFO; SIGEMPTYSET(actp->sa_mask); if (__sys_sigaction(sig, NULL, &kact) == 0 && kact.sa_handler != SIG_DFL && kact.sa_handler != SIG_IGN) __sys_sigaction(sig, actp, NULL); _thr_rwl_unlock(rwlp); } } _thr_signal_unblock(curthread); #endif } void _thr_signal_prefork(void) { int i; for (i = 1; i <= _SIG_MAXSIG; ++i) _thr_rwl_rdlock(&__libc_sigaction_slot(i)->lock); } void _thr_signal_postfork(void) { int i; for (i = 1; i <= _SIG_MAXSIG; ++i) _thr_rwl_unlock(&__libc_sigaction_slot(i)->lock); } void _thr_signal_postfork_child(void) { int i; for (i = 1; i <= _SIG_MAXSIG; ++i) { bzero(&__libc_sigaction_slot(i) -> lock, sizeof(struct urwlock)); } } void _thr_signal_deinit(void) { } int __thr_sigaction(int sig, const struct sigaction *act, struct sigaction *oact) { struct sigaction newact, oldact, oldact2; sigset_t oldset; struct usigaction *usa; int ret, err; if (!_SIG_VALID(sig) || sig == SIGCANCEL) { errno = EINVAL; return (-1); } ret = 0; err = 0; usa = __libc_sigaction_slot(sig); __sys_sigprocmask(SIG_SETMASK, &_thr_maskset, &oldset); _thr_rwl_wrlock(&usa->lock); if (act != NULL) { oldact2 = usa->sigact; newact = *act; /* * if a new sig handler is SIG_DFL or SIG_IGN, * don't remove old handler from __libc_sigact[], * so deferred signals still can use the handlers, * multiple threads invoking sigaction itself is * a race condition, so it is not a problem. */ if (newact.sa_handler != SIG_DFL && newact.sa_handler != SIG_IGN) { usa->sigact = newact; remove_thr_signals(&usa->sigact.sa_mask); newact.sa_flags &= ~SA_NODEFER; newact.sa_flags |= SA_SIGINFO; newact.sa_sigaction = thr_sighandler; newact.sa_mask = _thr_maskset; /* mask all signals */ } ret = __sys_sigaction(sig, &newact, &oldact); if (ret == -1) { err = errno; usa->sigact = oldact2; } } else if (oact != NULL) { ret = __sys_sigaction(sig, NULL, &oldact); err = errno; } if (oldact.sa_handler != SIG_DFL && oldact.sa_handler != SIG_IGN) { if (act != NULL) oldact = oldact2; else if (oact != NULL) oldact = usa->sigact; } _thr_rwl_unlock(&usa->lock); __sys_sigprocmask(SIG_SETMASK, &oldset, NULL); if (ret == 0) { if (oact != NULL) *oact = oldact; } else { errno = err; } return (ret); } int __thr_sigprocmask(int how, const sigset_t *set, sigset_t *oset) { const sigset_t *p = set; sigset_t newset; if (how != SIG_UNBLOCK) { if (set != NULL) { newset = *set; SIGDELSET(newset, SIGCANCEL); p = &newset; } } return (__sys_sigprocmask(how, p, oset)); } __weak_reference(_pthread_sigmask, pthread_sigmask); int _pthread_sigmask(int how, const sigset_t *set, sigset_t *oset) { if (__thr_sigprocmask(how, set, oset)) return (errno); return (0); } int _sigsuspend(const sigset_t * set) { sigset_t newset; return (__sys_sigsuspend(thr_remove_thr_signals(set, &newset))); } int __thr_sigsuspend(const sigset_t * set) { struct pthread *curthread; sigset_t newset; int ret, old; curthread = _get_curthread(); old = curthread->in_sigsuspend; curthread->in_sigsuspend = 1; _thr_cancel_enter(curthread); ret = __sys_sigsuspend(thr_remove_thr_signals(set, &newset)); _thr_cancel_leave(curthread, 1); curthread->in_sigsuspend = old; if (curthread->unblock_sigcancel) { curthread->unblock_sigcancel = 0; SIGEMPTYSET(newset); SIGADDSET(newset, SIGCANCEL); __sys_sigprocmask(SIG_UNBLOCK, &newset, NULL); } return (ret); } int _sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec * timeout) { sigset_t newset; return (__sys_sigtimedwait(thr_remove_thr_signals(set, &newset), info, timeout)); } /* * Cancellation behavior: * Thread may be canceled at start, if thread got signal, * it is not canceled. */ int __thr_sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec * timeout) { struct pthread *curthread = _get_curthread(); sigset_t newset; int ret; _thr_cancel_enter(curthread); ret = __sys_sigtimedwait(thr_remove_thr_signals(set, &newset), info, timeout); _thr_cancel_leave(curthread, (ret == -1)); return (ret); } int _sigwaitinfo(const sigset_t *set, siginfo_t *info) { sigset_t newset; return (__sys_sigwaitinfo(thr_remove_thr_signals(set, &newset), info)); } /* * Cancellation behavior: * Thread may be canceled at start, if thread got signal, * it is not canceled. */ int __thr_sigwaitinfo(const sigset_t *set, siginfo_t *info) { struct pthread *curthread = _get_curthread(); sigset_t newset; int ret; _thr_cancel_enter(curthread); ret = __sys_sigwaitinfo(thr_remove_thr_signals(set, &newset), info); _thr_cancel_leave(curthread, ret == -1); return (ret); } int _sigwait(const sigset_t *set, int *sig) { sigset_t newset; return (__sys_sigwait(thr_remove_thr_signals(set, &newset), sig)); } /* * Cancellation behavior: * Thread may be canceled at start, if thread got signal, * it is not canceled. */ int __thr_sigwait(const sigset_t *set, int *sig) { struct pthread *curthread = _get_curthread(); sigset_t newset; int ret; do { _thr_cancel_enter(curthread); ret = __sys_sigwait(thr_remove_thr_signals(set, &newset), sig); _thr_cancel_leave(curthread, (ret != 0)); } while (ret == EINTR); return (ret); } int __thr_setcontext(const ucontext_t *ucp) { ucontext_t uc; if (ucp == NULL) { errno = EINVAL; return (-1); } if (!SIGISMEMBER(uc.uc_sigmask, SIGCANCEL)) return __sys_setcontext(ucp); (void) memcpy(&uc, ucp, sizeof(uc)); SIGDELSET(uc.uc_sigmask, SIGCANCEL); return (__sys_setcontext(&uc)); } int __thr_swapcontext(ucontext_t *oucp, const ucontext_t *ucp) { ucontext_t uc; if (oucp == NULL || ucp == NULL) { errno = EINVAL; return (-1); } if (SIGISMEMBER(ucp->uc_sigmask, SIGCANCEL)) { (void) memcpy(&uc, ucp, sizeof(uc)); SIGDELSET(uc.uc_sigmask, SIGCANCEL); ucp = &uc; } return (__sys_swapcontext(oucp, ucp)); } Index: user/ngie/detangle-rc/share/man/man3/pthread_resume_np.3 =================================================================== --- user/ngie/detangle-rc/share/man/man3/pthread_resume_np.3 (revision 299126) +++ user/ngie/detangle-rc/share/man/man3/pthread_resume_np.3 (revision 299127) @@ -1,72 +1,72 @@ .\" Copyright (c) 2003 Alexey Zelkin .\" 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. .\" .\" $FreeBSD$ .\" .Dd February 13, 2003 .Dt PTHREAD_RESUME_NP 3 .Os .Sh NAME .Nm pthread_resume_np .Nd resume suspended thread .Sh LIBRARY .Lb libpthread .Sh SYNOPSIS .In pthread_np.h .Ft int .Fn pthread_resume_np "pthread_t tid" .Sh DESCRIPTION The .Fn pthread_resume_np function, called on a suspended thread, causes it to resume. If a thread specified by the .Fa tid argument is not suspended, no actions will be performed. .Sh RETURN VALUES If successful, .Fn pthread_resume_np function returns 0. Otherwise, an error number is returned to indicate the error. .Sh ERRORS The .Fn pthread_resume_np function will fail if: .Bl -tag -width Er .It Bq Er EINVAL The value specified by the .Fa tid argument is invalid. -.It Bq ESRC +.It Bq Er ESRC No thread could be found corresponding to the thread ID specified by the .Fa tid argument. .El .Sh SEE ALSO .Xr pthread_attr_setcreatesuspend_np 3 , .Xr pthread_resume_all_np 3 , .Xr pthread_suspend_all_np 3 , .Xr pthread_suspend_np 3 .Sh AUTHORS This manual page was written by .An Alexey Zelkin Aq Mt phantom@FreeBSD.org . Index: user/ngie/detangle-rc/share/man/man3/pthread_suspend_all_np.3 =================================================================== --- user/ngie/detangle-rc/share/man/man3/pthread_suspend_all_np.3 (revision 299126) +++ user/ngie/detangle-rc/share/man/man3/pthread_suspend_all_np.3 (revision 299127) @@ -1,53 +1,60 @@ .\" Copyright (c) 2003 Alexey Zelkin .\" 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. .\" .\" $FreeBSD$ .\" -.Dd February 13, 2003 +.Dd May 5, 2016 .Dt PTHREAD_SUSPEND_ALL_NP 3 .Os .Sh NAME .Nm pthread_suspend_all_np .Nd suspend all active threads .Sh LIBRARY .Lb libpthread .Sh SYNOPSIS .In pthread_np.h .Ft void .Fn pthread_suspend_all_np void .Sh DESCRIPTION The .Fn pthread_suspend_all_np function causes all active threads to be suspended. The only exception is the current thread, the thread that called the .Fn pthread_suspend_all_np function. +.Pp +It is not safe for the caller of the +.Fn pthread_suspend_all_np +function to use any non-async signal safe functions, besides +.Xr pthread_resume_all_np 3 , +until threads are resumed, unless measures are taken to ensure +that all threads are suspended at safe points. .Sh SEE ALSO .Xr pthread_resume_all_np 3 , .Xr pthread_resume_np 3 , .Xr pthread_suspend_np 3 .Sh AUTHORS This manual page was written by .An Alexey Zelkin Aq Mt phantom@FreeBSD.org . Index: user/ngie/detangle-rc/share/man/man3/pthread_suspend_np.3 =================================================================== --- user/ngie/detangle-rc/share/man/man3/pthread_suspend_np.3 (revision 299126) +++ user/ngie/detangle-rc/share/man/man3/pthread_suspend_np.3 (revision 299127) @@ -1,70 +1,77 @@ .\" Copyright (c) 2003 Alexey Zelkin .\" 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. .\" .\" $FreeBSD$ .\" -.Dd February 13, 2003 +.Dd May 5, 2016 .Dt PTHREAD_SUSPEND_NP 3 .Os .Sh NAME .Nm pthread_suspend_np .Nd suspend a thread .Sh LIBRARY .Lb libpthread .Sh SYNOPSIS .In pthread_np.h .Ft int .Fn pthread_suspend_np "pthread_t tid" .Sh DESCRIPTION The .Fn pthread_suspend_np function, called on an active thread, causes it to suspend. +.Pp +It is not safe for the caller of the +.Fn pthread_suspend_np +function to use any non-async signal safe functions, except +.Xr pthread_resume_np 3 , +until suspended thread is resumed, unless measures are taken to ensure +that the thread is suspended at a safe point. .Sh RETURN VALUES If successful, .Fn pthread_suspend_np function returns 0. Otherwise, an error number is returned to indicate the error. .Sh ERRORS The .Fn pthread_suspend_np function will fail if: .Bl -tag -width Er .It Bq Er EDEADLK An attempt was made to suspend the current thread. .It Bq Er EINVAL The value specified by the .Fa tid argument is invalid. -.It Bq ESRC +.It Bq Er ESRC No thread could be found corresponding to the thread ID specified by the .Fa tid argument. .El .Sh SEE ALSO .Xr pthread_resume_all_np 3 , .Xr pthread_resume_np 3 , .Xr pthread_suspend_all_np 3 .Sh AUTHORS This manual page was written by .An Alexey Zelkin Aq Mt phantom@FreeBSD.org . Index: user/ngie/detangle-rc/sys/arm/allwinner/a10/a10_intc.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/a10/a10_intc.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/a10/a10_intc.c (revision 299127) @@ -1,430 +1,434 @@ /*- * Copyright (c) 2012 Ganbold Tsagaankhuu * Copyright (c) 2016 Emmanuel Vadot * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INTRNG #include #endif #include #include #include #include #include #include #ifdef INTRNG #include "pic_if.h" #endif /** * Interrupt controller registers * */ #define SW_INT_VECTOR_REG 0x00 #define SW_INT_BASE_ADR_REG 0x04 #define SW_INT_PROTECTION_REG 0x08 #define SW_INT_NMI_CTRL_REG 0x0c #define SW_INT_IRQ_PENDING_REG0 0x10 #define SW_INT_IRQ_PENDING_REG1 0x14 #define SW_INT_IRQ_PENDING_REG2 0x18 #define SW_INT_FIQ_PENDING_REG0 0x20 #define SW_INT_FIQ_PENDING_REG1 0x24 #define SW_INT_FIQ_PENDING_REG2 0x28 #define SW_INT_SELECT_REG0 0x30 #define SW_INT_SELECT_REG1 0x34 #define SW_INT_SELECT_REG2 0x38 #define SW_INT_ENABLE_REG0 0x40 #define SW_INT_ENABLE_REG1 0x44 #define SW_INT_ENABLE_REG2 0x48 #define SW_INT_MASK_REG0 0x50 #define SW_INT_MASK_REG1 0x54 #define SW_INT_MASK_REG2 0x58 #define SW_INT_IRQNO_ENMI 0 #define A10_INTR_MAX_NIRQS 81 #define SW_INT_IRQ_PENDING_REG(_b) (0x10 + ((_b) * 4)) #define SW_INT_FIQ_PENDING_REG(_b) (0x20 + ((_b) * 4)) #define SW_INT_SELECT_REG(_b) (0x30 + ((_b) * 4)) #define SW_INT_ENABLE_REG(_b) (0x40 + ((_b) * 4)) #define SW_INT_MASK_REG(_b) (0x50 + ((_b) * 4)) #ifdef INTRNG struct a10_intr_irqsrc { struct intr_irqsrc isrc; u_int irq; }; #endif struct a10_aintc_softc { device_t sc_dev; struct resource * aintc_res; bus_space_tag_t aintc_bst; bus_space_handle_t aintc_bsh; struct mtx mtx; #ifdef INTRNG struct a10_intr_irqsrc isrcs[A10_INTR_MAX_NIRQS]; #endif }; #define aintc_read_4(sc, reg) \ bus_space_read_4(sc->aintc_bst, sc->aintc_bsh, reg) #define aintc_write_4(sc, reg, val) \ bus_space_write_4(sc->aintc_bst, sc->aintc_bsh, reg, val) static __inline void a10_intr_eoi(struct a10_aintc_softc *sc, u_int irq) { if (irq != SW_INT_IRQNO_ENMI) return; mtx_lock_spin(&sc->mtx); aintc_write_4(sc, SW_INT_IRQ_PENDING_REG(0), (1 << SW_INT_IRQNO_ENMI)); mtx_unlock_spin(&sc->mtx); } static void a10_intr_unmask(struct a10_aintc_softc *sc, u_int irq) { uint32_t bit, block, value; bit = (irq % 32); block = (irq / 32); mtx_lock_spin(&sc->mtx); value = aintc_read_4(sc, SW_INT_ENABLE_REG(block)); value |= (1 << bit); aintc_write_4(sc, SW_INT_ENABLE_REG(block), value); value = aintc_read_4(sc, SW_INT_MASK_REG(block)); value &= ~(1 << bit); aintc_write_4(sc, SW_INT_MASK_REG(block), value); mtx_unlock_spin(&sc->mtx); } static void a10_intr_mask(struct a10_aintc_softc *sc, u_int irq) { uint32_t bit, block, value; bit = (irq % 32); block = (irq / 32); mtx_lock_spin(&sc->mtx); value = aintc_read_4(sc, SW_INT_ENABLE_REG(block)); value &= ~(1 << bit); aintc_write_4(sc, SW_INT_ENABLE_REG(block), value); value = aintc_read_4(sc, SW_INT_MASK_REG(block)); value |= (1 << bit); aintc_write_4(sc, SW_INT_MASK_REG(block), value); mtx_unlock_spin(&sc->mtx); } static int a10_pending_irq(struct a10_aintc_softc *sc) { uint32_t value; int i, b; for (i = 0; i < 3; i++) { value = aintc_read_4(sc, SW_INT_IRQ_PENDING_REG(i)); if (value == 0) continue; for (b = 0; b < 32; b++) if (value & (1 << b)) { return (i * 32 + b); } } return (-1); } #ifndef INTRNG static struct a10_aintc_softc *a10_aintc_sc = NULL; int arm_get_next_irq(int last_irq) { return (a10_pending_irq(a10_aintc_sc)); } void arm_mask_irq(uintptr_t irq) { a10_intr_mask(a10_aintc_sc, irq); } void arm_unmask_irq(uintptr_t irq) { a10_intr_unmask(a10_aintc_sc, irq); a10_intr_eoi(a10_aintc_sc, irq); } #else /* INTRNG */ static int a10_intr(void *arg) { struct a10_aintc_softc *sc = arg; u_int irq; irq = a10_pending_irq(sc); if (irq == -1 || irq > A10_INTR_MAX_NIRQS) { device_printf(sc->sc_dev, "Spurious interrupt %d\n", irq); return (FILTER_HANDLED); } while (irq != -1) { if (irq > A10_INTR_MAX_NIRQS) { device_printf(sc->sc_dev, "Spurious interrupt %d\n", irq); return (FILTER_HANDLED); } if (intr_isrc_dispatch(&sc->isrcs[irq].isrc, curthread->td_intr_frame) != 0) { a10_intr_mask(sc, irq); a10_intr_eoi(sc, irq); device_printf(sc->sc_dev, "Stray interrupt %d disabled\n", irq); } arm_irq_memory_barrier(irq); irq = a10_pending_irq(sc); } return (FILTER_HANDLED); } static int a10_intr_pic_attach(struct a10_aintc_softc *sc) { int error; uint32_t irq; const char *name; intptr_t xref; name = device_get_nameunit(sc->sc_dev); for (irq = 0; irq < A10_INTR_MAX_NIRQS; irq++) { sc->isrcs[irq].irq = irq; error = intr_isrc_register(&sc->isrcs[irq].isrc, sc->sc_dev, 0, "%s,%u", name, irq); if (error != 0) return (error); } xref = OF_xref_from_node(ofw_bus_get_node(sc->sc_dev)); error = intr_pic_register(sc->sc_dev, xref); if (error != 0) return (error); return (intr_pic_claim_root(sc->sc_dev, xref, a10_intr, sc, 0)); } static void a10_intr_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct a10_aintc_softc *sc; u_int irq = ((struct a10_intr_irqsrc *)isrc)->irq; sc = device_get_softc(dev); arm_irq_memory_barrier(irq); a10_intr_unmask(sc, irq); } static void a10_intr_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct a10_aintc_softc *sc; u_int irq = ((struct a10_intr_irqsrc *)isrc)->irq; sc = device_get_softc(dev); a10_intr_mask(sc, irq); } static int a10_intr_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { + struct intr_map_data_fdt *daf; struct a10_aintc_softc *sc; - if (data->type != INTR_MAP_DATA_FDT || data->fdt.ncells != 1 || - data->fdt.cells[0] >= A10_INTR_MAX_NIRQS) + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + + daf = (struct intr_map_data_fdt *)data; + if (daf->ncells != 1 || daf->cells[0] >= A10_INTR_MAX_NIRQS) return (EINVAL); sc = device_get_softc(dev); - *isrcp = &sc->isrcs[data->fdt.cells[0]].isrc; + *isrcp = &sc->isrcs[daf->cells[0]].isrc; return (0); } static void a10_intr_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct a10_aintc_softc *sc = device_get_softc(dev); u_int irq = ((struct a10_intr_irqsrc *)isrc)->irq; a10_intr_mask(sc, irq); a10_intr_eoi(sc, irq); } static void a10_intr_post_ithread(device_t dev, struct intr_irqsrc *isrc) { a10_intr_enable_intr(dev, isrc); } static void a10_intr_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct a10_aintc_softc *sc = device_get_softc(dev); u_int irq = ((struct a10_intr_irqsrc *)isrc)->irq; a10_intr_eoi(sc, irq); } #endif /* INTRNG */ static int a10_aintc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "allwinner,sun4i-a10-ic")) return (ENXIO); device_set_desc(dev, "A10 AINTC Interrupt Controller"); return (BUS_PROBE_DEFAULT); } static int a10_aintc_attach(device_t dev) { struct a10_aintc_softc *sc = device_get_softc(dev); int rid = 0; int i; sc->sc_dev = dev; #ifndef INTRNG if (a10_aintc_sc) goto error; a10_aintc_sc = sc; #endif sc->aintc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->aintc_res) { device_printf(dev, "could not allocate resource\n"); goto error; } sc->aintc_bst = rman_get_bustag(sc->aintc_res); sc->aintc_bsh = rman_get_bushandle(sc->aintc_res); mtx_init(&sc->mtx, "A10 AINTC lock", "", MTX_SPIN); /* Disable & clear all interrupts */ for (i = 0; i < 3; i++) { aintc_write_4(sc, SW_INT_ENABLE_REG(i), 0); aintc_write_4(sc, SW_INT_MASK_REG(i), 0xffffffff); } /* enable protection mode*/ aintc_write_4(sc, SW_INT_PROTECTION_REG, 0x01); /* config the external interrupt source type*/ aintc_write_4(sc, SW_INT_NMI_CTRL_REG, 0x00); #ifdef INTRNG if (a10_intr_pic_attach(sc) != 0) { device_printf(dev, "could not attach PIC\n"); return (ENXIO); } #endif return (0); error: bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->aintc_res); return (ENXIO); } static device_method_t a10_aintc_methods[] = { DEVMETHOD(device_probe, a10_aintc_probe), DEVMETHOD(device_attach, a10_aintc_attach), #ifdef INTRNG /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, a10_intr_disable_intr), DEVMETHOD(pic_enable_intr, a10_intr_enable_intr), DEVMETHOD(pic_map_intr, a10_intr_map_intr), DEVMETHOD(pic_post_filter, a10_intr_post_filter), DEVMETHOD(pic_post_ithread, a10_intr_post_ithread), DEVMETHOD(pic_pre_ithread, a10_intr_pre_ithread), #endif { 0, 0 } }; static driver_t a10_aintc_driver = { "aintc", a10_aintc_methods, sizeof(struct a10_aintc_softc), }; static devclass_t a10_aintc_devclass; EARLY_DRIVER_MODULE(aintc, simplebus, a10_aintc_driver, a10_aintc_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_FIRST); Index: user/ngie/detangle-rc/sys/arm/allwinner/a10_ehci.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/a10_ehci.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/a10_ehci.c (revision 299127) @@ -1,353 +1,354 @@ /*- * Copyright (c) 2012 Ganbold Tsagaankhuu * 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. */ /* * Allwinner A10 attachment driver for the USB Enhanced Host Controller. */ #include __FBSDID("$FreeBSD$"); #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define EHCI_HC_DEVSTR "Allwinner Integrated USB 2.0 controller" #define SW_USB_PMU_IRQ_ENABLE 0x800 #define SW_SDRAM_REG_HPCR_USB1 (0x250 + ((1 << 2) * 4)) #define SW_SDRAM_REG_HPCR_USB2 (0x250 + ((1 << 2) * 5)) #define SW_SDRAM_BP_HPCR_ACCESS (1 << 0) #define SW_ULPI_BYPASS (1 << 0) #define SW_AHB_INCRX_ALIGN (1 << 8) #define SW_AHB_INCR4 (1 << 9) #define SW_AHB_INCR8 (1 << 10) #define USB_CONF(d) \ (void *)ofw_bus_search_compatible((d), compat_data)->ocd_data #define A10_READ_4(sc, reg) \ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) #define A10_WRITE_4(sc, reg, data) \ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) static device_attach_t a10_ehci_attach; static device_detach_t a10_ehci_detach; bs_r_1_proto(reversed); bs_w_1_proto(reversed); struct aw_ehci_softc { ehci_softc_t sc; clk_t clk; hwreset_t rst; }; struct aw_ehci_conf { bool sdram_init; }; static const struct aw_ehci_conf a10_ehci_conf = { .sdram_init = true, }; static const struct aw_ehci_conf a31_ehci_conf = { .sdram_init = false, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-ehci", (uintptr_t)&a10_ehci_conf }, { "allwinner,sun6i-a31-ehci", (uintptr_t)&a31_ehci_conf }, { "allwinner,sun7i-a20-ehci", (uintptr_t)&a10_ehci_conf }, + { "allwinner,sun8i-a83t-ehci", (uintptr_t)&a31_ehci_conf }, { NULL, (uintptr_t)NULL } }; static int a10_ehci_probe(device_t self) { if (!ofw_bus_status_okay(self)) return (ENXIO); if (ofw_bus_search_compatible(self, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(self, EHCI_HC_DEVSTR); return (BUS_PROBE_DEFAULT); } static int a10_ehci_attach(device_t self) { struct aw_ehci_softc *aw_sc = device_get_softc(self); ehci_softc_t *sc = &aw_sc->sc; const struct aw_ehci_conf *conf; bus_space_handle_t bsh; int err; int rid; uint32_t reg_value = 0; conf = USB_CONF(self); /* initialise some bus fields */ sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { return (ENOMEM); } sc->sc_bus.usbrev = USB_REV_2_0; rid = 0; sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(self, "Could not map memory\n"); goto error; } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); bsh = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); if (bus_space_subregion(sc->sc_io_tag, bsh, 0x00, sc->sc_io_size, &sc->sc_io_hdl) != 0) panic("%s: unable to subregion USB host registers", device_get_name(self)); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); goto error; } sc->sc_bus.bdev = device_add_child(self, "usbus", -1); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR); sprintf(sc->sc_vendor, "Allwinner"); err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } sc->sc_flags |= EHCI_SCFLG_DONTRESET; /* De-assert reset */ if (hwreset_get_by_ofw_idx(self, 0, &aw_sc->rst) == 0) { err = hwreset_deassert(aw_sc->rst); if (err != 0) { device_printf(self, "Could not de-assert reset\n"); goto error; } } /* Enable clock for USB */ err = clk_get_by_ofw_index(self, 0, &aw_sc->clk); if (err != 0) { device_printf(self, "Could not get clock\n"); goto error; } err = clk_enable(aw_sc->clk); if (err != 0) { device_printf(self, "Could not enable clock\n"); goto error; } /* Enable passby */ reg_value = A10_READ_4(sc, SW_USB_PMU_IRQ_ENABLE); reg_value |= SW_AHB_INCR8; /* AHB INCR8 enable */ reg_value |= SW_AHB_INCR4; /* AHB burst type INCR4 enable */ reg_value |= SW_AHB_INCRX_ALIGN; /* AHB INCRX align enable */ reg_value |= SW_ULPI_BYPASS; /* ULPI bypass enable */ A10_WRITE_4(sc, SW_USB_PMU_IRQ_ENABLE, reg_value); /* Configure port */ if (conf->sdram_init) { reg_value = A10_READ_4(sc, SW_SDRAM_REG_HPCR_USB2); reg_value |= SW_SDRAM_BP_HPCR_ACCESS; A10_WRITE_4(sc, SW_SDRAM_REG_HPCR_USB2, reg_value); } err = ehci_init(sc); if (!err) { err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed err=%d\n", err); goto error; } return (0); error: if (aw_sc->clk) clk_release(aw_sc->clk); a10_ehci_detach(self); return (ENXIO); } static int a10_ehci_detach(device_t self) { struct aw_ehci_softc *aw_sc = device_get_softc(self); ehci_softc_t *sc = &aw_sc->sc; const struct aw_ehci_conf *conf; device_t bdev; int err; uint32_t reg_value = 0; conf = USB_CONF(self); if (sc->sc_bus.bdev) { bdev = sc->sc_bus.bdev; device_detach(bdev); device_delete_child(self, bdev); } /* during module unload there are lots of children leftover */ device_delete_children(self); if (sc->sc_irq_res && sc->sc_intr_hdl) { /* * only call ehci_detach() after ehci_init() */ ehci_detach(sc); err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); /* Disable configure port */ if (conf->sdram_init) { reg_value = A10_READ_4(sc, SW_SDRAM_REG_HPCR_USB2); reg_value &= ~SW_SDRAM_BP_HPCR_ACCESS; A10_WRITE_4(sc, SW_SDRAM_REG_HPCR_USB2, reg_value); } /* Disable passby */ reg_value = A10_READ_4(sc, SW_USB_PMU_IRQ_ENABLE); reg_value &= ~SW_AHB_INCR8; /* AHB INCR8 disable */ reg_value &= ~SW_AHB_INCR4; /* AHB burst type INCR4 disable */ reg_value &= ~SW_AHB_INCRX_ALIGN; /* AHB INCRX align disable */ reg_value &= ~SW_ULPI_BYPASS; /* ULPI bypass disable */ A10_WRITE_4(sc, SW_USB_PMU_IRQ_ENABLE, reg_value); /* Disable clock for USB */ clk_disable(aw_sc->clk); clk_release(aw_sc->clk); /* Assert reset */ if (aw_sc->rst != NULL) { hwreset_assert(aw_sc->rst); hwreset_release(aw_sc->rst); } return (0); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a10_ehci_probe), DEVMETHOD(device_attach, a10_ehci_attach), DEVMETHOD(device_detach, a10_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t ehci_driver = { .name = "ehci", .methods = ehci_methods, .size = sizeof(ehci_softc_t), }; static devclass_t ehci_devclass; DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0); MODULE_DEPEND(ehci, usb, 1, 1, 1); Index: user/ngie/detangle-rc/sys/arm/allwinner/a10_gpio.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/a10_gpio.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/a10_gpio.c (revision 299127) @@ -1,691 +1,701 @@ /*- * Copyright (c) 2013 Ganbold Tsagaankhuu * Copyright (c) 2012 Oleksandr Tymoshenko * Copyright (c) 2012 Luiz Otavio O Souza. * 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. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #define A10_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN) #define A10_GPIO_NONE 0 #define A10_GPIO_PULLUP 1 #define A10_GPIO_PULLDOWN 2 #define A10_GPIO_INPUT 0 #define A10_GPIO_OUTPUT 1 #define AW_GPIO_DRV_MASK 0x3 #define AW_GPIO_PUD_MASK 0x3 #define AW_PINCTRL 1 #define AW_R_PINCTRL 2 /* Defined in a10_padconf.c */ #ifdef SOC_ALLWINNER_A10 extern const struct allwinner_padconf a10_padconf; #endif /* Defined in a20_padconf.c */ #ifdef SOC_ALLWINNER_A20 extern const struct allwinner_padconf a20_padconf; #endif /* Defined in a31_padconf.c */ #ifdef SOC_ALLWINNER_A31 extern const struct allwinner_padconf a31_padconf; #endif /* Defined in a31s_padconf.c */ #ifdef SOC_ALLWINNER_A31S extern const struct allwinner_padconf a31s_padconf; #endif #if defined(SOC_ALLWINNER_A31) || defined(SOC_ALLWINNER_A31S) extern const struct allwinner_padconf a31_r_padconf; #endif +/* Defined in a83t_padconf.c */ +#ifdef SOC_ALLWINNER_A83T +extern const struct allwinner_padconf a83t_padconf; +extern const struct allwinner_padconf a83t_r_padconf; +#endif + static struct ofw_compat_data compat_data[] = { #ifdef SOC_ALLWINNER_A10 {"allwinner,sun4i-a10-pinctrl", (uintptr_t)&a10_padconf}, #endif #ifdef SOC_ALLWINNER_A20 {"allwinner,sun7i-a20-pinctrl", (uintptr_t)&a20_padconf}, #endif #ifdef SOC_ALLWINNER_A31 {"allwinner,sun6i-a31-pinctrl", (uintptr_t)&a31_padconf}, #endif #ifdef SOC_ALLWINNER_A31S {"allwinner,sun6i-a31s-pinctrl", (uintptr_t)&a31s_padconf}, #endif #if defined(SOC_ALLWINNER_A31) || defined(SOC_ALLWINNER_A31S) {"allwinner,sun6i-a31-r-pinctrl", (uintptr_t)&a31_r_padconf}, +#endif +#ifdef SOC_ALLWINNER_A83T + {"allwinner,sun8i-a83t-pinctrl", (uintptr_t)&a83t_padconf}, + {"allwinner,sun8i-a83t-r-pinctrl", (uintptr_t)&a83t_r_padconf}, #endif {NULL, 0} }; struct a10_gpio_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource * sc_mem_res; struct resource * sc_irq_res; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; void * sc_intrhand; const struct allwinner_padconf * padconf; }; #define A10_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define A10_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define A10_GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define A10_GPIO_GP_CFG(_bank, _idx) 0x00 + ((_bank) * 0x24) + ((_idx) << 2) #define A10_GPIO_GP_DAT(_bank) 0x10 + ((_bank) * 0x24) #define A10_GPIO_GP_DRV(_bank, _idx) 0x14 + ((_bank) * 0x24) + ((_idx) << 2) #define A10_GPIO_GP_PUL(_bank, _idx) 0x1c + ((_bank) * 0x24) + ((_idx) << 2) #define A10_GPIO_GP_INT_CFG0 0x200 #define A10_GPIO_GP_INT_CFG1 0x204 #define A10_GPIO_GP_INT_CFG2 0x208 #define A10_GPIO_GP_INT_CFG3 0x20c #define A10_GPIO_GP_INT_CTL 0x210 #define A10_GPIO_GP_INT_STA 0x214 #define A10_GPIO_GP_INT_DEB 0x218 #define A10_GPIO_WRITE(_sc, _off, _val) \ bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _off, _val) #define A10_GPIO_READ(_sc, _off) \ bus_space_read_4(_sc->sc_bst, _sc->sc_bsh, _off) static uint32_t a10_gpio_get_function(struct a10_gpio_softc *sc, uint32_t pin) { uint32_t bank, func, offset; /* Must be called with lock held. */ A10_GPIO_LOCK_ASSERT(sc); if (pin > sc->padconf->npins) return (0); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x07) << 2); func = A10_GPIO_READ(sc, A10_GPIO_GP_CFG(bank, pin >> 3)); switch ((func >> offset) & 0x7) { case A10_GPIO_INPUT: return (GPIO_PIN_INPUT); case A10_GPIO_OUTPUT: return (GPIO_PIN_OUTPUT); } return (0); } static void a10_gpio_set_function(struct a10_gpio_softc *sc, uint32_t pin, uint32_t f) { uint32_t bank, data, offset; /* Must be called with lock held. */ A10_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x07) << 2); data = A10_GPIO_READ(sc, A10_GPIO_GP_CFG(bank, pin >> 3)); data &= ~(7 << offset); data |= (f << offset); A10_GPIO_WRITE(sc, A10_GPIO_GP_CFG(bank, pin >> 3), data); } static uint32_t a10_gpio_get_pud(struct a10_gpio_softc *sc, uint32_t pin) { uint32_t bank, offset, val; /* Must be called with lock held. */ A10_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = A10_GPIO_READ(sc, A10_GPIO_GP_PUL(bank, pin >> 4)); switch ((val >> offset) & 0x3) { case A10_GPIO_PULLDOWN: return (GPIO_PIN_PULLDOWN); case A10_GPIO_PULLUP: return (GPIO_PIN_PULLUP); } return (0); } static void a10_gpio_set_pud(struct a10_gpio_softc *sc, uint32_t pin, uint32_t state) { uint32_t bank, offset, val; /* Must be called with lock held. */ A10_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = A10_GPIO_READ(sc, A10_GPIO_GP_PUL(bank, pin >> 4)); val &= ~(AW_GPIO_PUD_MASK << offset); val |= (state << offset); A10_GPIO_WRITE(sc, A10_GPIO_GP_PUL(bank, pin >> 4), val); } static void a10_gpio_set_drv(struct a10_gpio_softc *sc, uint32_t pin, uint32_t drive) { uint32_t bank, offset, val; /* Must be called with lock held. */ A10_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = A10_GPIO_READ(sc, A10_GPIO_GP_DRV(bank, pin >> 4)); val &= ~(AW_GPIO_DRV_MASK << offset); val |= (drive << offset); A10_GPIO_WRITE(sc, A10_GPIO_GP_DRV(bank, pin >> 4), val); } static void a10_gpio_pin_configure(struct a10_gpio_softc *sc, uint32_t pin, uint32_t flags) { /* Must be called with lock held. */ A10_GPIO_LOCK_ASSERT(sc); /* Manage input/output. */ if (flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) { if (flags & GPIO_PIN_OUTPUT) a10_gpio_set_function(sc, pin, A10_GPIO_OUTPUT); else a10_gpio_set_function(sc, pin, A10_GPIO_INPUT); } /* Manage Pull-up/pull-down. */ if (flags & (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) { if (flags & GPIO_PIN_PULLUP) a10_gpio_set_pud(sc, pin, A10_GPIO_PULLUP); else a10_gpio_set_pud(sc, pin, A10_GPIO_PULLDOWN); } else a10_gpio_set_pud(sc, pin, A10_GPIO_NONE); } static device_t a10_gpio_get_bus(device_t dev) { struct a10_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int a10_gpio_pin_max(device_t dev, int *maxpin) { struct a10_gpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->padconf->npins - 1; return (0); } static int a10_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct a10_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->padconf->npins) return (EINVAL); *caps = A10_GPIO_DEFAULT_CAPS; return (0); } static int a10_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct a10_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->padconf->npins) return (EINVAL); A10_GPIO_LOCK(sc); *flags = a10_gpio_get_function(sc, pin); *flags |= a10_gpio_get_pud(sc, pin); A10_GPIO_UNLOCK(sc); return (0); } static int a10_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct a10_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->padconf->npins) return (EINVAL); snprintf(name, GPIOMAXNAME - 1, "%s", sc->padconf->pins[pin].name); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int a10_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct a10_gpio_softc *sc; sc = device_get_softc(dev); if (pin > sc->padconf->npins) return (EINVAL); A10_GPIO_LOCK(sc); a10_gpio_pin_configure(sc, pin, flags); A10_GPIO_UNLOCK(sc); return (0); } static int a10_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct a10_gpio_softc *sc; uint32_t bank, data; sc = device_get_softc(dev); if (pin > sc->padconf->npins) return (EINVAL); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; A10_GPIO_LOCK(sc); data = A10_GPIO_READ(sc, A10_GPIO_GP_DAT(bank)); if (value) data |= (1 << pin); else data &= ~(1 << pin); A10_GPIO_WRITE(sc, A10_GPIO_GP_DAT(bank), data); A10_GPIO_UNLOCK(sc); return (0); } static int a10_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct a10_gpio_softc *sc; uint32_t bank, reg_data; sc = device_get_softc(dev); if (pin > sc->padconf->npins) return (EINVAL); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; A10_GPIO_LOCK(sc); reg_data = A10_GPIO_READ(sc, A10_GPIO_GP_DAT(bank)); A10_GPIO_UNLOCK(sc); *val = (reg_data & (1 << pin)) ? 1 : 0; return (0); } static int a10_gpio_pin_toggle(device_t dev, uint32_t pin) { struct a10_gpio_softc *sc; uint32_t bank, data; sc = device_get_softc(dev); if (pin > sc->padconf->npins) return (EINVAL); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; A10_GPIO_LOCK(sc); data = A10_GPIO_READ(sc, A10_GPIO_GP_DAT(bank)); if (data & (1 << pin)) data &= ~(1 << pin); else data |= (1 << pin); A10_GPIO_WRITE(sc, A10_GPIO_GP_DAT(bank), data); A10_GPIO_UNLOCK(sc); return (0); } static int aw_find_pinnum_by_name(struct a10_gpio_softc *sc, const char *pinname) { int i; for (i = 0; i < sc->padconf->npins; i++) if (!strcmp(pinname, sc->padconf->pins[i].name)) return i; return (-1); } static int aw_find_pin_func(struct a10_gpio_softc *sc, int pin, const char *func) { int i; for (i = 0; i < AW_MAX_FUNC_BY_PIN; i++) if (sc->padconf->pins[pin].functions[i] && !strcmp(func, sc->padconf->pins[pin].functions[i])) return (i); return (-1); } static int aw_fdt_configure_pins(device_t dev, phandle_t cfgxref) { struct a10_gpio_softc *sc; phandle_t node; const char **pinlist = NULL; char *pin_function = NULL; uint32_t pin_drive, pin_pull; int pins_nb, pin_num, pin_func, i, ret; sc = device_get_softc(dev); node = OF_node_from_xref(cfgxref); ret = 0; /* Getting all prop for configuring pins */ pins_nb = ofw_bus_string_list_to_array(node, "allwinner,pins", &pinlist); if (pins_nb <= 0) return (ENOENT); if (OF_getprop_alloc(node, "allwinner,function", sizeof(*pin_function), (void **)&pin_function) == -1) { ret = ENOENT; goto out; } if (OF_getencprop(node, "allwinner,drive", &pin_drive, sizeof(pin_drive)) == -1) { ret = ENOENT; goto out; } if (OF_getencprop(node, "allwinner,pull", &pin_pull, sizeof(pin_pull)) == -1) { ret = ENOENT; goto out; } /* Configure each pin to the correct function, drive and pull */ for (i = 0; i < pins_nb; i++) { pin_num = aw_find_pinnum_by_name(sc, pinlist[i]); if (pin_num == -1) { ret = ENOENT; goto out; } pin_func = aw_find_pin_func(sc, pin_num, pin_function); if (pin_func == -1) { ret = ENOENT; goto out; } A10_GPIO_LOCK(sc); a10_gpio_set_function(sc, pin_num, pin_func); a10_gpio_set_drv(sc, pin_num, pin_drive); a10_gpio_set_pud(sc, pin_num, pin_pull); A10_GPIO_UNLOCK(sc); } out: free(pinlist, M_OFWPROP); free(pin_function, M_OFWPROP); return (ret); } static int a10_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner GPIO/Pinmux controller"); return (BUS_PROBE_DEFAULT); } static int a10_gpio_attach(device_t dev) { int rid, error; phandle_t gpio; struct a10_gpio_softc *sc; clk_t clk; hwreset_t rst; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "a10 gpio", "gpio", MTX_SPIN); rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_mem_res) { device_printf(dev, "cannot allocate memory window\n"); goto fail; } sc->sc_bst = rman_get_bustag(sc->sc_mem_res); sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (!sc->sc_irq_res) { device_printf(dev, "cannot allocate interrupt\n"); goto fail; } /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(gpio, "gpio-controller")) /* Node is not a GPIO controller. */ goto fail; /* Use the right pin data for the current SoC */ sc->padconf = (struct allwinner_padconf *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (hwreset_get_by_ofw_idx(dev, 0, &rst) == 0) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); return (error); } } if (clk_get_by_ofw_index(dev, 0, &clk) == 0) { error = clk_enable(clk); if (error != 0) { device_printf(dev, "could not enable clock\n"); return (error); } } sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) goto fail; /* * Register as a pinctrl device */ fdt_pinctrl_register(dev, "allwinner,pins"); fdt_pinctrl_configure_tree(dev); return (0); fail: if (sc->sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); mtx_destroy(&sc->sc_mtx); return (ENXIO); } static int a10_gpio_detach(device_t dev) { return (EBUSY); } static phandle_t a10_gpio_get_node(device_t dev, device_t bus) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(dev)); } static int a10_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { struct a10_gpio_softc *sc; int i; sc = device_get_softc(bus); /* The GPIO pins are mapped as: . */ for (i = 0; i < sc->padconf->npins; i++) if (sc->padconf->pins[i].port == gpios[0] && sc->padconf->pins[i].pin == gpios[1]) { *pin = i; break; } *flags = gpios[gcells - 1]; return (0); } static device_method_t a10_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a10_gpio_probe), DEVMETHOD(device_attach, a10_gpio_attach), DEVMETHOD(device_detach, a10_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, a10_gpio_get_bus), DEVMETHOD(gpio_pin_max, a10_gpio_pin_max), DEVMETHOD(gpio_pin_getname, a10_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, a10_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, a10_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, a10_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, a10_gpio_pin_get), DEVMETHOD(gpio_pin_set, a10_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, a10_gpio_pin_toggle), DEVMETHOD(gpio_map_gpios, a10_gpio_map_gpios), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, a10_gpio_get_node), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure,aw_fdt_configure_pins), DEVMETHOD_END }; static devclass_t a10_gpio_devclass; static driver_t a10_gpio_driver = { "gpio", a10_gpio_methods, sizeof(struct a10_gpio_softc), }; EARLY_DRIVER_MODULE(a10_gpio, simplebus, a10_gpio_driver, a10_gpio_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/arm/allwinner/a83t/a83t_padconf.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/a83t/a83t_padconf.c (nonexistent) +++ user/ngie/detangle-rc/sys/arm/allwinner/a83t/a83t_padconf.c (revision 299127) @@ -0,0 +1,162 @@ +/*- + * Copyright (c) 2016 Jared McNeill + * 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 ``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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include + +#ifdef SOC_ALLWINNER_A83T + +static const struct allwinner_pins a83t_pins[] = { + { "PB0", 1, 0, { "gpio_in", "gpio_out", "uart2", "jtag", NULL, NULL, "eint" } }, + { "PB1", 1, 1, { "gpio_in", "gpio_out", "uart2", "jtag", NULL, NULL, "eint" } }, + { "PB2", 1, 2, { "gpio_in", "gpio_out", "uart2", "jtag", NULL, NULL, "eint" } }, + { "PB3", 1, 3, { "gpio_in", "gpio_out", "uart2", "jtag", NULL, NULL, "eint" } }, + { "PB4", 1, 4, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, + { "PB5", 1, 5, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, + { "PB6", 1, 6, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, + { "PB7", 1, 7, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, + { "PB8", 1, 8, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, + { "PB9", 1, 9, { "gpio_in", "gpio_out", "uart0", NULL, NULL, NULL, "eint" } }, + { "PB10", 1, 10, { "gpio_in", "gpio_out", "uart0", NULL, NULL, NULL, "eint" } }, + + { "PC0", 2, 0, { "gpio_in", "gpio_out", "nand", "spi0" } }, + { "PC1", 2, 1, { "gpio_in", "gpio_out", "nand", "spi0" } }, + { "PC2", 2, 2, { "gpio_in", "gpio_out", "nand", "spi0" } }, + { "PC3", 2, 3, { "gpio_in", "gpio_out", "nand", "spi0" } }, + { "PC4", 2, 4, { "gpio_in", "gpio_out", "nand" } }, + { "PC5", 2, 5, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC6", 2, 6, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC7", 2, 7, { "gpio_in", "gpio_out", "nand" } }, + { "PC8", 2, 8, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC9", 2, 9, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC10", 2, 10, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC11", 2, 11, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC12", 2, 12, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC13", 2, 13, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC14", 2, 14, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC15", 2, 15, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC16", 2, 16, { "gpio_in", "gpio_out", "nand", "mmc2" } }, + { "PC17", 2, 17, { "gpio_in", "gpio_out", "nand" } }, + { "PC18", 2, 18, { "gpio_in", "gpio_out", "nand" } }, + + { "PD2", 3, 2, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD3", 3, 3, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD4", 3, 4, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD5", 3, 5, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD6", 3, 6, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD7", 3, 7, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD10", 3, 10, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD11", 3, 11, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD12", 3, 12, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD13", 3, 13, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD14", 3, 14, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD15", 3, 15, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, + { "PD18", 3, 18, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, + { "PD19", 3, 19, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, + { "PD20", 3, 20, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, + { "PD21", 3, 21, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, + { "PD22", 3, 22, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, + { "PD23", 3, 23, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, + { "PD24", 3, 24, { "gpio_in", "gpio_out", "lcd", "lvds" } }, + { "PD25", 3, 25, { "gpio_in", "gpio_out", "lcd", "lvds" } }, + { "PD26", 3, 26, { "gpio_in", "gpio_out", "lcd", "lvds" } }, + { "PD27", 3, 27, { "gpio_in", "gpio_out", "lcd", "lvds" } }, + { "PD28", 3, 28, { "gpio_in", "gpio_out", "pwm" } }, + { "PD29", 3, 29, { "gpio_in", "gpio_out" } }, + + { "PE0", 4, 0, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, + { "PE1", 4, 1, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, + { "PE2", 4, 2, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, + { "PE3", 4, 3, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, + { "PE4", 4, 4, { "gpio_in", "gpio_out", "csi" } }, + { "PE5", 4, 5, { "gpio_in", "gpio_out", "csi" } }, + { "PE6", 4, 6, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, + { "PE7", 4, 7, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, + { "PE8", 4, 8, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, + { "PE9", 4, 9, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, + { "PE10", 4, 10, { "gpio_in", "gpio_out", "csi", "uart4", "ccir" } }, + { "PE11", 4, 11, { "gpio_in", "gpio_out", "csi", "uart4", "ccir" } }, + { "PE12", 4, 12, { "gpio_in", "gpio_out", "csi", "uart4", "ccir" } }, + { "PE13", 4, 13, { "gpio_in", "gpio_out", "csi", "uart4", "ccir" } }, + { "PE14", 4, 14, { "gpio_in", "gpio_out", "csi", "twi2" } }, + { "PE15", 4, 15, { "gpio_in", "gpio_out", "csi", "twi2" } }, + { "PE16", 4, 16, { "gpio_in", "gpio_out" } }, + { "PE17", 4, 17, { "gpio_in", "gpio_out" } }, + { "PE18", 4, 18, { "gpio_in", "gpio_out", NULL, "owa" } }, + { "PE19", 4, 19, { "gpio_in", "gpio_out" } }, + + { "PF0", 5, 0, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, + { "PF1", 5, 1, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, + { "PF2", 5, 2, { "gpio_in", "gpio_out", "mmc0", "uart0" } }, + { "PF3", 5, 3, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, + { "PF4", 5, 4, { "gpio_in", "gpio_out", "mmc0", "uart0" } }, + { "PF5", 5, 5, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, + { "PF6", 5, 6, { "gpio_in", "gpio_out" } }, + + { "PG0", 6, 0, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, + { "PG1", 6, 1, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, + { "PG2", 6, 2, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, + { "PG3", 6, 3, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, + { "PG4", 6, 4, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, + { "PG5", 6, 5, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, + { "PG6", 6, 6, { "gpio_in", "gpio_out", "uart1", "spi1", NULL, NULL, "eint" } }, + { "PG7", 6, 7, { "gpio_in", "gpio_out", "uart1", "spi1", NULL, NULL, "eint" } }, + { "PG8", 6, 8, { "gpio_in", "gpio_out", "uart1", "spi1", NULL, NULL, "eint" } }, + { "PG9", 6, 9, { "gpio_in", "gpio_out", "uart1", "spi1", NULL, NULL, "eint" } }, + { "PG10", 6, 10, { "gpio_in", "gpio_out", "i2s1", "uart3", NULL, NULL, "eint" } }, + { "PG11", 6, 11, { "gpio_in", "gpio_out", "i2s1", "uart3", NULL, NULL, "eint" } }, + { "PG12", 6, 12, { "gpio_in", "gpio_out", "i2s1", "uart3", NULL, NULL, "eint" } }, + { "PG13", 6, 13, { "gpio_in", "gpio_out", "i2s1", "uart3", NULL, NULL, "eint" } }, + + { "PH0", 7, 0, { "gpio_in", "gpio_out", "i2c0", NULL, NULL, NULL, "eint" } }, + { "PH1", 7, 1, { "gpio_in", "gpio_out", "i2c0", NULL, NULL, NULL, "eint" } }, + { "PH2", 7, 2, { "gpio_in", "gpio_out", "i2c1", NULL, NULL, NULL, "eint" } }, + { "PH3", 7, 3, { "gpio_in", "gpio_out", "i2c1", NULL, NULL, NULL, "eint" } }, + { "PH4", 7, 4, { "gpio_in", "gpio_out", "i2c2", NULL, NULL, NULL, "eint" } }, + { "PH5", 7, 5, { "gpio_in", "gpio_out", "i2c2", NULL, NULL, NULL, "eint" } }, + { "PH6", 7, 6, { "gpio_in", "gpio_out", "hdmiddc", NULL, NULL, NULL, "eint" } }, + { "PH7", 7, 7, { "gpio_in", "gpio_out", "hdmiddc", NULL, NULL, NULL, "eint" } }, + { "PH8", 7, 8, { "gpio_in", "gpio_out", "hdmiddc", NULL, NULL, NULL, "eint" } }, + { "PH9", 7, 9, { "gpio_in", "gpio_out", NULL, NULL, NULL, NULL, "eint" } }, + { "PH10", 7, 10, { "gpio_in", "gpio_out", NULL, NULL, NULL, NULL, "eint" } }, + { "PH11", 7, 11, { "gpio_in", "gpio_out", NULL, NULL, NULL, NULL, "eint" } }, +}; + +const struct allwinner_padconf a83t_padconf = { + .npins = nitems(a83t_pins), + .pins = a83t_pins, +}; + +#endif /* !SOC_ALLWINNER_A83T */ Property changes on: user/ngie/detangle-rc/sys/arm/allwinner/a83t/a83t_padconf.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: user/ngie/detangle-rc/sys/arm/allwinner/a83t/a83t_r_padconf.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/a83t/a83t_r_padconf.c (nonexistent) +++ user/ngie/detangle-rc/sys/arm/allwinner/a83t/a83t_r_padconf.c (revision 299127) @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 2016 Jared McNeill + * 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 ``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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include + +#ifdef SOC_ALLWINNER_A83T + +static const struct allwinner_pins a83t_r_pins[] = { + { "PL0", 0, 0, { "gpio_in", "gpio_out", "s_rsb", "s_i2c", NULL, NULL, "eint" } }, + { "PL1", 0, 1, { "gpio_in", "gpio_out", "s_rsb", "s_i2c", NULL, NULL, "eint" } }, + { "PL2", 0, 2, { "gpio_in", "gpio_out", "s_uart", NULL, NULL, NULL, "eint" } }, + { "PL3", 0, 3, { "gpio_in", "gpio_out", "s_uart", NULL, NULL, NULL, "eint" } }, + { "PL4", 0, 4, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "eint" } }, + { "PL5", 0, 5, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "eint" } }, + { "PL6", 0, 6, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "eint" } }, + { "PL7", 0, 7, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "eint" } }, + { "PL8", 0, 8, { "gpio_in", "gpio_out", "s_i2c", NULL, NULL, NULL, "eint" } }, + { "PL9", 0, 9, { "gpio_in", "gpio_out", "s_i2c", NULL, NULL, NULL, "eint" } }, + { "PL10", 0, 10, { "gpio_in", "gpio_out", "s_pwm", NULL, NULL, NULL, "eint" } }, + { "PL11", 0, 11, { "gpio_in", "gpio_out", NULL, NULL, NULL, "eint" } }, + { "PL12", 0, 12, { "gpio_in", "gpio_out", "s_cir", NULL, NULL, NULL, "eint" } }, +}; + +const struct allwinner_padconf a83t_r_padconf = { + .npins = nitems(a83t_r_pins), + .pins = a83t_r_pins, +}; + +#endif /* !SOC_ALLWINNER_A83T */ Property changes on: user/ngie/detangle-rc/sys/arm/allwinner/a83t/a83t_r_padconf.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: user/ngie/detangle-rc/sys/arm/allwinner/a83t/files.a83t =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/a83t/files.a83t (nonexistent) +++ user/ngie/detangle-rc/sys/arm/allwinner/a83t/files.a83t (revision 299127) @@ -0,0 +1,4 @@ +# $FreeBSD$ + +arm/allwinner/a83t/a83t_padconf.c standard +arm/allwinner/a83t/a83t_r_padconf.c standard Property changes on: user/ngie/detangle-rc/sys/arm/allwinner/a83t/files.a83t ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: user/ngie/detangle-rc/sys/arm/allwinner/a83t/std.a83t =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/a83t/std.a83t (nonexistent) +++ user/ngie/detangle-rc/sys/arm/allwinner/a83t/std.a83t (revision 299127) @@ -0,0 +1,15 @@ +# Allwinner A83T common options +#$FreeBSD$ + +cpu CPU_CORTEXA +machine arm armv6 +makeoptions CONF_CFLAGS="-march=armv7a" + +makeoptions KERNVIRTADDR=0xc0200000 +options KERNVIRTADDR=0xc0200000 + +options IPI_IRQ_START=0 +options IPI_IRQ_END=15 + +files "../allwinner/files.allwinner" +files "../allwinner/a83t/files.a83t" Property changes on: user/ngie/detangle-rc/sys/arm/allwinner/a83t/std.a83t ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: user/ngie/detangle-rc/sys/arm/allwinner/allwinner_machdep.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/allwinner_machdep.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/allwinner_machdep.c (revision 299127) @@ -1,209 +1,233 @@ /*- * Copyright (c) 2012 Ganbold Tsagaankhuu * Copyright (c) 2015-2016 Emmanuel Vadot * All rights reserved. * * This code is derived from software written for Brini by Mark Brinicombe * * 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. * * from: FreeBSD: //depot/projects/arm/src/sys/arm/ti/ti_machdep.c */ #include "opt_ddb.h" #include "opt_platform.h" #include __FBSDID("$FreeBSD$"); #define _ARM32_BUS_DMA_PRIVATE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "platform_if.h" static u_int soc_type; static u_int soc_family; static int a10_attach(platform_t plat) { soc_type = ALLWINNERSOC_A10; soc_family = ALLWINNERSOC_SUN4I; return (0); } static int a20_attach(platform_t plat) { soc_type = ALLWINNERSOC_A20; soc_family = ALLWINNERSOC_SUN7I; return (0); } static int a31_attach(platform_t plat) { soc_type = ALLWINNERSOC_A31; soc_family = ALLWINNERSOC_SUN6I; return (0); } static int a31s_attach(platform_t plat) { soc_type = ALLWINNERSOC_A31S; soc_family = ALLWINNERSOC_SUN6I; return (0); } +static int +a83t_attach(platform_t plat) +{ + soc_type = ALLWINNERSOC_A83T; + soc_family = ALLWINNERSOC_SUN8I; + + return (0); +} + static vm_offset_t allwinner_lastaddr(platform_t plat) { return (devmap_lastaddr()); } /* * Set up static device mappings. * * This covers all the on-chip device with 1MB section mappings, which is good * for performance (uses fewer TLB entries for device access). * * XXX It also covers a block of SRAM and some GPU (mali400) stuff that maybe * shouldn't be device-mapped. The original code mapped a 4MB block, but * perhaps a 1MB block would be more appropriate. */ static int allwinner_devmap_init(platform_t plat) { devmap_add_entry(0x01C00000, 0x00400000); /* 4MB */ return (0); } struct arm32_dma_range * bus_dma_get_range(void) { return (NULL); } int bus_dma_get_range_nb(void) { return (0); } void cpu_reset() { aw_wdog_watchdog_reset(); printf("Reset failed!\n"); while (1); } #if defined(SOC_ALLWINNER_A10) static platform_method_t a10_methods[] = { PLATFORMMETHOD(platform_attach, a10_attach), PLATFORMMETHOD(platform_lastaddr, allwinner_lastaddr), PLATFORMMETHOD(platform_devmap_init, allwinner_devmap_init), PLATFORMMETHOD_END, }; FDT_PLATFORM_DEF(a10, "a10", 0, "allwinner,sun4i-a10", 200); #endif #if defined(SOC_ALLWINNER_A20) static platform_method_t a20_methods[] = { PLATFORMMETHOD(platform_attach, a20_attach), PLATFORMMETHOD(platform_lastaddr, allwinner_lastaddr), PLATFORMMETHOD(platform_devmap_init, allwinner_devmap_init), #ifdef SMP PLATFORMMETHOD(platform_mp_start_ap, a20_mp_start_ap), PLATFORMMETHOD(platform_mp_setmaxid, aw_mp_setmaxid), #endif PLATFORMMETHOD_END, }; FDT_PLATFORM_DEF(a20, "a20", 0, "allwinner,sun7i-a20", 200); #endif #if defined(SOC_ALLWINNER_A31) static platform_method_t a31_methods[] = { PLATFORMMETHOD(platform_attach, a31_attach), PLATFORMMETHOD(platform_lastaddr, allwinner_lastaddr), PLATFORMMETHOD(platform_devmap_init, allwinner_devmap_init), #ifdef SMP PLATFORMMETHOD(platform_mp_start_ap, a31_mp_start_ap), PLATFORMMETHOD(platform_mp_setmaxid, aw_mp_setmaxid), #endif PLATFORMMETHOD_END, }; FDT_PLATFORM_DEF(a31, "a31", 0, "allwinner,sun6i-a31", 200); #endif #if defined(SOC_ALLWINNER_A31S) static platform_method_t a31s_methods[] = { PLATFORMMETHOD(platform_attach, a31s_attach), PLATFORMMETHOD(platform_lastaddr, allwinner_lastaddr), PLATFORMMETHOD(platform_devmap_init, allwinner_devmap_init), #ifdef SMP PLATFORMMETHOD(platform_mp_start_ap, a31_mp_start_ap), PLATFORMMETHOD(platform_mp_setmaxid, aw_mp_setmaxid), #endif PLATFORMMETHOD_END, }; FDT_PLATFORM_DEF(a31s, "a31s", 0, "allwinner,sun6i-a31s", 200); +#endif + +#if defined(SOC_ALLWINNER_A83T) +static platform_method_t a83t_methods[] = { + PLATFORMMETHOD(platform_attach, a83t_attach), + PLATFORMMETHOD(platform_lastaddr, allwinner_lastaddr), + PLATFORMMETHOD(platform_devmap_init, allwinner_devmap_init), + +#ifdef SMP + PLATFORMMETHOD(platform_mp_start_ap, a83t_mp_start_ap), + PLATFORMMETHOD(platform_mp_setmaxid, aw_mp_setmaxid), +#endif + PLATFORMMETHOD_END, +}; +FDT_PLATFORM_DEF(a83t, "a83t", 0, "allwinner,sun8i-a83t", 200); #endif u_int allwinner_soc_type(void) { return (soc_type); } u_int allwinner_soc_family(void) { return (soc_family); } Index: user/ngie/detangle-rc/sys/arm/allwinner/allwinner_machdep.h =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/allwinner_machdep.h (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/allwinner_machdep.h (revision 299127) @@ -1,48 +1,50 @@ /*- * Copyright (c) 2015 Emmanuel Vadot * 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. * * $FreeBSD$ * */ #ifndef AW_MACHDEP_H #define AW_MACHDEP_H #define ALLWINNERSOC_A10 0x10000000 #define ALLWINNERSOC_A13 0x13000000 #define ALLWINNERSOC_A10S 0x10000001 #define ALLWINNERSOC_A20 0x20000000 #define ALLWINNERSOC_A31 0x31000000 #define ALLWINNERSOC_A31S 0x31000001 +#define ALLWINNERSOC_A83T 0x83000000 #define ALLWINNERSOC_SUN4I 0x40000000 #define ALLWINNERSOC_SUN5I 0x50000000 #define ALLWINNERSOC_SUN6I 0x60000000 #define ALLWINNERSOC_SUN7I 0x70000000 +#define ALLWINNERSOC_SUN8I 0x80000000 u_int allwinner_soc_type(void); u_int allwinner_soc_family(void); #endif /* AW_MACHDEP_H */ Index: user/ngie/detangle-rc/sys/arm/allwinner/aw_ccu.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/aw_ccu.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/aw_ccu.c (revision 299127) @@ -1,224 +1,299 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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$ */ /* * Allwinner oscillator clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include +#include #include #include #include #include "clkdev_if.h" #define CCU_BASE 0x01c20000 #define CCU_SIZE 0x400 +#define PRCM_BASE 0x01f01400 +#define PRCM_SIZE 0x200 + +#define SYSCTRL_BASE 0x01c00000 +#define SYSCTRL_SIZE 0x34 + struct aw_ccu_softc { struct simplebus_softc sc; bus_space_tag_t bst; - bus_space_handle_t bsh; + bus_space_handle_t ccu_bsh; + bus_space_handle_t prcm_bsh; + bus_space_handle_t sysctrl_bsh; struct mtx mtx; + int flags; }; +#define CLOCK_CCU (1 << 0) +#define CLOCK_PRCM (1 << 1) +#define CLOCK_SYSCTRL (1 << 2) + static struct ofw_compat_data compat_data[] = { - { "allwinner,sun4i-a10", 1 }, - { "allwinner,sun7i-a20", 1 }, - { "allwinner,sun6i-a31", 1 }, - { "allwinner,sun6i-a31s", 1 }, + { "allwinner,sun4i-a10", CLOCK_CCU }, + { "allwinner,sun7i-a20", CLOCK_CCU }, + { "allwinner,sun6i-a31", CLOCK_CCU }, + { "allwinner,sun6i-a31s", CLOCK_CCU }, + { "allwinner,sun8i-a83t", CLOCK_CCU|CLOCK_PRCM|CLOCK_SYSCTRL }, { NULL, 0 } }; static int -aw_ccu_check_addr(bus_addr_t addr) +aw_ccu_check_addr(struct aw_ccu_softc *sc, bus_addr_t addr, + bus_space_handle_t *pbsh, bus_size_t *poff) { - if (addr < CCU_BASE || addr >= (CCU_BASE + CCU_SIZE)) - return (EINVAL); - return (0); + if (addr >= CCU_BASE && addr < (CCU_BASE + CCU_SIZE) && + (sc->flags & CLOCK_CCU) != 0) { + *poff = addr - CCU_BASE; + *pbsh = sc->ccu_bsh; + return (0); + } + if (addr >= PRCM_BASE && addr < (PRCM_BASE + PRCM_SIZE) && + (sc->flags & CLOCK_PRCM) != 0) { + *poff = addr - PRCM_BASE; + *pbsh = sc->prcm_bsh; + return (0); + } + if (addr >= SYSCTRL_BASE && addr < (SYSCTRL_BASE + SYSCTRL_SIZE) && + (sc->flags & CLOCK_SYSCTRL) != 0) { + *poff = addr - SYSCTRL_BASE; + *pbsh = sc->sysctrl_bsh; + return (0); + } + return (EINVAL); } static int aw_ccu_write_4(device_t dev, bus_addr_t addr, uint32_t val) { struct aw_ccu_softc *sc; + bus_space_handle_t bsh; + bus_size_t reg; - if (aw_ccu_check_addr(addr) != 0) + sc = device_get_softc(dev); + + if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) return (EINVAL); - sc = device_get_softc(dev); mtx_assert(&sc->mtx, MA_OWNED); - bus_space_write_4(sc->bst, sc->bsh, addr - CCU_BASE, val); + bus_space_write_4(sc->bst, bsh, reg, val); return (0); } static int aw_ccu_read_4(device_t dev, bus_addr_t addr, uint32_t *val) { struct aw_ccu_softc *sc; + bus_space_handle_t bsh; + bus_size_t reg; - if (aw_ccu_check_addr(addr) != 0) + sc = device_get_softc(dev); + + if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) return (EINVAL); - sc = device_get_softc(dev); mtx_assert(&sc->mtx, MA_OWNED); - *val = bus_space_read_4(sc->bst, sc->bsh, addr - CCU_BASE); + *val = bus_space_read_4(sc->bst, bsh, reg); return (0); } static int aw_ccu_modify_4(device_t dev, bus_addr_t addr, uint32_t clr, uint32_t set) { struct aw_ccu_softc *sc; + bus_space_handle_t bsh; + bus_size_t reg; uint32_t val; - if (aw_ccu_check_addr(addr) != 0) + sc = device_get_softc(dev); + + if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) return (EINVAL); - sc = device_get_softc(dev); mtx_assert(&sc->mtx, MA_OWNED); - val = bus_space_read_4(sc->bst, sc->bsh, addr - CCU_BASE); + val = bus_space_read_4(sc->bst, bsh, reg); val &= ~clr; val |= set; - bus_space_write_4(sc->bst, sc->bsh, addr - CCU_BASE, val); + bus_space_write_4(sc->bst, bsh, reg, val); return (0); } static void aw_ccu_device_lock(device_t dev) { struct aw_ccu_softc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mtx); } static void aw_ccu_device_unlock(device_t dev) { struct aw_ccu_softc *sc; sc = device_get_softc(dev); mtx_unlock(&sc->mtx); } +static const struct ofw_compat_data * +aw_ccu_search_compatible(void) +{ + const struct ofw_compat_data *compat; + phandle_t root; + + root = OF_finddevice("/"); + for (compat = compat_data; compat_data->ocd_str != NULL; compat++) + if (fdt_is_compatible(root, compat->ocd_str)) + break; + + return (compat); +} + static int aw_ccu_probe(device_t dev) { const char *name; - device_t pdev; name = ofw_bus_get_name(dev); if (name == NULL || strcmp(name, "clocks") != 0) return (ENXIO); - pdev = device_get_parent(dev); - if (ofw_bus_search_compatible(pdev, compat_data)->ocd_data == 0) - return (0); + if (aw_ccu_search_compatible()->ocd_data == 0) + return (ENXIO); device_set_desc(dev, "Allwinner Clock Control Unit"); return (BUS_PROBE_SPECIFIC); } static int aw_ccu_attach(device_t dev) { struct aw_ccu_softc *sc; phandle_t node, child; device_t cdev; int error; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); simplebus_init(dev, node); + sc->flags = aw_ccu_search_compatible()->ocd_data; + /* - * Map CCU registers. The DT doesn't have a "reg" property for the - * /clocks node and child nodes have conflicting "reg" properties. + * Map registers. The DT doesn't have a "reg" property + * for the /clocks node and child nodes have conflicting "reg" + * properties. */ sc->bst = bus_get_bus_tag(dev); - error = bus_space_map(sc->bst, CCU_BASE, CCU_SIZE, 0, &sc->bsh); - if (error != 0) { - device_printf(dev, "couldn't map CCU: %d\n", error); - return (error); + if (sc->flags & CLOCK_CCU) { + error = bus_space_map(sc->bst, CCU_BASE, CCU_SIZE, 0, + &sc->ccu_bsh); + if (error != 0) { + device_printf(dev, "couldn't map CCU: %d\n", error); + return (error); + } + } + if (sc->flags & CLOCK_PRCM) { + error = bus_space_map(sc->bst, PRCM_BASE, PRCM_SIZE, 0, + &sc->prcm_bsh); + if (error != 0) { + device_printf(dev, "couldn't map PRCM: %d\n", error); + return (error); + } + } + if (sc->flags & CLOCK_SYSCTRL) { + error = bus_space_map(sc->bst, SYSCTRL_BASE, SYSCTRL_SIZE, 0, + &sc->sysctrl_bsh); + if (error != 0) { + device_printf(dev, "couldn't map SYSCTRL: %d\n", error); + return (error); + } } mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* Attach child devices */ for (child = OF_child(node); child > 0; child = OF_peer(child)) { cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL); if (cdev != NULL) device_probe_and_attach(cdev); } return (bus_generic_attach(dev)); } static device_method_t aw_ccu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_ccu_probe), DEVMETHOD(device_attach, aw_ccu_attach), /* clkdev interface */ DEVMETHOD(clkdev_write_4, aw_ccu_write_4), DEVMETHOD(clkdev_read_4, aw_ccu_read_4), DEVMETHOD(clkdev_modify_4, aw_ccu_modify_4), DEVMETHOD(clkdev_device_lock, aw_ccu_device_lock), DEVMETHOD(clkdev_device_unlock, aw_ccu_device_unlock), DEVMETHOD_END }; DEFINE_CLASS_1(aw_ccu, aw_ccu_driver, aw_ccu_methods, sizeof(struct aw_ccu_softc), simplebus_driver); static devclass_t aw_ccu_devclass; EARLY_DRIVER_MODULE(aw_ccu, simplebus, aw_ccu_driver, aw_ccu_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(aw_ccu, 1); Index: user/ngie/detangle-rc/sys/arm/allwinner/aw_mp.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/aw_mp.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/aw_mp.c (revision 299127) @@ -1,204 +1,301 @@ /*- * Copyright (c) 2014 Ganbold Tsagaankhuu * Copyright (c) 2016 Emmanuel Vadot * 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 ``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 #include #include #include #include #include #include #include #include /* Register for all dual-core SoC */ #define A20_CPUCFG_BASE 0x01c25c00 /* Register for all quad-core SoC */ #define CPUCFG_BASE 0x01f01c00 #define CPUCFG_SIZE 0x400 #define PRCM_BASE 0x01f01400 #define PRCM_SIZE 0x800 +/* Register for multi-cluster SoC */ +#define CPUXCFG_BASE 0x01700000 +#define CPUXCFG_SIZE 0x400 #define CPU_OFFSET 0x40 #define CPU_OFFSET_CTL 0x04 #define CPU_OFFSET_STATUS 0x08 #define CPU_RST_CTL(cpuid) ((cpuid + 1) * CPU_OFFSET) #define CPU_CTL(cpuid) (((cpuid + 1) * CPU_OFFSET) + CPU_OFFSET_CTL) #define CPU_STATUS(cpuid) (((cpuid + 1) * CPU_OFFSET) + CPU_OFFSET_STATUS) #define CPU_RESET (1 << 0) #define CPU_CORE_RESET (1 << 1) #define CPUCFG_GENCTL 0x184 #define CPUCFG_P_REG0 0x1a4 #define A20_CPU1_PWR_CLAMP 0x1b0 #define CPU_PWR_CLAMP_REG 0x140 #define CPU_PWR_CLAMP(cpu) ((cpu * 4) + CPU_PWR_CLAMP_REG) #define CPU_PWR_CLAMP_STEPS 8 #define A20_CPU1_PWROFF_REG 0x1b4 #define CPU_PWROFF 0x100 #define CPUCFG_DBGCTL0 0x1e0 #define CPUCFG_DBGCTL1 0x1e4 +#define CPUS_CL_RST(cl) (0x30 + (cluster) * 0x4) +#define CPUX_CL_CTRL0(cl) (0x0 + (cluster) * 0x10) +#define CPUX_CL_CTRL1(cl) (0x4 + (cluster) * 0x10) +#define CPUX_CL_CPU_STATUS(cl) (0x30 + (cluster) * 0x4) +#define CPUX_CL_RST(cl) (0x80 + (cluster) * 0x4) +#define PRCM_CL_PWROFF(cl) (0x100 + (cluster) * 0x4) +#define PRCM_CL_PWR_CLAMP(cl, cpu) (0x140 + (cluster) * 0x4 + (cpu) * 0x4) + void aw_mp_setmaxid(platform_t plat) { int ncpu; uint32_t reg; if (mp_ncpus != 0) return; reg = cp15_l2ctlr_get(); ncpu = CPUV7_L2CTLR_NPROC(reg); mp_ncpus = ncpu; mp_maxid = ncpu - 1; } static void aw_common_mp_start_ap(bus_space_handle_t cpucfg, bus_space_handle_t prcm) { int i, j; uint32_t val; dcache_wbinv_poc_all(); bus_space_write_4(fdtbus_bs_tag, cpucfg, CPUCFG_P_REG0, pmap_kextract((vm_offset_t)mpentry)); /* * Assert nCOREPORESET low and set L1RSTDISABLE low. * Ensure DBGPWRDUP is set to LOW to prevent any external * debug access to the processor. */ for (i = 1; i < mp_ncpus; i++) bus_space_write_4(fdtbus_bs_tag, cpucfg, CPU_RST_CTL(i), 0); /* Set L1RSTDISABLE low */ val = bus_space_read_4(fdtbus_bs_tag, cpucfg, CPUCFG_GENCTL); for (i = 1; i < mp_ncpus; i++) val &= ~(1 << i); bus_space_write_4(fdtbus_bs_tag, cpucfg, CPUCFG_GENCTL, val); /* Set DBGPWRDUP low */ val = bus_space_read_4(fdtbus_bs_tag, cpucfg, CPUCFG_DBGCTL1); for (i = 1; i < mp_ncpus; i++) val &= ~(1 << i); bus_space_write_4(fdtbus_bs_tag, cpucfg, CPUCFG_DBGCTL1, val); /* Release power clamp */ for (i = 1; i < mp_ncpus; i++) for (j = 0; j <= CPU_PWR_CLAMP_STEPS; j++) { if (prcm) { bus_space_write_4(fdtbus_bs_tag, prcm, CPU_PWR_CLAMP(i), 0xff >> j); } else { bus_space_write_4(fdtbus_bs_tag, cpucfg, A20_CPU1_PWR_CLAMP, 0xff >> j); } } DELAY(10000); /* Clear power-off gating */ if (prcm) { val = bus_space_read_4(fdtbus_bs_tag, prcm, CPU_PWROFF); for (i = 0; i < mp_ncpus; i++) val &= ~(1 << i); bus_space_write_4(fdtbus_bs_tag, prcm, CPU_PWROFF, val); } else { val = bus_space_read_4(fdtbus_bs_tag, cpucfg, A20_CPU1_PWROFF_REG); val &= ~(1 << 0); bus_space_write_4(fdtbus_bs_tag, cpucfg, A20_CPU1_PWROFF_REG, val); } DELAY(1000); /* De-assert cpu core reset */ for (i = 1; i < mp_ncpus; i++) bus_space_write_4(fdtbus_bs_tag, cpucfg, CPU_RST_CTL(i), CPU_RESET | CPU_CORE_RESET); /* Assert DBGPWRDUP signal */ val = bus_space_read_4(fdtbus_bs_tag, cpucfg, CPUCFG_DBGCTL1); for (i = 1; i < mp_ncpus; i++) val |= (1 << i); bus_space_write_4(fdtbus_bs_tag, cpucfg, CPUCFG_DBGCTL1, val); armv7_sev(); bus_space_unmap(fdtbus_bs_tag, cpucfg, CPUCFG_SIZE); } void a20_mp_start_ap(platform_t plat) { bus_space_handle_t cpucfg; if (bus_space_map(fdtbus_bs_tag, A20_CPUCFG_BASE, CPUCFG_SIZE, 0, &cpucfg) != 0) panic("Couldn't map the CPUCFG\n"); aw_common_mp_start_ap(cpucfg, 0); armv7_sev(); bus_space_unmap(fdtbus_bs_tag, cpucfg, CPUCFG_SIZE); } void a31_mp_start_ap(platform_t plat) { bus_space_handle_t cpucfg; bus_space_handle_t prcm; if (bus_space_map(fdtbus_bs_tag, CPUCFG_BASE, CPUCFG_SIZE, 0, &cpucfg) != 0) panic("Couldn't map the CPUCFG\n"); if (bus_space_map(fdtbus_bs_tag, PRCM_BASE, PRCM_SIZE, 0, &prcm) != 0) panic("Couldn't map the PRCM\n"); aw_common_mp_start_ap(cpucfg, prcm); armv7_sev(); bus_space_unmap(fdtbus_bs_tag, cpucfg, CPUCFG_SIZE); + bus_space_unmap(fdtbus_bs_tag, prcm, PRCM_SIZE); +} + +static void +aw_mc_mp_start_cpu(bus_space_handle_t cpuscfg, bus_space_handle_t cpuxcfg, + bus_space_handle_t prcm, int cluster, int cpu) +{ + uint32_t val; + int i; + + /* Assert core reset */ + val = bus_space_read_4(fdtbus_bs_tag, cpuxcfg, CPUX_CL_RST(cluster)); + val &= ~(1 << cpu); + bus_space_write_4(fdtbus_bs_tag, cpuxcfg, CPUX_CL_RST(cluster), val); + + /* Assert power-on reset */ + val = bus_space_read_4(fdtbus_bs_tag, cpuscfg, CPUS_CL_RST(cluster)); + val &= ~(1 << cpu); + bus_space_write_4(fdtbus_bs_tag, cpuscfg, CPUS_CL_RST(cluster), val); + + /* Disable automatic L1 cache invalidate at reset */ + val = bus_space_read_4(fdtbus_bs_tag, cpuxcfg, CPUX_CL_CTRL0(cluster)); + val &= ~(1 << cpu); + bus_space_write_4(fdtbus_bs_tag, cpuxcfg, CPUX_CL_CTRL0(cluster), val); + + /* Release power clamp */ + for (i = 0; i <= CPU_PWR_CLAMP_STEPS; i++) + bus_space_write_4(fdtbus_bs_tag, prcm, + PRCM_CL_PWR_CLAMP(cluster, cpu), 0xff >> i); + while (bus_space_read_4(fdtbus_bs_tag, prcm, + PRCM_CL_PWR_CLAMP(cluster, cpu)) != 0) + ; + + /* Clear power-off gating */ + val = bus_space_read_4(fdtbus_bs_tag, prcm, PRCM_CL_PWROFF(cluster)); + val &= ~(1 << cpu); + bus_space_write_4(fdtbus_bs_tag, prcm, PRCM_CL_PWROFF(cluster), val); + + /* De-assert power-on reset */ + val = bus_space_read_4(fdtbus_bs_tag, cpuscfg, CPUS_CL_RST(cluster)); + val |= (1 << cpu); + bus_space_write_4(fdtbus_bs_tag, cpuscfg, CPUS_CL_RST(cluster), val); + + /* De-assert core reset */ + val = bus_space_read_4(fdtbus_bs_tag, cpuxcfg, CPUX_CL_RST(cluster)); + val |= (1 << cpu); + bus_space_write_4(fdtbus_bs_tag, cpuxcfg, CPUX_CL_RST(cluster), val); +} + +static void +aw_mc_mp_start_ap(bus_space_handle_t cpuscfg, bus_space_handle_t cpuxcfg, + bus_space_handle_t prcm) +{ + int cluster, cpu; + + KASSERT(mp_ncpus <= 4, ("multiple clusters not yet supported")); + + dcache_wbinv_poc_all(); + + bus_space_write_4(fdtbus_bs_tag, cpuscfg, CPUCFG_P_REG0, + pmap_kextract((vm_offset_t)mpentry)); + + cluster = 0; + for (cpu = 1; cpu < mp_ncpus; cpu++) + aw_mc_mp_start_cpu(cpuscfg, cpuxcfg, prcm, cluster, cpu); +} + +void +a83t_mp_start_ap(platform_t plat) +{ + bus_space_handle_t cpuscfg, cpuxcfg, prcm; + + if (bus_space_map(fdtbus_bs_tag, CPUCFG_BASE, CPUCFG_SIZE, + 0, &cpuscfg) != 0) + panic("Couldn't map the CPUCFG\n"); + if (bus_space_map(fdtbus_bs_tag, CPUXCFG_BASE, CPUXCFG_SIZE, + 0, &cpuxcfg) != 0) + panic("Couldn't map the CPUXCFG\n"); + if (bus_space_map(fdtbus_bs_tag, PRCM_BASE, PRCM_SIZE, 0, + &prcm) != 0) + panic("Couldn't map the PRCM\n"); + + aw_mc_mp_start_ap(cpuscfg, cpuxcfg, prcm); + armv7_sev(); + bus_space_unmap(fdtbus_bs_tag, cpuxcfg, CPUXCFG_SIZE); + bus_space_unmap(fdtbus_bs_tag, cpuscfg, CPUCFG_SIZE); bus_space_unmap(fdtbus_bs_tag, prcm, PRCM_SIZE); } Index: user/ngie/detangle-rc/sys/arm/allwinner/aw_mp.h =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/aw_mp.h (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/aw_mp.h (revision 299127) @@ -1,35 +1,36 @@ /*- * Copyright (c) 2016 Emmanuel Vadot * 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 ``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 _AW_MP_H_ #define _AW_MP_H_ void aw_mp_setmaxid(platform_t plat); void a20_mp_start_ap(platform_t plat); void a31_mp_start_ap(platform_t plat); +void a83t_mp_start_ap(platform_t plat); #endif /* _AW_MP_H_ */ Index: user/ngie/detangle-rc/sys/arm/allwinner/aw_nmi.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/aw_nmi.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/aw_nmi.c (revision 299127) @@ -1,399 +1,403 @@ /*- * Copyright (c) 2016 Emmanuel Vadot * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pic_if.h" #define NMI_IRQ_CTRL_REG 0x0 #define NMI_IRQ_LOW_LEVEL 0x0 #define NMI_IRQ_LOW_EDGE 0x1 #define NMI_IRQ_HIGH_LEVEL 0x2 #define NMI_IRQ_HIGH_EDGE 0x3 #define NMI_IRQ_PENDING_REG 0x4 #define NMI_IRQ_ACK (1U << 0) #define A20_NMI_IRQ_ENABLE_REG 0x8 #define A31_NMI_IRQ_ENABLE_REG 0x34 #define NMI_IRQ_ENABLE (1U << 0) #define SC_NMI_READ(_sc, _reg) bus_read_4(_sc->res[0], _reg) #define SC_NMI_WRITE(_sc, _reg, _val) bus_write_4(_sc->res[0], _reg, _val) static struct resource_spec aw_nmi_res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0, 0 } }; struct aw_nmi_intr { struct intr_irqsrc isrc; u_int irq; enum intr_polarity pol; enum intr_trigger tri; }; struct aw_nmi_softc { device_t dev; struct resource * res[2]; void * intrcookie; struct aw_nmi_intr intr; uint8_t enable_reg; }; #define A20_NMI 1 #define A31_NMI 2 static struct ofw_compat_data compat_data[] = { {"allwinner,sun7i-a20-sc-nmi", A20_NMI}, {"allwinner,sun6i-a31-sc-nmi", A31_NMI}, {NULL, 0}, }; static int aw_nmi_intr(void *arg) { struct aw_nmi_softc *sc; sc = arg; if (SC_NMI_READ(sc, NMI_IRQ_PENDING_REG) == 0) { device_printf(sc->dev, "Spurious interrupt\n"); return (FILTER_HANDLED); } if (intr_isrc_dispatch(&sc->intr.isrc, curthread->td_intr_frame) != 0) { SC_NMI_WRITE(sc, sc->enable_reg, !NMI_IRQ_ENABLE); device_printf(sc->dev, "Stray interrupt, NMI disabled\n"); } return (FILTER_HANDLED); } static void aw_nmi_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct aw_nmi_softc *sc; sc = device_get_softc(dev); SC_NMI_WRITE(sc, sc->enable_reg, NMI_IRQ_ENABLE); } static void aw_nmi_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct aw_nmi_softc *sc; sc = device_get_softc(dev); SC_NMI_WRITE(sc, sc->enable_reg, !NMI_IRQ_ENABLE); } static int aw_nmi_map_fdt(device_t dev, u_int ncells, pcell_t *cells, u_int *irqp, enum intr_polarity *polp, enum intr_trigger *trigp) { u_int irq, tripol; enum intr_polarity pol; enum intr_trigger trig; if (ncells != 2) { device_printf(dev, "Invalid #interrupt-cells\n"); return (EINVAL); } irq = cells[0]; if (irq != 0) { device_printf(dev, "Controller only support irq 0\n"); return (EINVAL); } tripol = cells[1]; switch (tripol) { case IRQ_TYPE_EDGE_RISING: trig = INTR_TRIGGER_EDGE; pol = INTR_POLARITY_HIGH; break; case IRQ_TYPE_EDGE_FALLING: trig = INTR_TRIGGER_EDGE; pol = INTR_POLARITY_LOW; break; case IRQ_TYPE_LEVEL_HIGH: trig = INTR_TRIGGER_LEVEL; pol = INTR_POLARITY_HIGH; break; case IRQ_TYPE_LEVEL_LOW: trig = INTR_TRIGGER_LEVEL; pol = INTR_POLARITY_LOW; break; default: device_printf(dev, "unsupported trigger/polarity 0x%2x\n", tripol); return (ENOTSUP); } *irqp = irq; if (polp != NULL) *polp = pol; if (trigp != NULL) *trigp = trig; return (0); } static int aw_nmi_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { + struct intr_map_data_fdt *daf; struct aw_nmi_softc *sc; int error; u_int irq; - sc = device_get_softc(dev); if (data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); - error = aw_nmi_map_fdt(dev, data->fdt.ncells, data->fdt.cells, &irq, - NULL, NULL); + sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; + + error = aw_nmi_map_fdt(dev, daf->ncells, daf->cells, &irq, NULL, NULL); if (error == 0) *isrcp = &sc->intr.isrc; return (error); } static int aw_nmi_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { + struct intr_map_data_fdt *daf; struct aw_nmi_softc *sc; struct aw_nmi_intr *nmi_intr; int error, icfg; u_int irq; enum intr_trigger trig; enum intr_polarity pol; - sc = device_get_softc(dev); - nmi_intr = (struct aw_nmi_intr *)isrc; - /* Get config for interrupt. */ if (data == NULL || data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); - error = aw_nmi_map_fdt(dev, data->fdt.ncells, data->fdt.cells, &irq, - &pol, &trig); + + sc = device_get_softc(dev); + nmi_intr = (struct aw_nmi_intr *)isrc; + daf = (struct intr_map_data_fdt *)data; + + error = aw_nmi_map_fdt(dev, daf->ncells, daf->cells, &irq, &pol, &trig); if (error != 0) return (error); if (nmi_intr->irq != irq) return (EINVAL); /* Compare config if this is not first setup. */ if (isrc->isrc_handlers != 0) { if (pol != nmi_intr->pol || trig != nmi_intr->tri) return (EINVAL); else return (0); } nmi_intr->pol = pol; nmi_intr->tri = trig; if (trig == INTR_TRIGGER_LEVEL) { if (pol == INTR_POLARITY_LOW) icfg = NMI_IRQ_LOW_LEVEL; else icfg = NMI_IRQ_HIGH_LEVEL; } else { if (pol == INTR_POLARITY_HIGH) icfg = NMI_IRQ_HIGH_EDGE; else icfg = NMI_IRQ_LOW_EDGE; } SC_NMI_WRITE(sc, NMI_IRQ_CTRL_REG, icfg); return (0); } static int aw_nmi_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct aw_nmi_softc *sc; sc = device_get_softc(dev); if (isrc->isrc_handlers == 0) { sc->intr.pol = INTR_POLARITY_CONFORM; sc->intr.tri = INTR_TRIGGER_CONFORM; SC_NMI_WRITE(sc, sc->enable_reg, !NMI_IRQ_ENABLE); } return (0); } static void aw_nmi_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct aw_nmi_softc *sc; sc = device_get_softc(dev); aw_nmi_disable_intr(dev, isrc); SC_NMI_WRITE(sc, NMI_IRQ_PENDING_REG, NMI_IRQ_ACK); } static void aw_nmi_post_ithread(device_t dev, struct intr_irqsrc *isrc) { arm_irq_memory_barrier(0); aw_nmi_enable_intr(dev, isrc); } static void aw_nmi_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct aw_nmi_softc *sc; sc = device_get_softc(dev); arm_irq_memory_barrier(0); SC_NMI_WRITE(sc, NMI_IRQ_PENDING_REG, NMI_IRQ_ACK); } static int aw_nmi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner NMI Controller"); return (BUS_PROBE_DEFAULT); } static int aw_nmi_attach(device_t dev) { struct aw_nmi_softc *sc; phandle_t xref; sc = device_get_softc(dev); sc->dev = dev; if (bus_alloc_resources(dev, aw_nmi_res_spec, sc->res) != 0) { device_printf(dev, "can't allocate device resources\n"); return (ENXIO); } if ((bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC, aw_nmi_intr, NULL, sc, &sc->intrcookie))) { device_printf(dev, "unable to register interrupt handler\n"); bus_release_resources(dev, aw_nmi_res_spec, sc->res); return (ENXIO); } switch (ofw_bus_search_compatible(dev, compat_data)->ocd_data) { case A20_NMI: sc->enable_reg = A20_NMI_IRQ_ENABLE_REG; break; case A31_NMI: sc->enable_reg = A31_NMI_IRQ_ENABLE_REG; break; } /* Disable and clear interrupts */ SC_NMI_WRITE(sc, sc->enable_reg, !NMI_IRQ_ENABLE); SC_NMI_WRITE(sc, NMI_IRQ_PENDING_REG, NMI_IRQ_ACK); xref = OF_xref_from_node(ofw_bus_get_node(dev)); /* Register our isrc */ sc->intr.irq = 0; sc->intr.pol = INTR_POLARITY_CONFORM; sc->intr.tri = INTR_TRIGGER_CONFORM; if (intr_isrc_register(&sc->intr.isrc, sc->dev, 0, "%s,%u", device_get_nameunit(sc->dev), sc->intr.irq) != 0) goto error; if (intr_pic_register(dev, (intptr_t)xref) != 0) { device_printf(dev, "could not register pic\n"); goto error; } return (0); error: bus_teardown_intr(dev, sc->res[1], sc->intrcookie); bus_release_resources(dev, aw_nmi_res_spec, sc->res); return (ENXIO); } static device_method_t aw_nmi_methods[] = { DEVMETHOD(device_probe, aw_nmi_probe), DEVMETHOD(device_attach, aw_nmi_attach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, aw_nmi_disable_intr), DEVMETHOD(pic_enable_intr, aw_nmi_enable_intr), DEVMETHOD(pic_map_intr, aw_nmi_map_intr), DEVMETHOD(pic_setup_intr, aw_nmi_setup_intr), DEVMETHOD(pic_teardown_intr, aw_nmi_teardown_intr), DEVMETHOD(pic_post_filter, aw_nmi_post_filter), DEVMETHOD(pic_post_ithread, aw_nmi_post_ithread), DEVMETHOD(pic_pre_ithread, aw_nmi_pre_ithread), {0, 0}, }; static driver_t aw_nmi_driver = { "aw_nmi", aw_nmi_methods, sizeof(struct aw_nmi_softc), }; static devclass_t aw_nmi_devclass; EARLY_DRIVER_MODULE(aw_nmi, simplebus, aw_nmi_driver, aw_nmi_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST); Index: user/ngie/detangle-rc/sys/arm/allwinner/aw_usbphy.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/aw_usbphy.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/aw_usbphy.c (revision 299127) @@ -1,149 +1,150 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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$ */ /* * Allwinner USB PHY */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #define USBPHY_NUMOFF 3 static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-usb-phy", 1 }, { "allwinner,sun5i-a13-usb-phy", 1 }, { "allwinner,sun6i-a31-usb-phy", 1 }, { "allwinner,sun7i-a20-usb-phy", 1 }, + { "allwinner,sun8i-a83t-usb-phy", 1 }, { NULL, 0 } }; static int awusbphy_init(device_t dev) { char pname[20]; int error, off; regulator_t reg; hwreset_t rst; clk_t clk; /* Enable clocks */ for (off = 0; clk_get_by_ofw_index(dev, off, &clk) == 0; off++) { error = clk_enable(clk); if (error != 0) { device_printf(dev, "couldn't enable clock %s\n", clk_get_name(clk)); return (error); } } /* De-assert resets */ for (off = 0; hwreset_get_by_ofw_idx(dev, off, &rst) == 0; off++) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "couldn't de-assert reset %d\n", off); return (error); } } /* Enable regulator(s) */ for (off = 0; off < USBPHY_NUMOFF; off++) { snprintf(pname, sizeof(pname), "usb%d_vbus-supply", off); if (regulator_get_by_ofw_property(dev, pname, ®) != 0) continue; error = regulator_enable(reg); if (error != 0) { device_printf(dev, "couldn't enable regulator %s\n", pname); return (error); } } return (0); } static int awusbphy_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner USB PHY"); return (BUS_PROBE_DEFAULT); } static int awusbphy_attach(device_t dev) { int error; error = awusbphy_init(dev); if (error) device_printf(dev, "failed to initialize USB PHY, error %d\n", error); return (error); } static device_method_t awusbphy_methods[] = { /* Device interface */ DEVMETHOD(device_probe, awusbphy_probe), DEVMETHOD(device_attach, awusbphy_attach), DEVMETHOD_END }; static driver_t awusbphy_driver = { "awusbphy", awusbphy_methods, 0, }; static devclass_t awusbphy_devclass; DRIVER_MODULE(awusbphy, simplebus, awusbphy_driver, awusbphy_devclass, 0, 0); MODULE_VERSION(awusbphy, 1); Index: user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_ahbclk.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_ahbclk.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_ahbclk.c (revision 299127) @@ -1,308 +1,380 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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$ */ /* * Allwinner AHB clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define A10_AHB_CLK_DIV_RATIO (0x3 << 4) #define A10_AHB_CLK_DIV_RATIO_SHIFT 4 #define A13_AHB_CLK_SRC_SEL (0x3 << 6) #define A13_AHB_CLK_SRC_SEL_MAX 3 #define A13_AHB_CLK_SRC_SEL_SHIFT 6 #define A31_AHB1_PRE_DIV (0x3 << 6) #define A31_AHB1_PRE_DIV_SHIFT 6 #define A31_AHB1_CLK_SRC_SEL (0x3 << 12) #define A31_AHB1_CLK_SRC_SEL_PLL6 3 #define A31_AHB1_CLK_SRC_SEL_MAX 3 #define A31_AHB1_CLK_SRC_SEL_SHIFT 12 +#define A83T_AHB1_CLK_SRC_SEL (0x3 << 12) +#define A83T_AHB1_CLK_SRC_SEL_ISPLL(x) ((x) & 0x2) +#define A83T_AHB1_CLK_SRC_SEL_MAX 3 +#define A83T_AHB1_CLK_SRC_SEL_SHIFT 12 +#define A83T_AHB1_PRE_DIV (0x3 << 6) +#define A83T_AHB1_PRE_DIV_SHIFT 6 +#define A83T_AHB1_CLK_DIV_RATIO (0x3 << 4) +#define A83T_AHB1_CLK_DIV_RATIO_SHIFT 4 + +#define H3_AHB2_CLK_CFG (0x3 << 0) +#define H3_AHB2_CLK_CFG_SHIFT 0 +#define H3_AHB2_CLK_CFG_AHB1 0 +#define H3_AHB2_CLK_CFG_PLL_PERIPH_DIV2 1 +#define H3_AHB2_CLK_CFG_MAX 1 + enum aw_ahbclk_type { AW_A10_AHB = 1, AW_A13_AHB, AW_A31_AHB1, + AW_A83T_AHB1, + AW_H3_AHB2, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-ahb-clk", AW_A10_AHB }, { "allwinner,sun5i-a13-ahb-clk", AW_A13_AHB }, { "allwinner,sun6i-a31-ahb1-clk", AW_A31_AHB1 }, + { "allwinner,sun8i-a83t-ahb1-clk", AW_A83T_AHB1 }, + { "allwinner,sun8i-h3-ahb2-clk", AW_H3_AHB2 }, { NULL, 0 } }; struct aw_ahbclk_sc { device_t clkdev; bus_addr_t reg; enum aw_ahbclk_type type; }; #define AHBCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define AHBCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_ahbclk_init(struct clknode *clk, device_t dev) { struct aw_ahbclk_sc *sc; uint32_t val, index; sc = clknode_get_softc(clk); switch (sc->type) { case AW_A10_AHB: index = 0; break; case AW_A13_AHB: DEVICE_LOCK(sc); AHBCLK_READ(sc, &val); DEVICE_UNLOCK(sc); index = (val & A13_AHB_CLK_SRC_SEL) >> A13_AHB_CLK_SRC_SEL_SHIFT; break; case AW_A31_AHB1: DEVICE_LOCK(sc); AHBCLK_READ(sc, &val); DEVICE_UNLOCK(sc); index = (val & A31_AHB1_CLK_SRC_SEL) >> A31_AHB1_CLK_SRC_SEL_SHIFT; break; + case AW_A83T_AHB1: + DEVICE_LOCK(sc); + AHBCLK_READ(sc, &val); + DEVICE_UNLOCK(sc); + index = (val & A83T_AHB1_CLK_SRC_SEL) >> + A83T_AHB1_CLK_SRC_SEL_SHIFT; + break; + case AW_H3_AHB2: + DEVICE_LOCK(sc); + AHBCLK_READ(sc, &val); + DEVICE_UNLOCK(sc); + index = (val & H3_AHB2_CLK_CFG) >> H3_AHB2_CLK_CFG_SHIFT; + break; default: return (ENXIO); } clknode_init_parent_idx(clk, index); return (0); } static int aw_ahbclk_recalc_freq(struct clknode *clk, uint64_t *freq) { struct aw_ahbclk_sc *sc; uint32_t val, src_sel, div, pre_div; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); AHBCLK_READ(sc, &val); DEVICE_UNLOCK(sc); - div = 1 << ((val & A10_AHB_CLK_DIV_RATIO) >> - A10_AHB_CLK_DIV_RATIO_SHIFT); - switch (sc->type) { case AW_A31_AHB1: + div = 1 << ((val & A10_AHB_CLK_DIV_RATIO) >> + A10_AHB_CLK_DIV_RATIO_SHIFT); src_sel = (val & A31_AHB1_CLK_SRC_SEL) >> A31_AHB1_CLK_SRC_SEL_SHIFT; if (src_sel == A31_AHB1_CLK_SRC_SEL_PLL6) pre_div = ((val & A31_AHB1_PRE_DIV) >> A31_AHB1_PRE_DIV_SHIFT) + 1; else pre_div = 1; break; + case AW_A83T_AHB1: + div = 1 << ((val & A83T_AHB1_CLK_DIV_RATIO) >> + A83T_AHB1_CLK_DIV_RATIO_SHIFT); + src_sel = (val & A83T_AHB1_CLK_SRC_SEL) >> + A83T_AHB1_CLK_SRC_SEL_SHIFT; + if (A83T_AHB1_CLK_SRC_SEL_ISPLL(src_sel)) + pre_div = ((val & A83T_AHB1_PRE_DIV) >> + A83T_AHB1_PRE_DIV_SHIFT) + 1; + else + pre_div = 1; + break; + case AW_H3_AHB2: + src_sel = (val & H3_AHB2_CLK_CFG) >> H3_AHB2_CLK_CFG_SHIFT; + if (src_sel == H3_AHB2_CLK_CFG_PLL_PERIPH_DIV2) + div = 2; + else + div = 1; + pre_div = 1; + break; default: + div = 1 << ((val & A10_AHB_CLK_DIV_RATIO) >> + A10_AHB_CLK_DIV_RATIO_SHIFT); pre_div = 1; break; } *freq = *freq / pre_div / div; return (0); } static int aw_ahbclk_set_mux(struct clknode *clk, int index) { struct aw_ahbclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); switch (sc->type) { case AW_A10_AHB: if (index != 0) return (ERANGE); break; case AW_A13_AHB: if (index < 0 || index > A13_AHB_CLK_SRC_SEL_MAX) return (ERANGE); DEVICE_LOCK(sc); AHBCLK_READ(sc, &val); val &= ~A13_AHB_CLK_SRC_SEL; val |= (index << A13_AHB_CLK_SRC_SEL_SHIFT); + AHBCLK_WRITE(sc, val); + DEVICE_UNLOCK(sc); + break; + case AW_A83T_AHB1: + if (index < 0 || index > A83T_AHB1_CLK_SRC_SEL_MAX) + return (ERANGE); + DEVICE_LOCK(sc); + AHBCLK_READ(sc, &val); + val &= ~A83T_AHB1_CLK_SRC_SEL; + val |= (index << A83T_AHB1_CLK_SRC_SEL_SHIFT); + AHBCLK_WRITE(sc, val); + DEVICE_UNLOCK(sc); + break; + case AW_H3_AHB2: + if (index < 0 || index > H3_AHB2_CLK_CFG) + return (ERANGE); + DEVICE_LOCK(sc); + AHBCLK_READ(sc, &val); + val &= ~H3_AHB2_CLK_CFG; + val |= (index << H3_AHB2_CLK_CFG_SHIFT); AHBCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); break; default: return (ENXIO); } return (0); } static clknode_method_t aw_ahbclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_ahbclk_init), CLKNODEMETHOD(clknode_recalc_freq, aw_ahbclk_recalc_freq), CLKNODEMETHOD(clknode_set_mux, aw_ahbclk_set_mux), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_ahbclk_clknode, aw_ahbclk_clknode_class, aw_ahbclk_clknode_methods, sizeof(struct aw_ahbclk_sc), clknode_class); static int aw_ahbclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner AHB Clock"); return (BUS_PROBE_DEFAULT); } static int aw_ahbclk_attach(device_t dev) { struct clknode_init_def def; struct aw_ahbclk_sc *sc; struct clkdom *clkdom; struct clknode *clk; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; int error, ncells, i; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0) { device_printf(dev, "cannot get clock count\n"); return (error); } clkdom = clkdom_create(dev); memset(&def, 0, sizeof(def)); def.id = 1; def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", i); goto fail; } def.parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } def.parent_cnt = ncells; error = clk_parse_ofw_clk_name(dev, node, &def.name); if (error != 0) { device_printf(dev, "cannot parse clock name\n"); error = ENXIO; goto fail; } clk = clknode_create(clkdom, &aw_ahbclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } sc = clknode_get_softc(clk); sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; sc->reg = paddr; sc->clkdev = device_get_parent(dev); clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_ahbclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_ahbclk_probe), DEVMETHOD(device_attach, aw_ahbclk_attach), DEVMETHOD_END }; static driver_t aw_ahbclk_driver = { "aw_ahbclk", aw_ahbclk_methods, 0 }; static devclass_t aw_ahbclk_devclass; EARLY_DRIVER_MODULE(aw_ahbclk, simplebus, aw_ahbclk_driver, aw_ahbclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_apbclk.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_apbclk.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_apbclk.c (revision 299127) @@ -1,283 +1,307 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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$ */ /* * Allwinner APB clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" -#define APB0_CLK_RATIO (0x3 << 8) -#define APB0_CLK_RATIO_SHIFT 8 -#define APB1_CLK_SRC_SEL (0x3 << 24) -#define APB1_CLK_SRC_SEL_SHIFT 24 -#define APB1_CLK_SRC_SEL_MAX 0x3 -#define APB1_CLK_RAT_N (0x3 << 16) -#define APB1_CLK_RAT_N_SHIFT 16 -#define APB1_CLK_RAT_M (0x1f << 0) -#define APB1_CLK_RAT_M_SHIFT 0 +#define A10_APB0_CLK_RATIO (0x3 << 8) +#define A10_APB0_CLK_RATIO_SHIFT 8 +#define A10_APB1_CLK_SRC_SEL (0x3 << 24) +#define A10_APB1_CLK_SRC_SEL_SHIFT 24 +#define A10_APB1_CLK_SRC_SEL_MAX 0x3 +#define A10_APB1_CLK_RAT_N (0x3 << 16) +#define A10_APB1_CLK_RAT_N_SHIFT 16 +#define A10_APB1_CLK_RAT_M (0x1f << 0) +#define A10_APB1_CLK_RAT_M_SHIFT 0 +#define A23_APB0_CLK_RATIO (0x3 << 0) +#define A23_APB0_CLK_RATIO_SHIFT 0 +#define A83T_APB1_CLK_RATIO (0x3 << 8) +#define A83T_APB1_CLK_RATIO_SHIFT 8 enum aw_apbclk_type { AW_A10_APB0 = 1, AW_A10_APB1, + AW_A23_APB0, + AW_A83T_APB1, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-apb0-clk", AW_A10_APB0 }, { "allwinner,sun4i-a10-apb1-clk", AW_A10_APB1 }, + { "allwinner,sun8i-a23-apb0-clk", AW_A23_APB0 }, + { "allwinner,sun8i-a83t-apb1-clk", AW_A83T_APB1 }, { NULL, 0 } }; struct aw_apbclk_sc { device_t clkdev; bus_addr_t reg; enum aw_apbclk_type type; }; #define APBCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define APBCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_apbclk_init(struct clknode *clk, device_t dev) { struct aw_apbclk_sc *sc; uint32_t val, index; sc = clknode_get_softc(clk); switch (sc->type) { case AW_A10_APB0: + case AW_A23_APB0: + case AW_A83T_APB1: index = 0; break; case AW_A10_APB1: DEVICE_LOCK(sc); APBCLK_READ(sc, &val); DEVICE_UNLOCK(sc); - index = (val & APB1_CLK_SRC_SEL) >> APB1_CLK_SRC_SEL_SHIFT; + index = (val & A10_APB1_CLK_SRC_SEL) >> + A10_APB1_CLK_SRC_SEL_SHIFT; break; default: return (ENXIO); } clknode_init_parent_idx(clk, index); return (0); } static int aw_apbclk_recalc_freq(struct clknode *clk, uint64_t *freq) { struct aw_apbclk_sc *sc; uint32_t val, div, m, n; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); APBCLK_READ(sc, &val); DEVICE_UNLOCK(sc); switch (sc->type) { case AW_A10_APB0: - div = 1 << ((val & APB0_CLK_RATIO) >> APB0_CLK_RATIO_SHIFT); + div = 1 << ((val & A10_APB0_CLK_RATIO) >> + A10_APB0_CLK_RATIO_SHIFT); if (div == 1) div = 2; *freq = *freq / div; break; case AW_A10_APB1: - n = 1 << ((val & APB1_CLK_RAT_N) >> APB1_CLK_RAT_N_SHIFT); - m = ((val & APB1_CLK_RAT_N) >> APB1_CLK_RAT_M_SHIFT) + 1; + n = 1 << ((val & A10_APB1_CLK_RAT_N) >> + A10_APB1_CLK_RAT_N_SHIFT); + m = ((val & A10_APB1_CLK_RAT_N) >> + A10_APB1_CLK_RAT_M_SHIFT) + 1; *freq = *freq / n / m; break; + case AW_A23_APB0: + div = 1 << ((val & A23_APB0_CLK_RATIO) >> + A23_APB0_CLK_RATIO_SHIFT); + *freq = *freq / div; + break; + case AW_A83T_APB1: + div = ((val & A83T_APB1_CLK_RATIO) >> + A83T_APB1_CLK_RATIO_SHIFT) + 1; + *freq = *freq / div; + break; default: return (ENXIO); } return (0); } static int aw_apbclk_set_mux(struct clknode *clk, int index) { struct aw_apbclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); if (sc->type != AW_A10_APB1) return (ENXIO); - if (index < 0 || index > APB1_CLK_SRC_SEL_MAX) + if (index < 0 || index > A10_APB1_CLK_SRC_SEL_MAX) return (ERANGE); DEVICE_LOCK(sc); APBCLK_READ(sc, &val); - val &= ~APB1_CLK_SRC_SEL; - val |= (index << APB1_CLK_SRC_SEL_SHIFT); + val &= ~A10_APB1_CLK_SRC_SEL; + val |= (index << A10_APB1_CLK_SRC_SEL_SHIFT); APBCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static clknode_method_t aw_apbclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_apbclk_init), CLKNODEMETHOD(clknode_recalc_freq, aw_apbclk_recalc_freq), CLKNODEMETHOD(clknode_set_mux, aw_apbclk_set_mux), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_apbclk_clknode, aw_apbclk_clknode_class, aw_apbclk_clknode_methods, sizeof(struct aw_apbclk_sc), clknode_class); static int aw_apbclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner APB Clock"); return (BUS_PROBE_DEFAULT); } static int aw_apbclk_attach(device_t dev) { struct clknode_init_def def; struct aw_apbclk_sc *sc; struct clkdom *clkdom; struct clknode *clk; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; int error, ncells, i; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0) { device_printf(dev, "cannot get clock count\n"); return (error); } clkdom = clkdom_create(dev); memset(&def, 0, sizeof(def)); error = clk_parse_ofw_clk_name(dev, node, &def.name); if (error != 0) { device_printf(dev, "cannot parse clock name\n"); error = ENXIO; goto fail; } def.id = 1; def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", i); goto fail; } def.parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } def.parent_cnt = ncells; clk = clknode_create(clkdom, &aw_apbclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } sc = clknode_get_softc(clk); sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; sc->reg = paddr; sc->clkdev = device_get_parent(dev); clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_apbclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_apbclk_probe), DEVMETHOD(device_attach, aw_apbclk_attach), DEVMETHOD_END }; static driver_t aw_apbclk_driver = { "aw_apbclk", aw_apbclk_methods, 0 }; static devclass_t aw_apbclk_devclass; EARLY_DRIVER_MODULE(aw_apbclk, simplebus, aw_apbclk_driver, aw_apbclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_cpusclk.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_cpusclk.c (nonexistent) +++ user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_cpusclk.c (revision 299127) @@ -0,0 +1,320 @@ +/*- + * Copyright (c) 2016 Jared McNeill + * 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 ``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$ + */ + +/* + * Allwinner CPUS clock + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "clkdev_if.h" + +#define A80_CPUS_CLK_SRC_SEL (0x3 << 16) +#define A80_CPUS_CLK_SRC_SEL_SHIFT 16 +#define A80_CPUS_CLK_SRC_SEL_X32KI 0 +#define A80_CPUS_CLK_SRC_SEL_OSC24M 1 +#define A80_CPUS_CLK_SRC_SEL_PLL_PERIPH 2 +#define A80_CPUS_CLK_SRC_SEL_PLL_AUDIO 3 +#define A80_CPUS_POST_DIV (0x1f << 8) +#define A80_CPUS_POST_DIV_SHIFT 8 +#define A80_CPUS_CLK_RATIO (0x3 << 4) +#define A80_CPUS_CLK_RATIO_SHIFT 4 + +#define A83T_CPUS_CLK_SRC_SEL (0x3 << 16) +#define A83T_CPUS_CLK_SRC_SEL_SHIFT 16 +#define A83T_CPUS_CLK_SRC_SEL_X32KI 0 +#define A83T_CPUS_CLK_SRC_SEL_OSC24M 1 +#define A83T_CPUS_CLK_SRC_SEL_PLL_PERIPH 2 +#define A83T_CPUS_CLK_SRC_SEL_INTERNAL_OSC 3 +#define A83T_CPUS_POST_DIV (0x1f << 8) +#define A83T_CPUS_POST_DIV_SHIFT 8 +#define A83T_CPUS_CLK_RATIO (0x3 << 4) +#define A83T_CPUS_CLK_RATIO_SHIFT 4 + +enum aw_cpusclk_type { + AW_A80_CPUS = 1, + AW_A83T_CPUS, +}; + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun9i-a80-cpus-clk", AW_A80_CPUS }, + { "allwinner,sun8i-a83t-cpus-clk", AW_A83T_CPUS }, + { NULL, 0 } +}; + +struct aw_cpusclk_sc { + device_t clkdev; + bus_addr_t reg; + enum aw_cpusclk_type type; +}; + +#define CPUSCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) +#define CPUSCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) +#define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) +#define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) + +static int +aw_cpusclk_init(struct clknode *clk, device_t dev) +{ + struct aw_cpusclk_sc *sc; + uint32_t val, mask, shift, index; + + sc = clknode_get_softc(clk); + + switch (sc->type) { + case AW_A80_CPUS: + mask = A80_CPUS_CLK_SRC_SEL; + shift = A80_CPUS_CLK_SRC_SEL_SHIFT; + break; + case AW_A83T_CPUS: + mask = A83T_CPUS_CLK_SRC_SEL; + shift = A83T_CPUS_CLK_SRC_SEL_SHIFT; + break; + default: + return (ENXIO); + } + + DEVICE_LOCK(sc); + CPUSCLK_READ(sc, &val); + DEVICE_UNLOCK(sc); + index = (val & mask) >> shift; + + clknode_init_parent_idx(clk, index); + return (0); +} + +static int +aw_cpusclk_recalc_freq(struct clknode *clk, uint64_t *freq) +{ + struct aw_cpusclk_sc *sc; + uint32_t val, src_sel, post_div, clk_ratio; + + sc = clknode_get_softc(clk); + + DEVICE_LOCK(sc); + CPUSCLK_READ(sc, &val); + DEVICE_UNLOCK(sc); + + switch (sc->type) { + case AW_A80_CPUS: + src_sel = (val & A80_CPUS_CLK_SRC_SEL) >> + A80_CPUS_CLK_SRC_SEL_SHIFT; + post_div = ((val & A80_CPUS_POST_DIV) >> + A80_CPUS_POST_DIV_SHIFT) + 1; + clk_ratio = ((val & A80_CPUS_CLK_RATIO) >> + A80_CPUS_CLK_RATIO_SHIFT) + 1; + if (src_sel == A80_CPUS_CLK_SRC_SEL_PLL_PERIPH) + *freq = *freq / post_div / clk_ratio; + else + *freq = *freq / clk_ratio; + break; + case AW_A83T_CPUS: + src_sel = (val & A83T_CPUS_CLK_SRC_SEL) >> + A83T_CPUS_CLK_SRC_SEL_SHIFT; + post_div = ((val & A83T_CPUS_POST_DIV) >> + A83T_CPUS_POST_DIV_SHIFT) + 1; + clk_ratio = 1 << ((val & A83T_CPUS_CLK_RATIO) >> + A83T_CPUS_CLK_RATIO_SHIFT); + if (src_sel == A83T_CPUS_CLK_SRC_SEL_PLL_PERIPH) + *freq = *freq / post_div / clk_ratio; + else + *freq = *freq / clk_ratio; + break; + default: + return (EINVAL); + } + + return (0); +} + +static int +aw_cpusclk_set_mux(struct clknode *clk, int index) +{ + struct aw_cpusclk_sc *sc; + uint32_t mask, shift, val; + + sc = clknode_get_softc(clk); + + switch (sc->type) { + case AW_A80_CPUS: + mask = A80_CPUS_CLK_SRC_SEL; + shift = A80_CPUS_CLK_SRC_SEL_SHIFT; + break; + case AW_A83T_CPUS: + mask = A83T_CPUS_CLK_SRC_SEL; + shift = A83T_CPUS_CLK_SRC_SEL_SHIFT; + break; + default: + return (ENXIO); + } + + DEVICE_LOCK(sc); + CPUSCLK_READ(sc, &val); + val &= ~mask; + val |= (index << shift); + CPUSCLK_WRITE(sc, val); + DEVICE_UNLOCK(sc); + + return (0); +} + +static clknode_method_t aw_cpusclk_clknode_methods[] = { + /* Device interface */ + CLKNODEMETHOD(clknode_init, aw_cpusclk_init), + CLKNODEMETHOD(clknode_recalc_freq, aw_cpusclk_recalc_freq), + CLKNODEMETHOD(clknode_set_mux, aw_cpusclk_set_mux), + CLKNODEMETHOD_END +}; +DEFINE_CLASS_1(aw_cpusclk_clknode, aw_cpusclk_clknode_class, + aw_cpusclk_clknode_methods, sizeof(struct aw_cpusclk_sc), clknode_class); + +static int +aw_cpusclk_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Allwinner CPUS Clock"); + return (BUS_PROBE_DEFAULT); +} + +static int +aw_cpusclk_attach(device_t dev) +{ + struct clknode_init_def def; + struct aw_cpusclk_sc *sc; + struct clkdom *clkdom; + struct clknode *clk; + clk_t clk_parent; + bus_addr_t paddr; + bus_size_t psize; + phandle_t node; + int error, ncells, i; + + node = ofw_bus_get_node(dev); + + if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { + device_printf(dev, "cannot parse 'reg' property\n"); + return (ENXIO); + } + + error = ofw_bus_parse_xref_list_get_length(node, "clocks", + "#clock-cells", &ncells); + if (error != 0) { + device_printf(dev, "cannot get clock count\n"); + return (error); + } + + clkdom = clkdom_create(dev); + + memset(&def, 0, sizeof(def)); + def.id = 1; + def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, + M_WAITOK); + for (i = 0; i < ncells; i++) { + error = clk_get_by_ofw_index(dev, i, &clk_parent); + if (error != 0) { + device_printf(dev, "cannot get clock %d\n", i); + goto fail; + } + def.parent_names[i] = clk_get_name(clk_parent); + clk_release(clk_parent); + } + def.parent_cnt = ncells; + + error = clk_parse_ofw_clk_name(dev, node, &def.name); + if (error != 0) { + device_printf(dev, "cannot parse clock name\n"); + error = ENXIO; + goto fail; + } + + clk = clknode_create(clkdom, &aw_cpusclk_clknode_class, &def); + if (clk == NULL) { + device_printf(dev, "cannot create clknode\n"); + error = ENXIO; + goto fail; + } + sc = clknode_get_softc(clk); + sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; + sc->reg = paddr; + sc->clkdev = device_get_parent(dev); + + clknode_register(clkdom, clk); + + if (clkdom_finit(clkdom) != 0) { + device_printf(dev, "cannot finalize clkdom initialization\n"); + error = ENXIO; + goto fail; + } + + if (bootverbose) + clkdom_dump(clkdom); + + return (0); + +fail: + return (error); +} + +static device_method_t aw_cpusclk_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aw_cpusclk_probe), + DEVMETHOD(device_attach, aw_cpusclk_attach), + + DEVMETHOD_END +}; + +static driver_t aw_cpusclk_driver = { + "aw_cpusclk", + aw_cpusclk_methods, + 0 +}; + +static devclass_t aw_cpusclk_devclass; + +EARLY_DRIVER_MODULE(aw_cpusclk, simplebus, aw_cpusclk_driver, + aw_cpusclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Property changes on: user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_cpusclk.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_gate.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_gate.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_gate.c (revision 299127) @@ -1,198 +1,206 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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$ */ /* * Allwinner clock gates */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #define GATE_OFFSET(index) ((index / 32) * 4) #define GATE_SHIFT(index) (index % 32) static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-dram-gates-clk", (uintptr_t)"Allwinner DRAM Clock Gates" }, { "allwinner,sun4i-a10-ahb-gates-clk", (uintptr_t)"Allwinner AHB Clock Gates" }, { "allwinner,sun4i-a10-apb0-gates-clk", (uintptr_t)"Allwinner APB0 Clock Gates" }, { "allwinner,sun4i-a10-apb1-gates-clk", (uintptr_t)"Allwinner APB1 Clock Gates" }, { "allwinner,sun7i-a20-ahb-gates-clk", (uintptr_t)"Allwinner AHB Clock Gates" }, { "allwinner,sun7i-a20-apb0-gates-clk", (uintptr_t)"Allwinner APB0 Clock Gates" }, { "allwinner,sun7i-a20-apb1-gates-clk", (uintptr_t)"Allwinner APB1 Clock Gates" }, { "allwinner,sun6i-a31-ahb1-gates-clk", (uintptr_t)"Allwinner AHB1 Clock Gates" }, { "allwinner,sun6i-a31-apb0-gates-clk", (uintptr_t)"Allwinner APB0 Clock Gates" }, { "allwinner,sun6i-a31-apb1-gates-clk", (uintptr_t)"Allwinner APB1 Clock Gates" }, { "allwinner,sun6i-a31-apb2-gates-clk", (uintptr_t)"Allwinner APB2 Clock Gates" }, + { "allwinner,sun8i-a83t-bus-gates-clk", + (uintptr_t)"Allwinner Bus Clock Gates" }, + { "allwinner,sun8i-a83t-apb0-gates-clk", + (uintptr_t)"Allwinner APB0 Clock Gates" }, + + { "allwinner,sun9i-a80-apbs-gates-clk", + (uintptr_t)"Allwinner APBS Clock Gates" }, + { NULL, 0 } }; static int aw_gate_create(device_t dev, bus_addr_t paddr, struct clkdom *clkdom, const char *pclkname, const char *clkname, int index) { const char *parent_names[1] = { pclkname }; struct clk_gate_def def; memset(&def, 0, sizeof(def)); def.clkdef.id = index; def.clkdef.name = clkname; def.clkdef.parent_names = parent_names; def.clkdef.parent_cnt = 1; def.offset = paddr + GATE_OFFSET(index); def.shift = GATE_SHIFT(index); def.mask = 1; def.on_value = 1; def.off_value = 0; return (clknode_gate_register(clkdom, &def)); } static int aw_gate_probe(device_t dev) { const char *d; if (!ofw_bus_status_okay(dev)) return (ENXIO); d = (const char *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (d == NULL) return (ENXIO); device_set_desc(dev, d); return (BUS_PROBE_DEFAULT); } static int aw_gate_attach(device_t dev) { struct clkdom *clkdom; const char **names; int index, nout, error; uint32_t *indices; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; node = ofw_bus_get_node(dev); indices = NULL; if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } clkdom = clkdom_create(dev); nout = clk_parse_ofw_out_names(dev, node, &names, &indices); if (nout == 0) { device_printf(dev, "no clock outputs found\n"); error = ENOENT; goto fail; } if (indices == NULL) { device_printf(dev, "no clock-indices property\n"); error = ENXIO; goto fail; } error = clk_get_by_ofw_index(dev, 0, &clk_parent); if (error != 0) { device_printf(dev, "cannot parse clock parent\n"); return (ENXIO); } for (index = 0; index < nout; index++) { error = aw_gate_create(dev, paddr, clkdom, clk_get_name(clk_parent), names[index], indices[index]); if (error) goto fail; } if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_gate_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_gate_probe), DEVMETHOD(device_attach, aw_gate_attach), DEVMETHOD_END }; static driver_t aw_gate_driver = { "aw_gate", aw_gate_methods, 0 }; static devclass_t aw_gate_devclass; EARLY_DRIVER_MODULE(aw_gate, simplebus, aw_gate_driver, aw_gate_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_gmacclk.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_gmacclk.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_gmacclk.c (revision 299127) @@ -1,259 +1,302 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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$ */ /* * Allwinner GMAC clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define GMAC_CLK_PIT (0x1 << 2) #define GMAC_CLK_PIT_SHIFT 2 #define GMAC_CLK_PIT_MII 0 #define GMAC_CLK_PIT_RGMII 1 #define GMAC_CLK_SRC (0x3 << 0) #define GMAC_CLK_SRC_SHIFT 0 #define GMAC_CLK_SRC_MII 0 #define GMAC_CLK_SRC_EXT_RGMII 1 #define GMAC_CLK_SRC_RGMII 2 +#define EMAC_TXC_DIV_CFG (1 << 15) +#define EMAC_TXC_DIV_CFG_SHIFT 15 +#define EMAC_TXC_DIV_CFG_125MHZ 0 +#define EMAC_TXC_DIV_CFG_25MHZ 1 +#define EMAC_PHY_SELECT (1 << 16) +#define EMAC_PHY_SELECT_SHIFT 16 +#define EMAC_PHY_SELECT_INT 0 +#define EMAC_PHY_SELECT_EXT 1 +#define EMAC_ETXDC (0x7 << 10) +#define EMAC_ETXDC_SHIFT 10 +#define EMAC_ERXDC (0x1f << 5) +#define EMAC_ERXDC_SHIFT 5 + #define CLK_IDX_MII 0 #define CLK_IDX_RGMII 1 #define CLK_IDX_COUNT 2 +enum aw_gmacclk_type { + GMACCLK_A20 = 1, + GMACCLK_A83T, +}; + static struct ofw_compat_data compat_data[] = { - { "allwinner,sun7i-a20-gmac-clk", 1 }, + { "allwinner,sun7i-a20-gmac-clk", GMACCLK_A20 }, + { "allwinner,sun8i-a83t-emac-clk", GMACCLK_A83T }, { NULL, 0 } }; struct aw_gmacclk_sc { device_t clkdev; bus_addr_t reg; + enum aw_gmacclk_type type; + + int rx_delay; + int tx_delay; }; #define GMACCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define GMACCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_gmacclk_init(struct clknode *clk, device_t dev) { struct aw_gmacclk_sc *sc; uint32_t val, index; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); GMACCLK_READ(sc, &val); DEVICE_UNLOCK(sc); switch ((val & GMAC_CLK_SRC) >> GMAC_CLK_SRC_SHIFT) { case GMAC_CLK_SRC_MII: index = CLK_IDX_MII; break; case GMAC_CLK_SRC_RGMII: index = CLK_IDX_RGMII; break; default: return (ENXIO); } clknode_init_parent_idx(clk, index); return (0); } static int aw_gmacclk_set_mux(struct clknode *clk, int index) { struct aw_gmacclk_sc *sc; - uint32_t val, clk_src, pit; + uint32_t val, clk_src, pit, txc_div; int error; sc = clknode_get_softc(clk); error = 0; switch (index) { case CLK_IDX_MII: clk_src = GMAC_CLK_SRC_MII; pit = GMAC_CLK_PIT_MII; + txc_div = EMAC_TXC_DIV_CFG_25MHZ; break; case CLK_IDX_RGMII: clk_src = GMAC_CLK_SRC_RGMII; pit = GMAC_CLK_PIT_RGMII; + txc_div = EMAC_TXC_DIV_CFG_125MHZ; break; default: return (ENXIO); } DEVICE_LOCK(sc); GMACCLK_READ(sc, &val); val &= ~(GMAC_CLK_SRC | GMAC_CLK_PIT); val |= (clk_src << GMAC_CLK_SRC_SHIFT); val |= (pit << GMAC_CLK_PIT_SHIFT); + if (sc->type == GMACCLK_A83T) { + val &= ~EMAC_TXC_DIV_CFG; + val |= (txc_div << EMAC_TXC_DIV_CFG_SHIFT); + val &= ~EMAC_PHY_SELECT; + val |= (EMAC_PHY_SELECT_EXT << EMAC_PHY_SELECT_SHIFT); + if (sc->tx_delay >= 0) { + val &= ~EMAC_ETXDC; + val |= (sc->tx_delay << EMAC_ETXDC_SHIFT); + } + if (sc->rx_delay >= 0) { + val &= ~EMAC_ERXDC; + val |= (sc->rx_delay << EMAC_ERXDC_SHIFT); + } + } GMACCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static clknode_method_t aw_gmacclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_gmacclk_init), CLKNODEMETHOD(clknode_set_mux, aw_gmacclk_set_mux), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_gmacclk_clknode, aw_gmacclk_clknode_class, aw_gmacclk_clknode_methods, sizeof(struct aw_gmacclk_sc), clknode_class); static int aw_gmacclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); - device_set_desc(dev, "Allwinner Module Clock"); + device_set_desc(dev, "Allwinner GMAC Clock"); return (BUS_PROBE_DEFAULT); } static int aw_gmacclk_attach(device_t dev) { struct clknode_init_def def; struct aw_gmacclk_sc *sc; struct clkdom *clkdom; struct clknode *clk; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; int error, ncells, i; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0 || ncells != CLK_IDX_COUNT) { device_printf(dev, "couldn't find parent clocks\n"); return (ENXIO); } clkdom = clkdom_create(dev); memset(&def, 0, sizeof(def)); error = clk_parse_ofw_clk_name(dev, node, &def.name); if (error != 0) { device_printf(dev, "cannot parse clock name\n"); error = ENXIO; goto fail; } def.id = 1; def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", error); goto fail; } def.parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } def.parent_cnt = ncells; clk = clknode_create(clkdom, &aw_gmacclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } sc = clknode_get_softc(clk); sc->reg = paddr; sc->clkdev = device_get_parent(dev); + sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; + sc->tx_delay = sc->rx_delay = -1; + OF_getencprop(node, "tx-delay", &sc->tx_delay, sizeof(sc->tx_delay)); + OF_getencprop(node, "rx-delay", &sc->rx_delay, sizeof(sc->rx_delay)); clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_gmacclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_gmacclk_probe), DEVMETHOD(device_attach, aw_gmacclk_attach), DEVMETHOD_END }; static driver_t aw_gmacclk_driver = { "aw_gmacclk", aw_gmacclk_methods, 0 }; static devclass_t aw_gmacclk_devclass; EARLY_DRIVER_MODULE(aw_gmacclk, simplebus, aw_gmacclk_driver, aw_gmacclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_pll.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_pll.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_pll.c (revision 299127) @@ -1,757 +1,784 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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$ */ /* * Allwinner PLL clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define AW_PLL_ENABLE (1 << 31) #define A10_PLL1_OUT_EXT_DIVP (0x3 << 16) #define A10_PLL1_OUT_EXT_DIVP_SHIFT 16 #define A10_PLL1_FACTOR_N (0x1f << 8) #define A10_PLL1_FACTOR_N_SHIFT 8 #define A10_PLL1_FACTOR_K (0x3 << 4) #define A10_PLL1_FACTOR_K_SHIFT 4 #define A10_PLL1_FACTOR_M (0x3 << 0) #define A10_PLL1_FACTOR_M_SHIFT 0 #define A10_PLL2_POST_DIV (0xf << 26) #define A10_PLL2_POST_DIV_SHIFT 26 #define A10_PLL2_FACTOR_N (0x7f << 8) #define A10_PLL2_FACTOR_N_SHIFT 8 #define A10_PLL2_PRE_DIV (0x1f << 0) #define A10_PLL2_PRE_DIV_SHIFT 0 #define A10_PLL3_MODE_SEL (0x1 << 15) #define A10_PLL3_MODE_SEL_FRACT (0 << 15) #define A10_PLL3_MODE_SEL_INT (1 << 15) #define A10_PLL3_FUNC_SET (0x1 << 14) #define A10_PLL3_FUNC_SET_270MHZ (0 << 14) #define A10_PLL3_FUNC_SET_297MHZ (1 << 14) #define A10_PLL3_FACTOR_M (0x7f << 0) #define A10_PLL3_FACTOR_M_SHIFT 0 #define A10_PLL3_REF_FREQ 3000000 #define A10_PLL5_OUT_EXT_DIVP (0x3 << 16) #define A10_PLL5_OUT_EXT_DIVP_SHIFT 16 #define A10_PLL5_FACTOR_N (0x1f << 8) #define A10_PLL5_FACTOR_N_SHIFT 8 #define A10_PLL5_FACTOR_K (0x3 << 4) #define A10_PLL5_FACTOR_K_SHIFT 4 #define A10_PLL5_FACTOR_M1 (0x3 << 2) #define A10_PLL5_FACTOR_M1_SHIFT 2 #define A10_PLL5_FACTOR_M (0x3 << 0) #define A10_PLL5_FACTOR_M_SHIFT 0 #define A10_PLL6_BYPASS_EN (1 << 30) #define A10_PLL6_SATA_CLK_EN (1 << 14) #define A10_PLL6_FACTOR_N (0x1f << 8) #define A10_PLL6_FACTOR_N_SHIFT 8 #define A10_PLL6_FACTOR_K (0x3 << 4) #define A10_PLL6_FACTOR_K_SHIFT 4 #define A10_PLL6_FACTOR_M (0x3 << 0) #define A10_PLL6_FACTOR_M_SHIFT 0 #define A10_PLL2_POST_DIV (0xf << 26) #define A31_PLL1_LOCK (1 << 28) #define A31_PLL1_CPU_SIGMA_DELTA_EN (1 << 24) #define A31_PLL1_FACTOR_N (0x1f << 8) #define A31_PLL1_FACTOR_N_SHIFT 8 #define A31_PLL1_FACTOR_K (0x3 << 4) #define A31_PLL1_FACTOR_K_SHIFT 4 #define A31_PLL1_FACTOR_M (0x3 << 0) #define A31_PLL1_FACTOR_M_SHIFT 0 #define A31_PLL6_LOCK (1 << 28) #define A31_PLL6_BYPASS_EN (1 << 25) #define A31_PLL6_CLK_OUT_EN (1 << 24) #define A31_PLL6_24M_OUT_EN (1 << 18) #define A31_PLL6_24M_POST_DIV (0x3 << 16) #define A31_PLL6_24M_POST_DIV_SHIFT 16 #define A31_PLL6_FACTOR_N (0x1f << 8) #define A31_PLL6_FACTOR_N_SHIFT 8 #define A31_PLL6_FACTOR_K (0x3 << 4) #define A31_PLL6_FACTOR_K_SHIFT 4 #define A31_PLL6_DEFAULT_N 0x18 #define A31_PLL6_DEFAULT_K 0x1 #define A31_PLL6_TIMEOUT 10 +#define A80_PLL4_CLK_OUT_EN (1 << 20) +#define A80_PLL4_PLL_DIV2 (1 << 18) +#define A80_PLL4_PLL_DIV1 (1 << 16) +#define A80_PLL4_FACTOR_N (0xff << 8) +#define A80_PLL4_FACTOR_N_SHIFT 8 + #define CLKID_A10_PLL3_1X 0 #define CLKID_A10_PLL3_2X 1 #define CLKID_A10_PLL5_DDR 0 #define CLKID_A10_PLL5_OTHER 1 #define CLKID_A10_PLL6_SATA 0 #define CLKID_A10_PLL6_OTHER 1 #define CLKID_A10_PLL6 2 #define CLKID_A10_PLL6_DIV_4 3 #define CLKID_A31_PLL6 0 #define CLKID_A31_PLL6_X2 1 enum aw_pll_type { AWPLL_A10_PLL1 = 1, AWPLL_A10_PLL2, AWPLL_A10_PLL3, AWPLL_A10_PLL5, AWPLL_A10_PLL6, AWPLL_A31_PLL1, AWPLL_A31_PLL6, + AWPLL_A80_PLL4, }; struct aw_pll_sc { enum aw_pll_type type; device_t clkdev; bus_addr_t reg; int id; }; struct aw_pll_funcs { int (*recalc)(struct aw_pll_sc *, uint64_t *); int (*set_freq)(struct aw_pll_sc *, uint64_t, uint64_t *, int); int (*init)(device_t, bus_addr_t, struct clknode_init_def *); }; #define PLL_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define PLL_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int a10_pll1_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, n, k, p; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); p = 1 << ((val & A10_PLL1_OUT_EXT_DIVP) >> A10_PLL1_OUT_EXT_DIVP_SHIFT); m = ((val & A10_PLL1_FACTOR_M) >> A10_PLL1_FACTOR_M_SHIFT) + 1; k = ((val & A10_PLL1_FACTOR_K) >> A10_PLL1_FACTOR_K_SHIFT) + 1; n = (val & A10_PLL1_FACTOR_N) >> A10_PLL1_FACTOR_N_SHIFT; if (n == 0) n = 1; *freq = (*freq * n * k) / (m * p); return (0); } static int a10_pll2_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, post_div, n, pre_div; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); post_div = (val & A10_PLL2_POST_DIV) >> A10_PLL2_POST_DIV_SHIFT; if (post_div == 0) post_div = 1; n = (val & A10_PLL2_FACTOR_N) >> A10_PLL2_FACTOR_N_SHIFT; if (n == 0) n = 1; pre_div = (val & A10_PLL2_PRE_DIV) >> A10_PLL2_PRE_DIV_SHIFT; if (pre_div == 0) pre_div = 1; switch (sc->id) { case SUN4I_A10_PLL2_1X: *freq = (*freq * 2 * n) / pre_div / post_div / 2; break; case SUN4I_A10_PLL2_2X: *freq = (*freq * 2 * n) / pre_div / 4; break; case SUN4I_A10_PLL2_4X: *freq = (*freq * 2 * n) / pre_div / 2; break; case SUN4I_A10_PLL2_8X: *freq = (*freq * 2 * n) / pre_div; break; default: return (EINVAL); } return (0); } static int a10_pll2_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t val, post_div, n, pre_div; if (sc->id != SUN4I_A10_PLL2_1X) return (ENXIO); /* * Audio Codec needs PLL2-1X to be either 24576000 or 22579200. * * PLL2-1X output frequency is (48MHz * n) / pre_div / post_div / 2. * To get as close as possible to the desired rate, we use a * pre-divider of 21 and a post-divider of 4. With these values, * a multiplier of 86 or 79 gets us close to the target rates. */ if (*fout != 24576000 && *fout != 22579200) return (EINVAL); pre_div = 21; post_div = 4; n = (*fout * pre_div * post_div * 2) / (2 * fin); DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~(A10_PLL2_POST_DIV | A10_PLL2_FACTOR_N | A10_PLL2_PRE_DIV); val |= (post_div << A10_PLL2_POST_DIV_SHIFT); val |= (n << A10_PLL2_FACTOR_N_SHIFT); val |= (pre_div << A10_PLL2_PRE_DIV_SHIFT); PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int a10_pll3_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); if ((val & A10_PLL3_MODE_SEL) == A10_PLL3_MODE_SEL_INT) { /* In integer mode, output is 3MHz * m */ m = (val & A10_PLL3_FACTOR_M) >> A10_PLL3_FACTOR_M_SHIFT; *freq = A10_PLL3_REF_FREQ * m; } else { /* In fractional mode, output is either 270MHz or 297MHz */ if ((val & A10_PLL3_FUNC_SET) == A10_PLL3_FUNC_SET_270MHZ) *freq = 270000000; else *freq = 297000000; } if (sc->id == CLKID_A10_PLL3_2X) *freq *= 2; return (0); } static int a10_pll3_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t val, m, mode, func; m = *fout / A10_PLL3_REF_FREQ; if (sc->id == CLKID_A10_PLL3_2X) m /= 2; mode = A10_PLL3_MODE_SEL_INT; func = 0; *fout = m * A10_PLL3_REF_FREQ; if (sc->id == CLKID_A10_PLL3_2X) *fout *= 2; DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~(A10_PLL3_MODE_SEL | A10_PLL3_FUNC_SET | A10_PLL3_FACTOR_M); val |= mode; val |= func; val |= (m << A10_PLL3_FACTOR_M_SHIFT); PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int a10_pll3_init(device_t dev, bus_addr_t reg, struct clknode_init_def *def) { uint32_t val; /* Allow changing PLL frequency while enabled */ def->flags = CLK_NODE_GLITCH_FREE; /* Set PLL to 297MHz */ CLKDEV_DEVICE_LOCK(dev); CLKDEV_READ_4(dev, reg, &val); val &= ~(A10_PLL3_MODE_SEL | A10_PLL3_FUNC_SET | A10_PLL3_FACTOR_M); val |= A10_PLL3_MODE_SEL_FRACT; val |= A10_PLL3_FUNC_SET_297MHZ; CLKDEV_WRITE_4(dev, reg, val); CLKDEV_DEVICE_UNLOCK(dev); return (0); } static int a10_pll5_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, n, k, p; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); p = 1 << ((val & A10_PLL5_OUT_EXT_DIVP) >> A10_PLL5_OUT_EXT_DIVP_SHIFT); m = ((val & A10_PLL5_FACTOR_M) >> A10_PLL5_FACTOR_M_SHIFT) + 1; k = ((val & A10_PLL5_FACTOR_K) >> A10_PLL5_FACTOR_K_SHIFT) + 1; n = (val & A10_PLL5_FACTOR_N) >> A10_PLL5_FACTOR_N_SHIFT; if (n == 0) return (ENXIO); switch (sc->id) { case CLKID_A10_PLL5_DDR: *freq = (*freq * n * k) / m; break; case CLKID_A10_PLL5_OTHER: *freq = (*freq * n * k) / p; break; default: return (ENXIO); } return (0); } static int a10_pll6_init(device_t dev, bus_addr_t reg, struct clknode_init_def *def) { uint32_t val, m, n, k; /* * SATA needs PLL6 to be a 100MHz clock. * * The SATA output frequency is (24MHz * n * k) / m / 6. * To get to 100MHz, k & m must be equal and n must be 25. */ m = k = 0; n = 25; CLKDEV_DEVICE_LOCK(dev); CLKDEV_READ_4(dev, reg, &val); val &= ~(A10_PLL6_FACTOR_N | A10_PLL6_FACTOR_K | A10_PLL6_FACTOR_M); val &= ~A10_PLL6_BYPASS_EN; val |= A10_PLL6_SATA_CLK_EN; val |= (n << A10_PLL6_FACTOR_N_SHIFT); val |= (k << A10_PLL6_FACTOR_K_SHIFT); val |= (m << A10_PLL6_FACTOR_M_SHIFT); CLKDEV_WRITE_4(dev, reg, val); CLKDEV_DEVICE_UNLOCK(dev); return (0); } static int a10_pll6_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, k, n; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); m = ((val & A10_PLL6_FACTOR_M) >> A10_PLL6_FACTOR_M_SHIFT) + 1; k = ((val & A10_PLL6_FACTOR_K) >> A10_PLL6_FACTOR_K_SHIFT) + 1; n = (val & A10_PLL6_FACTOR_N) >> A10_PLL6_FACTOR_N_SHIFT; if (n == 0) return (ENXIO); switch (sc->id) { case CLKID_A10_PLL6_SATA: *freq = (*freq * n * k) / m / 6; break; case CLKID_A10_PLL6_OTHER: *freq = (*freq * n * k) / 2; break; case CLKID_A10_PLL6: *freq = (*freq * n * k); break; case CLKID_A10_PLL6_DIV_4: *freq = (*freq * n * k) / 4; break; default: return (ENXIO); } return (0); } static int a10_pll6_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { if (sc->id != CLKID_A10_PLL6_SATA) return (ENXIO); /* PLL6 SATA output has been set to 100MHz in a10_pll6_init */ if (*fout != 100000000) return (ERANGE); return (0); } static int a31_pll1_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, n, k; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); m = ((val & A31_PLL1_FACTOR_M) >> A31_PLL1_FACTOR_M_SHIFT) + 1; k = ((val & A31_PLL1_FACTOR_K) >> A31_PLL1_FACTOR_K_SHIFT) + 1; n = ((val & A31_PLL1_FACTOR_N) >> A31_PLL1_FACTOR_N_SHIFT) + 1; *freq = (*freq * n * k) / m; return (0); } static int a31_pll6_init(device_t dev, bus_addr_t reg, struct clknode_init_def *def) { uint32_t val; int retry; if (def->id != CLKID_A31_PLL6) return (0); /* * The datasheet recommends that PLL6 output should be fixed to * 600MHz. */ CLKDEV_DEVICE_LOCK(dev); CLKDEV_READ_4(dev, reg, &val); val &= ~(A31_PLL6_FACTOR_N | A31_PLL6_FACTOR_K | A31_PLL6_BYPASS_EN); val |= (A31_PLL6_DEFAULT_N << A31_PLL6_FACTOR_N_SHIFT); val |= (A31_PLL6_DEFAULT_K << A31_PLL6_FACTOR_K_SHIFT); CLKDEV_WRITE_4(dev, reg, val); /* Wait for PLL to become stable */ for (retry = A31_PLL6_TIMEOUT; retry > 0; retry--) { CLKDEV_READ_4(dev, reg, &val); if ((val & A31_PLL6_LOCK) == A31_PLL6_LOCK) break; DELAY(1); } CLKDEV_DEVICE_UNLOCK(dev); if (retry == 0) return (ETIMEDOUT); return (0); } static int a31_pll6_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, k, n; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); k = ((val & A10_PLL6_FACTOR_K) >> A10_PLL6_FACTOR_K_SHIFT) + 1; n = ((val & A10_PLL6_FACTOR_N) >> A10_PLL6_FACTOR_N_SHIFT) + 1; switch (sc->id) { case CLKID_A31_PLL6: *freq = (*freq * n * k) / 2; break; case CLKID_A31_PLL6_X2: *freq = *freq * n * k; break; default: return (ENXIO); } return (0); } +static int +a80_pll4_recalc(struct aw_pll_sc *sc, uint64_t *freq) +{ + uint32_t val, n, div1, div2; + + DEVICE_LOCK(sc); + PLL_READ(sc, &val); + DEVICE_UNLOCK(sc); + + n = (val & A80_PLL4_FACTOR_N) >> A80_PLL4_FACTOR_N_SHIFT; + div1 = (val & A80_PLL4_PLL_DIV1) == 0 ? 1 : 2; + div2 = (val & A80_PLL4_PLL_DIV2) == 0 ? 1 : 2; + + *freq = (*freq * n) / div1 / div2; + + return (0); +} + #define PLL(_type, _recalc, _set_freq, _init) \ [(_type)] = { \ .recalc = (_recalc), \ .set_freq = (_set_freq), \ .init = (_init) \ } static struct aw_pll_funcs aw_pll_func[] = { PLL(AWPLL_A10_PLL1, a10_pll1_recalc, NULL, NULL), PLL(AWPLL_A10_PLL2, a10_pll2_recalc, a10_pll2_set_freq, NULL), PLL(AWPLL_A10_PLL3, a10_pll3_recalc, a10_pll3_set_freq, a10_pll3_init), PLL(AWPLL_A10_PLL5, a10_pll5_recalc, NULL, NULL), PLL(AWPLL_A10_PLL6, a10_pll6_recalc, a10_pll6_set_freq, a10_pll6_init), PLL(AWPLL_A31_PLL1, a31_pll1_recalc, NULL, NULL), PLL(AWPLL_A31_PLL6, a31_pll6_recalc, NULL, a31_pll6_init), + PLL(AWPLL_A80_PLL4, a80_pll4_recalc, NULL, NULL), }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-pll1-clk", AWPLL_A10_PLL1 }, { "allwinner,sun4i-a10-pll2-clk", AWPLL_A10_PLL2 }, { "allwinner,sun4i-a10-pll3-clk", AWPLL_A10_PLL3 }, { "allwinner,sun4i-a10-pll5-clk", AWPLL_A10_PLL5 }, { "allwinner,sun4i-a10-pll6-clk", AWPLL_A10_PLL6 }, { "allwinner,sun6i-a31-pll1-clk", AWPLL_A31_PLL1 }, { "allwinner,sun6i-a31-pll6-clk", AWPLL_A31_PLL6 }, + { "allwinner,sun9i-a80-pll4-clk", AWPLL_A80_PLL4 }, { NULL, 0 } }; static int aw_pll_init(struct clknode *clk, device_t dev) { clknode_init_parent_idx(clk, 0); return (0); } static int aw_pll_set_gate(struct clknode *clk, bool enable) { struct aw_pll_sc *sc; uint32_t val; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); PLL_READ(sc, &val); if (enable) val |= AW_PLL_ENABLE; else val &= ~AW_PLL_ENABLE; PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_pll_recalc(struct clknode *clk, uint64_t *freq) { struct aw_pll_sc *sc; sc = clknode_get_softc(clk); if (aw_pll_func[sc->type].recalc == NULL) return (ENXIO); return (aw_pll_func[sc->type].recalc(sc, freq)); } static int aw_pll_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop) { struct aw_pll_sc *sc; sc = clknode_get_softc(clk); *stop = 1; if (aw_pll_func[sc->type].set_freq == NULL) return (ENXIO); return (aw_pll_func[sc->type].set_freq(sc, fin, fout, flags)); } static clknode_method_t aw_pll_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_pll_init), CLKNODEMETHOD(clknode_set_gate, aw_pll_set_gate), CLKNODEMETHOD(clknode_recalc_freq, aw_pll_recalc), CLKNODEMETHOD(clknode_set_freq, aw_pll_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_pll_clknode, aw_pll_clknode_class, aw_pll_clknode_methods, sizeof(struct aw_pll_sc), clknode_class); static int aw_pll_create(device_t dev, bus_addr_t paddr, struct clkdom *clkdom, const char *pclkname, const char *clkname, int index) { enum aw_pll_type type; struct clknode_init_def clkdef; struct aw_pll_sc *sc; struct clknode *clk; int error; type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; memset(&clkdef, 0, sizeof(clkdef)); clkdef.id = index; clkdef.name = clkname; if (pclkname != NULL) { clkdef.parent_names = malloc(sizeof(char *), M_OFWPROP, M_WAITOK); clkdef.parent_names[0] = pclkname; clkdef.parent_cnt = 1; } else clkdef.parent_cnt = 0; if (aw_pll_func[type].init != NULL) { error = aw_pll_func[type].init(device_get_parent(dev), paddr, &clkdef); if (error != 0) { device_printf(dev, "clock %s init failed\n", clkname); return (error); } } clk = clknode_create(clkdom, &aw_pll_clknode_class, &clkdef); if (clk == NULL) { device_printf(dev, "cannot create clock node\n"); return (ENXIO); } sc = clknode_get_softc(clk); sc->clkdev = device_get_parent(dev); sc->reg = paddr; sc->type = type; sc->id = clkdef.id; clknode_register(clkdom, clk); free(__DECONST(char *, clkdef.parent_names), M_OFWPROP); return (0); } static int aw_pll_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner PLL Clock"); return (BUS_PROBE_DEFAULT); } static int aw_pll_attach(device_t dev) { struct clkdom *clkdom; const char **names; int index, nout, error; clk_t clk_parent; uint32_t *indices; bus_addr_t paddr; bus_size_t psize; phandle_t node; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "couldn't parse 'reg' property\n"); return (ENXIO); } clkdom = clkdom_create(dev); nout = clk_parse_ofw_out_names(dev, node, &names, &indices); if (nout == 0) { device_printf(dev, "no clock outputs found\n"); error = ENOENT; goto fail; } if (clk_get_by_ofw_index(dev, 0, &clk_parent) != 0) clk_parent = NULL; for (index = 0; index < nout; index++) { error = aw_pll_create(dev, paddr, clkdom, clk_parent ? clk_get_name(clk_parent) : NULL, names[index], nout == 1 ? 1 : index); if (error) goto fail; } if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_pll_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_pll_probe), DEVMETHOD(device_attach, aw_pll_attach), DEVMETHOD_END }; static driver_t aw_pll_driver = { "aw_pll", aw_pll_methods, 0, }; static devclass_t aw_pll_devclass; EARLY_DRIVER_MODULE(aw_pll, simplebus, aw_pll_driver, aw_pll_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_usbclk.c =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_usbclk.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/clk/aw_usbclk.c (revision 299127) @@ -1,246 +1,259 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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$ */ /* * Allwinner USB clocks */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #include "hwreset_if.h" #define A10_SCLK_GATING_USBPHY (1 << 8) #define A10_SCLK_GATING_OHCI1 (1 << 7) #define A10_SCLK_GATING_OHCI0 (1 << 6) #define USBPHY2_RST (1 << 2) #define USBPHY1_RST (1 << 1) #define USBPHY0_RST (1 << 0) enum aw_usbclk_type { AW_A10_USBCLK = 1, AW_A31_USBCLK, + AW_A83T_USBCLK, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-usb-clk", AW_A10_USBCLK }, { "allwinner,sun6i-a31-usb-clk", AW_A31_USBCLK }, + { "allwinner,sun8i-a83t-usb-clk", AW_A83T_USBCLK }, { NULL, 0 } }; /* Clock indices for A10, as there is no clock-indices property in the DT */ static uint32_t aw_usbclk_indices_a10[] = { 6, 7, 8 }; struct aw_usbclk_softc { bus_addr_t reg; }; static int aw_usbclk_hwreset_assert(device_t dev, intptr_t id, bool value) { struct aw_usbclk_softc *sc; uint32_t mask; device_t pdev; int error; sc = device_get_softc(dev); pdev = device_get_parent(dev); mask = USBPHY0_RST << id; CLKDEV_DEVICE_LOCK(pdev); error = CLKDEV_MODIFY_4(pdev, sc->reg, mask, value ? 0 : mask); CLKDEV_DEVICE_UNLOCK(pdev); return (error); } static int aw_usbclk_hwreset_is_asserted(device_t dev, intptr_t id, bool *value) { struct aw_usbclk_softc *sc; uint32_t mask, val; device_t pdev; int error; sc = device_get_softc(dev); pdev = device_get_parent(dev); mask = USBPHY0_RST << id; CLKDEV_DEVICE_LOCK(pdev); error = CLKDEV_READ_4(pdev, sc->reg, &val); CLKDEV_DEVICE_UNLOCK(pdev); if (error) return (error); *value = (val & mask) != 0 ? false : true; return (0); } static int aw_usbclk_create(device_t dev, bus_addr_t paddr, struct clkdom *clkdom, const char *pclkname, const char *clkname, int index) { const char *parent_names[1] = { pclkname }; struct clk_gate_def def; memset(&def, 0, sizeof(def)); def.clkdef.id = index; def.clkdef.name = clkname; def.clkdef.parent_names = parent_names; def.clkdef.parent_cnt = 1; def.offset = paddr; def.shift = index; def.mask = 1; def.on_value = 1; def.off_value = 0; return (clknode_gate_register(clkdom, &def)); } static int aw_usbclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner USB Clocks"); return (BUS_PROBE_DEFAULT); } static int aw_usbclk_attach(device_t dev) { struct aw_usbclk_softc *sc; struct clkdom *clkdom; const char **names; + const char *pname; int index, nout, error; enum aw_usbclk_type type; uint32_t *indices; - clk_t clk_parent; + clk_t clk_parent, clk_parent_pll; bus_size_t psize; phandle_t node; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); indices = NULL; type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } clkdom = clkdom_create(dev); nout = clk_parse_ofw_out_names(dev, node, &names, &indices); if (nout == 0) { device_printf(dev, "no clock outputs found\n"); error = ENOENT; goto fail; } if (indices == NULL && type == AW_A10_USBCLK) indices = aw_usbclk_indices_a10; error = clk_get_by_ofw_index(dev, 0, &clk_parent); if (error != 0) { device_printf(dev, "cannot parse clock parent\n"); return (ENXIO); } + if (type == AW_A83T_USBCLK) { + error = clk_get_by_ofw_index(dev, 1, &clk_parent_pll); + if (error != 0) { + device_printf(dev, "cannot parse pll clock parent\n"); + return (ENXIO); + } + } for (index = 0; index < nout; index++) { - error = aw_usbclk_create(dev, sc->reg, clkdom, - clk_get_name(clk_parent), names[index], - indices != NULL ? indices[index] : index); + if (strcmp(names[index], "usb_hsic_pll") == 0) + pname = clk_get_name(clk_parent_pll); + else + pname = clk_get_name(clk_parent); + error = aw_usbclk_create(dev, sc->reg, clkdom, pname, + names[index], indices != NULL ? indices[index] : index); if (error) goto fail; } if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); hwreset_register_ofw_provider(dev); return (0); fail: return (error); } static device_method_t aw_usbclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_usbclk_probe), DEVMETHOD(device_attach, aw_usbclk_attach), /* Reset interface */ DEVMETHOD(hwreset_assert, aw_usbclk_hwreset_assert), DEVMETHOD(hwreset_is_asserted, aw_usbclk_hwreset_is_asserted), DEVMETHOD_END }; static driver_t aw_usbclk_driver = { "aw_usbclk", aw_usbclk_methods, sizeof(struct aw_usbclk_softc) }; static devclass_t aw_usbclk_devclass; EARLY_DRIVER_MODULE(aw_usbclk, simplebus, aw_usbclk_driver, aw_usbclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/arm/allwinner/files.allwinner =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/files.allwinner (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/files.allwinner (revision 299127) @@ -1,48 +1,49 @@ # $FreeBSD$ kern/kern_clocksource.c standard arm/allwinner/a10_ahci.c optional ahci arm/allwinner/a10_codec.c optional sound arm/allwinner/a10_common.c standard arm/allwinner/a10_dmac.c standard arm/allwinner/a10_ehci.c optional ehci arm/allwinner/aw_usbphy.c optional ehci arm/allwinner/a10_gpio.c optional gpio arm/allwinner/a10_mmc.c optional mmc arm/allwinner/a10_sramc.c standard arm/allwinner/aw_nmi.c optional intrng arm/allwinner/aw_if_dwc.c optional dwc arm/allwinner/aw_rtc.c standard arm/allwinner/aw_wdog.c standard arm/allwinner/a20/a20_cpu_cfg.c standard arm/allwinner/allwinner_machdep.c standard arm/allwinner/aw_mp.c optional smp arm/allwinner/axp209.c optional axp209 arm/allwinner/if_awg.c optional awg arm/allwinner/if_emac.c optional emac arm/allwinner/sunxi_dma_if.m standard dev/iicbus/twsi/a10_twsi.c optional twsi #arm/allwinner/console.c standard arm/allwinner/a10_fb.c optional vt arm/allwinner/a10_hdmi.c optional hdmi arm/allwinner/a10_hdmiaudio.c optional hdmi sound arm/arm/hdmi_if.m optional hdmi arm/allwinner/aw_reset.c standard arm/allwinner/aw_ccu.c standard arm/allwinner/clk/aw_ahbclk.c standard arm/allwinner/clk/aw_apbclk.c standard arm/allwinner/clk/aw_axiclk.c standard arm/allwinner/clk/aw_codecclk.c standard arm/allwinner/clk/aw_cpuclk.c standard +arm/allwinner/clk/aw_cpusclk.c standard arm/allwinner/clk/aw_debeclk.c standard arm/allwinner/clk/aw_gate.c standard arm/allwinner/clk/aw_gmacclk.c standard arm/allwinner/clk/aw_hdmiclk.c standard arm/allwinner/clk/aw_lcdclk.c standard arm/allwinner/clk/aw_modclk.c standard arm/allwinner/clk/aw_mmcclk.c standard arm/allwinner/clk/aw_oscclk.c standard arm/allwinner/clk/aw_pll.c standard arm/allwinner/clk/aw_usbclk.c standard Index: user/ngie/detangle-rc/sys/arm/allwinner/std.allwinner =================================================================== --- user/ngie/detangle-rc/sys/arm/allwinner/std.allwinner (revision 299126) +++ user/ngie/detangle-rc/sys/arm/allwinner/std.allwinner (revision 299127) @@ -1,16 +1,17 @@ # Allwinner common options #$FreeBSD$ cpu CPU_CORTEXA machine arm armv6 makeoptions CONF_CFLAGS="-march=armv7a" makeoptions KERNVIRTADDR=0xc0200000 options KERNVIRTADDR=0xc0200000 options IPI_IRQ_START=0 options IPI_IRQ_END=15 files "../allwinner/files.allwinner" files "../allwinner/a20/files.a20" files "../allwinner/a31/files.a31" +files "../allwinner/a83t/files.a83t" Index: user/ngie/detangle-rc/sys/arm/arm/genassym.c =================================================================== --- user/ngie/detangle-rc/sys/arm/arm/genassym.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/arm/genassym.c (revision 299127) @@ -1,154 +1,155 @@ /*- * Copyright (c) 2004 Olivier Houchard * 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. * */ #include __FBSDID("$FreeBSD$"); #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 ASSYM(KERNBASE, KERNBASE); #if __ARM_ARCH >= 6 ASSYM(CPU_ASID_KERNEL,CPU_ASID_KERNEL); #endif ASSYM(PCB_ONFAULT, offsetof(struct pcb, pcb_onfault)); #if __ARM_ARCH < 6 ASSYM(PCB_DACR, offsetof(struct pcb, pcb_dacr)); #endif ASSYM(PCB_PAGEDIR, offsetof(struct pcb, pcb_pagedir)); #if __ARM_ARCH < 6 ASSYM(PCB_L1VEC, offsetof(struct pcb, pcb_l1vec)); ASSYM(PCB_PL1VEC, offsetof(struct pcb, pcb_pl1vec)); #endif ASSYM(PCB_R4, offsetof(struct pcb, pcb_regs.sf_r4)); ASSYM(PCB_R5, offsetof(struct pcb, pcb_regs.sf_r5)); ASSYM(PCB_R6, offsetof(struct pcb, pcb_regs.sf_r6)); ASSYM(PCB_R7, offsetof(struct pcb, pcb_regs.sf_r7)); ASSYM(PCB_R8, offsetof(struct pcb, pcb_regs.sf_r8)); ASSYM(PCB_R9, offsetof(struct pcb, pcb_regs.sf_r9)); ASSYM(PCB_R10, offsetof(struct pcb, pcb_regs.sf_r10)); ASSYM(PCB_R11, offsetof(struct pcb, pcb_regs.sf_r11)); ASSYM(PCB_R12, offsetof(struct pcb, pcb_regs.sf_r12)); ASSYM(PCB_SP, offsetof(struct pcb, pcb_regs.sf_sp)); ASSYM(PCB_LR, offsetof(struct pcb, pcb_regs.sf_lr)); ASSYM(PCB_PC, offsetof(struct pcb, pcb_regs.sf_pc)); ASSYM(PC_CURPCB, offsetof(struct pcpu, pc_curpcb)); ASSYM(PC_CURTHREAD, offsetof(struct pcpu, pc_curthread)); ASSYM(M_LEN, offsetof(struct mbuf, m_len)); ASSYM(M_DATA, offsetof(struct mbuf, m_data)); ASSYM(M_NEXT, offsetof(struct mbuf, m_next)); ASSYM(IP_SRC, offsetof(struct ip, ip_src)); ASSYM(IP_DST, offsetof(struct ip, ip_dst)); ASSYM(CF_CONTEXT_SWITCH, offsetof(struct cpu_functions, cf_context_switch)); ASSYM(CF_DCACHE_WB_RANGE, offsetof(struct cpu_functions, cf_dcache_wb_range)); ASSYM(CF_IDCACHE_WBINV_ALL, offsetof(struct cpu_functions, cf_idcache_wbinv_all)); ASSYM(CF_L2CACHE_WBINV_ALL, offsetof(struct cpu_functions, cf_l2cache_wbinv_all)); ASSYM(CF_TLB_FLUSHID_SE, offsetof(struct cpu_functions, cf_tlb_flushID_SE)); ASSYM(TD_PCB, offsetof(struct thread, td_pcb)); ASSYM(TD_FLAGS, offsetof(struct thread, td_flags)); ASSYM(TD_PROC, offsetof(struct thread, td_proc)); ASSYM(TD_MD, offsetof(struct thread, td_md)); ASSYM(TD_LOCK, offsetof(struct thread, td_lock)); ASSYM(MD_TP, offsetof(struct mdthread, md_tp)); ASSYM(MD_RAS_START, offsetof(struct mdthread, md_ras_start)); ASSYM(MD_RAS_END, offsetof(struct mdthread, md_ras_end)); ASSYM(TF_SPSR, offsetof(struct trapframe, tf_spsr)); ASSYM(TF_R0, offsetof(struct trapframe, tf_r0)); ASSYM(TF_R1, offsetof(struct trapframe, tf_r1)); ASSYM(TF_PC, offsetof(struct trapframe, tf_pc)); ASSYM(P_PID, offsetof(struct proc, p_pid)); ASSYM(P_FLAG, offsetof(struct proc, p_flag)); ASSYM(SIGF_UC, offsetof(struct sigframe, sf_uc)); #if __ARM_ARCH < 6 ASSYM(ARM_TP_ADDRESS, ARM_TP_ADDRESS); ASSYM(ARM_RAS_START, ARM_RAS_START); ASSYM(ARM_RAS_END, ARM_RAS_END); #endif #ifdef VFP ASSYM(PCB_VFPSTATE, offsetof(struct pcb, pcb_vfpstate)); #endif #if __ARM_ARCH >= 6 ASSYM(PC_CURPMAP, offsetof(struct pcpu, pc_curpmap)); #endif ASSYM(PAGE_SIZE, PAGE_SIZE); #if __ARM_ARCH < 6 ASSYM(PMAP_DOMAIN_KERNEL, PMAP_DOMAIN_KERNEL); #endif #ifdef PMAP_INCLUDE_PTE_SYNC ASSYM(PMAP_INCLUDE_PTE_SYNC, 1); #endif ASSYM(TDF_ASTPENDING, TDF_ASTPENDING); ASSYM(TDF_NEEDRESCHED, TDF_NEEDRESCHED); ASSYM(MAXCOMLEN, MAXCOMLEN); ASSYM(MAXCPU, MAXCPU); ASSYM(_NCPUWORDS, _NCPUWORDS); ASSYM(NIRQ, NIRQ); ASSYM(PCPU_SIZE, sizeof(struct pcpu)); ASSYM(P_VMSPACE, offsetof(struct proc, p_vmspace)); ASSYM(VM_PMAP, offsetof(struct vmspace, vm_pmap)); ASSYM(PM_ACTIVE, offsetof(struct pmap, pm_active)); ASSYM(PC_CPUID, offsetof(struct pcpu, pc_cpuid)); ASSYM(VM_MAXUSER_ADDRESS, VM_MAXUSER_ADDRESS); ASSYM(DCACHE_LINE_SIZE, offsetof(struct cpuinfo, dcache_line_size)); ASSYM(DCACHE_LINE_MASK, offsetof(struct cpuinfo, dcache_line_mask)); ASSYM(ICACHE_LINE_SIZE, offsetof(struct cpuinfo, icache_line_size)); ASSYM(ICACHE_LINE_MASK, offsetof(struct cpuinfo, icache_line_mask)); Index: user/ngie/detangle-rc/sys/arm/arm/gic.c =================================================================== --- user/ngie/detangle-rc/sys/arm/arm/gic.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/arm/gic.c (revision 299127) @@ -1,1570 +1,1574 @@ /*- * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * * Developed by Damjan Marion * * Based on OMAP4 GIC code by Ben Gray * * 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. * 3. The name of the company nor the name of the author may be used to * endorse or promote products derived from this software without specific * prior written permission. * * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INTRNG #include #endif #include #include #include #include #include #include #include #ifdef INTRNG #include "pic_if.h" #endif #define GIC_DEBUG_SPURIOUS /* We are using GICv2 register naming */ /* Distributor Registers */ #define GICD_CTLR 0x000 /* v1 ICDDCR */ #define GICD_TYPER 0x004 /* v1 ICDICTR */ #define GICD_IIDR 0x008 /* v1 ICDIIDR */ #define GICD_IGROUPR(n) (0x0080 + ((n) * 4)) /* v1 ICDISER */ #define GICD_ISENABLER(n) (0x0100 + ((n) * 4)) /* v1 ICDISER */ #define GICD_ICENABLER(n) (0x0180 + ((n) * 4)) /* v1 ICDICER */ #define GICD_ISPENDR(n) (0x0200 + ((n) * 4)) /* v1 ICDISPR */ #define GICD_ICPENDR(n) (0x0280 + ((n) * 4)) /* v1 ICDICPR */ #define GICD_ICACTIVER(n) (0x0380 + ((n) * 4)) /* v1 ICDABR */ #define GICD_IPRIORITYR(n) (0x0400 + ((n) * 4)) /* v1 ICDIPR */ #define GICD_ITARGETSR(n) (0x0800 + ((n) * 4)) /* v1 ICDIPTR */ #define GICD_ICFGR(n) (0x0C00 + ((n) * 4)) /* v1 ICDICFR */ #define GICD_SGIR(n) (0x0F00 + ((n) * 4)) /* v1 ICDSGIR */ #define GICD_SGI_TARGET_SHIFT 16 /* CPU Registers */ #define GICC_CTLR 0x0000 /* v1 ICCICR */ #define GICC_PMR 0x0004 /* v1 ICCPMR */ #define GICC_BPR 0x0008 /* v1 ICCBPR */ #define GICC_IAR 0x000C /* v1 ICCIAR */ #define GICC_EOIR 0x0010 /* v1 ICCEOIR */ #define GICC_RPR 0x0014 /* v1 ICCRPR */ #define GICC_HPPIR 0x0018 /* v1 ICCHPIR */ #define GICC_ABPR 0x001C /* v1 ICCABPR */ #define GICC_IIDR 0x00FC /* v1 ICCIIDR*/ #define GIC_FIRST_SGI 0 /* Irqs 0-15 are SGIs/IPIs. */ #define GIC_LAST_SGI 15 #define GIC_FIRST_PPI 16 /* Irqs 16-31 are private (per */ #define GIC_LAST_PPI 31 /* core) peripheral interrupts. */ #define GIC_FIRST_SPI 32 /* Irqs 32+ are shared peripherals. */ /* First bit is a polarity bit (0 - low, 1 - high) */ #define GICD_ICFGR_POL_LOW (0 << 0) #define GICD_ICFGR_POL_HIGH (1 << 0) #define GICD_ICFGR_POL_MASK 0x1 /* Second bit is a trigger bit (0 - level, 1 - edge) */ #define GICD_ICFGR_TRIG_LVL (0 << 1) #define GICD_ICFGR_TRIG_EDGE (1 << 1) #define GICD_ICFGR_TRIG_MASK 0x2 #ifndef GIC_DEFAULT_ICFGR_INIT #define GIC_DEFAULT_ICFGR_INIT 0x00000000 #endif #ifdef INTRNG struct gic_irqsrc { struct intr_irqsrc gi_isrc; uint32_t gi_irq; enum intr_polarity gi_pol; enum intr_trigger gi_trig; #define GI_FLAG_EARLY_EOI (1 << 0) u_int gi_flags; }; static u_int gic_irq_cpu; static int arm_gic_intr(void *); static int arm_gic_bind_intr(device_t dev, struct intr_irqsrc *isrc); #ifdef SMP static u_int sgi_to_ipi[GIC_LAST_SGI - GIC_FIRST_SGI + 1]; static u_int sgi_first_unused = GIC_FIRST_SGI; #endif #endif #ifdef INTRNG struct arm_gic_range { uint64_t bus; uint64_t host; uint64_t size; }; struct arm_gic_devinfo { struct ofw_bus_devinfo obdinfo; struct resource_list rl; }; #endif struct arm_gic_softc { device_t gic_dev; #ifdef INTRNG void * gic_intrhand; struct gic_irqsrc * gic_irqs; #endif struct resource * gic_res[3]; bus_space_tag_t gic_c_bst; bus_space_tag_t gic_d_bst; bus_space_handle_t gic_c_bsh; bus_space_handle_t gic_d_bsh; uint8_t ver; struct mtx mutex; uint32_t nirqs; #ifdef GIC_DEBUG_SPURIOUS uint32_t last_irq[MAXCPU]; #endif #ifdef INTRNG /* FDT child data */ pcell_t addr_cells; pcell_t size_cells; int nranges; struct arm_gic_range * ranges; #endif }; #ifdef INTRNG #define GIC_INTR_ISRC(sc, irq) (&sc->gic_irqs[irq].gi_isrc) #endif static struct resource_spec arm_gic_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, /* Distributor registers */ { SYS_RES_MEMORY, 1, RF_ACTIVE }, /* CPU Interrupt Intf. registers */ #ifdef INTRNG { SYS_RES_IRQ, 0, RF_ACTIVE | RF_OPTIONAL }, /* Parent interrupt */ #endif { -1, 0 } }; static u_int arm_gic_map[MAXCPU]; static struct arm_gic_softc *gic_sc = NULL; #define gic_c_read_4(_sc, _reg) \ bus_space_read_4((_sc)->gic_c_bst, (_sc)->gic_c_bsh, (_reg)) #define gic_c_write_4(_sc, _reg, _val) \ bus_space_write_4((_sc)->gic_c_bst, (_sc)->gic_c_bsh, (_reg), (_val)) #define gic_d_read_4(_sc, _reg) \ bus_space_read_4((_sc)->gic_d_bst, (_sc)->gic_d_bsh, (_reg)) #define gic_d_write_1(_sc, _reg, _val) \ bus_space_write_1((_sc)->gic_d_bst, (_sc)->gic_d_bsh, (_reg), (_val)) #define gic_d_write_4(_sc, _reg, _val) \ bus_space_write_4((_sc)->gic_d_bst, (_sc)->gic_d_bsh, (_reg), (_val)) #ifndef INTRNG static int gic_config_irq(int irq, enum intr_trigger trig, enum intr_polarity pol); static void gic_post_filter(void *); #endif static struct ofw_compat_data compat_data[] = { {"arm,gic", true}, /* Non-standard, used in FreeBSD dts. */ {"arm,gic-400", true}, {"arm,cortex-a15-gic", true}, {"arm,cortex-a9-gic", true}, {"arm,cortex-a7-gic", true}, {"arm,arm11mp-gic", true}, {"brcm,brahma-b15-gic", true}, {"qcom,msm-qgic2", true}, {NULL, false} }; static int arm_gic_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "ARM Generic Interrupt Controller"); return (BUS_PROBE_DEFAULT); } #ifdef INTRNG static inline void gic_irq_unmask(struct arm_gic_softc *sc, u_int irq) { gic_d_write_4(sc, GICD_ISENABLER(irq >> 5), (1UL << (irq & 0x1F))); } static inline void gic_irq_mask(struct arm_gic_softc *sc, u_int irq) { gic_d_write_4(sc, GICD_ICENABLER(irq >> 5), (1UL << (irq & 0x1F))); } #endif static uint8_t gic_cpu_mask(struct arm_gic_softc *sc) { uint32_t mask; int i; /* Read the current cpuid mask by reading ITARGETSR{0..7} */ for (i = 0; i < 8; i++) { mask = gic_d_read_4(sc, GICD_ITARGETSR(i)); if (mask != 0) break; } /* No mask found, assume we are on CPU interface 0 */ if (mask == 0) return (1); /* Collect the mask in the lower byte */ mask |= mask >> 16; mask |= mask >> 8; return (mask); } #ifdef SMP #ifdef INTRNG static void arm_gic_init_secondary(device_t dev) { struct arm_gic_softc *sc = device_get_softc(dev); u_int irq, cpu; /* Set the mask so we can find this CPU to send it IPIs */ cpu = PCPU_GET(cpuid); arm_gic_map[cpu] = gic_cpu_mask(sc); for (irq = 0; irq < sc->nirqs; irq += 4) gic_d_write_4(sc, GICD_IPRIORITYR(irq >> 2), 0); /* Set all the interrupts to be in Group 0 (secure) */ for (irq = 0; irq < sc->nirqs; irq += 32) { gic_d_write_4(sc, GICD_IGROUPR(irq >> 5), 0); } /* Enable CPU interface */ gic_c_write_4(sc, GICC_CTLR, 1); /* Set priority mask register. */ gic_c_write_4(sc, GICC_PMR, 0xff); /* Enable interrupt distribution */ gic_d_write_4(sc, GICD_CTLR, 0x01); /* Unmask attached SGI interrupts. */ for (irq = GIC_FIRST_SGI; irq <= GIC_LAST_SGI; irq++) if (intr_isrc_init_on_cpu(GIC_INTR_ISRC(sc, irq), cpu)) gic_irq_unmask(sc, irq); /* Unmask attached PPI interrupts. */ for (irq = GIC_FIRST_PPI; irq <= GIC_LAST_PPI; irq++) if (intr_isrc_init_on_cpu(GIC_INTR_ISRC(sc, irq), cpu)) gic_irq_unmask(sc, irq); } #else static void arm_gic_init_secondary(device_t dev) { struct arm_gic_softc *sc = device_get_softc(dev); int i; /* Set the mask so we can find this CPU to send it IPIs */ arm_gic_map[PCPU_GET(cpuid)] = gic_cpu_mask(sc); for (i = 0; i < sc->nirqs; i += 4) gic_d_write_4(sc, GICD_IPRIORITYR(i >> 2), 0); /* Set all the interrupts to be in Group 0 (secure) */ for (i = 0; i < sc->nirqs; i += 32) { gic_d_write_4(sc, GICD_IGROUPR(i >> 5), 0); } /* Enable CPU interface */ gic_c_write_4(sc, GICC_CTLR, 1); /* Set priority mask register. */ gic_c_write_4(sc, GICC_PMR, 0xff); /* Enable interrupt distribution */ gic_d_write_4(sc, GICD_CTLR, 0x01); /* * Activate the timer interrupts: virtual, secure, and non-secure. */ gic_d_write_4(sc, GICD_ISENABLER(27 >> 5), (1UL << (27 & 0x1F))); gic_d_write_4(sc, GICD_ISENABLER(29 >> 5), (1UL << (29 & 0x1F))); gic_d_write_4(sc, GICD_ISENABLER(30 >> 5), (1UL << (30 & 0x1F))); } #endif /* INTRNG */ #endif /* SMP */ #ifndef INTRNG int gic_decode_fdt(phandle_t iparent, pcell_t *intr, int *interrupt, int *trig, int *pol) { static u_int num_intr_cells; static phandle_t self; struct ofw_compat_data *ocd; if (self == 0) { for (ocd = compat_data; ocd->ocd_str != NULL; ocd++) { if (fdt_is_compatible(iparent, ocd->ocd_str)) { self = iparent; break; } } } if (self != iparent) return (ENXIO); if (num_intr_cells == 0) { if (OF_searchencprop(OF_node_from_xref(iparent), "#interrupt-cells", &num_intr_cells, sizeof(num_intr_cells)) == -1) { num_intr_cells = 1; } } if (num_intr_cells == 1) { *interrupt = fdt32_to_cpu(intr[0]); *trig = INTR_TRIGGER_CONFORM; *pol = INTR_POLARITY_CONFORM; } else { if (fdt32_to_cpu(intr[0]) == 0) *interrupt = fdt32_to_cpu(intr[1]) + GIC_FIRST_SPI; else *interrupt = fdt32_to_cpu(intr[1]) + GIC_FIRST_PPI; /* * In intr[2], bits[3:0] are trigger type and level flags. * 1 = low-to-high edge triggered * 2 = high-to-low edge triggered * 4 = active high level-sensitive * 8 = active low level-sensitive * The hardware only supports active-high-level or rising-edge * for SPIs */ if (*interrupt >= GIC_FIRST_SPI && fdt32_to_cpu(intr[2]) & 0x0a) { printf("unsupported trigger/polarity configuration " "0x%02x\n", fdt32_to_cpu(intr[2]) & 0x0f); } *pol = INTR_POLARITY_CONFORM; if (fdt32_to_cpu(intr[2]) & 0x03) *trig = INTR_TRIGGER_EDGE; else *trig = INTR_TRIGGER_LEVEL; } return (0); } #endif #ifdef INTRNG static inline intptr_t gic_xref(device_t dev) { #ifdef FDT return (OF_xref_from_node(ofw_bus_get_node(dev))); #else return (0); #endif } static int arm_gic_register_isrcs(struct arm_gic_softc *sc, uint32_t num) { int error; uint32_t irq; struct gic_irqsrc *irqs; struct intr_irqsrc *isrc; const char *name; irqs = malloc(num * sizeof(struct gic_irqsrc), M_DEVBUF, M_WAITOK | M_ZERO); name = device_get_nameunit(sc->gic_dev); for (irq = 0; irq < num; irq++) { irqs[irq].gi_irq = irq; irqs[irq].gi_pol = INTR_POLARITY_CONFORM; irqs[irq].gi_trig = INTR_TRIGGER_CONFORM; isrc = &irqs[irq].gi_isrc; if (irq <= GIC_LAST_SGI) { error = intr_isrc_register(isrc, sc->gic_dev, INTR_ISRCF_IPI, "%s,i%u", name, irq - GIC_FIRST_SGI); } else if (irq <= GIC_LAST_PPI) { error = intr_isrc_register(isrc, sc->gic_dev, INTR_ISRCF_PPI, "%s,p%u", name, irq - GIC_FIRST_PPI); } else { error = intr_isrc_register(isrc, sc->gic_dev, 0, "%s,s%u", name, irq - GIC_FIRST_SPI); } if (error != 0) { /* XXX call intr_isrc_deregister() */ free(irqs, M_DEVBUF); return (error); } } sc->gic_irqs = irqs; sc->nirqs = num; return (0); } static int arm_gic_fill_ranges(phandle_t node, struct arm_gic_softc *sc) { pcell_t host_cells; cell_t *base_ranges; ssize_t nbase_ranges; int i, j, k; host_cells = 1; OF_getencprop(OF_parent(node), "#address-cells", &host_cells, sizeof(host_cells)); sc->addr_cells = 2; OF_getencprop(node, "#address-cells", &sc->addr_cells, sizeof(sc->addr_cells)); sc->size_cells = 2; OF_getencprop(node, "#size-cells", &sc->size_cells, sizeof(sc->size_cells)); nbase_ranges = OF_getproplen(node, "ranges"); if (nbase_ranges < 0) return (-1); sc->nranges = nbase_ranges / sizeof(cell_t) / (sc->addr_cells + host_cells + sc->size_cells); if (sc->nranges == 0) return (0); sc->ranges = malloc(sc->nranges * sizeof(sc->ranges[0]), M_DEVBUF, M_WAITOK); base_ranges = malloc(nbase_ranges, M_DEVBUF, M_WAITOK); OF_getencprop(node, "ranges", base_ranges, nbase_ranges); for (i = 0, j = 0; i < sc->nranges; i++) { sc->ranges[i].bus = 0; for (k = 0; k < sc->addr_cells; k++) { sc->ranges[i].bus <<= 32; sc->ranges[i].bus |= base_ranges[j++]; } sc->ranges[i].host = 0; for (k = 0; k < host_cells; k++) { sc->ranges[i].host <<= 32; sc->ranges[i].host |= base_ranges[j++]; } sc->ranges[i].size = 0; for (k = 0; k < sc->size_cells; k++) { sc->ranges[i].size <<= 32; sc->ranges[i].size |= base_ranges[j++]; } } free(base_ranges, M_DEVBUF); return (sc->nranges); } static bool arm_gic_add_children(device_t dev) { struct arm_gic_softc *sc; struct arm_gic_devinfo *dinfo; phandle_t child, node; device_t cdev; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); /* If we have no children don't probe for them */ child = OF_child(node); if (child == 0) return (false); if (arm_gic_fill_ranges(node, sc) < 0) { device_printf(dev, "Have a child, but no ranges\n"); return (false); } for (; child != 0; child = OF_peer(child)) { dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&dinfo->obdinfo, child) != 0) { free(dinfo, M_DEVBUF); continue; } resource_list_init(&dinfo->rl); ofw_bus_reg_to_rl(dev, child, sc->addr_cells, sc->size_cells, &dinfo->rl); cdev = device_add_child(dev, NULL, -1); if (cdev == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", dinfo->obdinfo.obd_name); resource_list_free(&dinfo->rl); ofw_bus_gen_destroy_devinfo(&dinfo->obdinfo); free(dinfo, M_DEVBUF); continue; } device_set_ivars(cdev, dinfo); } return (true); } #endif static int arm_gic_attach(device_t dev) { struct arm_gic_softc *sc; int i; uint32_t icciidr, mask, nirqs; #ifdef INTRNG phandle_t pxref; intptr_t xref = gic_xref(dev); #endif if (gic_sc) return (ENXIO); sc = device_get_softc(dev); if (bus_alloc_resources(dev, arm_gic_spec, sc->gic_res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } sc->gic_dev = dev; gic_sc = sc; /* Initialize mutex */ mtx_init(&sc->mutex, "GIC lock", "", MTX_SPIN); /* Distributor Interface */ sc->gic_d_bst = rman_get_bustag(sc->gic_res[0]); sc->gic_d_bsh = rman_get_bushandle(sc->gic_res[0]); /* CPU Interface */ sc->gic_c_bst = rman_get_bustag(sc->gic_res[1]); sc->gic_c_bsh = rman_get_bushandle(sc->gic_res[1]); /* Disable interrupt forwarding to the CPU interface */ gic_d_write_4(sc, GICD_CTLR, 0x00); /* Get the number of interrupts */ nirqs = gic_d_read_4(sc, GICD_TYPER); nirqs = 32 * ((nirqs & 0x1f) + 1); #ifdef INTRNG if (arm_gic_register_isrcs(sc, nirqs)) { device_printf(dev, "could not register irqs\n"); goto cleanup; } #else sc->nirqs = nirqs; /* Set up function pointers */ arm_post_filter = gic_post_filter; arm_config_irq = gic_config_irq; #endif icciidr = gic_c_read_4(sc, GICC_IIDR); device_printf(dev,"pn 0x%x, arch 0x%x, rev 0x%x, implementer 0x%x irqs %u\n", icciidr>>20, (icciidr>>16) & 0xF, (icciidr>>12) & 0xf, (icciidr & 0xfff), sc->nirqs); /* Set all global interrupts to be level triggered, active low. */ for (i = 32; i < sc->nirqs; i += 16) { gic_d_write_4(sc, GICD_ICFGR(i >> 4), GIC_DEFAULT_ICFGR_INIT); } /* Disable all interrupts. */ for (i = 32; i < sc->nirqs; i += 32) { gic_d_write_4(sc, GICD_ICENABLER(i >> 5), 0xFFFFFFFF); } /* Find the current cpu mask */ mask = gic_cpu_mask(sc); /* Set the mask so we can find this CPU to send it IPIs */ arm_gic_map[PCPU_GET(cpuid)] = mask; /* Set all four targets to this cpu */ mask |= mask << 8; mask |= mask << 16; for (i = 0; i < sc->nirqs; i += 4) { gic_d_write_4(sc, GICD_IPRIORITYR(i >> 2), 0); if (i > 32) { gic_d_write_4(sc, GICD_ITARGETSR(i >> 2), mask); } } /* Set all the interrupts to be in Group 0 (secure) */ for (i = 0; i < sc->nirqs; i += 32) { gic_d_write_4(sc, GICD_IGROUPR(i >> 5), 0); } /* Enable CPU interface */ gic_c_write_4(sc, GICC_CTLR, 1); /* Set priority mask register. */ gic_c_write_4(sc, GICC_PMR, 0xff); /* Enable interrupt distribution */ gic_d_write_4(sc, GICD_CTLR, 0x01); #ifndef INTRNG return (0); #else /* * Now, when everything is initialized, it's right time to * register interrupt controller to interrupt framefork. */ if (intr_pic_register(dev, xref) != 0) { device_printf(dev, "could not register PIC\n"); goto cleanup; } /* * Controller is root if: * - doesn't have interrupt parent * - his interrupt parent is this controller */ pxref = ofw_bus_find_iparent(ofw_bus_get_node(dev)); if (pxref == 0 || xref == pxref) { if (intr_pic_claim_root(dev, xref, arm_gic_intr, sc, GIC_LAST_SGI - GIC_FIRST_SGI + 1) != 0) { device_printf(dev, "could not set PIC as a root\n"); intr_pic_deregister(dev, xref); goto cleanup; } } else { if (sc->gic_res[2] == NULL) { device_printf(dev, "not root PIC must have defined interrupt\n"); intr_pic_deregister(dev, xref); goto cleanup; } if (bus_setup_intr(dev, sc->gic_res[2], INTR_TYPE_CLK, arm_gic_intr, NULL, sc, &sc->gic_intrhand)) { device_printf(dev, "could not setup irq handler\n"); intr_pic_deregister(dev, xref); goto cleanup; } } OF_device_register_xref(xref, dev); /* If we have children probe and attach them */ if (arm_gic_add_children(dev)) { bus_generic_probe(dev); return (bus_generic_attach(dev)); } return (0); cleanup: /* * XXX - not implemented arm_gic_detach() should be called ! */ if (sc->gic_irqs != NULL) free(sc->gic_irqs, M_DEVBUF); bus_release_resources(dev, arm_gic_spec, sc->gic_res); return(ENXIO); #endif } #ifdef INTRNG static struct resource * arm_gic_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct arm_gic_softc *sc; struct arm_gic_devinfo *di; struct resource_list_entry *rle; int j; KASSERT(type == SYS_RES_MEMORY, ("Invalid resoure type %x", type)); sc = device_get_softc(bus); /* * Request for the default allocation with a given rid: use resource * list stored in the local device info. */ if (RMAN_IS_DEFAULT_RANGE(start, end)) { if ((di = device_get_ivars(child)) == NULL) return (NULL); if (type == SYS_RES_IOPORT) type = SYS_RES_MEMORY; rle = resource_list_find(&di->rl, type, *rid); if (rle == NULL) { if (bootverbose) device_printf(bus, "no default resources for " "rid = %d, type = %d\n", *rid, type); return (NULL); } start = rle->start; end = rle->end; count = rle->count; } /* Remap through ranges property */ for (j = 0; j < sc->nranges; j++) { if (start >= sc->ranges[j].bus && end < sc->ranges[j].bus + sc->ranges[j].size) { start -= sc->ranges[j].bus; start += sc->ranges[j].host; end -= sc->ranges[j].bus; end += sc->ranges[j].host; break; } } if (j == sc->nranges && sc->nranges != 0) { if (bootverbose) device_printf(bus, "Could not map resource " "%#jx-%#jx\n", (uintmax_t)start, (uintmax_t)end); return (NULL); } return (bus_generic_alloc_resource(bus, child, type, rid, start, end, count, flags)); } static const struct ofw_bus_devinfo * arm_gic_ofw_get_devinfo(device_t bus __unused, device_t child) { struct arm_gic_devinfo *di; di = device_get_ivars(child); return (&di->obdinfo); } static int arm_gic_intr(void *arg) { struct arm_gic_softc *sc = arg; struct gic_irqsrc *gi; uint32_t irq_active_reg, irq; struct trapframe *tf; irq_active_reg = gic_c_read_4(sc, GICC_IAR); irq = irq_active_reg & 0x3FF; /* * 1. We do EOI here because recent read value from active interrupt * register must be used for it. Another approach is to save this * value into associated interrupt source. * 2. EOI must be done on same CPU where interrupt has fired. Thus * we must ensure that interrupted thread does not migrate to * another CPU. * 3. EOI cannot be delayed by any preemption which could happen on * critical_exit() used in MI intr code, when interrupt thread is * scheduled. See next point. * 4. IPI_RENDEZVOUS assumes that no preemption is permitted during * an action and any use of critical_exit() could break this * assumption. See comments within smp_rendezvous_action(). * 5. We always return FILTER_HANDLED as this is an interrupt * controller dispatch function. Otherwise, in cascaded interrupt * case, the whole interrupt subtree would be masked. */ if (irq >= sc->nirqs) { #ifdef GIC_DEBUG_SPURIOUS device_printf(sc->gic_dev, "Spurious interrupt detected: last irq: %d on CPU%d\n", sc->last_irq[PCPU_GET(cpuid)], PCPU_GET(cpuid)); #endif return (FILTER_HANDLED); } tf = curthread->td_intr_frame; dispatch_irq: gi = sc->gic_irqs + irq; /* * Note that GIC_FIRST_SGI is zero and is not used in 'if' statement * as compiler complains that comparing u_int >= 0 is always true. */ if (irq <= GIC_LAST_SGI) { #ifdef SMP /* Call EOI for all IPI before dispatch. */ gic_c_write_4(sc, GICC_EOIR, irq_active_reg); intr_ipi_dispatch(sgi_to_ipi[gi->gi_irq], tf); goto next_irq; #else device_printf(sc->gic_dev, "SGI %u on UP system detected\n", irq - GIC_FIRST_SGI); gic_c_write_4(sc, GICC_EOIR, irq_active_reg); goto next_irq; #endif } #ifdef GIC_DEBUG_SPURIOUS sc->last_irq[PCPU_GET(cpuid)] = irq; #endif if ((gi->gi_flags & GI_FLAG_EARLY_EOI) == GI_FLAG_EARLY_EOI) gic_c_write_4(sc, GICC_EOIR, irq_active_reg); if (intr_isrc_dispatch(&gi->gi_isrc, tf) != 0) { gic_irq_mask(sc, irq); if ((gi->gi_flags & GI_FLAG_EARLY_EOI) != GI_FLAG_EARLY_EOI) gic_c_write_4(sc, GICC_EOIR, irq_active_reg); device_printf(sc->gic_dev, "Stray irq %u disabled\n", irq); } next_irq: arm_irq_memory_barrier(irq); irq_active_reg = gic_c_read_4(sc, GICC_IAR); irq = irq_active_reg & 0x3FF; if (irq < sc->nirqs) goto dispatch_irq; return (FILTER_HANDLED); } static void gic_config(struct arm_gic_softc *sc, u_int irq, enum intr_trigger trig, enum intr_polarity pol) { uint32_t reg; uint32_t mask; if (irq < GIC_FIRST_SPI) return; mtx_lock_spin(&sc->mutex); reg = gic_d_read_4(sc, GICD_ICFGR(irq >> 4)); mask = (reg >> 2*(irq % 16)) & 0x3; if (pol == INTR_POLARITY_LOW) { mask &= ~GICD_ICFGR_POL_MASK; mask |= GICD_ICFGR_POL_LOW; } else if (pol == INTR_POLARITY_HIGH) { mask &= ~GICD_ICFGR_POL_MASK; mask |= GICD_ICFGR_POL_HIGH; } if (trig == INTR_TRIGGER_LEVEL) { mask &= ~GICD_ICFGR_TRIG_MASK; mask |= GICD_ICFGR_TRIG_LVL; } else if (trig == INTR_TRIGGER_EDGE) { mask &= ~GICD_ICFGR_TRIG_MASK; mask |= GICD_ICFGR_TRIG_EDGE; } /* Set mask */ reg = reg & ~(0x3 << 2*(irq % 16)); reg = reg | (mask << 2*(irq % 16)); gic_d_write_4(sc, GICD_ICFGR(irq >> 4), reg); mtx_unlock_spin(&sc->mutex); } static int gic_bind(struct arm_gic_softc *sc, u_int irq, cpuset_t *cpus) { uint32_t cpu, end, mask; end = min(mp_ncpus, 8); for (cpu = end; cpu < MAXCPU; cpu++) if (CPU_ISSET(cpu, cpus)) return (EINVAL); for (mask = 0, cpu = 0; cpu < end; cpu++) if (CPU_ISSET(cpu, cpus)) mask |= 1 << cpu; gic_d_write_1(sc, GICD_ITARGETSR(0) + irq, mask); return (0); } #ifdef FDT static int gic_map_fdt(device_t dev, u_int ncells, pcell_t *cells, u_int *irqp, enum intr_polarity *polp, enum intr_trigger *trigp) { if (ncells == 1) { *irqp = cells[0]; *polp = INTR_POLARITY_CONFORM; *trigp = INTR_TRIGGER_CONFORM; return (0); } if (ncells == 3) { u_int irq, tripol; /* * The 1st cell is the interrupt type: * 0 = SPI * 1 = PPI * The 2nd cell contains the interrupt number: * [0 - 987] for SPI * [0 - 15] for PPI * The 3rd cell is the flags, encoded as follows: * bits[3:0] trigger type and level flags * 1 = low-to-high edge triggered * 2 = high-to-low edge triggered * 4 = active high level-sensitive * 8 = active low level-sensitive * bits[15:8] PPI interrupt cpu mask * Each bit corresponds to each of the 8 possible cpus * attached to the GIC. A bit set to '1' indicated * the interrupt is wired to that CPU. */ switch (cells[0]) { case 0: irq = GIC_FIRST_SPI + cells[1]; /* SPI irq is checked later. */ break; case 1: irq = GIC_FIRST_PPI + cells[1]; if (irq > GIC_LAST_PPI) { device_printf(dev, "unsupported PPI interrupt " "number %u\n", cells[1]); return (EINVAL); } break; default: device_printf(dev, "unsupported interrupt type " "configuration %u\n", cells[0]); return (EINVAL); } tripol = cells[2] & 0xff; if (tripol & 0xf0 || (tripol & 0x0a && cells[0] == 0)) device_printf(dev, "unsupported trigger/polarity " "configuration 0x%02x\n", tripol); *irqp = irq; *polp = INTR_POLARITY_CONFORM; *trigp = tripol & 0x03 ? INTR_TRIGGER_EDGE : INTR_TRIGGER_LEVEL; return (0); } return (EINVAL); } #endif static int gic_map_intr(device_t dev, struct intr_map_data *data, u_int *irqp, enum intr_polarity *polp, enum intr_trigger *trigp) { u_int irq; enum intr_polarity pol; enum intr_trigger trig; struct arm_gic_softc *sc; +#ifdef FDT + struct intr_map_data_fdt *daf; +#endif sc = device_get_softc(dev); switch (data->type) { #ifdef FDT case INTR_MAP_DATA_FDT: - if (gic_map_fdt(dev, data->fdt.ncells, data->fdt.cells, &irq, - &pol, &trig) != 0) + daf = (struct intr_map_data_fdt *)data; + if (gic_map_fdt(dev, daf->ncells, daf->cells, &irq, &pol, + &trig) != 0) return (EINVAL); break; #endif default: - return (EINVAL); + return (ENOTSUP); } if (irq >= sc->nirqs) return (EINVAL); if (pol != INTR_POLARITY_CONFORM && pol != INTR_POLARITY_LOW && pol != INTR_POLARITY_HIGH) return (EINVAL); if (trig != INTR_TRIGGER_CONFORM && trig != INTR_TRIGGER_EDGE && trig != INTR_TRIGGER_LEVEL) return (EINVAL); *irqp = irq; if (polp != NULL) *polp = pol; if (trigp != NULL) *trigp = trig; return (0); } static int arm_gic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int error; u_int irq; struct arm_gic_softc *sc; error = gic_map_intr(dev, data, &irq, NULL, NULL); if (error == 0) { sc = device_get_softc(dev); *isrcp = GIC_INTR_ISRC(sc, irq); } return (error); } static int arm_gic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct arm_gic_softc *sc = device_get_softc(dev); struct gic_irqsrc *gi = (struct gic_irqsrc *)isrc; u_int irq; enum intr_trigger trig; enum intr_polarity pol; if (data == NULL) return (ENOTSUP); /* Get config for resource. */ if (gic_map_intr(dev, data, &irq, &pol, &trig)) return (EINVAL); if (gi->gi_irq != irq) return (EINVAL); /* Compare config if this is not first setup. */ if (isrc->isrc_handlers != 0) { if ((pol != INTR_POLARITY_CONFORM && pol != gi->gi_pol) || (trig != INTR_TRIGGER_CONFORM && trig != gi->gi_trig)) return (EINVAL); else return (0); } if (pol == INTR_POLARITY_CONFORM) pol = INTR_POLARITY_LOW; /* just pick some */ if (trig == INTR_TRIGGER_CONFORM) trig = INTR_TRIGGER_EDGE; /* just pick some */ gi->gi_pol = pol; gi->gi_trig = trig; /* Edge triggered interrupts need an early EOI sent */ if (gi->gi_pol == INTR_TRIGGER_EDGE) gi->gi_flags |= GI_FLAG_EARLY_EOI; /* * XXX - In case that per CPU interrupt is going to be enabled in time * when SMP is already started, we need some IPI call which * enables it on others CPUs. Further, it's more complicated as * pic_enable_source() and pic_disable_source() should act on * per CPU basis only. Thus, it should be solved here somehow. */ if (isrc->isrc_flags & INTR_ISRCF_PPI) CPU_SET(PCPU_GET(cpuid), &isrc->isrc_cpu); gic_config(sc, gi->gi_irq, trig, pol); arm_gic_bind_intr(dev, isrc); return (0); } static int arm_gic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct gic_irqsrc *gi = (struct gic_irqsrc *)isrc; if (isrc->isrc_handlers == 0) { gi->gi_pol = INTR_POLARITY_CONFORM; gi->gi_trig = INTR_TRIGGER_CONFORM; } return (0); } static void arm_gic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct arm_gic_softc *sc = device_get_softc(dev); struct gic_irqsrc *gi = (struct gic_irqsrc *)isrc; arm_irq_memory_barrier(gi->gi_irq); gic_irq_unmask(sc, gi->gi_irq); } static void arm_gic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct arm_gic_softc *sc = device_get_softc(dev); struct gic_irqsrc *gi = (struct gic_irqsrc *)isrc; gic_irq_mask(sc, gi->gi_irq); } static void arm_gic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct arm_gic_softc *sc = device_get_softc(dev); struct gic_irqsrc *gi = (struct gic_irqsrc *)isrc; arm_gic_disable_intr(dev, isrc); gic_c_write_4(sc, GICC_EOIR, gi->gi_irq); } static void arm_gic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { arm_irq_memory_barrier(0); arm_gic_enable_intr(dev, isrc); } static void arm_gic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct arm_gic_softc *sc = device_get_softc(dev); struct gic_irqsrc *gi = (struct gic_irqsrc *)isrc; /* EOI for edge-triggered done earlier. */ if ((gi->gi_flags & GI_FLAG_EARLY_EOI) == GI_FLAG_EARLY_EOI) return; arm_irq_memory_barrier(0); gic_c_write_4(sc, GICC_EOIR, gi->gi_irq); } static int arm_gic_bind_intr(device_t dev, struct intr_irqsrc *isrc) { struct arm_gic_softc *sc = device_get_softc(dev); struct gic_irqsrc *gi = (struct gic_irqsrc *)isrc; if (gi->gi_irq < GIC_FIRST_SPI) return (EINVAL); if (CPU_EMPTY(&isrc->isrc_cpu)) { gic_irq_cpu = intr_irq_next_cpu(gic_irq_cpu, &all_cpus); CPU_SETOF(gic_irq_cpu, &isrc->isrc_cpu); } return (gic_bind(sc, gi->gi_irq, &isrc->isrc_cpu)); } #ifdef SMP static void arm_gic_ipi_send(device_t dev, struct intr_irqsrc *isrc, cpuset_t cpus, u_int ipi) { struct arm_gic_softc *sc = device_get_softc(dev); struct gic_irqsrc *gi = (struct gic_irqsrc *)isrc; uint32_t val = 0, i; for (i = 0; i < MAXCPU; i++) if (CPU_ISSET(i, &cpus)) val |= arm_gic_map[i] << GICD_SGI_TARGET_SHIFT; gic_d_write_4(sc, GICD_SGIR(0), val | gi->gi_irq); } static int arm_gic_ipi_setup(device_t dev, u_int ipi, struct intr_irqsrc **isrcp) { struct intr_irqsrc *isrc; struct arm_gic_softc *sc = device_get_softc(dev); if (sgi_first_unused > GIC_LAST_SGI) return (ENOSPC); isrc = GIC_INTR_ISRC(sc, sgi_first_unused); sgi_to_ipi[sgi_first_unused++] = ipi; CPU_SET(PCPU_GET(cpuid), &isrc->isrc_cpu); *isrcp = isrc; return (0); } #endif #else static int arm_gic_next_irq(struct arm_gic_softc *sc, int last_irq) { uint32_t active_irq; active_irq = gic_c_read_4(sc, GICC_IAR); /* * Immediately EOIR the SGIs, because doing so requires the other * bits (ie CPU number), not just the IRQ number, and we do not * have this information later. */ if ((active_irq & 0x3ff) <= GIC_LAST_SGI) gic_c_write_4(sc, GICC_EOIR, active_irq); active_irq &= 0x3FF; if (active_irq == 0x3FF) { if (last_irq == -1) device_printf(sc->gic_dev, "Spurious interrupt detected\n"); return -1; } return active_irq; } static int arm_gic_config(device_t dev, int irq, enum intr_trigger trig, enum intr_polarity pol) { struct arm_gic_softc *sc = device_get_softc(dev); uint32_t reg; uint32_t mask; /* Function is public-accessible, so validate input arguments */ if ((irq < 0) || (irq >= sc->nirqs)) goto invalid_args; if ((trig != INTR_TRIGGER_EDGE) && (trig != INTR_TRIGGER_LEVEL) && (trig != INTR_TRIGGER_CONFORM)) goto invalid_args; if ((pol != INTR_POLARITY_HIGH) && (pol != INTR_POLARITY_LOW) && (pol != INTR_POLARITY_CONFORM)) goto invalid_args; mtx_lock_spin(&sc->mutex); reg = gic_d_read_4(sc, GICD_ICFGR(irq >> 4)); mask = (reg >> 2*(irq % 16)) & 0x3; if (pol == INTR_POLARITY_LOW) { mask &= ~GICD_ICFGR_POL_MASK; mask |= GICD_ICFGR_POL_LOW; } else if (pol == INTR_POLARITY_HIGH) { mask &= ~GICD_ICFGR_POL_MASK; mask |= GICD_ICFGR_POL_HIGH; } if (trig == INTR_TRIGGER_LEVEL) { mask &= ~GICD_ICFGR_TRIG_MASK; mask |= GICD_ICFGR_TRIG_LVL; } else if (trig == INTR_TRIGGER_EDGE) { mask &= ~GICD_ICFGR_TRIG_MASK; mask |= GICD_ICFGR_TRIG_EDGE; } /* Set mask */ reg = reg & ~(0x3 << 2*(irq % 16)); reg = reg | (mask << 2*(irq % 16)); gic_d_write_4(sc, GICD_ICFGR(irq >> 4), reg); mtx_unlock_spin(&sc->mutex); return (0); invalid_args: device_printf(dev, "gic_config_irg, invalid parameters\n"); return (EINVAL); } static void arm_gic_mask(device_t dev, int irq) { struct arm_gic_softc *sc = device_get_softc(dev); gic_d_write_4(sc, GICD_ICENABLER(irq >> 5), (1UL << (irq & 0x1F))); gic_c_write_4(sc, GICC_EOIR, irq); /* XXX - not allowed */ } static void arm_gic_unmask(device_t dev, int irq) { struct arm_gic_softc *sc = device_get_softc(dev); if (irq > GIC_LAST_SGI) arm_irq_memory_barrier(irq); gic_d_write_4(sc, GICD_ISENABLER(irq >> 5), (1UL << (irq & 0x1F))); } #ifdef SMP static void arm_gic_ipi_send(device_t dev, cpuset_t cpus, u_int ipi) { struct arm_gic_softc *sc = device_get_softc(dev); uint32_t val = 0, i; for (i = 0; i < MAXCPU; i++) if (CPU_ISSET(i, &cpus)) val |= arm_gic_map[i] << GICD_SGI_TARGET_SHIFT; gic_d_write_4(sc, GICD_SGIR(0), val | ipi); } static int arm_gic_ipi_read(device_t dev, int i) { if (i != -1) { /* * The intr code will automagically give the frame pointer * if the interrupt argument is 0. */ if ((unsigned int)i > 16) return (0); return (i); } return (0x3ff); } static void arm_gic_ipi_clear(device_t dev, int ipi) { /* no-op */ } #endif static void gic_post_filter(void *arg) { struct arm_gic_softc *sc = gic_sc; uintptr_t irq = (uintptr_t) arg; if (irq > GIC_LAST_SGI) arm_irq_memory_barrier(irq); gic_c_write_4(sc, GICC_EOIR, irq); } static int gic_config_irq(int irq, enum intr_trigger trig, enum intr_polarity pol) { return (arm_gic_config(gic_sc->gic_dev, irq, trig, pol)); } void arm_mask_irq(uintptr_t nb) { arm_gic_mask(gic_sc->gic_dev, nb); } void arm_unmask_irq(uintptr_t nb) { arm_gic_unmask(gic_sc->gic_dev, nb); } int arm_get_next_irq(int last_irq) { return (arm_gic_next_irq(gic_sc, last_irq)); } #ifdef SMP void intr_pic_init_secondary(void) { arm_gic_init_secondary(gic_sc->gic_dev); } void pic_ipi_send(cpuset_t cpus, u_int ipi) { arm_gic_ipi_send(gic_sc->gic_dev, cpus, ipi); } int pic_ipi_read(int i) { return (arm_gic_ipi_read(gic_sc->gic_dev, i)); } void pic_ipi_clear(int ipi) { arm_gic_ipi_clear(gic_sc->gic_dev, ipi); } #endif #endif /* INTRNG */ static device_method_t arm_gic_methods[] = { /* Device interface */ DEVMETHOD(device_probe, arm_gic_probe), DEVMETHOD(device_attach, arm_gic_attach), #ifdef INTRNG /* Bus interface */ DEVMETHOD(bus_add_child, bus_generic_add_child), DEVMETHOD(bus_alloc_resource, arm_gic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource,bus_generic_activate_resource), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, arm_gic_ofw_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, arm_gic_disable_intr), DEVMETHOD(pic_enable_intr, arm_gic_enable_intr), DEVMETHOD(pic_map_intr, arm_gic_map_intr), DEVMETHOD(pic_setup_intr, arm_gic_setup_intr), DEVMETHOD(pic_teardown_intr, arm_gic_teardown_intr), DEVMETHOD(pic_post_filter, arm_gic_post_filter), DEVMETHOD(pic_post_ithread, arm_gic_post_ithread), DEVMETHOD(pic_pre_ithread, arm_gic_pre_ithread), #ifdef SMP DEVMETHOD(pic_bind_intr, arm_gic_bind_intr), DEVMETHOD(pic_init_secondary, arm_gic_init_secondary), DEVMETHOD(pic_ipi_send, arm_gic_ipi_send), DEVMETHOD(pic_ipi_setup, arm_gic_ipi_setup), #endif #endif { 0, 0 } }; static driver_t arm_gic_driver = { "gic", arm_gic_methods, sizeof(struct arm_gic_softc), }; static devclass_t arm_gic_devclass; EARLY_DRIVER_MODULE(gic, simplebus, arm_gic_driver, arm_gic_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); EARLY_DRIVER_MODULE(gic, ofwbus, arm_gic_driver, arm_gic_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); #ifdef INTRNG /* * GICv2m support -- the GICv2 MSI/MSI-X controller. */ #define GICV2M_MSI_TYPER 0x008 #define MSI_TYPER_SPI_BASE(x) (((x) >> 16) & 0x3ff) #define MSI_TYPER_SPI_COUNT(x) (((x) >> 0) & 0x3ff) #define GICv2M_MSI_SETSPI_NS 0x040 #define GICV2M_MSI_IIDR 0xFCC struct arm_gicv2m_softc { struct resource *sc_mem; struct mtx sc_mutex; u_int sc_spi_start; u_int sc_spi_count; u_int sc_spi_offset; }; static struct ofw_compat_data gicv2m_compat_data[] = { {"arm,gic-v2m-frame", true}, {NULL, false} }; static int arm_gicv2m_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, gicv2m_compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "ARM Generic Interrupt Controller MSI/MSIX"); return (BUS_PROBE_DEFAULT); } static int arm_gicv2m_attach(device_t dev) { struct arm_gicv2m_softc *sc; uint32_t typer; int rid; sc = device_get_softc(dev); rid = 0; sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem == NULL) { device_printf(dev, "Unable to allocate resources\n"); return (ENXIO); } typer = bus_read_4(sc->sc_mem, GICV2M_MSI_TYPER); sc->sc_spi_start = MSI_TYPER_SPI_BASE(typer); sc->sc_spi_count = MSI_TYPER_SPI_COUNT(typer); mtx_init(&sc->sc_mutex, "GICv2m lock", "", MTX_DEF); if (bootverbose) device_printf(dev, "using spi %u to %u\n", sc->sc_spi_start, sc->sc_spi_start + sc->sc_spi_count - 1); return (0); } static device_method_t arm_gicv2m_methods[] = { /* Device interface */ DEVMETHOD(device_probe, arm_gicv2m_probe), DEVMETHOD(device_attach, arm_gicv2m_attach), /* End */ DEVMETHOD_END }; DEFINE_CLASS_0(gicv2m, arm_gicv2m_driver, arm_gicv2m_methods, sizeof(struct arm_gicv2m_softc)); static devclass_t arm_gicv2m_devclass; EARLY_DRIVER_MODULE(gicv2m, gic, arm_gicv2m_driver, arm_gicv2m_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); #endif Index: user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2835_gpio.c =================================================================== --- user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2835_gpio.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2835_gpio.c (revision 299127) @@ -1,1407 +1,1411 @@ /*- * Copyright (c) 2012 Oleksandr Tymoshenko * Copyright (c) 2012-2015 Luiz Otavio O Souza * 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. * */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #ifdef INTRNG #include "pic_if.h" #endif #ifdef DEBUG #define dprintf(fmt, args...) do { printf("%s(): ", __func__); \ printf(fmt,##args); } while (0) #else #define dprintf(fmt, args...) #endif #define BCM_GPIO_IRQS 4 #define BCM_GPIO_PINS 54 #define BCM_GPIO_PINS_PER_BANK 32 #define BCM_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN) static struct resource_spec bcm_gpio_res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, /* bank 0 interrupt */ { SYS_RES_IRQ, 1, RF_ACTIVE }, /* bank 1 interrupt */ { SYS_RES_IRQ, 2, RF_ACTIVE }, /* bank 1 interrupt (mirrored) */ { SYS_RES_IRQ, 3, RF_ACTIVE }, /* bank 0-1 interrupt (united) */ { -1, 0, 0 } }; struct bcm_gpio_sysctl { struct bcm_gpio_softc *sc; uint32_t pin; }; #ifdef INTRNG struct bcm_gpio_irqsrc { struct intr_irqsrc bgi_isrc; uint32_t bgi_irq; uint32_t bgi_reg; uint32_t bgi_mask; }; #endif struct bcm_gpio_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource * sc_res[BCM_GPIO_IRQS + 1]; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; void * sc_intrhand[BCM_GPIO_IRQS]; int sc_gpio_npins; int sc_ro_npins; int sc_ro_pins[BCM_GPIO_PINS]; struct gpio_pin sc_gpio_pins[BCM_GPIO_PINS]; #ifndef INTRNG struct intr_event * sc_events[BCM_GPIO_PINS]; #endif struct bcm_gpio_sysctl sc_sysctl[BCM_GPIO_PINS]; #ifdef INTRNG struct bcm_gpio_irqsrc sc_isrcs[BCM_GPIO_PINS]; #else enum intr_trigger sc_irq_trigger[BCM_GPIO_PINS]; enum intr_polarity sc_irq_polarity[BCM_GPIO_PINS]; #endif }; enum bcm_gpio_pud { BCM_GPIO_NONE, BCM_GPIO_PULLDOWN, BCM_GPIO_PULLUP, }; #define BCM_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define BCM_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define BCM_GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define BCM_GPIO_WRITE(_sc, _off, _val) \ bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, _off, _val) #define BCM_GPIO_READ(_sc, _off) \ bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, _off) #define BCM_GPIO_CLEAR_BITS(_sc, _off, _bits) \ BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) & ~(_bits)) #define BCM_GPIO_SET_BITS(_sc, _off, _bits) \ BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) | _bits) #define BCM_GPIO_BANK(a) (a / BCM_GPIO_PINS_PER_BANK) #define BCM_GPIO_MASK(a) (1U << (a % BCM_GPIO_PINS_PER_BANK)) #define BCM_GPIO_GPFSEL(_bank) (0x00 + _bank * 4) /* Function Select */ #define BCM_GPIO_GPSET(_bank) (0x1c + _bank * 4) /* Pin Out Set */ #define BCM_GPIO_GPCLR(_bank) (0x28 + _bank * 4) /* Pin Out Clear */ #define BCM_GPIO_GPLEV(_bank) (0x34 + _bank * 4) /* Pin Level */ #define BCM_GPIO_GPEDS(_bank) (0x40 + _bank * 4) /* Event Status */ #define BCM_GPIO_GPREN(_bank) (0x4c + _bank * 4) /* Rising Edge irq */ #define BCM_GPIO_GPFEN(_bank) (0x58 + _bank * 4) /* Falling Edge irq */ #define BCM_GPIO_GPHEN(_bank) (0x64 + _bank * 4) /* High Level irq */ #define BCM_GPIO_GPLEN(_bank) (0x70 + _bank * 4) /* Low Level irq */ #define BCM_GPIO_GPAREN(_bank) (0x7c + _bank * 4) /* Async Rising Edge */ #define BCM_GPIO_GPAFEN(_bank) (0x88 + _bank * 4) /* Async Falling Egde */ #define BCM_GPIO_GPPUD(_bank) (0x94) /* Pin Pull up/down */ #define BCM_GPIO_GPPUDCLK(_bank) (0x98 + _bank * 4) /* Pin Pull up clock */ static struct bcm_gpio_softc *bcm_gpio_sc = NULL; #ifdef INTRNG static int bcm_gpio_intr_bank0(void *arg); static int bcm_gpio_intr_bank1(void *arg); static int bcm_gpio_pic_attach(struct bcm_gpio_softc *sc); static int bcm_gpio_pic_detach(struct bcm_gpio_softc *sc); #endif static int bcm_gpio_pin_is_ro(struct bcm_gpio_softc *sc, int pin) { int i; for (i = 0; i < sc->sc_ro_npins; i++) if (pin == sc->sc_ro_pins[i]) return (1); return (0); } static uint32_t bcm_gpio_get_function(struct bcm_gpio_softc *sc, uint32_t pin) { uint32_t bank, func, offset; /* Five banks, 10 pins per bank, 3 bits per pin. */ bank = pin / 10; offset = (pin - bank * 10) * 3; BCM_GPIO_LOCK(sc); func = (BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank)) >> offset) & 7; BCM_GPIO_UNLOCK(sc); return (func); } static void bcm_gpio_func_str(uint32_t nfunc, char *buf, int bufsize) { switch (nfunc) { case BCM_GPIO_INPUT: strncpy(buf, "input", bufsize); break; case BCM_GPIO_OUTPUT: strncpy(buf, "output", bufsize); break; case BCM_GPIO_ALT0: strncpy(buf, "alt0", bufsize); break; case BCM_GPIO_ALT1: strncpy(buf, "alt1", bufsize); break; case BCM_GPIO_ALT2: strncpy(buf, "alt2", bufsize); break; case BCM_GPIO_ALT3: strncpy(buf, "alt3", bufsize); break; case BCM_GPIO_ALT4: strncpy(buf, "alt4", bufsize); break; case BCM_GPIO_ALT5: strncpy(buf, "alt5", bufsize); break; default: strncpy(buf, "invalid", bufsize); } } static int bcm_gpio_str_func(char *func, uint32_t *nfunc) { if (strcasecmp(func, "input") == 0) *nfunc = BCM_GPIO_INPUT; else if (strcasecmp(func, "output") == 0) *nfunc = BCM_GPIO_OUTPUT; else if (strcasecmp(func, "alt0") == 0) *nfunc = BCM_GPIO_ALT0; else if (strcasecmp(func, "alt1") == 0) *nfunc = BCM_GPIO_ALT1; else if (strcasecmp(func, "alt2") == 0) *nfunc = BCM_GPIO_ALT2; else if (strcasecmp(func, "alt3") == 0) *nfunc = BCM_GPIO_ALT3; else if (strcasecmp(func, "alt4") == 0) *nfunc = BCM_GPIO_ALT4; else if (strcasecmp(func, "alt5") == 0) *nfunc = BCM_GPIO_ALT5; else return (-1); return (0); } static uint32_t bcm_gpio_func_flag(uint32_t nfunc) { switch (nfunc) { case BCM_GPIO_INPUT: return (GPIO_PIN_INPUT); case BCM_GPIO_OUTPUT: return (GPIO_PIN_OUTPUT); } return (0); } static void bcm_gpio_set_function(struct bcm_gpio_softc *sc, uint32_t pin, uint32_t f) { uint32_t bank, data, offset; /* Must be called with lock held. */ BCM_GPIO_LOCK_ASSERT(sc); /* Five banks, 10 pins per bank, 3 bits per pin. */ bank = pin / 10; offset = (pin - bank * 10) * 3; data = BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank)); data &= ~(7 << offset); data |= (f << offset); BCM_GPIO_WRITE(sc, BCM_GPIO_GPFSEL(bank), data); } static void bcm_gpio_set_pud(struct bcm_gpio_softc *sc, uint32_t pin, uint32_t state) { uint32_t bank; /* Must be called with lock held. */ BCM_GPIO_LOCK_ASSERT(sc); bank = BCM_GPIO_BANK(pin); BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUD(0), state); BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUDCLK(bank), BCM_GPIO_MASK(pin)); BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUD(0), 0); BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUDCLK(bank), 0); } void bcm_gpio_set_alternate(device_t dev, uint32_t pin, uint32_t nfunc) { struct bcm_gpio_softc *sc; int i; sc = device_get_softc(dev); BCM_GPIO_LOCK(sc); /* Disable pull-up or pull-down on pin. */ bcm_gpio_set_pud(sc, pin, BCM_GPIO_NONE); /* And now set the pin function. */ bcm_gpio_set_function(sc, pin, nfunc); /* Update the pin flags. */ for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i < sc->sc_gpio_npins) sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(nfunc); BCM_GPIO_UNLOCK(sc); } static void bcm_gpio_pin_configure(struct bcm_gpio_softc *sc, struct gpio_pin *pin, unsigned int flags) { BCM_GPIO_LOCK(sc); /* * Manage input/output. */ if (flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) { pin->gp_flags &= ~(GPIO_PIN_INPUT|GPIO_PIN_OUTPUT); if (flags & GPIO_PIN_OUTPUT) { pin->gp_flags |= GPIO_PIN_OUTPUT; bcm_gpio_set_function(sc, pin->gp_pin, BCM_GPIO_OUTPUT); } else { pin->gp_flags |= GPIO_PIN_INPUT; bcm_gpio_set_function(sc, pin->gp_pin, BCM_GPIO_INPUT); } } /* Manage Pull-up/pull-down. */ pin->gp_flags &= ~(GPIO_PIN_PULLUP|GPIO_PIN_PULLDOWN); if (flags & (GPIO_PIN_PULLUP|GPIO_PIN_PULLDOWN)) { if (flags & GPIO_PIN_PULLUP) { pin->gp_flags |= GPIO_PIN_PULLUP; bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_PULLUP); } else { pin->gp_flags |= GPIO_PIN_PULLDOWN; bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_PULLDOWN); } } else bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_NONE); BCM_GPIO_UNLOCK(sc); } static device_t bcm_gpio_get_bus(device_t dev) { struct bcm_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int bcm_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = BCM_GPIO_PINS - 1; return (0); } static int bcm_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); *caps = sc->sc_gpio_pins[i].gp_caps; BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); *flags = sc->sc_gpio_pins[i].gp_flags; BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); memcpy(name, sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never touch on read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); bcm_gpio_pin_configure(sc, &sc->sc_gpio_pins[i], flags); return (0); } static int bcm_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, reg; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never write to read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); BCM_GPIO_LOCK(sc); bank = BCM_GPIO_BANK(pin); if (value) reg = BCM_GPIO_GPSET(bank); else reg = BCM_GPIO_GPCLR(bank); BCM_GPIO_WRITE(sc, reg, BCM_GPIO_MASK(pin)); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, reg_data; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); bank = BCM_GPIO_BANK(pin); BCM_GPIO_LOCK(sc); reg_data = BCM_GPIO_READ(sc, BCM_GPIO_GPLEV(bank)); BCM_GPIO_UNLOCK(sc); *val = (reg_data & BCM_GPIO_MASK(pin)) ? 1 : 0; return (0); } static int bcm_gpio_pin_toggle(device_t dev, uint32_t pin) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, data, reg; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never write to read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); BCM_GPIO_LOCK(sc); bank = BCM_GPIO_BANK(pin); data = BCM_GPIO_READ(sc, BCM_GPIO_GPLEV(bank)); if (data & BCM_GPIO_MASK(pin)) reg = BCM_GPIO_GPCLR(bank); else reg = BCM_GPIO_GPSET(bank); BCM_GPIO_WRITE(sc, reg, BCM_GPIO_MASK(pin)); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_func_proc(SYSCTL_HANDLER_ARGS) { char buf[16]; struct bcm_gpio_softc *sc; struct bcm_gpio_sysctl *sc_sysctl; uint32_t nfunc; int error; sc_sysctl = arg1; sc = sc_sysctl->sc; /* Get the current pin function. */ nfunc = bcm_gpio_get_function(sc, sc_sysctl->pin); bcm_gpio_func_str(nfunc, buf, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Ignore changes on read-only pins. */ if (bcm_gpio_pin_is_ro(sc, sc_sysctl->pin)) return (0); /* Parse the user supplied string and check for a valid pin function. */ if (bcm_gpio_str_func(buf, &nfunc) != 0) return (EINVAL); /* Update the pin alternate function. */ bcm_gpio_set_alternate(sc->sc_dev, sc_sysctl->pin, nfunc); return (0); } static void bcm_gpio_sysctl_init(struct bcm_gpio_softc *sc) { char pinbuf[3]; struct bcm_gpio_sysctl *sc_sysctl; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node, *pin_node, *pinN_node; struct sysctl_oid_list *tree, *pin_tree, *pinN_tree; int i; /* * Add per-pin sysctl tree/handlers. */ ctx = device_get_sysctl_ctx(sc->sc_dev); tree_node = device_get_sysctl_tree(sc->sc_dev); tree = SYSCTL_CHILDREN(tree_node); pin_node = SYSCTL_ADD_NODE(ctx, tree, OID_AUTO, "pin", CTLFLAG_RD, NULL, "GPIO Pins"); pin_tree = SYSCTL_CHILDREN(pin_node); for (i = 0; i < sc->sc_gpio_npins; i++) { snprintf(pinbuf, sizeof(pinbuf), "%d", i); pinN_node = SYSCTL_ADD_NODE(ctx, pin_tree, OID_AUTO, pinbuf, CTLFLAG_RD, NULL, "GPIO Pin"); pinN_tree = SYSCTL_CHILDREN(pinN_node); sc->sc_sysctl[i].sc = sc; sc_sysctl = &sc->sc_sysctl[i]; sc_sysctl->sc = sc; sc_sysctl->pin = sc->sc_gpio_pins[i].gp_pin; SYSCTL_ADD_PROC(ctx, pinN_tree, OID_AUTO, "function", CTLFLAG_RW | CTLTYPE_STRING, sc_sysctl, sizeof(struct bcm_gpio_sysctl), bcm_gpio_func_proc, "A", "Pin Function"); } } static int bcm_gpio_get_ro_pins(struct bcm_gpio_softc *sc, phandle_t node, const char *propname, const char *label) { int i, need_comma, npins, range_start, range_stop; pcell_t *pins; /* Get the property data. */ npins = OF_getencprop_alloc(node, propname, sizeof(*pins), (void **)&pins); if (npins < 0) return (-1); if (npins == 0) { free(pins, M_OFWPROP); return (0); } for (i = 0; i < npins; i++) sc->sc_ro_pins[i + sc->sc_ro_npins] = pins[i]; sc->sc_ro_npins += npins; need_comma = 0; device_printf(sc->sc_dev, "%s pins: ", label); range_start = range_stop = pins[0]; for (i = 1; i < npins; i++) { if (pins[i] != range_stop + 1) { if (need_comma) printf(","); if (range_start != range_stop) printf("%d-%d", range_start, range_stop); else printf("%d", range_start); range_start = range_stop = pins[i]; need_comma = 1; } else range_stop++; } if (need_comma) printf(","); if (range_start != range_stop) printf("%d-%d.\n", range_start, range_stop); else printf("%d.\n", range_start); free(pins, M_OFWPROP); return (0); } static int bcm_gpio_get_reserved_pins(struct bcm_gpio_softc *sc) { char *name; phandle_t gpio, node, reserved; ssize_t len; /* Get read-only pins. */ gpio = ofw_bus_get_node(sc->sc_dev); if (bcm_gpio_get_ro_pins(sc, gpio, "broadcom,read-only", "read-only") != 0) return (-1); /* Traverse the GPIO subnodes to find the reserved pins node. */ reserved = 0; node = OF_child(gpio); while ((node != 0) && (reserved == 0)) { len = OF_getprop_alloc(node, "name", 1, (void **)&name); if (len == -1) return (-1); if (strcmp(name, "reserved") == 0) reserved = node; free(name, M_OFWPROP); node = OF_peer(node); } if (reserved == 0) return (-1); /* Get the reserved pins. */ if (bcm_gpio_get_ro_pins(sc, reserved, "broadcom,pins", "reserved") != 0) return (-1); return (0); } #ifndef INTRNG static int bcm_gpio_intr(void *arg) { int bank_last, irq; struct bcm_gpio_softc *sc; struct intr_event *event; uint32_t bank, mask, reg; sc = (struct bcm_gpio_softc *)arg; reg = 0; bank_last = -1; for (irq = 0; irq < BCM_GPIO_PINS; irq++) { bank = BCM_GPIO_BANK(irq); mask = BCM_GPIO_MASK(irq); if (bank != bank_last) { reg = BCM_GPIO_READ(sc, BCM_GPIO_GPEDS(bank)); bank_last = bank; } if (reg & mask) { event = sc->sc_events[irq]; if (event != NULL && !TAILQ_EMPTY(&event->ie_handlers)) intr_event_handle(event, NULL); else { device_printf(sc->sc_dev, "Stray IRQ %d\n", irq); } /* Clear the Status bit by writing '1' to it. */ BCM_GPIO_WRITE(sc, BCM_GPIO_GPEDS(bank), mask); } } return (FILTER_HANDLED); } #endif static int bcm_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "broadcom,bcm2835-gpio")) return (ENXIO); device_set_desc(dev, "BCM2708/2835 GPIO controller"); return (BUS_PROBE_DEFAULT); } #ifdef INTRNG static int bcm_gpio_intr_attach(device_t dev) { struct bcm_gpio_softc *sc; /* * Only first two interrupt lines are used. Third line is * mirrored second line and forth line is common for all banks. */ sc = device_get_softc(dev); if (sc->sc_res[1] == NULL || sc->sc_res[2] == NULL) return (-1); if (bcm_gpio_pic_attach(sc) != 0) { device_printf(dev, "unable to attach PIC\n"); return (-1); } if (bus_setup_intr(dev, sc->sc_res[1], INTR_TYPE_MISC | INTR_MPSAFE, bcm_gpio_intr_bank0, NULL, sc, &sc->sc_intrhand[0]) != 0) return (-1); if (bus_setup_intr(dev, sc->sc_res[2], INTR_TYPE_MISC | INTR_MPSAFE, bcm_gpio_intr_bank1, NULL, sc, &sc->sc_intrhand[1]) != 0) return (-1); return (0); } static void bcm_gpio_intr_detach(device_t dev) { struct bcm_gpio_softc *sc; sc = device_get_softc(dev); if (sc->sc_intrhand[0] != NULL) bus_teardown_intr(dev, sc->sc_res[1], sc->sc_intrhand[0]); if (sc->sc_intrhand[1] != NULL) bus_teardown_intr(dev, sc->sc_res[2], sc->sc_intrhand[1]); bcm_gpio_pic_detach(sc); } #else static int bcm_gpio_intr_attach(device_t dev) { struct bcm_gpio_softc *sc; int i; sc = device_get_softc(dev); for (i = 0; i < BCM_GPIO_IRQS; i++) { if (bus_setup_intr(dev, sc->sc_res[i + 1], INTR_TYPE_MISC | INTR_MPSAFE, bcm_gpio_intr, NULL, sc, &sc->sc_intrhand[i]) != 0) { return (-1); } } return (0); } static void bcm_gpio_intr_detach(device_t dev) { struct bcm_gpio_softc *sc; int i; sc = device_get_softc(dev); for (i = 0; i < BCM_GPIO_IRQS; i++) { if (sc->sc_intrhand[i]) { bus_teardown_intr(dev, sc->sc_res[i + 1], sc->sc_intrhand[i]); } } } #endif static int bcm_gpio_attach(device_t dev) { int i, j; phandle_t gpio; struct bcm_gpio_softc *sc; uint32_t func; if (bcm_gpio_sc != NULL) return (ENXIO); bcm_gpio_sc = sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "bcm gpio", "gpio", MTX_SPIN); if (bus_alloc_resources(dev, bcm_gpio_res_spec, sc->sc_res) != 0) { device_printf(dev, "cannot allocate resources\n"); goto fail; } sc->sc_bst = rman_get_bustag(sc->sc_res[0]); sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]); /* Setup the GPIO interrupt handler. */ if (bcm_gpio_intr_attach(dev)) { device_printf(dev, "unable to setup the gpio irq handler\n"); goto fail; } /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(gpio, "gpio-controller")) /* Node is not a GPIO controller. */ goto fail; /* * Find the read-only pins. These are pins we never touch or bad * things could happen. */ if (bcm_gpio_get_reserved_pins(sc) == -1) goto fail; /* Initialize the software controlled pins. */ for (i = 0, j = 0; j < BCM_GPIO_PINS; j++) { snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME, "pin %d", j); func = bcm_gpio_get_function(sc, j); sc->sc_gpio_pins[i].gp_pin = j; sc->sc_gpio_pins[i].gp_caps = BCM_GPIO_DEFAULT_CAPS; sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(func); #ifndef INTRNG /* The default is active-low interrupts. */ sc->sc_irq_trigger[i] = INTR_TRIGGER_LEVEL; sc->sc_irq_polarity[i] = INTR_POLARITY_LOW; #endif i++; } sc->sc_gpio_npins = i; bcm_gpio_sysctl_init(sc); sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) goto fail; return (0); fail: bcm_gpio_intr_detach(dev); bus_release_resources(dev, bcm_gpio_res_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); return (ENXIO); } static int bcm_gpio_detach(device_t dev) { return (EBUSY); } #ifdef INTRNG static inline void bcm_gpio_isrc_eoi(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { uint32_t bank; /* Write 1 to clear. */ bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_WRITE(sc, BCM_GPIO_GPEDS(bank), bgi->bgi_mask); } static inline bool bcm_gpio_isrc_is_level(struct bcm_gpio_irqsrc *bgi) { uint32_t bank; bank = BCM_GPIO_BANK(bgi->bgi_irq); return (bgi->bgi_reg == BCM_GPIO_GPHEN(bank) || bgi->bgi_reg == BCM_GPIO_GPLEN(bank)); } static inline void bcm_gpio_isrc_mask(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { BCM_GPIO_LOCK(sc); BCM_GPIO_CLEAR_BITS(sc, bgi->bgi_reg, bgi->bgi_mask); BCM_GPIO_UNLOCK(bcm_gpio_sc); } static inline void bcm_gpio_isrc_unmask(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { BCM_GPIO_LOCK(sc); BCM_GPIO_SET_BITS(sc, bgi->bgi_reg, bgi->bgi_mask); BCM_GPIO_UNLOCK(sc); } static int bcm_gpio_intr_internal(struct bcm_gpio_softc *sc, uint32_t bank) { u_int irq; struct bcm_gpio_irqsrc *bgi; uint32_t reg; /* Do not care of spurious interrupt on GPIO. */ reg = BCM_GPIO_READ(sc, BCM_GPIO_GPEDS(bank)); while (reg != 0) { irq = BCM_GPIO_PINS_PER_BANK * bank + ffs(reg) - 1; bgi = sc->sc_isrcs + irq; if (!bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); if (intr_isrc_dispatch(&bgi->bgi_isrc, curthread->td_intr_frame) != 0) { bcm_gpio_isrc_mask(sc, bgi); if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq); } reg &= ~bgi->bgi_mask; } return (FILTER_HANDLED); } static int bcm_gpio_intr_bank0(void *arg) { return (bcm_gpio_intr_internal(arg, 0)); } static int bcm_gpio_intr_bank1(void *arg) { return (bcm_gpio_intr_internal(arg, 1)); } static int bcm_gpio_pic_attach(struct bcm_gpio_softc *sc) { int error; uint32_t irq; const char *name; name = device_get_nameunit(sc->sc_dev); for (irq = 0; irq < BCM_GPIO_PINS; irq++) { sc->sc_isrcs[irq].bgi_irq = irq; sc->sc_isrcs[irq].bgi_mask = BCM_GPIO_MASK(irq); sc->sc_isrcs[irq].bgi_reg = 0; error = intr_isrc_register(&sc->sc_isrcs[irq].bgi_isrc, sc->sc_dev, 0, "%s,%u", name, irq); if (error != 0) return (error); /* XXX deregister ISRCs */ } return (intr_pic_register(sc->sc_dev, OF_xref_from_node(ofw_bus_get_node(sc->sc_dev)))); } static int bcm_gpio_pic_detach(struct bcm_gpio_softc *sc) { /* * There has not been established any procedure yet * how to detach PIC from living system correctly. */ device_printf(sc->sc_dev, "%s: not implemented yet\n", __func__); return (EBUSY); } static void bcm_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; bcm_gpio_isrc_mask(sc, bgi); } static void bcm_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; arm_irq_memory_barrier(bgi->bgi_irq); bcm_gpio_isrc_unmask(sc, bgi); } static int bcm_gpio_pic_map_fdt(struct bcm_gpio_softc *sc, u_int ncells, pcell_t *cells, u_int *irqp, uint32_t *regp) { u_int irq; uint32_t reg, bank; /* * The first cell is the interrupt number. * The second cell is used to specify flags: * bits[3:0] trigger type and level flags: * 1 = low-to-high edge triggered. * 2 = high-to-low edge triggered. * 4 = active high level-sensitive. * 8 = active low level-sensitive. */ if (ncells != 2) return (EINVAL); irq = cells[0]; if (irq >= BCM_GPIO_PINS || bcm_gpio_pin_is_ro(sc, irq)) return (EINVAL); /* * All interrupt types could be set for an interrupt at one moment. * At least, the combination of 'low-to-high' and 'high-to-low' edge * triggered interrupt types can make a sense. However, no combo is * supported now. */ bank = BCM_GPIO_BANK(irq); if (cells[1] == 1) reg = BCM_GPIO_GPREN(bank); else if (cells[1] == 2) reg = BCM_GPIO_GPFEN(bank); else if (cells[1] == 4) reg = BCM_GPIO_GPHEN(bank); else if (cells[1] == 8) reg = BCM_GPIO_GPLEN(bank); else return (EINVAL); *irqp = irq; if (regp != NULL) *regp = reg; return (0); } static int bcm_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int error; u_int irq; + struct intr_map_data_fdt *daf; struct bcm_gpio_softc *sc; if (data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); sc = device_get_softc(dev); - error = bcm_gpio_pic_map_fdt(sc, data->fdt.ncells, data->fdt.cells, - &irq, NULL); + daf = (struct intr_map_data_fdt *)data; + + error = bcm_gpio_pic_map_fdt(sc, daf->ncells, daf->cells, &irq, NULL); if (error == 0) *isrcp = &sc->sc_isrcs[irq].bgi_isrc; return (error); } static void bcm_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); } static void bcm_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { bcm_gpio_pic_enable_intr(dev, isrc); } static void bcm_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; bcm_gpio_isrc_mask(sc, bgi); if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); } static int bcm_gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { u_int irq; uint32_t bank, reg; struct bcm_gpio_softc *sc; struct bcm_gpio_irqsrc *bgi; + struct intr_map_data_fdt *daf; if (data == NULL || data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); sc = device_get_softc(dev); bgi = (struct bcm_gpio_irqsrc *)isrc; + daf = (struct intr_map_data_fdt *)data; /* Get and check config for an interrupt. */ - if (bcm_gpio_pic_map_fdt(sc, data->fdt.ncells, data->fdt.cells, &irq, + if (bcm_gpio_pic_map_fdt(sc, daf->ncells, daf->cells, &irq, ®) != 0 || bgi->bgi_irq != irq) return (EINVAL); /* * If this is a setup for another handler, * only check that its configuration match. */ if (isrc->isrc_handlers != 0) return (bgi->bgi_reg == reg ? 0 : EINVAL); bank = BCM_GPIO_BANK(irq); BCM_GPIO_LOCK(sc); BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPHEN(bank), bgi->bgi_mask); BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPLEN(bank), bgi->bgi_mask); bgi->bgi_reg = reg; BCM_GPIO_SET_BITS(sc, reg, bgi->bgi_mask); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; if (isrc->isrc_handlers == 0) { BCM_GPIO_LOCK(sc); BCM_GPIO_CLEAR_BITS(sc, bgi->bgi_reg, bgi->bgi_mask); bgi->bgi_reg = 0; BCM_GPIO_UNLOCK(sc); } return (0); } #else static uint32_t bcm_gpio_intr_reg(struct bcm_gpio_softc *sc, unsigned int irq, uint32_t bank) { if (irq > BCM_GPIO_PINS) return (0); if (sc->sc_irq_trigger[irq] == INTR_TRIGGER_LEVEL) { if (sc->sc_irq_polarity[irq] == INTR_POLARITY_LOW) return (BCM_GPIO_GPLEN(bank)); else if (sc->sc_irq_polarity[irq] == INTR_POLARITY_HIGH) return (BCM_GPIO_GPHEN(bank)); } else if (sc->sc_irq_trigger[irq] == INTR_TRIGGER_EDGE) { if (sc->sc_irq_polarity[irq] == INTR_POLARITY_LOW) return (BCM_GPIO_GPFEN(bank)); else if (sc->sc_irq_polarity[irq] == INTR_POLARITY_HIGH) return (BCM_GPIO_GPREN(bank)); } return (0); } static void bcm_gpio_mask_irq(void *source) { uint32_t bank, mask, reg; unsigned int irq; irq = (unsigned int)source; if (irq > BCM_GPIO_PINS) return; if (bcm_gpio_pin_is_ro(bcm_gpio_sc, irq)) return; bank = BCM_GPIO_BANK(irq); mask = BCM_GPIO_MASK(irq); BCM_GPIO_LOCK(bcm_gpio_sc); reg = bcm_gpio_intr_reg(bcm_gpio_sc, irq, bank); if (reg != 0) BCM_GPIO_CLEAR_BITS(bcm_gpio_sc, reg, mask); BCM_GPIO_UNLOCK(bcm_gpio_sc); } static void bcm_gpio_unmask_irq(void *source) { uint32_t bank, mask, reg; unsigned int irq; irq = (unsigned int)source; if (irq > BCM_GPIO_PINS) return; if (bcm_gpio_pin_is_ro(bcm_gpio_sc, irq)) return; bank = BCM_GPIO_BANK(irq); mask = BCM_GPIO_MASK(irq); BCM_GPIO_LOCK(bcm_gpio_sc); reg = bcm_gpio_intr_reg(bcm_gpio_sc, irq, bank); if (reg != 0) BCM_GPIO_SET_BITS(bcm_gpio_sc, reg, mask); BCM_GPIO_UNLOCK(bcm_gpio_sc); } static int bcm_gpio_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { int pin; if (type != SYS_RES_IRQ) return (ENXIO); /* Unmask the interrupt. */ pin = rman_get_start(res); bcm_gpio_unmask_irq((void *)pin); return (0); } static int bcm_gpio_deactivate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { int pin; if (type != SYS_RES_IRQ) return (ENXIO); /* Mask the interrupt. */ pin = rman_get_start(res); bcm_gpio_mask_irq((void *)pin); return (0); } static int bcm_gpio_config_intr(device_t dev, int irq, enum intr_trigger trig, enum intr_polarity pol) { int bank; struct bcm_gpio_softc *sc; uint32_t mask, oldreg, reg; if (irq > BCM_GPIO_PINS) return (EINVAL); /* There is no standard trigger or polarity. */ if (trig == INTR_TRIGGER_CONFORM || pol == INTR_POLARITY_CONFORM) return (EINVAL); sc = device_get_softc(dev); if (bcm_gpio_pin_is_ro(sc, irq)) return (EINVAL); bank = BCM_GPIO_BANK(irq); mask = BCM_GPIO_MASK(irq); BCM_GPIO_LOCK(sc); oldreg = bcm_gpio_intr_reg(sc, irq, bank); sc->sc_irq_trigger[irq] = trig; sc->sc_irq_polarity[irq] = pol; reg = bcm_gpio_intr_reg(sc, irq, bank); if (reg != 0) BCM_GPIO_SET_BITS(sc, reg, mask); if (reg != oldreg && oldreg != 0) BCM_GPIO_CLEAR_BITS(sc, oldreg, mask); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_setup_intr(device_t bus, device_t child, struct resource *ires, int flags, driver_filter_t *filt, driver_intr_t *handler, void *arg, void **cookiep) { struct bcm_gpio_softc *sc; struct intr_event *event; int pin, error; sc = device_get_softc(bus); pin = rman_get_start(ires); if (pin > BCM_GPIO_PINS) panic("%s: bad pin %d", __func__, pin); event = sc->sc_events[pin]; if (event == NULL) { error = intr_event_create(&event, (void *)pin, 0, pin, bcm_gpio_mask_irq, bcm_gpio_unmask_irq, NULL, NULL, "gpio%d pin%d:", device_get_unit(bus), pin); if (error != 0) return (error); sc->sc_events[pin] = event; } intr_event_add_handler(event, device_get_nameunit(child), filt, handler, arg, intr_priority(flags), flags, cookiep); return (0); } static int bcm_gpio_teardown_intr(device_t dev, device_t child, struct resource *ires, void *cookie) { struct bcm_gpio_softc *sc; int pin, err; sc = device_get_softc(dev); pin = rman_get_start(ires); if (pin > BCM_GPIO_PINS) panic("%s: bad pin %d", __func__, pin); if (sc->sc_events[pin] == NULL) panic("Trying to teardown unoccupied IRQ"); err = intr_event_remove_handler(cookie); if (!err) sc->sc_events[pin] = NULL; return (err); } #endif static phandle_t bcm_gpio_get_node(device_t bus, device_t dev) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } static device_method_t bcm_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bcm_gpio_probe), DEVMETHOD(device_attach, bcm_gpio_attach), DEVMETHOD(device_detach, bcm_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, bcm_gpio_get_bus), DEVMETHOD(gpio_pin_max, bcm_gpio_pin_max), DEVMETHOD(gpio_pin_getname, bcm_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, bcm_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, bcm_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, bcm_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, bcm_gpio_pin_get), DEVMETHOD(gpio_pin_set, bcm_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, bcm_gpio_pin_toggle), #ifdef INTRNG /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, bcm_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, bcm_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, bcm_gpio_pic_map_intr), DEVMETHOD(pic_post_filter, bcm_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, bcm_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, bcm_gpio_pic_pre_ithread), DEVMETHOD(pic_setup_intr, bcm_gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, bcm_gpio_pic_teardown_intr), #else /* Bus interface */ DEVMETHOD(bus_activate_resource, bcm_gpio_activate_resource), DEVMETHOD(bus_deactivate_resource, bcm_gpio_deactivate_resource), DEVMETHOD(bus_config_intr, bcm_gpio_config_intr), DEVMETHOD(bus_setup_intr, bcm_gpio_setup_intr), DEVMETHOD(bus_teardown_intr, bcm_gpio_teardown_intr), #endif /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, bcm_gpio_get_node), DEVMETHOD_END }; static devclass_t bcm_gpio_devclass; static driver_t bcm_gpio_driver = { "gpio", bcm_gpio_methods, sizeof(struct bcm_gpio_softc), }; DRIVER_MODULE(bcm_gpio, simplebus, bcm_gpio_driver, bcm_gpio_devclass, 0, 0); Index: user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2835_intr.c =================================================================== --- user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2835_intr.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2835_intr.c (revision 299127) @@ -1,537 +1,540 @@ /*- * Copyright (c) 2012 Damjan Marion * All rights reserved. * * Based on OMAP3 INTC code by Ben Gray * * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SOC_BCM2836 #include #endif #ifdef INTRNG #include "pic_if.h" #endif #define INTC_PENDING_BASIC 0x00 #define INTC_PENDING_BANK1 0x04 #define INTC_PENDING_BANK2 0x08 #define INTC_FIQ_CONTROL 0x0C #define INTC_ENABLE_BANK1 0x10 #define INTC_ENABLE_BANK2 0x14 #define INTC_ENABLE_BASIC 0x18 #define INTC_DISABLE_BANK1 0x1C #define INTC_DISABLE_BANK2 0x20 #define INTC_DISABLE_BASIC 0x24 #define INTC_PENDING_BASIC_ARM 0x0000FF #define INTC_PENDING_BASIC_GPU1_PEND 0x000100 #define INTC_PENDING_BASIC_GPU2_PEND 0x000200 #define INTC_PENDING_BASIC_GPU1_7 0x000400 #define INTC_PENDING_BASIC_GPU1_9 0x000800 #define INTC_PENDING_BASIC_GPU1_10 0x001000 #define INTC_PENDING_BASIC_GPU1_18 0x002000 #define INTC_PENDING_BASIC_GPU1_19 0x004000 #define INTC_PENDING_BASIC_GPU2_21 0x008000 #define INTC_PENDING_BASIC_GPU2_22 0x010000 #define INTC_PENDING_BASIC_GPU2_23 0x020000 #define INTC_PENDING_BASIC_GPU2_24 0x040000 #define INTC_PENDING_BASIC_GPU2_25 0x080000 #define INTC_PENDING_BASIC_GPU2_30 0x100000 #define INTC_PENDING_BASIC_MASK 0x1FFFFF #define INTC_PENDING_BASIC_GPU1_MASK (INTC_PENDING_BASIC_GPU1_7 | \ INTC_PENDING_BASIC_GPU1_9 | \ INTC_PENDING_BASIC_GPU1_10 | \ INTC_PENDING_BASIC_GPU1_18 | \ INTC_PENDING_BASIC_GPU1_19) #define INTC_PENDING_BASIC_GPU2_MASK (INTC_PENDING_BASIC_GPU2_21 | \ INTC_PENDING_BASIC_GPU2_22 | \ INTC_PENDING_BASIC_GPU2_23 | \ INTC_PENDING_BASIC_GPU2_24 | \ INTC_PENDING_BASIC_GPU2_25 | \ INTC_PENDING_BASIC_GPU2_30) #define INTC_PENDING_BANK1_MASK (~((1 << 7) | (1 << 9) | (1 << 10) | \ (1 << 18) | (1 << 19))) #define INTC_PENDING_BANK2_MASK (~((1 << 21) | (1 << 22) | (1 << 23) | \ (1 << 24) | (1 << 25) | (1 << 30))) #define BANK1_START 8 #define BANK1_END (BANK1_START + 32 - 1) #define BANK2_START (BANK1_START + 32) #define BANK2_END (BANK2_START + 32 - 1) #ifndef INTRNG #define BANK3_START (BANK2_START + 32) #define BANK3_END (BANK3_START + 32 - 1) #endif #define IS_IRQ_BASIC(n) (((n) >= 0) && ((n) < BANK1_START)) #define IS_IRQ_BANK1(n) (((n) >= BANK1_START) && ((n) <= BANK1_END)) #define IS_IRQ_BANK2(n) (((n) >= BANK2_START) && ((n) <= BANK2_END)) #ifndef INTRNG #define ID_IRQ_BCM2836(n) (((n) >= BANK3_START) && ((n) <= BANK3_END)) #endif #define IRQ_BANK1(n) ((n) - BANK1_START) #define IRQ_BANK2(n) ((n) - BANK2_START) #ifdef DEBUG #define dprintf(fmt, args...) printf(fmt, ##args) #else #define dprintf(fmt, args...) #endif #ifdef INTRNG #define BCM_INTC_NIRQS 72 /* 8 + 32 + 32 */ struct bcm_intc_irqsrc { struct intr_irqsrc bii_isrc; u_int bii_irq; uint16_t bii_disable_reg; uint16_t bii_enable_reg; uint32_t bii_mask; }; #endif struct bcm_intc_softc { device_t sc_dev; struct resource * intc_res; bus_space_tag_t intc_bst; bus_space_handle_t intc_bsh; #ifdef INTRNG struct resource * intc_irq_res; void * intc_irq_hdl; struct bcm_intc_irqsrc intc_isrcs[BCM_INTC_NIRQS]; #endif }; static struct bcm_intc_softc *bcm_intc_sc = NULL; #define intc_read_4(_sc, reg) \ bus_space_read_4((_sc)->intc_bst, (_sc)->intc_bsh, (reg)) #define intc_write_4(_sc, reg, val) \ bus_space_write_4((_sc)->intc_bst, (_sc)->intc_bsh, (reg), (val)) #ifdef INTRNG static inline void bcm_intc_isrc_mask(struct bcm_intc_softc *sc, struct bcm_intc_irqsrc *bii) { intc_write_4(sc, bii->bii_disable_reg, bii->bii_mask); } static inline void bcm_intc_isrc_unmask(struct bcm_intc_softc *sc, struct bcm_intc_irqsrc *bii) { intc_write_4(sc, bii->bii_enable_reg, bii->bii_mask); } static inline int bcm2835_intc_active_intr(struct bcm_intc_softc *sc) { uint32_t pending, pending_gpu; pending = intc_read_4(sc, INTC_PENDING_BASIC) & INTC_PENDING_BASIC_MASK; if (pending == 0) return (-1); if (pending & INTC_PENDING_BASIC_ARM) return (ffs(pending) - 1); if (pending & INTC_PENDING_BASIC_GPU1_MASK) { if (pending & INTC_PENDING_BASIC_GPU1_7) return (BANK1_START + 7); if (pending & INTC_PENDING_BASIC_GPU1_9) return (BANK1_START + 9); if (pending & INTC_PENDING_BASIC_GPU1_10) return (BANK1_START + 10); if (pending & INTC_PENDING_BASIC_GPU1_18) return (BANK1_START + 18); if (pending & INTC_PENDING_BASIC_GPU1_19) return (BANK1_START + 19); } if (pending & INTC_PENDING_BASIC_GPU2_MASK) { if (pending & INTC_PENDING_BASIC_GPU2_21) return (BANK2_START + 21); if (pending & INTC_PENDING_BASIC_GPU2_22) return (BANK2_START + 22); if (pending & INTC_PENDING_BASIC_GPU2_23) return (BANK2_START + 23); if (pending & INTC_PENDING_BASIC_GPU2_24) return (BANK2_START + 24); if (pending & INTC_PENDING_BASIC_GPU2_25) return (BANK2_START + 25); if (pending & INTC_PENDING_BASIC_GPU2_30) return (BANK2_START + 30); } if (pending & INTC_PENDING_BASIC_GPU1_PEND) { pending_gpu = intc_read_4(sc, INTC_PENDING_BANK1); pending_gpu &= INTC_PENDING_BANK1_MASK; if (pending_gpu != 0) return (BANK1_START + ffs(pending_gpu) - 1); } if (pending & INTC_PENDING_BASIC_GPU2_PEND) { pending_gpu = intc_read_4(sc, INTC_PENDING_BANK2); pending_gpu &= INTC_PENDING_BANK2_MASK; if (pending_gpu != 0) return (BANK2_START + ffs(pending_gpu) - 1); } return (-1); /* It shouldn't end here, but it's hardware. */ } static int bcm2835_intc_intr(void *arg) { int irq, num; struct bcm_intc_softc *sc = arg; for (num = 0; ; num++) { irq = bcm2835_intc_active_intr(sc); if (irq == -1) break; if (intr_isrc_dispatch(&sc->intc_isrcs[irq].bii_isrc, curthread->td_intr_frame) != 0) { bcm_intc_isrc_mask(sc, &sc->intc_isrcs[irq]); device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq); } arm_irq_memory_barrier(0); /* XXX */ } if (num == 0) device_printf(sc->sc_dev, "Spurious interrupt detected\n"); return (FILTER_HANDLED); } static void bcm_intc_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct bcm_intc_irqsrc *bii = (struct bcm_intc_irqsrc *)isrc; arm_irq_memory_barrier(bii->bii_irq); bcm_intc_isrc_unmask(device_get_softc(dev), bii); } static void bcm_intc_disable_intr(device_t dev, struct intr_irqsrc *isrc) { bcm_intc_isrc_mask(device_get_softc(dev), (struct bcm_intc_irqsrc *)isrc); } static int bcm_intc_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { u_int irq; + struct intr_map_data_fdt *daf; struct bcm_intc_softc *sc; if (data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); - if (data->fdt.ncells == 1) - irq = data->fdt.cells[0]; - else if (data->fdt.ncells == 2) - irq = data->fdt.cells[0] * 32 + data->fdt.cells[1]; + + daf = (struct intr_map_data_fdt *)data; + if (daf->ncells == 1) + irq = daf->cells[0]; + else if (daf->ncells == 2) + irq = daf->cells[0] * 32 + daf->cells[1]; else return (EINVAL); if (irq >= BCM_INTC_NIRQS) return (EINVAL); sc = device_get_softc(dev); *isrcp = &sc->intc_isrcs[irq].bii_isrc; return (0); } static void bcm_intc_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { bcm_intc_disable_intr(dev, isrc); } static void bcm_intc_post_ithread(device_t dev, struct intr_irqsrc *isrc) { bcm_intc_enable_intr(dev, isrc); } static void bcm_intc_post_filter(device_t dev, struct intr_irqsrc *isrc) { } static int bcm_intc_pic_register(struct bcm_intc_softc *sc, intptr_t xref) { struct bcm_intc_irqsrc *bii; int error; uint32_t irq; const char *name; name = device_get_nameunit(sc->sc_dev); for (irq = 0; irq < BCM_INTC_NIRQS; irq++) { bii = &sc->intc_isrcs[irq]; bii->bii_irq = irq; if (IS_IRQ_BASIC(irq)) { bii->bii_disable_reg = INTC_DISABLE_BASIC; bii->bii_enable_reg = INTC_ENABLE_BASIC; bii->bii_mask = 1 << irq; } else if (IS_IRQ_BANK1(irq)) { bii->bii_disable_reg = INTC_DISABLE_BANK1; bii->bii_enable_reg = INTC_ENABLE_BANK1; bii->bii_mask = 1 << IRQ_BANK1(irq); } else if (IS_IRQ_BANK2(irq)) { bii->bii_disable_reg = INTC_DISABLE_BANK2; bii->bii_enable_reg = INTC_ENABLE_BANK2; bii->bii_mask = 1 << IRQ_BANK2(irq); } else return (ENXIO); error = intr_isrc_register(&bii->bii_isrc, sc->sc_dev, 0, "%s,%u", name, irq); if (error != 0) return (error); } return (intr_pic_register(sc->sc_dev, xref)); } #endif static int bcm_intc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "broadcom,bcm2835-armctrl-ic")) return (ENXIO); device_set_desc(dev, "BCM2835 Interrupt Controller"); return (BUS_PROBE_DEFAULT); } static int bcm_intc_attach(device_t dev) { struct bcm_intc_softc *sc = device_get_softc(dev); int rid = 0; #ifdef INTRNG intptr_t xref; #endif sc->sc_dev = dev; if (bcm_intc_sc) return (ENXIO); sc->intc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->intc_res == NULL) { device_printf(dev, "could not allocate memory resource\n"); return (ENXIO); } #ifdef INTRNG xref = OF_xref_from_node(ofw_bus_get_node(dev)); if (bcm_intc_pic_register(sc, xref) != 0) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->intc_res); device_printf(dev, "could not register PIC\n"); return (ENXIO); } rid = 0; sc->intc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->intc_irq_res == NULL) { if (intr_pic_claim_root(dev, xref, bcm2835_intc_intr, sc, 0) != 0) { /* XXX clean up */ device_printf(dev, "could not set PIC as a root\n"); return (ENXIO); } } else { if (bus_setup_intr(dev, sc->intc_irq_res, INTR_TYPE_CLK, bcm2835_intc_intr, NULL, sc, &sc->intc_irq_hdl)) { /* XXX clean up */ device_printf(dev, "could not setup irq handler\n"); return (ENXIO); } } #endif sc->intc_bst = rman_get_bustag(sc->intc_res); sc->intc_bsh = rman_get_bushandle(sc->intc_res); bcm_intc_sc = sc; return (0); } static device_method_t bcm_intc_methods[] = { DEVMETHOD(device_probe, bcm_intc_probe), DEVMETHOD(device_attach, bcm_intc_attach), #ifdef INTRNG DEVMETHOD(pic_disable_intr, bcm_intc_disable_intr), DEVMETHOD(pic_enable_intr, bcm_intc_enable_intr), DEVMETHOD(pic_map_intr, bcm_intc_map_intr), DEVMETHOD(pic_post_filter, bcm_intc_post_filter), DEVMETHOD(pic_post_ithread, bcm_intc_post_ithread), DEVMETHOD(pic_pre_ithread, bcm_intc_pre_ithread), #endif { 0, 0 } }; static driver_t bcm_intc_driver = { "intc", bcm_intc_methods, sizeof(struct bcm_intc_softc), }; static devclass_t bcm_intc_devclass; DRIVER_MODULE(intc, simplebus, bcm_intc_driver, bcm_intc_devclass, 0, 0); #ifndef INTRNG int arm_get_next_irq(int last_irq) { struct bcm_intc_softc *sc = bcm_intc_sc; uint32_t pending; int32_t irq = last_irq + 1; #ifdef SOC_BCM2836 int ret; #endif /* Sanity check */ if (irq < 0) irq = 0; #ifdef SOC_BCM2836 if ((ret = bcm2836_get_next_irq(irq)) < 0) return (-1); if (ret != BCM2836_GPU_IRQ) return (ret + BANK3_START); #endif /* TODO: should we mask last_irq? */ if (irq < BANK1_START) { pending = intc_read_4(sc, INTC_PENDING_BASIC); if ((pending & 0xFF) == 0) { irq = BANK1_START; /* skip to next bank */ } else do { if (pending & (1 << irq)) return irq; irq++; } while (irq < BANK1_START); } if (irq < BANK2_START) { pending = intc_read_4(sc, INTC_PENDING_BANK1); if (pending == 0) { irq = BANK2_START; /* skip to next bank */ } else do { if (pending & (1 << IRQ_BANK1(irq))) return irq; irq++; } while (irq < BANK2_START); } if (irq < BANK3_START) { pending = intc_read_4(sc, INTC_PENDING_BANK2); if (pending != 0) do { if (pending & (1 << IRQ_BANK2(irq))) return irq; irq++; } while (irq < BANK3_START); } return (-1); } void arm_mask_irq(uintptr_t nb) { struct bcm_intc_softc *sc = bcm_intc_sc; dprintf("%s: %d\n", __func__, nb); if (IS_IRQ_BASIC(nb)) intc_write_4(sc, INTC_DISABLE_BASIC, (1 << nb)); else if (IS_IRQ_BANK1(nb)) intc_write_4(sc, INTC_DISABLE_BANK1, (1 << IRQ_BANK1(nb))); else if (IS_IRQ_BANK2(nb)) intc_write_4(sc, INTC_DISABLE_BANK2, (1 << IRQ_BANK2(nb))); #ifdef SOC_BCM2836 else if (ID_IRQ_BCM2836(nb)) bcm2836_mask_irq(nb - BANK3_START); #endif else printf("arm_mask_irq: Invalid IRQ number: %d\n", nb); } void arm_unmask_irq(uintptr_t nb) { struct bcm_intc_softc *sc = bcm_intc_sc; dprintf("%s: %d\n", __func__, nb); if (IS_IRQ_BASIC(nb)) intc_write_4(sc, INTC_ENABLE_BASIC, (1 << nb)); else if (IS_IRQ_BANK1(nb)) intc_write_4(sc, INTC_ENABLE_BANK1, (1 << IRQ_BANK1(nb))); else if (IS_IRQ_BANK2(nb)) intc_write_4(sc, INTC_ENABLE_BANK2, (1 << IRQ_BANK2(nb))); #ifdef SOC_BCM2836 else if (ID_IRQ_BCM2836(nb)) bcm2836_unmask_irq(nb - BANK3_START); #endif else printf("arm_mask_irq: Invalid IRQ number: %d\n", nb); } #ifdef SMP void intr_pic_init_secondary(void) { } #endif #endif Index: user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2836.c =================================================================== --- user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2836.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/broadcom/bcm2835/bcm2836.c (revision 299127) @@ -1,909 +1,912 @@ /* * Copyright 2015 Andrew Turner. * Copyright 2016 Svatopluk Kraus * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #ifdef SMP #include #endif #include #include #include #ifdef SMP #include #endif #include #include #ifdef INTRNG #include "pic_if.h" #else #include #define ARM_LOCAL_BASE 0x40000000 #define ARM_LOCAL_SIZE 0x00001000 #define ARM_LOCAL_CONTROL 0x00 #define ARM_LOCAL_PRESCALER 0x08 #define PRESCALER_19_2 0x80000000 /* 19.2 MHz */ #define ARM_LOCAL_INT_TIMER(n) (0x40 + (n) * 4) #define ARM_LOCAL_INT_MAILBOX(n) (0x50 + (n) * 4) #define ARM_LOCAL_INT_PENDING(n) (0x60 + (n) * 4) #define INT_PENDING_MASK 0x011f #define MAILBOX0_IRQ 4 #define MAILBOX0_IRQEN (1 << 0) #endif #ifdef INTRNG #define BCM_LINTC_CONTROL_REG 0x00 #define BCM_LINTC_PRESCALER_REG 0x08 #define BCM_LINTC_GPU_ROUTING_REG 0x0c #define BCM_LINTC_PMU_ROUTING_SET_REG 0x10 #define BCM_LINTC_PMU_ROUTING_CLR_REG 0x14 #define BCM_LINTC_TIMER_CFG_REG(n) (0x40 + (n) * 4) #define BCM_LINTC_MBOX_CFG_REG(n) (0x50 + (n) * 4) #define BCM_LINTC_PENDING_REG(n) (0x60 + (n) * 4) #define BCM_LINTC_MBOX0_SET_REG(n) (0x80 + (n) * 16) #define BCM_LINTC_MBOX1_SET_REG(n) (0x84 + (n) * 16) #define BCM_LINTC_MBOX2_SET_REG(n) (0x88 + (n) * 16) #define BCM_LINTC_MBOX3_SET_REG(n) (0x8C + (n) * 16) #define BCM_LINTC_MBOX0_CLR_REG(n) (0xC0 + (n) * 16) #define BCM_LINTC_MBOX1_CLR_REG(n) (0xC4 + (n) * 16) #define BCM_LINTC_MBOX2_CLR_REG(n) (0xC8 + (n) * 16) #define BCM_LINTC_MBOX3_CLR_REG(n) (0xCC + (n) * 16) /* Prescaler Register */ #define BCM_LINTC_PSR_19_2 0x80000000 /* 19.2 MHz */ /* GPU Interrupt Routing Register */ #define BCM_LINTC_GIRR_IRQ_CORE(n) (n) #define BCM_LINTC_GIRR_FIQ_CORE(n) ((n) << 2) /* PMU Interrupt Routing Register */ #define BCM_LINTC_PIRR_IRQ_EN_CORE(n) (1 << (n)) #define BCM_LINTC_PIRR_FIQ_EN_CORE(n) (1 << ((n) + 4)) /* Timer Config Register */ #define BCM_LINTC_TCR_IRQ_EN_TIMER(n) (1 << (n)) #define BCM_LINTC_TCR_FIQ_EN_TIMER(n) (1 << ((n) + 4)) /* MBOX Config Register */ #define BCM_LINTC_MCR_IRQ_EN_MBOX(n) (1 << (n)) #define BCM_LINTC_MCR_FIQ_EN_MBOX(n) (1 << ((n) + 4)) #define BCM_LINTC_CNTPSIRQ_IRQ 0 #define BCM_LINTC_CNTPNSIRQ_IRQ 1 #define BCM_LINTC_CNTHPIRQ_IRQ 2 #define BCM_LINTC_CNTVIRQ_IRQ 3 #define BCM_LINTC_MBOX0_IRQ 4 #define BCM_LINTC_MBOX1_IRQ 5 #define BCM_LINTC_MBOX2_IRQ 6 #define BCM_LINTC_MBOX3_IRQ 7 #define BCM_LINTC_GPU_IRQ 8 #define BCM_LINTC_PMU_IRQ 9 #define BCM_LINTC_AXI_IRQ 10 #define BCM_LINTC_LTIMER_IRQ 11 #define BCM_LINTC_NIRQS 12 #define BCM_LINTC_TIMER0_IRQ BCM_LINTC_CNTPSIRQ_IRQ #define BCM_LINTC_TIMER1_IRQ BCM_LINTC_CNTPNSIRQ_IRQ #define BCM_LINTC_TIMER2_IRQ BCM_LINTC_CNTHPIRQ_IRQ #define BCM_LINTC_TIMER3_IRQ BCM_LINTC_CNTVIRQ_IRQ #define BCM_LINTC_TIMER0_IRQ_MASK (1 << BCM_LINTC_TIMER0_IRQ) #define BCM_LINTC_TIMER1_IRQ_MASK (1 << BCM_LINTC_TIMER1_IRQ) #define BCM_LINTC_TIMER2_IRQ_MASK (1 << BCM_LINTC_TIMER2_IRQ) #define BCM_LINTC_TIMER3_IRQ_MASK (1 << BCM_LINTC_TIMER3_IRQ) #define BCM_LINTC_MBOX0_IRQ_MASK (1 << BCM_LINTC_MBOX0_IRQ) #define BCM_LINTC_GPU_IRQ_MASK (1 << BCM_LINTC_GPU_IRQ) #define BCM_LINTC_PMU_IRQ_MASK (1 << BCM_LINTC_PMU_IRQ) #define BCM_LINTC_UP_PENDING_MASK \ (BCM_LINTC_TIMER0_IRQ_MASK | \ BCM_LINTC_TIMER1_IRQ_MASK | \ BCM_LINTC_TIMER2_IRQ_MASK | \ BCM_LINTC_TIMER3_IRQ_MASK | \ BCM_LINTC_GPU_IRQ_MASK | \ BCM_LINTC_PMU_IRQ_MASK) #define BCM_LINTC_SMP_PENDING_MASK \ (BCM_LINTC_UP_PENDING_MASK | \ BCM_LINTC_MBOX0_IRQ_MASK) #ifdef SMP #define BCM_LINTC_PENDING_MASK BCM_LINTC_SMP_PENDING_MASK #else #define BCM_LINTC_PENDING_MASK BCM_LINTC_UP_PENDING_MASK #endif struct bcm_lintc_irqsrc { struct intr_irqsrc bli_isrc; u_int bli_irq; union { u_int bli_mask; /* for timers */ u_int bli_value; /* for GPU */ }; }; struct bcm_lintc_softc { device_t bls_dev; struct mtx bls_mtx; struct resource * bls_mem; bus_space_tag_t bls_bst; bus_space_handle_t bls_bsh; struct bcm_lintc_irqsrc bls_isrcs[BCM_LINTC_NIRQS]; }; static struct bcm_lintc_softc *bcm_lintc_sc; #ifdef SMP #define BCM_LINTC_NIPIS 32 /* only mailbox 0 is used for IPI */ CTASSERT(INTR_IPI_COUNT <= BCM_LINTC_NIPIS); #endif #define BCM_LINTC_LOCK(sc) mtx_lock_spin(&(sc)->bls_mtx) #define BCM_LINTC_UNLOCK(sc) mtx_unlock_spin(&(sc)->bls_mtx) #define BCM_LINTC_LOCK_INIT(sc) mtx_init(&(sc)->bls_mtx, \ device_get_nameunit((sc)->bls_dev), "bmc_local_intc", MTX_SPIN) #define BCM_LINTC_LOCK_DESTROY(sc) mtx_destroy(&(sc)->bls_mtx) #define bcm_lintc_read_4(sc, reg) \ bus_space_read_4((sc)->bls_bst, (sc)->bls_bsh, (reg)) #define bcm_lintc_write_4(sc, reg, val) \ bus_space_write_4((sc)->bls_bst, (sc)->bls_bsh, (reg), (val)) static inline void bcm_lintc_rwreg_clr(struct bcm_lintc_softc *sc, uint32_t reg, uint32_t mask) { bcm_lintc_write_4(sc, reg, bcm_lintc_read_4(sc, reg) & ~mask); } static inline void bcm_lintc_rwreg_set(struct bcm_lintc_softc *sc, uint32_t reg, uint32_t mask) { bcm_lintc_write_4(sc, reg, bcm_lintc_read_4(sc, reg) | mask); } static void bcm_lintc_timer_mask(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli) { cpuset_t *cpus; uint32_t cpu; cpus = &bli->bli_isrc.isrc_cpu; BCM_LINTC_LOCK(sc); for (cpu = 0; cpu < 4; cpu++) if (CPU_ISSET(cpu, cpus)) bcm_lintc_rwreg_clr(sc, BCM_LINTC_TIMER_CFG_REG(cpu), bli->bli_mask); BCM_LINTC_UNLOCK(sc); } static void bcm_lintc_timer_unmask(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli) { cpuset_t *cpus; uint32_t cpu; cpus = &bli->bli_isrc.isrc_cpu; BCM_LINTC_LOCK(sc); for (cpu = 0; cpu < 4; cpu++) if (CPU_ISSET(cpu, cpus)) bcm_lintc_rwreg_set(sc, BCM_LINTC_TIMER_CFG_REG(cpu), bli->bli_mask); BCM_LINTC_UNLOCK(sc); } static inline void bcm_lintc_gpu_mask(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli) { /* It's accessed just and only by one core. */ bcm_lintc_write_4(sc, BCM_LINTC_GPU_ROUTING_REG, 0); } static inline void bcm_lintc_gpu_unmask(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli) { /* It's accessed just and only by one core. */ bcm_lintc_write_4(sc, BCM_LINTC_GPU_ROUTING_REG, bli->bli_value); } static inline void bcm_lintc_pmu_mask(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli) { cpuset_t *cpus; uint32_t cpu, mask; mask = 0; cpus = &bli->bli_isrc.isrc_cpu; BCM_LINTC_LOCK(sc); for (cpu = 0; cpu < 4; cpu++) if (CPU_ISSET(cpu, cpus)) mask |= BCM_LINTC_PIRR_IRQ_EN_CORE(cpu); /* Write-clear register. */ bcm_lintc_write_4(sc, BCM_LINTC_PMU_ROUTING_CLR_REG, mask); BCM_LINTC_UNLOCK(sc); } static inline void bcm_lintc_pmu_unmask(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli) { cpuset_t *cpus; uint32_t cpu, mask; mask = 0; cpus = &bli->bli_isrc.isrc_cpu; BCM_LINTC_LOCK(sc); for (cpu = 0; cpu < 4; cpu++) if (CPU_ISSET(cpu, cpus)) mask |= BCM_LINTC_PIRR_IRQ_EN_CORE(cpu); /* Write-set register. */ bcm_lintc_write_4(sc, BCM_LINTC_PMU_ROUTING_SET_REG, mask); BCM_LINTC_UNLOCK(sc); } static void bcm_lintc_mask(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli) { switch (bli->bli_irq) { case BCM_LINTC_TIMER0_IRQ: case BCM_LINTC_TIMER1_IRQ: case BCM_LINTC_TIMER2_IRQ: case BCM_LINTC_TIMER3_IRQ: bcm_lintc_timer_mask(sc, bli); return; case BCM_LINTC_MBOX0_IRQ: case BCM_LINTC_MBOX1_IRQ: case BCM_LINTC_MBOX2_IRQ: case BCM_LINTC_MBOX3_IRQ: return; case BCM_LINTC_GPU_IRQ: bcm_lintc_gpu_mask(sc, bli); return; case BCM_LINTC_PMU_IRQ: bcm_lintc_pmu_mask(sc, bli); return; default: panic("%s: not implemented for irq %u", __func__, bli->bli_irq); } } static void bcm_lintc_unmask(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli) { switch (bli->bli_irq) { case BCM_LINTC_TIMER0_IRQ: case BCM_LINTC_TIMER1_IRQ: case BCM_LINTC_TIMER2_IRQ: case BCM_LINTC_TIMER3_IRQ: bcm_lintc_timer_unmask(sc, bli); return; case BCM_LINTC_MBOX0_IRQ: case BCM_LINTC_MBOX1_IRQ: case BCM_LINTC_MBOX2_IRQ: case BCM_LINTC_MBOX3_IRQ: return; case BCM_LINTC_GPU_IRQ: bcm_lintc_gpu_unmask(sc, bli); return; case BCM_LINTC_PMU_IRQ: bcm_lintc_pmu_unmask(sc, bli); return; default: panic("%s: not implemented for irq %u", __func__, bli->bli_irq); } } #ifdef SMP static inline void bcm_lintc_ipi_write(struct bcm_lintc_softc *sc, cpuset_t cpus, u_int ipi) { u_int cpu; uint32_t mask; mask = 1 << ipi; for (cpu = 0; cpu < mp_ncpus; cpu++) if (CPU_ISSET(cpu, &cpus)) bcm_lintc_write_4(sc, BCM_LINTC_MBOX0_SET_REG(cpu), mask); } static inline void bcm_lintc_ipi_dispatch(struct bcm_lintc_softc *sc, u_int cpu, struct trapframe *tf) { u_int ipi; uint32_t mask; mask = bcm_lintc_read_4(sc, BCM_LINTC_MBOX0_CLR_REG(cpu)); if (mask == 0) { device_printf(sc->bls_dev, "Spurious ipi detected\n"); return; } for (ipi = 0; mask != 0; mask >>= 1, ipi++) { if ((mask & 0x01) == 0) continue; /* * Clear an IPI before dispatching to not miss anyone * and make sure that it's observed by everybody. */ bcm_lintc_write_4(sc, BCM_LINTC_MBOX0_CLR_REG(cpu), 1 << ipi); dsb(); intr_ipi_dispatch(ipi, tf); } } #endif static inline void bcm_lintc_irq_dispatch(struct bcm_lintc_softc *sc, u_int irq, struct trapframe *tf) { struct bcm_lintc_irqsrc *bli; bli = &sc->bls_isrcs[irq]; if (intr_isrc_dispatch(&bli->bli_isrc, tf) != 0) device_printf(sc->bls_dev, "Stray irq %u detected\n", irq); } static int bcm_lintc_intr(void *arg) { struct bcm_lintc_softc *sc; u_int cpu; uint32_t num, reg; struct trapframe *tf; sc = arg; cpu = PCPU_GET(cpuid); tf = curthread->td_intr_frame; for (num = 0; ; num++) { reg = bcm_lintc_read_4(sc, BCM_LINTC_PENDING_REG(cpu)); if ((reg & BCM_LINTC_PENDING_MASK) == 0) break; #ifdef SMP if (reg & BCM_LINTC_MBOX0_IRQ_MASK) bcm_lintc_ipi_dispatch(sc, cpu, tf); #endif if (reg & BCM_LINTC_TIMER0_IRQ_MASK) bcm_lintc_irq_dispatch(sc, BCM_LINTC_TIMER0_IRQ, tf); if (reg & BCM_LINTC_TIMER1_IRQ_MASK) bcm_lintc_irq_dispatch(sc, BCM_LINTC_TIMER1_IRQ, tf); if (reg & BCM_LINTC_TIMER2_IRQ_MASK) bcm_lintc_irq_dispatch(sc, BCM_LINTC_TIMER2_IRQ, tf); if (reg & BCM_LINTC_TIMER3_IRQ_MASK) bcm_lintc_irq_dispatch(sc, BCM_LINTC_TIMER3_IRQ, tf); if (reg & BCM_LINTC_GPU_IRQ_MASK) bcm_lintc_irq_dispatch(sc, BCM_LINTC_GPU_IRQ, tf); if (reg & BCM_LINTC_PMU_IRQ_MASK) bcm_lintc_irq_dispatch(sc, BCM_LINTC_PMU_IRQ, tf); arm_irq_memory_barrier(0); /* XXX */ } reg &= ~BCM_LINTC_PENDING_MASK; if (reg != 0) device_printf(sc->bls_dev, "Unknown interrupt(s) %x\n", reg); else if (num == 0) device_printf(sc->bls_dev, "Spurious interrupt detected\n"); return (FILTER_HANDLED); } static void bcm_lintc_disable_intr(device_t dev, struct intr_irqsrc *isrc) { bcm_lintc_mask(device_get_softc(dev), (struct bcm_lintc_irqsrc *)isrc); } static void bcm_lintc_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct bcm_lintc_irqsrc *bli = (struct bcm_lintc_irqsrc *)isrc; arm_irq_memory_barrier(bli->bli_irq); bcm_lintc_unmask(device_get_softc(dev), bli); } static int bcm_lintc_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { + struct intr_map_data_fdt *daf; struct bcm_lintc_softc *sc; if (data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); - if (data->fdt.ncells != 1 || data->fdt.cells[0] >= BCM_LINTC_NIRQS) + + daf = (struct intr_map_data_fdt *)data; + if (daf->ncells != 1 || daf->cells[0] >= BCM_LINTC_NIRQS) return (EINVAL); sc = device_get_softc(dev); - *isrcp = &sc->bls_isrcs[data->fdt.cells[0]].bli_isrc; + *isrcp = &sc->bls_isrcs[daf->cells[0]].bli_isrc; return (0); } static void bcm_lintc_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct bcm_lintc_irqsrc *bli = (struct bcm_lintc_irqsrc *)isrc; if (bli->bli_irq == BCM_LINTC_GPU_IRQ) bcm_lintc_gpu_mask(device_get_softc(dev), bli); else { /* * Handler for PPI interrupt does not make sense much unless * there is one bound ithread for each core for it. Thus the * interrupt can be masked on current core only while ithread * bounded to this core ensures unmasking on the same core. */ panic ("%s: handlers are not supported", __func__); } } static void bcm_lintc_post_ithread(device_t dev, struct intr_irqsrc *isrc) { struct bcm_lintc_irqsrc *bli = (struct bcm_lintc_irqsrc *)isrc; if (bli->bli_irq == BCM_LINTC_GPU_IRQ) bcm_lintc_gpu_unmask(device_get_softc(dev), bli); else { /* See comment in bcm_lintc_pre_ithread(). */ panic ("%s: handlers are not supported", __func__); } } static void bcm_lintc_post_filter(device_t dev, struct intr_irqsrc *isrc) { } static int bcm_lintc_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct bcm_lintc_softc *sc; if (isrc->isrc_handlers == 0 && isrc->isrc_flags & INTR_ISRCF_PPI) { sc = device_get_softc(dev); BCM_LINTC_LOCK(sc); CPU_SET(PCPU_GET(cpuid), &isrc->isrc_cpu); BCM_LINTC_UNLOCK(sc); } return (0); } #ifdef SMP static void bcm_lintc_init_rwreg_on_ap(struct bcm_lintc_softc *sc, u_int cpu, u_int irq, uint32_t reg, uint32_t mask) { if (intr_isrc_init_on_cpu(&sc->bls_isrcs[irq].bli_isrc, cpu)) bcm_lintc_rwreg_set(sc, reg, mask); } static void bcm_lintc_init_pmu_on_ap(struct bcm_lintc_softc *sc, u_int cpu) { struct intr_irqsrc *isrc = &sc->bls_isrcs[BCM_LINTC_PMU_IRQ].bli_isrc; if (intr_isrc_init_on_cpu(isrc, cpu)) { /* Write-set register. */ bcm_lintc_write_4(sc, BCM_LINTC_PMU_ROUTING_SET_REG, BCM_LINTC_PIRR_IRQ_EN_CORE(cpu)); } } static void bcm_lintc_init_secondary(device_t dev) { u_int cpu; struct bcm_lintc_softc *sc; cpu = PCPU_GET(cpuid); sc = device_get_softc(dev); BCM_LINTC_LOCK(sc); bcm_lintc_init_rwreg_on_ap(sc, cpu, BCM_LINTC_TIMER0_IRQ, BCM_LINTC_TIMER_CFG_REG(cpu), BCM_LINTC_TCR_IRQ_EN_TIMER(0)); bcm_lintc_init_rwreg_on_ap(sc, cpu, BCM_LINTC_TIMER1_IRQ, BCM_LINTC_TIMER_CFG_REG(cpu), BCM_LINTC_TCR_IRQ_EN_TIMER(1)); bcm_lintc_init_rwreg_on_ap(sc, cpu, BCM_LINTC_TIMER2_IRQ, BCM_LINTC_TIMER_CFG_REG(cpu), BCM_LINTC_TCR_IRQ_EN_TIMER(2)); bcm_lintc_init_rwreg_on_ap(sc, cpu, BCM_LINTC_TIMER3_IRQ, BCM_LINTC_TIMER_CFG_REG(cpu), BCM_LINTC_TCR_IRQ_EN_TIMER(3)); bcm_lintc_init_pmu_on_ap(sc, cpu); BCM_LINTC_UNLOCK(sc); } static void bcm_lintc_ipi_send(device_t dev, struct intr_irqsrc *isrc, cpuset_t cpus, u_int ipi) { struct bcm_lintc_softc *sc = device_get_softc(dev); KASSERT(isrc == &sc->bls_isrcs[BCM_LINTC_MBOX0_IRQ].bli_isrc, ("%s: bad ISRC %p argument", __func__, isrc)); bcm_lintc_ipi_write(sc, cpus, ipi); } static int bcm_lintc_ipi_setup(device_t dev, u_int ipi, struct intr_irqsrc **isrcp) { struct bcm_lintc_softc *sc = device_get_softc(dev); KASSERT(ipi < BCM_LINTC_NIPIS, ("%s: too high ipi %u", __func__, ipi)); *isrcp = &sc->bls_isrcs[BCM_LINTC_MBOX0_IRQ].bli_isrc; return (0); } #endif static int bcm_lintc_pic_attach(struct bcm_lintc_softc *sc) { struct bcm_lintc_irqsrc *bisrcs; int error; u_int flags; uint32_t irq; const char *name; intptr_t xref; bisrcs = sc->bls_isrcs; name = device_get_nameunit(sc->bls_dev); for (irq = 0; irq < BCM_LINTC_NIRQS; irq++) { bisrcs[irq].bli_irq = irq; switch (irq) { case BCM_LINTC_TIMER0_IRQ: bisrcs[irq].bli_mask = BCM_LINTC_TCR_IRQ_EN_TIMER(0); flags = INTR_ISRCF_PPI; break; case BCM_LINTC_TIMER1_IRQ: bisrcs[irq].bli_mask = BCM_LINTC_TCR_IRQ_EN_TIMER(1); flags = INTR_ISRCF_PPI; break; case BCM_LINTC_TIMER2_IRQ: bisrcs[irq].bli_mask = BCM_LINTC_TCR_IRQ_EN_TIMER(2); flags = INTR_ISRCF_PPI; break; case BCM_LINTC_TIMER3_IRQ: bisrcs[irq].bli_mask = BCM_LINTC_TCR_IRQ_EN_TIMER(3); flags = INTR_ISRCF_PPI; break; case BCM_LINTC_MBOX0_IRQ: case BCM_LINTC_MBOX1_IRQ: case BCM_LINTC_MBOX2_IRQ: case BCM_LINTC_MBOX3_IRQ: bisrcs[irq].bli_value = 0; /* not used */ flags = INTR_ISRCF_IPI; break; case BCM_LINTC_GPU_IRQ: bisrcs[irq].bli_value = BCM_LINTC_GIRR_IRQ_CORE(0); flags = 0; break; case BCM_LINTC_PMU_IRQ: bisrcs[irq].bli_value = 0; /* not used */ flags = INTR_ISRCF_PPI; break; default: bisrcs[irq].bli_value = 0; /* not used */ flags = 0; break; } error = intr_isrc_register(&bisrcs[irq].bli_isrc, sc->bls_dev, flags, "%s,%u", name, irq); if (error != 0) return (error); } xref = OF_xref_from_node(ofw_bus_get_node(sc->bls_dev)); error = intr_pic_register(sc->bls_dev, xref); if (error != 0) return (error); return (intr_pic_claim_root(sc->bls_dev, xref, bcm_lintc_intr, sc, 0)); } static int bcm_lintc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "brcm,bcm2836-l1-intc")) return (ENXIO); device_set_desc(dev, "BCM2836 Interrupt Controller"); return (BUS_PROBE_DEFAULT); } static int bcm_lintc_attach(device_t dev) { struct bcm_lintc_softc *sc; int cpu, rid; sc = device_get_softc(dev); sc->bls_dev = dev; if (bcm_lintc_sc != NULL) return (ENXIO); rid = 0; sc->bls_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->bls_mem == NULL) { device_printf(dev, "could not allocate memory resource\n"); return (ENXIO); } sc->bls_bst = rman_get_bustag(sc->bls_mem); sc->bls_bsh = rman_get_bushandle(sc->bls_mem); bcm_lintc_write_4(sc, BCM_LINTC_CONTROL_REG, 0); bcm_lintc_write_4(sc, BCM_LINTC_PRESCALER_REG, BCM_LINTC_PSR_19_2); /* Disable all timers on all cores. */ for (cpu = 0; cpu < 4; cpu++) bcm_lintc_write_4(sc, BCM_LINTC_TIMER_CFG_REG(cpu), 0); #ifdef SMP /* Enable mailbox 0 on all cores used for IPI. */ for (cpu = 0; cpu < 4; cpu++) bcm_lintc_write_4(sc, BCM_LINTC_MBOX_CFG_REG(cpu), BCM_LINTC_MCR_IRQ_EN_MBOX(0)); #endif if (bcm_lintc_pic_attach(sc) != 0) { device_printf(dev, "could not attach PIC\n"); return (ENXIO); } BCM_LINTC_LOCK_INIT(sc); bcm_lintc_sc = sc; return (0); } static device_method_t bcm_lintc_methods[] = { DEVMETHOD(device_probe, bcm_lintc_probe), DEVMETHOD(device_attach, bcm_lintc_attach), DEVMETHOD(pic_disable_intr, bcm_lintc_disable_intr), DEVMETHOD(pic_enable_intr, bcm_lintc_enable_intr), DEVMETHOD(pic_map_intr, bcm_lintc_map_intr), DEVMETHOD(pic_post_filter, bcm_lintc_post_filter), DEVMETHOD(pic_post_ithread, bcm_lintc_post_ithread), DEVMETHOD(pic_pre_ithread, bcm_lintc_pre_ithread), DEVMETHOD(pic_setup_intr, bcm_lintc_setup_intr), #ifdef SMP DEVMETHOD(pic_init_secondary, bcm_lintc_init_secondary), DEVMETHOD(pic_ipi_send, bcm_lintc_ipi_send), DEVMETHOD(pic_ipi_setup, bcm_lintc_ipi_setup), #endif DEVMETHOD_END }; static driver_t bcm_lintc_driver = { "local_intc", bcm_lintc_methods, sizeof(struct bcm_lintc_softc), }; static devclass_t bcm_lintc_devclass; EARLY_DRIVER_MODULE(local_intc, simplebus, bcm_lintc_driver, bcm_lintc_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); #else /* * A driver for features of the bcm2836. */ struct bcm2836_softc { device_t sc_dev; struct resource *sc_mem; }; static device_identify_t bcm2836_identify; static device_probe_t bcm2836_probe; static device_attach_t bcm2836_attach; struct bcm2836_softc *softc; static void bcm2836_identify(driver_t *driver, device_t parent) { if (BUS_ADD_CHILD(parent, 0, "bcm2836", -1) == NULL) device_printf(parent, "add child failed\n"); } static int bcm2836_probe(device_t dev) { if (softc != NULL) return (ENXIO); device_set_desc(dev, "Broadcom bcm2836"); return (BUS_PROBE_DEFAULT); } static int bcm2836_attach(device_t dev) { int i, rid; softc = device_get_softc(dev); softc->sc_dev = dev; rid = 0; softc->sc_mem = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, ARM_LOCAL_BASE, ARM_LOCAL_BASE + ARM_LOCAL_SIZE, ARM_LOCAL_SIZE, RF_ACTIVE); if (softc->sc_mem == NULL) { device_printf(dev, "could not allocate memory resource\n"); return (ENXIO); } bus_write_4(softc->sc_mem, ARM_LOCAL_CONTROL, 0); bus_write_4(softc->sc_mem, ARM_LOCAL_PRESCALER, PRESCALER_19_2); for (i = 0; i < 4; i++) bus_write_4(softc->sc_mem, ARM_LOCAL_INT_TIMER(i), 0); for (i = 0; i < 4; i++) bus_write_4(softc->sc_mem, ARM_LOCAL_INT_MAILBOX(i), 1); return (0); } int bcm2836_get_next_irq(int last_irq) { uint32_t reg; int cpu; int irq; cpu = PCPU_GET(cpuid); reg = bus_read_4(softc->sc_mem, ARM_LOCAL_INT_PENDING(cpu)); reg &= INT_PENDING_MASK; if (reg == 0) return (-1); irq = ffs(reg) - 1; return (irq); } void bcm2836_mask_irq(uintptr_t irq) { uint32_t reg; #ifdef SMP int cpu; #endif int i; if (irq < MAILBOX0_IRQ) { for (i = 0; i < 4; i++) { reg = bus_read_4(softc->sc_mem, ARM_LOCAL_INT_TIMER(i)); reg &= ~(1 << irq); bus_write_4(softc->sc_mem, ARM_LOCAL_INT_TIMER(i), reg); } #ifdef SMP } else if (irq == MAILBOX0_IRQ) { /* Mailbox 0 for IPI */ cpu = PCPU_GET(cpuid); reg = bus_read_4(softc->sc_mem, ARM_LOCAL_INT_MAILBOX(cpu)); reg &= ~MAILBOX0_IRQEN; bus_write_4(softc->sc_mem, ARM_LOCAL_INT_MAILBOX(cpu), reg); #endif } } void bcm2836_unmask_irq(uintptr_t irq) { uint32_t reg; #ifdef SMP int cpu; #endif int i; if (irq < MAILBOX0_IRQ) { for (i = 0; i < 4; i++) { reg = bus_read_4(softc->sc_mem, ARM_LOCAL_INT_TIMER(i)); reg |= (1 << irq); bus_write_4(softc->sc_mem, ARM_LOCAL_INT_TIMER(i), reg); } #ifdef SMP } else if (irq == MAILBOX0_IRQ) { /* Mailbox 0 for IPI */ cpu = PCPU_GET(cpuid); reg = bus_read_4(softc->sc_mem, ARM_LOCAL_INT_MAILBOX(cpu)); reg |= MAILBOX0_IRQEN; bus_write_4(softc->sc_mem, ARM_LOCAL_INT_MAILBOX(cpu), reg); #endif } } static device_method_t bcm2836_methods[] = { /* Device interface */ DEVMETHOD(device_identify, bcm2836_identify), DEVMETHOD(device_probe, bcm2836_probe), DEVMETHOD(device_attach, bcm2836_attach), DEVMETHOD_END }; static devclass_t bcm2836_devclass; static driver_t bcm2836_driver = { "bcm2836", bcm2836_methods, sizeof(struct bcm2836_softc), }; EARLY_DRIVER_MODULE(bcm2836, nexus, bcm2836_driver, bcm2836_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); #endif Index: user/ngie/detangle-rc/sys/arm/conf/ALLWINNER =================================================================== --- user/ngie/detangle-rc/sys/arm/conf/ALLWINNER (revision 299126) +++ user/ngie/detangle-rc/sys/arm/conf/ALLWINNER (revision 299127) @@ -1,144 +1,146 @@ # # ALLWINNER -- Custom configuration for the Allwinner A20 and A31 ARM SoC # # For more information on this file, please read the config(5) manual page, # and/or the handbook section on Kernel Configuration Files: # # http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/handbook/kernelconfig-config.html # # The handbook is also available locally in /usr/share/doc/handbook # if you've installed the doc distribution, otherwise always see the # FreeBSD World Wide Web server (http://www.FreeBSD.org/) for the # latest information. # # An exhaustive list of options and more detailed explanations of the # device lines is also present in the ../../conf/NOTES and NOTES files. # If you are in doubt as to the purpose or necessity of a line, check first # in NOTES. # # $FreeBSD$ ident ALLWINNER include "std.armv6" include "../allwinner/std.allwinner" options INTRNG options SOC_ALLWINNER_A20 options SOC_ALLWINNER_A31 options SOC_ALLWINNER_A31S +options SOC_ALLWINNER_A83T options HZ=100 options SCHED_ULE # ULE scheduler options SMP # Enable multiple cores options PLATFORM options PLATFORM_SMP options MULTIDELAY # Debugging for use in -current makeoptions DEBUG=-g # Build kernel with gdb(1) debug symbols options ALT_BREAK_TO_DEBUGGER #options VERBOSE_SYSINIT # Enable verbose sysinit messages options KDB # Enable kernel debugger support # For minimum debugger support (stable branch) use: #options KDB_TRACE # Print a stack trace for a panic # For full debugger support use this instead: options DDB # Enable the kernel debugger options INVARIANTS # Enable calls of extra sanity checking options INVARIANT_SUPPORT # Extra sanity checks of internal structures, required by INVARIANTS options WITNESS # Enable checks to detect deadlocks and cycles options WITNESS_SKIPSPIN # Don't run witness on spinlocks for speed #options DIAGNOSTIC # NFS root from boopt/dhcp #options BOOTP #options BOOTP_NFSROOT #options BOOTP_COMPAT #options BOOTP_NFSV3 #options BOOTP_WIRED_TO=dwc0 # EXT_RESOURCES pseudo devices options EXT_RESOURCES device clk device phy device hwreset device regulator # Interrupt controller device gic # ARM Generic Timer device generic_timer # MMC/SD/SDIO Card slot support device mmc # mmc/sd bus device mmcsd # mmc/sd flash cards # ATA controllers device ahci # AHCI-compatible SATA controllers #device ata # Legacy ATA/SATA controllers # Console and misc device uart device uart_snps device pty device snp device md device random # Entropy device # I2C support device iicbus device iic device twsi device axp209 # AXP209 Power Management Unit # GPIO device gpio device gpioled device scbus # SCSI bus (required for ATA/SCSI) device da # Direct Access (disks) device pass # Passthrough device (direct ATA/SCSI access) # USB support options USB_HOST_ALIGN=64 # Align usb buffers to cache line size. device usb options USB_DEBUG #options USB_REQ_DEBUG #options USB_VERBOSE #device uhci #device ohci device ehci device umass # Ethernet device loop device ether device mii device bpf #device emac # 10/100 integrated EMAC controller device dwc # 10/100/1000 integrated GMAC controller +device awg # 10/100/1000 integrated EMAC controller # USB ethernet support, requires miibus device miibus # Sound support device sound # Framebuffer support device vt device kbdmux device ums device ukbd device videomode device hdmi # Pinmux device fdt_pinctrl # Flattened Device Tree options FDT # Configure using FDT/DTB data makeoptions MODULES_EXTRA=dtb/allwinner Index: user/ngie/detangle-rc/sys/arm/freescale/imx/imx_gpio.c =================================================================== --- user/ngie/detangle-rc/sys/arm/freescale/imx/imx_gpio.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/freescale/imx/imx_gpio.c (revision 299127) @@ -1,749 +1,753 @@ /*- * Copyright (c) 2012, 2013 The FreeBSD Foundation * All rights reserved. * * This software was developed by Oleksandr Rybalko under sponsorship * from the FreeBSD Foundation. * * 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. */ /* * Freescale i.MX515 GPIO driver. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #ifdef INTRNG #include "pic_if.h" #endif #define WRITE4(_sc, _r, _v) \ bus_space_write_4((_sc)->sc_iot, (_sc)->sc_ioh, (_r), (_v)) #define READ4(_sc, _r) \ bus_space_read_4((_sc)->sc_iot, (_sc)->sc_ioh, (_r)) #define SET4(_sc, _r, _m) \ WRITE4((_sc), (_r), READ4((_sc), (_r)) | (_m)) #define CLEAR4(_sc, _r, _m) \ WRITE4((_sc), (_r), READ4((_sc), (_r)) & ~(_m)) /* Registers definition for Freescale i.MX515 GPIO controller */ #define IMX_GPIO_DR_REG 0x000 /* Pin Data */ #define IMX_GPIO_OE_REG 0x004 /* Set Pin Output */ #define IMX_GPIO_PSR_REG 0x008 /* Pad Status */ #define IMX_GPIO_ICR1_REG 0x00C /* Interrupt Configuration */ #define IMX_GPIO_ICR2_REG 0x010 /* Interrupt Configuration */ #define GPIO_ICR_COND_LOW 0 #define GPIO_ICR_COND_HIGH 1 #define GPIO_ICR_COND_RISE 2 #define GPIO_ICR_COND_FALL 3 #define IMX_GPIO_IMR_REG 0x014 /* Interrupt Mask Register */ #define IMX_GPIO_ISR_REG 0x018 /* Interrupt Status Register */ #define IMX_GPIO_EDGE_REG 0x01C /* Edge Detect Register */ #define DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT) #define NGPIO 32 #ifdef INTRNG struct gpio_irqsrc { struct intr_irqsrc gi_isrc; u_int gi_irq; enum intr_polarity gi_pol; enum intr_trigger gi_trig; }; #endif struct imx51_gpio_softc { device_t dev; device_t sc_busdev; struct mtx sc_mtx; struct resource *sc_res[3]; /* 1 x mem, 2 x IRQ */ void *gpio_ih[2]; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; int gpio_npins; struct gpio_pin gpio_pins[NGPIO]; #ifdef INTRNG struct gpio_irqsrc gpio_pic_irqsrc[NGPIO]; #endif }; static struct ofw_compat_data compat_data[] = { {"fsl,imx6q-gpio", 1}, {"fsl,imx53-gpio", 1}, {"fsl,imx51-gpio", 1}, {NULL, 0} }; static struct resource_spec imx_gpio_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0 } }; /* * Helpers */ static void imx51_gpio_pin_configure(struct imx51_gpio_softc *, struct gpio_pin *, uint32_t); /* * Driver stuff */ static int imx51_gpio_probe(device_t); static int imx51_gpio_attach(device_t); static int imx51_gpio_detach(device_t); /* * GPIO interface */ static device_t imx51_gpio_get_bus(device_t); static int imx51_gpio_pin_max(device_t, int *); static int imx51_gpio_pin_getcaps(device_t, uint32_t, uint32_t *); static int imx51_gpio_pin_getflags(device_t, uint32_t, uint32_t *); static int imx51_gpio_pin_getname(device_t, uint32_t, char *); static int imx51_gpio_pin_setflags(device_t, uint32_t, uint32_t); static int imx51_gpio_pin_set(device_t, uint32_t, unsigned int); static int imx51_gpio_pin_get(device_t, uint32_t, unsigned int *); static int imx51_gpio_pin_toggle(device_t, uint32_t pin); #ifdef INTRNG static int gpio_pic_map_fdt(device_t dev, u_int ncells, pcell_t *cells, u_int *irqp, enum intr_polarity *polp, enum intr_trigger *trigp) { struct imx51_gpio_softc *sc; u_int irq, tripol; enum intr_polarity pol; enum intr_trigger trig; sc = device_get_softc(dev); /* * From devicetree/bindings/gpio/fsl-imx-gpio.txt: * #interrupt-cells: 2. The first cell is the GPIO number. The second * cell bits[3:0] is used to specify trigger type and level flags: * 1 = low-to-high edge triggered. * 2 = high-to-low edge triggered. * 4 = active high level-sensitive. * 8 = active low level-sensitive. * We can do any single one of these modes, but nothing in combo. */ if (ncells != 2) { device_printf(sc->dev, "Invalid #interrupt-cells"); return (EINVAL); } irq = cells[0]; tripol = cells[1]; if (irq >= sc->gpio_npins) { device_printf(sc->dev, "Invalid interrupt number %d", irq); return (EINVAL); } switch (tripol) { case 1: trig = INTR_TRIGGER_EDGE; pol = INTR_POLARITY_HIGH; break; case 2: trig = INTR_TRIGGER_EDGE; pol = INTR_POLARITY_LOW; break; case 4: trig = INTR_TRIGGER_LEVEL; pol = INTR_POLARITY_HIGH; break; case 8: trig = INTR_TRIGGER_LEVEL; pol = INTR_POLARITY_LOW; break; default: device_printf(sc->dev, "unsupported trigger/polarity 0x%2x\n", tripol); return (ENOTSUP); } *irqp = irq; if (polp != NULL) *polp = pol; if (trigp != NULL) *trigp = trig; return (0); } static int gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int error; u_int irq; + struct intr_map_data_fdt *daf; struct imx51_gpio_softc *sc; if (data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); - error = gpio_pic_map_fdt(dev, data->fdt.ncells, data->fdt.cells, &irq, - NULL, NULL); + daf = (struct intr_map_data_fdt *)data; + error = gpio_pic_map_fdt(dev, daf->ncells, daf->cells, &irq, NULL, + NULL); if (error == 0) { sc = device_get_softc(dev); *isrcp = &sc->gpio_pic_irqsrc[irq].gi_isrc; } return (error); } static int gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct imx51_gpio_softc *sc; struct gpio_irqsrc *gi; sc = device_get_softc(dev); if (isrc->isrc_handlers == 0) { gi = (struct gpio_irqsrc *)isrc; gi->gi_pol = INTR_POLARITY_CONFORM; gi->gi_trig = INTR_TRIGGER_CONFORM; // XXX Not sure this is necessary mtx_lock_spin(&sc->sc_mtx); CLEAR4(sc, IMX_GPIO_IMR_REG, (1U << gi->gi_irq)); WRITE4(sc, IMX_GPIO_ISR_REG, (1U << gi->gi_irq)); mtx_unlock_spin(&sc->sc_mtx); } return (0); } static int gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { + struct intr_map_data_fdt *daf; struct imx51_gpio_softc *sc; struct gpio_irqsrc *gi; int error, icfg; u_int irq, reg, shift, wrk; enum intr_trigger trig; enum intr_polarity pol; sc = device_get_softc(dev); gi = (struct gpio_irqsrc *)isrc; /* Get config for interrupt. */ if (data == NULL || data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); - error = gpio_pic_map_fdt(dev, data->fdt.ncells, data->fdt.cells, &irq, - &pol, &trig); + daf = (struct intr_map_data_fdt *)data; + error = gpio_pic_map_fdt(dev, daf->ncells, daf->cells, &irq, &pol, + &trig); if (error != 0) return (error); if (gi->gi_irq != irq) return (EINVAL); /* Compare config if this is not first setup. */ if (isrc->isrc_handlers != 0) { if (pol != gi->gi_pol || trig != gi->gi_trig) return (EINVAL); else return (0); } gi->gi_pol = pol; gi->gi_trig = trig; if (trig == INTR_TRIGGER_LEVEL) { if (pol == INTR_POLARITY_LOW) icfg = GPIO_ICR_COND_LOW; else icfg = GPIO_ICR_COND_HIGH; } else { if (pol == INTR_POLARITY_HIGH) icfg = GPIO_ICR_COND_FALL; else icfg = GPIO_ICR_COND_RISE; } if (irq < 16) { reg = IMX_GPIO_ICR1_REG; shift = 2 * irq; } else { reg = IMX_GPIO_ICR2_REG; shift = 2 * (irq - 16); } mtx_lock_spin(&sc->sc_mtx); CLEAR4(sc, IMX_GPIO_IMR_REG, (1U << irq)); WRITE4(sc, IMX_GPIO_ISR_REG, (1U << irq)); wrk = READ4(sc, reg); wrk &= ~(0x03 << shift); wrk |= icfg << shift; WRITE4(sc, reg, wrk); mtx_unlock_spin(&sc->sc_mtx); return (0); } /* * this is mask_intr */ static void gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct imx51_gpio_softc *sc; u_int irq; sc = device_get_softc(dev); irq = ((struct gpio_irqsrc *)isrc)->gi_irq; mtx_lock_spin(&sc->sc_mtx); CLEAR4(sc, IMX_GPIO_IMR_REG, (1U << irq)); mtx_unlock_spin(&sc->sc_mtx); } /* * this is unmask_intr */ static void gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct imx51_gpio_softc *sc; u_int irq; sc = device_get_softc(dev); irq = ((struct gpio_irqsrc *)isrc)->gi_irq; mtx_lock_spin(&sc->sc_mtx); SET4(sc, IMX_GPIO_IMR_REG, (1U << irq)); mtx_unlock_spin(&sc->sc_mtx); } static void gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct imx51_gpio_softc *sc; u_int irq; sc = device_get_softc(dev); irq = ((struct gpio_irqsrc *)isrc)->gi_irq; arm_irq_memory_barrier(0); /* EOI. W1C reg so no r-m-w, no locking needed. */ WRITE4(sc, IMX_GPIO_ISR_REG, (1U << irq)); } static void gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { arm_irq_memory_barrier(0); gpio_pic_enable_intr(dev, isrc); } static void gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { gpio_pic_disable_intr(dev, isrc); } static int gpio_pic_filter(void *arg) { struct imx51_gpio_softc *sc; struct intr_irqsrc *isrc; uint32_t i, interrupts; sc = arg; mtx_lock_spin(&sc->sc_mtx); interrupts = READ4(sc, IMX_GPIO_ISR_REG) & READ4(sc, IMX_GPIO_IMR_REG); mtx_unlock_spin(&sc->sc_mtx); for (i = 0; interrupts != 0; i++, interrupts >>= 1) { if ((interrupts & 0x1) == 0) continue; isrc = &sc->gpio_pic_irqsrc[i].gi_isrc; if (intr_isrc_dispatch(isrc, curthread->td_intr_frame) != 0) { gpio_pic_disable_intr(sc->dev, isrc); gpio_pic_post_filter(sc->dev, isrc); device_printf(sc->dev, "Stray irq %u disabled\n", i); } } return (FILTER_HANDLED); } /* * Initialize our isrcs and register them with intrng. */ static int gpio_pic_register_isrcs(struct imx51_gpio_softc *sc) { int error; uint32_t irq; const char *name; name = device_get_nameunit(sc->dev); for (irq = 0; irq < NGPIO; irq++) { sc->gpio_pic_irqsrc[irq].gi_irq = irq; sc->gpio_pic_irqsrc[irq].gi_pol = INTR_POLARITY_CONFORM; sc->gpio_pic_irqsrc[irq].gi_trig = INTR_TRIGGER_CONFORM; error = intr_isrc_register(&sc->gpio_pic_irqsrc[irq].gi_isrc, sc->dev, 0, "%s,%u", name, irq); if (error != 0) { /* XXX call intr_isrc_deregister() */ device_printf(sc->dev, "%s failed", __func__); return (error); } } return (0); } #endif /* * */ static void imx51_gpio_pin_configure(struct imx51_gpio_softc *sc, struct gpio_pin *pin, unsigned int flags) { u_int newflags; mtx_lock_spin(&sc->sc_mtx); /* * Manage input/output; other flags not supported yet. * * Note that changes to pin->gp_flags must be acccumulated in newflags * and stored with a single writeback to gp_flags at the end, to enable * unlocked reads of that value elsewhere. */ if (flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) { newflags = pin->gp_flags & ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); if (flags & GPIO_PIN_OUTPUT) { newflags |= GPIO_PIN_OUTPUT; SET4(sc, IMX_GPIO_OE_REG, (1U << pin->gp_pin)); } else { newflags |= GPIO_PIN_INPUT; CLEAR4(sc, IMX_GPIO_OE_REG, (1U << pin->gp_pin)); } pin->gp_flags = newflags; } mtx_unlock_spin(&sc->sc_mtx); } static device_t imx51_gpio_get_bus(device_t dev) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int imx51_gpio_pin_max(device_t dev, int *maxpin) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->gpio_npins - 1; return (0); } static int imx51_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); *caps = sc->gpio_pins[pin].gp_caps; return (0); } static int imx51_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); *flags = sc->gpio_pins[pin].gp_flags; return (0); } static int imx51_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); mtx_lock_spin(&sc->sc_mtx); memcpy(name, sc->gpio_pins[pin].gp_name, GPIOMAXNAME); mtx_unlock_spin(&sc->sc_mtx); return (0); } static int imx51_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); imx51_gpio_pin_configure(sc, &sc->gpio_pins[pin], flags); return (0); } static int imx51_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); mtx_lock_spin(&sc->sc_mtx); if (value) SET4(sc, IMX_GPIO_DR_REG, (1U << pin)); else CLEAR4(sc, IMX_GPIO_DR_REG, (1U << pin)); mtx_unlock_spin(&sc->sc_mtx); return (0); } static int imx51_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); *val = (READ4(sc, IMX_GPIO_DR_REG) >> pin) & 1; return (0); } static int imx51_gpio_pin_toggle(device_t dev, uint32_t pin) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); mtx_lock_spin(&sc->sc_mtx); WRITE4(sc, IMX_GPIO_DR_REG, (READ4(sc, IMX_GPIO_DR_REG) ^ (1U << pin))); mtx_unlock_spin(&sc->sc_mtx); return (0); } static int imx51_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { device_set_desc(dev, "Freescale i.MX GPIO Controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int imx51_gpio_attach(device_t dev) { struct imx51_gpio_softc *sc; int i, irq, unit; sc = device_get_softc(dev); sc->dev = dev; sc->gpio_npins = NGPIO; mtx_init(&sc->sc_mtx, device_get_nameunit(sc->dev), NULL, MTX_SPIN); if (bus_alloc_resources(dev, imx_gpio_spec, sc->sc_res)) { device_printf(dev, "could not allocate resources\n"); bus_release_resources(dev, imx_gpio_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); return (ENXIO); } sc->sc_iot = rman_get_bustag(sc->sc_res[0]); sc->sc_ioh = rman_get_bushandle(sc->sc_res[0]); /* * Mask off all interrupts in hardware, then set up interrupt handling. */ WRITE4(sc, IMX_GPIO_IMR_REG, 0); for (irq = 0; irq < 2; irq++) { #ifdef INTRNG if ((bus_setup_intr(dev, sc->sc_res[1 + irq], INTR_TYPE_CLK, gpio_pic_filter, NULL, sc, &sc->gpio_ih[irq]))) { device_printf(dev, "WARNING: unable to register interrupt handler\n"); imx51_gpio_detach(dev); return (ENXIO); } #endif } unit = device_get_unit(dev); for (i = 0; i < sc->gpio_npins; i++) { sc->gpio_pins[i].gp_pin = i; sc->gpio_pins[i].gp_caps = DEFAULT_CAPS; sc->gpio_pins[i].gp_flags = (READ4(sc, IMX_GPIO_OE_REG) & (1U << i)) ? GPIO_PIN_OUTPUT : GPIO_PIN_INPUT; snprintf(sc->gpio_pins[i].gp_name, GPIOMAXNAME, "imx_gpio%d.%d", unit, i); } #ifdef INTRNG gpio_pic_register_isrcs(sc); intr_pic_register(dev, OF_xref_from_node(ofw_bus_get_node(dev))); #endif sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) { imx51_gpio_detach(dev); return (ENXIO); } return (0); } static int imx51_gpio_detach(device_t dev) { int irq; struct imx51_gpio_softc *sc; sc = device_get_softc(dev); gpiobus_detach_bus(dev); for (irq = 1; irq <= 2; irq++) { if (sc->gpio_ih[irq]) bus_teardown_intr(dev, sc->sc_res[irq], sc->gpio_ih[irq]); } bus_release_resources(dev, imx_gpio_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); return(0); } static device_method_t imx51_gpio_methods[] = { DEVMETHOD(device_probe, imx51_gpio_probe), DEVMETHOD(device_attach, imx51_gpio_attach), DEVMETHOD(device_detach, imx51_gpio_detach), #ifdef INTRNG /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, gpio_pic_map_intr), DEVMETHOD(pic_setup_intr, gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, gpio_pic_teardown_intr), DEVMETHOD(pic_post_filter, gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, gpio_pic_pre_ithread), #endif /* GPIO protocol */ DEVMETHOD(gpio_get_bus, imx51_gpio_get_bus), DEVMETHOD(gpio_pin_max, imx51_gpio_pin_max), DEVMETHOD(gpio_pin_getname, imx51_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, imx51_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, imx51_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, imx51_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, imx51_gpio_pin_get), DEVMETHOD(gpio_pin_set, imx51_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, imx51_gpio_pin_toggle), {0, 0}, }; static driver_t imx51_gpio_driver = { "gpio", imx51_gpio_methods, sizeof(struct imx51_gpio_softc), }; static devclass_t imx51_gpio_devclass; DRIVER_MODULE(imx51_gpio, simplebus, imx51_gpio_driver, imx51_gpio_devclass, 0, 0); Index: user/ngie/detangle-rc/sys/arm/mv/mpic.c =================================================================== --- user/ngie/detangle-rc/sys/arm/mv/mpic.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/mv/mpic.c (revision 299127) @@ -1,631 +1,635 @@ /*- * Copyright (c) 2006 Benno Rice. * Copyright (C) 2007-2011 MARVELL INTERNATIONAL LTD. * Copyright (c) 2012 Semihalf. * All rights reserved. * * Developed by Semihalf. * * 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 ``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. * * from: FreeBSD: //depot/projects/arm/src/sys/arm/xscale/pxa2x0/pxa2x0_icu.c, rev 1 * from: FreeBSD: src/sys/arm/mv/ic.c,v 1.5 2011/02/08 01:49:30 */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INTRNG #include "pic_if.h" #endif #ifdef DEBUG #define debugf(fmt, args...) do { printf("%s(): ", __func__); \ printf(fmt,##args); } while (0) #else #define debugf(fmt, args...) #endif #define MPIC_INT_ERR 4 #define MPIC_INT_MSI 96 #define MPIC_IRQ_MASK 0x3ff #define MPIC_CTRL 0x0 #define MPIC_SOFT_INT 0x4 #define MPIC_SOFT_INT_DRBL1 (1 << 5) #define MPIC_ERR_CAUSE 0x20 #define MPIC_ISE 0x30 #define MPIC_ICE 0x34 #define MPIC_INT_CTL(irq) (0x100 + (irq)*4) #define MPIC_INT_IRQ_FIQ_MASK(cpuid) (0x101 << (cpuid)) #define MPIC_CTRL_NIRQS(ctrl) (((ctrl) >> 2) & 0x3ff) #define MPIC_IN_DRBL 0x08 #define MPIC_IN_DRBL_MASK 0x0c #define MPIC_PPI_CAUSE 0x10 #define MPIC_CTP 0x40 #define MPIC_IIACK 0x44 #define MPIC_ISM 0x48 #define MPIC_ICM 0x4c #define MPIC_ERR_MASK 0xe50 #define MPIC_PPI 32 #ifdef INTRNG struct mv_mpic_irqsrc { struct intr_irqsrc mmi_isrc; u_int mmi_irq; }; #endif struct mv_mpic_softc { device_t sc_dev; struct resource * mpic_res[4]; bus_space_tag_t mpic_bst; bus_space_handle_t mpic_bsh; bus_space_tag_t cpu_bst; bus_space_handle_t cpu_bsh; bus_space_tag_t drbl_bst; bus_space_handle_t drbl_bsh; struct mtx mtx; #ifdef INTRNG struct mv_mpic_irqsrc * mpic_isrcs; #endif int nirqs; void * intr_hand; }; static struct resource_spec mv_mpic_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_MEMORY, 1, RF_ACTIVE }, { SYS_RES_MEMORY, 2, RF_ACTIVE | RF_OPTIONAL }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_OPTIONAL }, { -1, 0 } }; static struct ofw_compat_data compat_data[] = { {"mrvl,mpic", true}, {"marvell,mpic", true}, {NULL, false} }; static struct mv_mpic_softc *mv_mpic_sc = NULL; void mpic_send_ipi(int cpus, u_int ipi); static int mv_mpic_probe(device_t); static int mv_mpic_attach(device_t); uint32_t mv_mpic_get_cause(void); uint32_t mv_mpic_get_cause_err(void); uint32_t mv_mpic_get_msi(void); static void mpic_unmask_irq(uintptr_t nb); static void mpic_mask_irq(uintptr_t nb); static void mpic_mask_irq_err(uintptr_t nb); static void mpic_unmask_irq_err(uintptr_t nb); static int mpic_intr(void *arg); static void mpic_unmask_msi(void); #ifndef INTRNG static void arm_mask_irq_err(uintptr_t); static void arm_unmask_irq_err(uintptr_t); #endif #define MPIC_WRITE(softc, reg, val) \ bus_space_write_4((softc)->mpic_bst, (softc)->mpic_bsh, (reg), (val)) #define MPIC_READ(softc, reg) \ bus_space_read_4((softc)->mpic_bst, (softc)->mpic_bsh, (reg)) #define MPIC_CPU_WRITE(softc, reg, val) \ bus_space_write_4((softc)->cpu_bst, (softc)->cpu_bsh, (reg), (val)) #define MPIC_CPU_READ(softc, reg) \ bus_space_read_4((softc)->cpu_bst, (softc)->cpu_bsh, (reg)) #define MPIC_DRBL_WRITE(softc, reg, val) \ bus_space_write_4((softc)->drbl_bst, (softc)->drbl_bsh, (reg), (val)) #define MPIC_DRBL_READ(softc, reg) \ bus_space_read_4((softc)->drbl_bst, (softc)->drbl_bsh, (reg)) static int mv_mpic_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "Marvell Integrated Interrupt Controller"); return (0); } #ifdef INTRNG static int mv_mpic_register_isrcs(struct mv_mpic_softc *sc) { int error; uint32_t irq; struct intr_irqsrc *isrc; const char *name; sc->mpic_isrcs = malloc(sc->nirqs * sizeof (*sc->mpic_isrcs), M_DEVBUF, M_WAITOK | M_ZERO); name = device_get_nameunit(sc->sc_dev); for (irq = 0; irq < sc->nirqs; irq++) { sc->mpic_isrcs[irq].mmi_irq = irq; isrc = &sc->mpic_isrcs[irq].mmi_isrc; if (irq < MPIC_PPI) { error = intr_isrc_register(isrc, sc->sc_dev, INTR_ISRCF_PPI, "%s", name); } else { error = intr_isrc_register(isrc, sc->sc_dev, 0, "%s", name); } if (error != 0) { /* XXX call intr_isrc_deregister() */ device_printf(sc->sc_dev, "%s failed", __func__); return (error); } } return (0); } #endif static int mv_mpic_attach(device_t dev) { struct mv_mpic_softc *sc; int error; uint32_t val; sc = (struct mv_mpic_softc *)device_get_softc(dev); if (mv_mpic_sc != NULL) return (ENXIO); mv_mpic_sc = sc; sc->sc_dev = dev; mtx_init(&sc->mtx, "MPIC lock", NULL, MTX_SPIN); error = bus_alloc_resources(dev, mv_mpic_spec, sc->mpic_res); if (error) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } #ifdef INTRNG if (sc->mpic_res[3] == NULL) device_printf(dev, "No interrupt to use.\n"); else bus_setup_intr(dev, sc->mpic_res[3], INTR_TYPE_CLK, mpic_intr, NULL, sc, &sc->intr_hand); #endif sc->mpic_bst = rman_get_bustag(sc->mpic_res[0]); sc->mpic_bsh = rman_get_bushandle(sc->mpic_res[0]); sc->cpu_bst = rman_get_bustag(sc->mpic_res[1]); sc->cpu_bsh = rman_get_bushandle(sc->mpic_res[1]); if (sc->mpic_res[2] != NULL) { /* This is required only if MSIs are used. */ sc->drbl_bst = rman_get_bustag(sc->mpic_res[2]); sc->drbl_bsh = rman_get_bushandle(sc->mpic_res[2]); } bus_space_write_4(mv_mpic_sc->mpic_bst, mv_mpic_sc->mpic_bsh, MPIC_CTRL, 1); MPIC_CPU_WRITE(mv_mpic_sc, MPIC_CTP, 0); val = MPIC_READ(mv_mpic_sc, MPIC_CTRL); sc->nirqs = MPIC_CTRL_NIRQS(val); #ifdef INTRNG if (mv_mpic_register_isrcs(sc) != 0) { device_printf(dev, "could not register PIC ISRCs\n"); bus_release_resources(dev, mv_mpic_spec, sc->mpic_res); return (ENXIO); } if (intr_pic_register(dev, OF_xref_from_device(dev)) != 0) { device_printf(dev, "could not register PIC\n"); bus_release_resources(dev, mv_mpic_spec, sc->mpic_res); return (ENXIO); } #endif mpic_unmask_msi(); return (0); } #ifdef INTRNG static int mpic_intr(void *arg) { struct mv_mpic_softc *sc; uint32_t cause, irqsrc; unsigned int irq; u_int cpuid; sc = arg; cpuid = PCPU_GET(cpuid); irq = 0; for (cause = MPIC_CPU_READ(sc, MPIC_PPI_CAUSE); cause > 0; cause >>= 1, irq++) { if (cause & 1) { irqsrc = MPIC_READ(sc, MPIC_INT_CTL(irq)); if ((irqsrc & MPIC_INT_IRQ_FIQ_MASK(cpuid)) == 0) continue; if (intr_isrc_dispatch(&sc->mpic_isrcs[irq].mmi_isrc, curthread->td_intr_frame) != 0) { mpic_mask_irq(irq); device_printf(sc->sc_dev, "Stray irq %u " "disabled\n", irq); } } } return (FILTER_HANDLED); } static void mpic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mv_mpic_irqsrc *)isrc)->mmi_irq; mpic_mask_irq(irq); } static void mpic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mv_mpic_irqsrc *)isrc)->mmi_irq; mpic_unmask_irq(irq); } static int mpic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { + struct intr_map_data_fdt *daf; struct mv_mpic_softc *sc; + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; - if (data->type != INTR_MAP_DATA_FDT || data->fdt.ncells !=1 || - data->fdt.cells[0] >= sc->nirqs) + if (daf->ncells !=1 || daf->cells[0] >= sc->nirqs) return (EINVAL); - *isrcp = &sc->mpic_isrcs[data->fdt.cells[0]].mmi_isrc; + *isrcp = &sc->mpic_isrcs[daf->cells[0]].mmi_isrc; return (0); } static void mpic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { mpic_disable_intr(dev, isrc); } static void mpic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { mpic_enable_intr(dev, isrc); } #endif static device_method_t mv_mpic_methods[] = { DEVMETHOD(device_probe, mv_mpic_probe), DEVMETHOD(device_attach, mv_mpic_attach), #ifdef INTRNG DEVMETHOD(pic_disable_intr, mpic_disable_intr), DEVMETHOD(pic_enable_intr, mpic_enable_intr), DEVMETHOD(pic_map_intr, mpic_map_intr), DEVMETHOD(pic_post_ithread, mpic_post_ithread), DEVMETHOD(pic_pre_ithread, mpic_pre_ithread), #endif { 0, 0 } }; static driver_t mv_mpic_driver = { "mpic", mv_mpic_methods, sizeof(struct mv_mpic_softc), }; static devclass_t mv_mpic_devclass; EARLY_DRIVER_MODULE(mpic, simplebus, mv_mpic_driver, mv_mpic_devclass, 0, 0, BUS_PASS_INTERRUPT); #ifndef INTRNG int arm_get_next_irq(int last) { u_int irq, next = -1; irq = mv_mpic_get_cause() & MPIC_IRQ_MASK; CTR2(KTR_INTR, "%s: irq:%#x", __func__, irq); if (irq != MPIC_IRQ_MASK) { if (irq == MPIC_INT_ERR) irq = mv_mpic_get_cause_err(); if (irq == MPIC_INT_MSI) irq = mv_mpic_get_msi(); next = irq; } CTR3(KTR_INTR, "%s: last=%d, next=%d", __func__, last, next); return (next); } /* * XXX We can make arm_enable_irq to operate on ICE and then mask/unmask only * by ISM/ICM and remove access to ICE in masking operation */ void arm_mask_irq(uintptr_t nb) { mpic_mask_irq(nb); } static void arm_mask_irq_err(uintptr_t nb) { mpic_mask_irq_err(nb); } void arm_unmask_irq(uintptr_t nb) { mpic_unmask_irq(nb); } void arm_unmask_irq_err(uintptr_t nb) { mpic_unmask_irq_err(nb); } #endif static void mpic_unmask_msi(void) { mpic_unmask_irq(MPIC_INT_MSI); } static void mpic_unmask_irq_err(uintptr_t nb) { uint32_t mask; uint8_t bit_off; bus_space_write_4(mv_mpic_sc->mpic_bst, mv_mpic_sc->mpic_bsh, MPIC_ISE, MPIC_INT_ERR); MPIC_CPU_WRITE(mv_mpic_sc, MPIC_ICM, MPIC_INT_ERR); bit_off = nb - ERR_IRQ; mask = MPIC_CPU_READ(mv_mpic_sc, MPIC_ERR_MASK); mask |= (1 << bit_off); MPIC_CPU_WRITE(mv_mpic_sc, MPIC_ERR_MASK, mask); } static void mpic_mask_irq_err(uintptr_t nb) { uint32_t mask; uint8_t bit_off; bit_off = nb - ERR_IRQ; mask = MPIC_CPU_READ(mv_mpic_sc, MPIC_ERR_MASK); mask &= ~(1 << bit_off); MPIC_CPU_WRITE(mv_mpic_sc, MPIC_ERR_MASK, mask); } static void mpic_unmask_irq(uintptr_t nb) { if (nb < ERR_IRQ) { bus_space_write_4(mv_mpic_sc->mpic_bst, mv_mpic_sc->mpic_bsh, MPIC_ISE, nb); MPIC_CPU_WRITE(mv_mpic_sc, MPIC_ICM, nb); } else if (nb < MSI_IRQ) mpic_unmask_irq_err(nb); if (nb == 0) MPIC_CPU_WRITE(mv_mpic_sc, MPIC_IN_DRBL_MASK, 0xffffffff); } static void mpic_mask_irq(uintptr_t nb) { if (nb < ERR_IRQ) { bus_space_write_4(mv_mpic_sc->mpic_bst, mv_mpic_sc->mpic_bsh, MPIC_ICE, nb); MPIC_CPU_WRITE(mv_mpic_sc, MPIC_ISM, nb); } else if (nb < MSI_IRQ) mpic_mask_irq_err(nb); } uint32_t mv_mpic_get_cause(void) { return (MPIC_CPU_READ(mv_mpic_sc, MPIC_IIACK)); } uint32_t mv_mpic_get_cause_err(void) { uint32_t err_cause; uint8_t bit_off; err_cause = bus_space_read_4(mv_mpic_sc->mpic_bst, mv_mpic_sc->mpic_bsh, MPIC_ERR_CAUSE); if (err_cause) bit_off = ffs(err_cause) - 1; else return (-1); debugf("%s: irq:%x cause:%x\n", __func__, bit_off, err_cause); return (ERR_IRQ + bit_off); } uint32_t mv_mpic_get_msi(void) { uint32_t cause; uint8_t bit_off; KASSERT(mv_mpic_sc->drbl_bst != NULL, ("No doorbell in mv_mpic_get_msi")); cause = MPIC_DRBL_READ(mv_mpic_sc, 0); if (cause) bit_off = ffs(cause) - 1; else return (-1); debugf("%s: irq:%x cause:%x\n", __func__, bit_off, cause); cause &= ~(1 << bit_off); MPIC_DRBL_WRITE(mv_mpic_sc, 0, cause); return (MSI_IRQ + bit_off); } int mv_msi_data(int irq, uint64_t *addr, uint32_t *data) { u_long phys, base, size; phandle_t node; int error; node = ofw_bus_get_node(mv_mpic_sc->sc_dev); /* Get physical address of register space */ error = fdt_get_range(OF_parent(node), 0, &phys, &size); if (error) { printf("%s: Cannot get register physical address, err:%d", __func__, error); return (error); } /* Get offset of MPIC register space */ error = fdt_regsize(node, &base, &size); if (error) { printf("%s: Cannot get MPIC register offset, err:%d", __func__, error); return (error); } *addr = phys + base + MPIC_SOFT_INT; *data = MPIC_SOFT_INT_DRBL1 | irq; return (0); } #if defined(SMP) && defined(SOC_MV_ARMADAXP) void intr_pic_init_secondary(void) { } void pic_ipi_send(cpuset_t cpus, u_int ipi) { uint32_t val, i; val = 0x00000000; for (i = 0; i < MAXCPU; i++) if (CPU_ISSET(i, &cpus)) val |= (1 << (8 + i)); val |= ipi; bus_space_write_4(mv_mpic_sc->mpic_bst, mv_mpic_sc->mpic_bsh, MPIC_SOFT_INT, val); } int pic_ipi_read(int i __unused) { uint32_t val; int ipi; val = MPIC_CPU_READ(mv_mpic_sc, MPIC_IN_DRBL); if (val) { ipi = ffs(val) - 1; MPIC_CPU_WRITE(mv_mpic_sc, MPIC_IN_DRBL, ~(1 << ipi)); return (ipi); } return (0x3ff); } void pic_ipi_clear(int ipi) { } #endif Index: user/ngie/detangle-rc/sys/arm/nvidia/tegra_gpio.c =================================================================== --- user/ngie/detangle-rc/sys/arm/nvidia/tegra_gpio.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/nvidia/tegra_gpio.c (revision 299127) @@ -1,883 +1,893 @@ /*- * Copyright (c) 2016 Michal Meloun * 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. */ #include __FBSDID("$FreeBSD$"); /* * Tegra GPIO driver. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pic_if.h" #define GPIO_LOCK(_sc) mtx_lock(&(_sc)->mtx) #define GPIO_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx) #define GPIO_LOCK_INIT(_sc) mtx_init(&_sc->mtx, \ device_get_nameunit(_sc->dev), "tegra_gpio", MTX_DEF) #define GPIO_LOCK_DESTROY(_sc) mtx_destroy(&_sc->mtx); #define GPIO_ASSERT_LOCKED(_sc) mtx_assert(&_sc->mtx, MA_OWNED); #define GPIO_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->mtx, MA_NOTOWNED); #define WR4(_sc, _r, _v) bus_write_4((_sc)->mem_res, (_r), (_v)) #define RD4(_sc, _r) bus_read_4((_sc)->mem_res, (_r)) #define GPIO_BANK_OFFS 0x100 /* Bank offset */ #define GPIO_NUM_BANKS 8 /* Total number per bank */ #define GPIO_REGS_IN_BANK 4 /* Total registers in bank */ #define GPIO_PINS_IN_REG 8 /* Total pin in register */ #define GPIO_BANKNUM(n) ((n) / (GPIO_REGS_IN_BANK * GPIO_PINS_IN_REG)) #define GPIO_PORTNUM(n) (((n) / GPIO_PINS_IN_REG) % GPIO_REGS_IN_BANK) #define GPIO_BIT(n) ((n) % GPIO_PINS_IN_REG) #define GPIO_REGNUM(n) (GPIO_BANKNUM(n) * GPIO_BANK_OFFS + \ GPIO_PORTNUM(n) * 4) #define NGPIO ((GPIO_NUM_BANKS * GPIO_REGS_IN_BANK * GPIO_PINS_IN_REG) - 8) /* Register offsets */ #define GPIO_CNF 0x00 #define GPIO_OE 0x10 #define GPIO_OUT 0x20 #define GPIO_IN 0x30 #define GPIO_INT_STA 0x40 #define GPIO_INT_ENB 0x50 #define GPIO_INT_LVL 0x60 #define GPIO_INT_LVL_DELTA (1 << 16) #define GPIO_INT_LVL_EDGE (1 << 8) #define GPIO_INT_LVL_HIGH (1 << 0) #define GPIO_INT_LVL_MASK (GPIO_INT_LVL_DELTA | \ GPIO_INT_LVL_EDGE | GPIO_INT_LVL_HIGH) #define GPIO_INT_CLR 0x70 #define GPIO_MSK_CNF 0x80 #define GPIO_MSK_OE 0x90 #define GPIO_MSK_OUT 0xA0 #define GPIO_MSK_INT_STA 0xC0 #define GPIO_MSK_INT_ENB 0xD0 #define GPIO_MSK_INT_LVL 0xE0 char *tegra_gpio_port_names[] = { "A", "B", "C", "D", /* Bank 0 */ "E", "F", "G", "H", /* Bank 1 */ "I", "J", "K", "L", /* Bank 2 */ "M", "N", "O", "P", /* Bank 3 */ "Q", "R", "S", "T", /* Bank 4 */ "U", "V", "W", "X", /* Bank 5 */ "Y", "Z", "AA", "BB", /* Bank 6 */ "CC", "DD", "EE" /* Bank 7 */ }; struct tegra_gpio_irqsrc { struct intr_irqsrc isrc; u_int irq; uint32_t cfgreg; }; struct tegra_gpio_softc; struct tegra_gpio_irq_cookie { struct tegra_gpio_softc *sc; int bank_num; }; struct tegra_gpio_softc { device_t dev; device_t busdev; struct mtx mtx; struct resource *mem_res; struct resource *irq_res[GPIO_NUM_BANKS]; void *irq_ih[GPIO_NUM_BANKS]; struct tegra_gpio_irq_cookie irq_cookies[GPIO_NUM_BANKS]; int gpio_npins; struct gpio_pin gpio_pins[NGPIO]; struct tegra_gpio_irqsrc *isrcs; }; static struct ofw_compat_data compat_data[] = { {"nvidia,tegra124-gpio", 1}, {NULL, 0} }; /* -------------------------------------------------------------------------- * * GPIO * */ static inline void gpio_write_masked(struct tegra_gpio_softc *sc, bus_size_t reg, struct gpio_pin *pin, uint32_t val) { uint32_t tmp; int bit; bit = GPIO_BIT(pin->gp_pin); tmp = 0x100 << bit; /* mask */ tmp |= (val & 1) << bit; /* value */ bus_write_4(sc->mem_res, reg + GPIO_REGNUM(pin->gp_pin), tmp); } static inline uint32_t gpio_read(struct tegra_gpio_softc *sc, bus_size_t reg, struct gpio_pin *pin) { int bit; uint32_t val; bit = GPIO_BIT(pin->gp_pin); val = bus_read_4(sc->mem_res, reg + GPIO_REGNUM(pin->gp_pin)); return (val >> bit) & 1; } static void tegra_gpio_pin_configure(struct tegra_gpio_softc *sc, struct gpio_pin *pin, unsigned int flags) { if ((flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) == 0) return; /* Manage input/output */ pin->gp_flags &= ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); if (flags & GPIO_PIN_OUTPUT) { pin->gp_flags |= GPIO_PIN_OUTPUT; gpio_write_masked(sc, GPIO_MSK_OE, pin, 1); } else { pin->gp_flags |= GPIO_PIN_INPUT; gpio_write_masked(sc, GPIO_MSK_OE, pin, 0); } } static device_t tegra_gpio_get_bus(device_t dev) { struct tegra_gpio_softc *sc; sc = device_get_softc(dev); return (sc->busdev); } static int tegra_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = NGPIO - 1; return (0); } static int tegra_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct tegra_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); *caps = sc->gpio_pins[pin].gp_caps; GPIO_UNLOCK(sc); return (0); } static int tegra_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct tegra_gpio_softc *sc; int cnf; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); cnf = gpio_read(sc, GPIO_CNF, &sc->gpio_pins[pin]); if (cnf == 0) { GPIO_UNLOCK(sc); return (ENXIO); } *flags = sc->gpio_pins[pin].gp_flags; GPIO_UNLOCK(sc); return (0); } static int tegra_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct tegra_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); memcpy(name, sc->gpio_pins[pin].gp_name, GPIOMAXNAME); GPIO_UNLOCK(sc); return (0); } static int tegra_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct tegra_gpio_softc *sc; int cnf; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); cnf = gpio_read(sc, GPIO_CNF, &sc->gpio_pins[pin]); if (cnf == 0) { /* XXX - allow this for while .... GPIO_UNLOCK(sc); return (ENXIO); */ gpio_write_masked(sc, GPIO_MSK_CNF, &sc->gpio_pins[pin], 1); } tegra_gpio_pin_configure(sc, &sc->gpio_pins[pin], flags); GPIO_UNLOCK(sc); return (0); } static int tegra_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct tegra_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); gpio_write_masked(sc, GPIO_MSK_OUT, &sc->gpio_pins[pin], value); GPIO_UNLOCK(sc); return (0); } static int tegra_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct tegra_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); *val = gpio_read(sc, GPIO_IN, &sc->gpio_pins[pin]); GPIO_UNLOCK(sc); return (0); } static int tegra_gpio_pin_toggle(device_t dev, uint32_t pin) { struct tegra_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); gpio_write_masked(sc, GPIO_MSK_OE, &sc->gpio_pins[pin], gpio_read(sc, GPIO_IN, &sc->gpio_pins[pin]) ^ 1); GPIO_UNLOCK(sc); return (0); } /* -------------------------------------------------------------------------- * * Interrupts * */ static inline void intr_write_masked(struct tegra_gpio_softc *sc, bus_addr_t reg, struct tegra_gpio_irqsrc *tgi, uint32_t val) { uint32_t tmp; int bit; bit = GPIO_BIT(tgi->irq); tmp = 0x100 << bit; /* mask */ tmp |= (val & 1) << bit; /* value */ bus_write_4(sc->mem_res, reg + GPIO_REGNUM(tgi->irq), tmp); } static inline void intr_write_modify(struct tegra_gpio_softc *sc, bus_addr_t reg, struct tegra_gpio_irqsrc *tgi, uint32_t val, uint32_t mask) { uint32_t tmp; int bit; bit = GPIO_BIT(tgi->irq); GPIO_LOCK(sc); tmp = bus_read_4(sc->mem_res, reg + GPIO_REGNUM(tgi->irq)); tmp &= ~(mask << bit); tmp |= val << bit; bus_write_4(sc->mem_res, reg + GPIO_REGNUM(tgi->irq), tmp); GPIO_UNLOCK(sc); } static inline void tegra_gpio_isrc_mask(struct tegra_gpio_softc *sc, struct tegra_gpio_irqsrc *tgi, uint32_t val) { intr_write_masked(sc, GPIO_MSK_INT_ENB, tgi, val); } static inline void tegra_gpio_isrc_eoi(struct tegra_gpio_softc *sc, struct tegra_gpio_irqsrc *tgi) { intr_write_masked(sc, GPIO_INT_CLR, tgi, 1); } static inline bool tegra_gpio_isrc_is_level(struct tegra_gpio_irqsrc *tgi) { return (tgi->cfgreg & GPIO_INT_LVL_EDGE); } static int tegra_gpio_intr(void *arg) { u_int irq, i, j, val, basepin; struct tegra_gpio_softc *sc; struct trapframe *tf; struct tegra_gpio_irqsrc *tgi; struct tegra_gpio_irq_cookie *cookie; cookie = (struct tegra_gpio_irq_cookie *)arg; sc = cookie->sc; tf = curthread->td_intr_frame; for (i = 0; i < GPIO_REGS_IN_BANK; i++) { basepin = cookie->bank_num * GPIO_REGS_IN_BANK * GPIO_PINS_IN_REG + i * GPIO_PINS_IN_REG; val = bus_read_4(sc->mem_res, GPIO_INT_STA + GPIO_REGNUM(basepin)); val &= bus_read_4(sc->mem_res, GPIO_INT_ENB + GPIO_REGNUM(basepin)); /* Interrupt handling */ for (j = 0; j < GPIO_PINS_IN_REG; j++) { if ((val & (1 << j)) == 0) continue; irq = basepin + j; tgi = &sc->isrcs[irq]; if (!tegra_gpio_isrc_is_level(tgi)) tegra_gpio_isrc_eoi(sc, tgi); if (intr_isrc_dispatch(&tgi->isrc, tf) != 0) { tegra_gpio_isrc_mask(sc, tgi, 0); if (tegra_gpio_isrc_is_level(tgi)) tegra_gpio_isrc_eoi(sc, tgi); device_printf(sc->dev, "Stray irq %u disabled\n", irq); } } } return (FILTER_HANDLED); } static int tegra_gpio_pic_attach(struct tegra_gpio_softc *sc) { int error; uint32_t irq; const char *name; sc->isrcs = malloc(sizeof(*sc->isrcs) * sc->gpio_npins, M_DEVBUF, M_WAITOK | M_ZERO); name = device_get_nameunit(sc->dev); for (irq = 0; irq < sc->gpio_npins; irq++) { sc->isrcs[irq].irq = irq; sc->isrcs[irq].cfgreg = 0; error = intr_isrc_register(&sc->isrcs[irq].isrc, sc->dev, 0, "%s,%u", name, irq); if (error != 0) return (error); /* XXX deregister ISRCs */ } return (intr_pic_register(sc->dev, OF_xref_from_node(ofw_bus_get_node(sc->dev)))); } static int tegra_gpio_pic_detach(struct tegra_gpio_softc *sc) { /* * There has not been established any procedure yet * how to detach PIC from living system correctly. */ device_printf(sc->dev, "%s: not implemented yet\n", __func__); return (EBUSY); } static void tegra_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct tegra_gpio_softc *sc; struct tegra_gpio_irqsrc *tgi; sc = device_get_softc(dev); tgi = (struct tegra_gpio_irqsrc *)isrc; tegra_gpio_isrc_mask(sc, tgi, 0); } static void tegra_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct tegra_gpio_softc *sc; struct tegra_gpio_irqsrc *tgi; sc = device_get_softc(dev); tgi = (struct tegra_gpio_irqsrc *)isrc; tegra_gpio_isrc_mask(sc, tgi, 1); } static int tegra_gpio_pic_map_fdt(struct tegra_gpio_softc *sc, u_int ncells, pcell_t *cells, u_int *irqp, uint32_t *regp) { uint32_t reg; /* * The first cell is the interrupt number. * The second cell is used to specify flags: * bits[3:0] trigger type and level flags: * 1 = low-to-high edge triggered. * 2 = high-to-low edge triggered. * 4 = active high level-sensitive. * 8 = active low level-sensitive. */ if (ncells != 2 || cells[0] >= sc->gpio_npins) return (EINVAL); /* * All interrupt types could be set for an interrupt at one moment. * At least, the combination of 'low-to-high' and 'high-to-low' edge * triggered interrupt types can make a sense. */ if (cells[1] == 1) reg = GPIO_INT_LVL_EDGE | GPIO_INT_LVL_HIGH; else if (cells[1] == 2) reg = GPIO_INT_LVL_EDGE; else if (cells[1] == 3) reg = GPIO_INT_LVL_EDGE | GPIO_INT_LVL_DELTA; else if (cells[1] == 4) reg = GPIO_INT_LVL_HIGH; else if (cells[1] == 8) reg = 0; else return (EINVAL); *irqp = cells[0]; if (regp != NULL) *regp = reg; return (0); } static int tegra_gpio_pic_map_gpio(struct tegra_gpio_softc *sc, u_int gpio_pin_num, u_int gpio_pin_flags, u_int intr_mode, u_int *irqp, uint32_t *regp) { uint32_t reg; if (gpio_pin_num >= sc->gpio_npins) return (EINVAL); switch (intr_mode) { case GPIO_INTR_CONFORM: case GPIO_INTR_LEVEL_LOW: reg = 0; break; case GPIO_INTR_LEVEL_HIGH: reg = GPIO_INT_LVL_HIGH; break; case GPIO_INTR_EDGE_RISING: reg = GPIO_INT_LVL_EDGE | GPIO_INT_LVL_HIGH; break; case GPIO_INTR_EDGE_FALLING: reg = GPIO_INT_LVL_EDGE; break; case GPIO_INTR_EDGE_BOTH: reg = GPIO_INT_LVL_EDGE | GPIO_INT_LVL_DELTA; break; default: return (EINVAL); } *irqp = gpio_pin_num; if (regp != NULL) *regp = reg; return (0); } static int tegra_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int rv; u_int irq; struct tegra_gpio_softc *sc; sc = device_get_softc(dev); - if (data->type == INTR_MAP_DATA_FDT) - rv = tegra_gpio_pic_map_fdt(sc, data->fdt.ncells, - data->fdt.cells, &irq, NULL); - else if (data->type == INTR_MAP_DATA_GPIO) - rv = tegra_gpio_pic_map_gpio(sc, data->gpio.gpio_pin_num, - data->gpio.gpio_pin_flags, data->gpio.gpio_intr_mode, - &irq, NULL); - else + if (data->type == INTR_MAP_DATA_FDT) { + struct intr_map_data_fdt *daf; + + daf = (struct intr_map_data_fdt *)data; + rv = tegra_gpio_pic_map_fdt(sc, daf->ncells, daf->cells, &irq, + NULL); + } else if (data->type == INTR_MAP_DATA_GPIO) { + struct intr_map_data_gpio *dag; + + dag = (struct intr_map_data_gpio *)data; + rv = tegra_gpio_pic_map_gpio(sc, dag->gpio_pin_num, + dag->gpio_pin_flags, dag->gpio_intr_mode, &irq, NULL); + } else return (ENOTSUP); if (rv == 0) *isrcp = &sc->isrcs[irq].isrc; return (rv); } static void tegra_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct tegra_gpio_softc *sc; struct tegra_gpio_irqsrc *tgi; sc = device_get_softc(dev); tgi = (struct tegra_gpio_irqsrc *)isrc; if (tegra_gpio_isrc_is_level(tgi)) tegra_gpio_isrc_eoi(sc, tgi); } static void tegra_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { struct tegra_gpio_softc *sc; struct tegra_gpio_irqsrc *tgi; sc = device_get_softc(dev); tgi = (struct tegra_gpio_irqsrc *)isrc; tegra_gpio_isrc_mask(sc, tgi, 1); } static void tegra_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct tegra_gpio_softc *sc; struct tegra_gpio_irqsrc *tgi; sc = device_get_softc(dev); tgi = (struct tegra_gpio_irqsrc *)isrc; tegra_gpio_isrc_mask(sc, tgi, 0); if (tegra_gpio_isrc_is_level(tgi)) tegra_gpio_isrc_eoi(sc, tgi); } static int tegra_gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { u_int irq; uint32_t cfgreg; int rv; struct tegra_gpio_softc *sc; struct tegra_gpio_irqsrc *tgi; sc = device_get_softc(dev); tgi = (struct tegra_gpio_irqsrc *)isrc; if (data == NULL) return (ENOTSUP); /* Get and check config for an interrupt. */ - if (data->type == INTR_MAP_DATA_FDT) - rv = tegra_gpio_pic_map_fdt(sc, data->fdt.ncells, - data->fdt.cells, &irq, &cfgreg); - else if (data->type == INTR_MAP_DATA_GPIO) - rv = tegra_gpio_pic_map_gpio(sc, data->gpio.gpio_pin_num, - data->gpio.gpio_pin_flags, data->gpio.gpio_intr_mode, - &irq, &cfgreg); - else + if (data->type == INTR_MAP_DATA_FDT) { + struct intr_map_data_fdt *daf; + + daf = (struct intr_map_data_fdt *)data; + rv = tegra_gpio_pic_map_fdt(sc, daf->ncells, daf->cells, &irq, + &cfgreg); + } else if (data->type == INTR_MAP_DATA_GPIO) { + struct intr_map_data_gpio *dag; + + dag = (struct intr_map_data_gpio *)data; + rv = tegra_gpio_pic_map_gpio(sc, dag->gpio_pin_num, + dag->gpio_pin_flags, dag->gpio_intr_mode, &irq, &cfgreg); + } else return (ENOTSUP); if (rv != 0) return (EINVAL); /* * If this is a setup for another handler, * only check that its configuration match. */ if (isrc->isrc_handlers != 0) return (tgi->cfgreg == cfgreg ? 0 : EINVAL); tgi->cfgreg = cfgreg; intr_write_modify(sc, GPIO_INT_LVL, tgi, cfgreg, GPIO_INT_LVL_MASK); tegra_gpio_pic_enable_intr(dev, isrc); return (0); } static int tegra_gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct tegra_gpio_softc *sc; struct tegra_gpio_irqsrc *tgi; sc = device_get_softc(dev); tgi = (struct tegra_gpio_irqsrc *)isrc; if (isrc->isrc_handlers == 0) tegra_gpio_isrc_mask(sc, tgi, 0); return (0); } static int tegra_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { device_set_desc(dev, "Tegra GPIO Controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } /* -------------------------------------------------------------------------- * * Bus * */ static int tegra_gpio_detach(device_t dev) { struct tegra_gpio_softc *sc; int i; sc = device_get_softc(dev); KASSERT(mtx_initialized(&sc->mtx), ("gpio mutex not initialized")); for (i = 0; i < GPIO_NUM_BANKS; i++) { if (sc->irq_ih[i] != NULL) bus_teardown_intr(dev, sc->irq_res[i], sc->irq_ih[i]); } if (sc->isrcs != NULL) tegra_gpio_pic_detach(sc); gpiobus_detach_bus(dev); for (i = 0; i < GPIO_NUM_BANKS; i++) { if (sc->irq_res[i] != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res[i]); } if (sc->mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res); GPIO_LOCK_DESTROY(sc); return(0); } static int tegra_gpio_attach(device_t dev) { struct tegra_gpio_softc *sc; int i, rid; sc = device_get_softc(dev); sc->dev = dev; GPIO_LOCK_INIT(sc); /* Allocate bus_space resources. */ rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); tegra_gpio_detach(dev); return (ENXIO); } sc->gpio_npins = NGPIO; for (i = 0; i < sc->gpio_npins; i++) { sc->gpio_pins[i].gp_pin = i; sc->gpio_pins[i].gp_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_INTR_LEVEL_LOW | GPIO_INTR_LEVEL_HIGH | GPIO_INTR_EDGE_RISING | GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH; snprintf(sc->gpio_pins[i].gp_name, GPIOMAXNAME, "gpio_%s.%d", tegra_gpio_port_names[ i / GPIO_PINS_IN_REG], i % GPIO_PINS_IN_REG); sc->gpio_pins[i].gp_flags = gpio_read(sc, GPIO_OE, &sc->gpio_pins[i]) != 0 ? GPIO_PIN_OUTPUT : GPIO_PIN_INPUT; } /* Init interrupt related registes. */ for (i = 0; i < sc->gpio_npins; i += GPIO_PINS_IN_REG) { bus_write_4(sc->mem_res, GPIO_INT_ENB + GPIO_REGNUM(i), 0); bus_write_4(sc->mem_res, GPIO_INT_STA + GPIO_REGNUM(i), 0xFF); bus_write_4(sc->mem_res, GPIO_INT_CLR + GPIO_REGNUM(i), 0xFF); } /* Allocate interrupts. */ for (i = 0; i < GPIO_NUM_BANKS; i++) { sc->irq_cookies[i].sc = sc; sc->irq_cookies[i].bank_num = i; rid = i; sc->irq_res[i] = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->irq_res[i] == NULL) { device_printf(dev, "Cannot allocate IRQ resources\n"); tegra_gpio_detach(dev); return (ENXIO); } if ((bus_setup_intr(dev, sc->irq_res[i], INTR_TYPE_MISC | INTR_MPSAFE, tegra_gpio_intr, NULL, &sc->irq_cookies[i], &sc->irq_ih[i]))) { device_printf(dev, "WARNING: unable to register interrupt handler\n"); tegra_gpio_detach(dev); return (ENXIO); } } if (tegra_gpio_pic_attach(sc) != 0) { device_printf(dev, "WARNING: unable to attach PIC\n"); tegra_gpio_detach(dev); return (ENXIO); } sc->busdev = gpiobus_attach_bus(dev); if (sc->busdev == NULL) { tegra_gpio_detach(dev); return (ENXIO); } return (bus_generic_attach(dev)); } static int tegra_map_gpios(device_t dev, phandle_t pdev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { if (gcells != 2) return (ERANGE); *pin = gpios[0]; *flags= gpios[1]; return (0); } static phandle_t tegra_gpio_get_node(device_t bus, device_t dev) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } static device_method_t tegra_gpio_methods[] = { DEVMETHOD(device_probe, tegra_gpio_probe), DEVMETHOD(device_attach, tegra_gpio_attach), DEVMETHOD(device_detach, tegra_gpio_detach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, tegra_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, tegra_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, tegra_gpio_pic_map_intr), DEVMETHOD(pic_setup_intr, tegra_gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, tegra_gpio_pic_teardown_intr), DEVMETHOD(pic_post_filter, tegra_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, tegra_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, tegra_gpio_pic_pre_ithread), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, tegra_gpio_get_bus), DEVMETHOD(gpio_pin_max, tegra_gpio_pin_max), DEVMETHOD(gpio_pin_getname, tegra_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, tegra_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, tegra_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, tegra_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, tegra_gpio_pin_get), DEVMETHOD(gpio_pin_set, tegra_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, tegra_gpio_pin_toggle), DEVMETHOD(gpio_map_gpios, tegra_map_gpios), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, tegra_gpio_get_node), DEVMETHOD_END }; static driver_t tegra_gpio_driver = { "gpio", tegra_gpio_methods, sizeof(struct tegra_gpio_softc), }; static devclass_t tegra_gpio_devclass; EARLY_DRIVER_MODULE(tegra_gpio, simplebus, tegra_gpio_driver, tegra_gpio_devclass, 0, 0, 70); Index: user/ngie/detangle-rc/sys/arm/ti/aintc.c =================================================================== --- user/ngie/detangle-rc/sys/arm/ti/aintc.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/ti/aintc.c (revision 299127) @@ -1,378 +1,382 @@ /*- * Copyright (c) 2012 Damjan Marion * All rights reserved. * * Based on OMAP3 INTC code by Ben Gray * * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INTRNG #include "pic_if.h" #endif #define INTC_REVISION 0x00 #define INTC_SYSCONFIG 0x10 #define INTC_SYSSTATUS 0x14 #define INTC_SIR_IRQ 0x40 #define INTC_CONTROL 0x48 #define INTC_THRESHOLD 0x68 #define INTC_MIR_CLEAR(x) (0x88 + ((x) * 0x20)) #define INTC_MIR_SET(x) (0x8C + ((x) * 0x20)) #define INTC_ISR_SET(x) (0x90 + ((x) * 0x20)) #define INTC_ISR_CLEAR(x) (0x94 + ((x) * 0x20)) #define INTC_SIR_SPURIOUS_MASK 0xffffff80 #define INTC_SIR_ACTIVE_MASK 0x7f #define INTC_NIRQS 128 #ifdef INTRNG struct ti_aintc_irqsrc { struct intr_irqsrc tai_isrc; u_int tai_irq; }; #endif struct ti_aintc_softc { device_t sc_dev; struct resource * aintc_res[3]; bus_space_tag_t aintc_bst; bus_space_handle_t aintc_bsh; uint8_t ver; #ifdef INTRNG struct ti_aintc_irqsrc aintc_isrcs[INTC_NIRQS]; #endif }; static struct resource_spec ti_aintc_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; static struct ti_aintc_softc *ti_aintc_sc = NULL; #define aintc_read_4(_sc, reg) \ bus_space_read_4((_sc)->aintc_bst, (_sc)->aintc_bsh, (reg)) #define aintc_write_4(_sc, reg, val) \ bus_space_write_4((_sc)->aintc_bst, (_sc)->aintc_bsh, (reg), (val)) /* List of compatible strings for FDT tree */ static struct ofw_compat_data compat_data[] = { {"ti,am33xx-intc", 1}, {"ti,omap2-intc", 1}, {NULL, 0}, }; #ifdef INTRNG static inline void ti_aintc_irq_eoi(struct ti_aintc_softc *sc) { aintc_write_4(sc, INTC_CONTROL, 1); } static inline void ti_aintc_irq_mask(struct ti_aintc_softc *sc, u_int irq) { aintc_write_4(sc, INTC_MIR_SET(irq >> 5), (1UL << (irq & 0x1F))); } static inline void ti_aintc_irq_unmask(struct ti_aintc_softc *sc, u_int irq) { aintc_write_4(sc, INTC_MIR_CLEAR(irq >> 5), (1UL << (irq & 0x1F))); } static int ti_aintc_intr(void *arg) { uint32_t irq; struct ti_aintc_softc *sc = arg; /* Get active interrupt */ irq = aintc_read_4(sc, INTC_SIR_IRQ); if ((irq & INTC_SIR_SPURIOUS_MASK) != 0) { device_printf(sc->sc_dev, "Spurious interrupt detected (0x%08x)\n", irq); ti_aintc_irq_eoi(sc); return (FILTER_HANDLED); } /* Only level-sensitive interrupts detection is supported. */ irq &= INTC_SIR_ACTIVE_MASK; if (intr_isrc_dispatch(&sc->aintc_isrcs[irq].tai_isrc, curthread->td_intr_frame) != 0) { ti_aintc_irq_mask(sc, irq); ti_aintc_irq_eoi(sc); device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq); } arm_irq_memory_barrier(irq); /* XXX */ return (FILTER_HANDLED); } static void ti_aintc_enable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq = ((struct ti_aintc_irqsrc *)isrc)->tai_irq; struct ti_aintc_softc *sc = device_get_softc(dev); arm_irq_memory_barrier(irq); ti_aintc_irq_unmask(sc, irq); } static void ti_aintc_disable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq = ((struct ti_aintc_irqsrc *)isrc)->tai_irq; struct ti_aintc_softc *sc = device_get_softc(dev); ti_aintc_irq_mask(sc, irq); } static int ti_aintc_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { + struct intr_map_data_fdt *daf; struct ti_aintc_softc *sc; - if (data->type != INTR_MAP_DATA_FDT || data->fdt.ncells != 1 || - data->fdt.cells[0] >= INTC_NIRQS) + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + + daf = (struct intr_map_data_fdt *)data; + if (daf->ncells != 1 || daf->cells[0] >= INTC_NIRQS) return (EINVAL); sc = device_get_softc(dev); - *isrcp = &sc->aintc_isrcs[data->fdt.cells[0]].tai_isrc; + *isrcp = &sc->aintc_isrcs[daf->cells[0]].tai_isrc; return (0); } static void ti_aintc_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { u_int irq = ((struct ti_aintc_irqsrc *)isrc)->tai_irq; struct ti_aintc_softc *sc = device_get_softc(dev); ti_aintc_irq_mask(sc, irq); ti_aintc_irq_eoi(sc); } static void ti_aintc_post_ithread(device_t dev, struct intr_irqsrc *isrc) { ti_aintc_enable_intr(dev, isrc); } static void ti_aintc_post_filter(device_t dev, struct intr_irqsrc *isrc) { ti_aintc_irq_eoi(device_get_softc(dev)); } static int ti_aintc_pic_attach(struct ti_aintc_softc *sc) { int error; uint32_t irq; const char *name; intptr_t xref; name = device_get_nameunit(sc->sc_dev); for (irq = 0; irq < INTC_NIRQS; irq++) { sc->aintc_isrcs[irq].tai_irq = irq; error = intr_isrc_register(&sc->aintc_isrcs[irq].tai_isrc, sc->sc_dev, 0, "%s,%u", name, irq); if (error != 0) return (error); } xref = OF_xref_from_node(ofw_bus_get_node(sc->sc_dev)); error = intr_pic_register(sc->sc_dev, xref); if (error != 0) return (error); return (intr_pic_claim_root(sc->sc_dev, xref, ti_aintc_intr, sc, 0)); } #else static void aintc_post_filter(void *arg) { arm_irq_memory_barrier(0); aintc_write_4(ti_aintc_sc, INTC_CONTROL, 1); /* EOI */ } #endif static int ti_aintc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "TI AINTC Interrupt Controller"); return (BUS_PROBE_DEFAULT); } static int ti_aintc_attach(device_t dev) { struct ti_aintc_softc *sc = device_get_softc(dev); uint32_t x; sc->sc_dev = dev; if (ti_aintc_sc) return (ENXIO); if (bus_alloc_resources(dev, ti_aintc_spec, sc->aintc_res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } sc->aintc_bst = rman_get_bustag(sc->aintc_res[0]); sc->aintc_bsh = rman_get_bushandle(sc->aintc_res[0]); ti_aintc_sc = sc; x = aintc_read_4(sc, INTC_REVISION); device_printf(dev, "Revision %u.%u\n",(x >> 4) & 0xF, x & 0xF); /* SoftReset */ aintc_write_4(sc, INTC_SYSCONFIG, 2); /* Wait for reset to complete */ while(!(aintc_read_4(sc, INTC_SYSSTATUS) & 1)); /*Set Priority Threshold */ aintc_write_4(sc, INTC_THRESHOLD, 0xFF); #ifndef INTRNG arm_post_filter = aintc_post_filter; #else if (ti_aintc_pic_attach(sc) != 0) { device_printf(dev, "could not attach PIC\n"); return (ENXIO); } #endif return (0); } static device_method_t ti_aintc_methods[] = { DEVMETHOD(device_probe, ti_aintc_probe), DEVMETHOD(device_attach, ti_aintc_attach), #ifdef INTRNG DEVMETHOD(pic_disable_intr, ti_aintc_disable_intr), DEVMETHOD(pic_enable_intr, ti_aintc_enable_intr), DEVMETHOD(pic_map_intr, ti_aintc_map_intr), DEVMETHOD(pic_post_filter, ti_aintc_post_filter), DEVMETHOD(pic_post_ithread, ti_aintc_post_ithread), DEVMETHOD(pic_pre_ithread, ti_aintc_pre_ithread), #endif { 0, 0 } }; static driver_t ti_aintc_driver = { "aintc", ti_aintc_methods, sizeof(struct ti_aintc_softc), }; static devclass_t ti_aintc_devclass; EARLY_DRIVER_MODULE(aintc, simplebus, ti_aintc_driver, ti_aintc_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); SIMPLEBUS_PNP_INFO(compat_data); #ifndef INTRNG int arm_get_next_irq(int last_irq) { struct ti_aintc_softc *sc = ti_aintc_sc; uint32_t active_irq; /* Get the next active interrupt */ active_irq = aintc_read_4(sc, INTC_SIR_IRQ); /* Check for spurious interrupt */ if ((active_irq & 0xffffff80)) { device_printf(sc->sc_dev, "Spurious interrupt detected (0x%08x)\n", active_irq); aintc_write_4(sc, INTC_SIR_IRQ, 0); return -1; } if (active_irq != last_irq) return active_irq; else return -1; } void arm_mask_irq(uintptr_t nb) { struct ti_aintc_softc *sc = ti_aintc_sc; aintc_write_4(sc, INTC_MIR_SET(nb >> 5), (1UL << (nb & 0x1F))); aintc_write_4(sc, INTC_CONTROL, 1); /* EOI */ } void arm_unmask_irq(uintptr_t nb) { struct ti_aintc_softc *sc = ti_aintc_sc; arm_irq_memory_barrier(nb); aintc_write_4(sc, INTC_MIR_CLEAR(nb >> 5), (1UL << (nb & 0x1F))); } #endif Index: user/ngie/detangle-rc/sys/arm/ti/ti_gpio.c =================================================================== --- user/ngie/detangle-rc/sys/arm/ti/ti_gpio.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm/ti/ti_gpio.c (revision 299127) @@ -1,1332 +1,1336 @@ /*- * Copyright (c) 2011 Ben Gray . * Copyright (c) 2014 Luiz Otavio O Souza . * 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 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 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. */ /** * Beware that the OMAP4 datasheet(s) lists GPIO banks 1-6, whereas the code * here uses 0-5. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #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 "gpio_if.h" #include "ti_gpio_if.h" #ifdef INTRNG #include "pic_if.h" #endif #if !defined(SOC_OMAP4) && !defined(SOC_TI_AM335X) #error "Unknown SoC" #endif /* Register definitions */ #define TI_GPIO_REVISION 0x0000 #define TI_GPIO_SYSCONFIG 0x0010 #define TI_GPIO_IRQSTATUS_RAW_0 0x0024 #define TI_GPIO_IRQSTATUS_RAW_1 0x0028 #define TI_GPIO_IRQSTATUS_0 0x002C /* writing a 0 has no effect */ #define TI_GPIO_IRQSTATUS_1 0x0030 /* writing a 0 has no effect */ #define TI_GPIO_IRQSTATUS_SET_0 0x0034 /* writing a 0 has no effect */ #define TI_GPIO_IRQSTATUS_SET_1 0x0038 /* writing a 0 has no effect */ #define TI_GPIO_IRQSTATUS_CLR_0 0x003C /* writing a 0 has no effect */ #define TI_GPIO_IRQSTATUS_CLR_1 0x0040 /* writing a 0 has no effect */ #define TI_GPIO_IRQWAKEN_0 0x0044 #define TI_GPIO_IRQWAKEN_1 0x0048 #define TI_GPIO_SYSSTATUS 0x0114 #define TI_GPIO_IRQSTATUS1 0x0118 #define TI_GPIO_IRQENABLE1 0x011C #define TI_GPIO_WAKEUPENABLE 0x0120 #define TI_GPIO_IRQSTATUS2 0x0128 #define TI_GPIO_IRQENABLE2 0x012C #define TI_GPIO_CTRL 0x0130 #define TI_GPIO_OE 0x0134 #define TI_GPIO_DATAIN 0x0138 #define TI_GPIO_DATAOUT 0x013C #define TI_GPIO_LEVELDETECT0 0x0140 /* RW register */ #define TI_GPIO_LEVELDETECT1 0x0144 /* RW register */ #define TI_GPIO_RISINGDETECT 0x0148 /* RW register */ #define TI_GPIO_FALLINGDETECT 0x014C /* RW register */ #define TI_GPIO_DEBOUNCENABLE 0x0150 #define TI_GPIO_DEBOUNCINGTIME 0x0154 #define TI_GPIO_CLEARWKUPENA 0x0180 #define TI_GPIO_SETWKUENA 0x0184 #define TI_GPIO_CLEARDATAOUT 0x0190 #define TI_GPIO_SETDATAOUT 0x0194 /* Other SoC Specific definitions */ #define OMAP4_FIRST_GPIO_BANK 1 #define OMAP4_INTR_PER_BANK 1 #define OMAP4_GPIO_REV 0x50600801 #define AM335X_FIRST_GPIO_BANK 0 #define AM335X_INTR_PER_BANK 2 #define AM335X_GPIO_REV 0x50600801 #define PINS_PER_BANK 32 #define TI_GPIO_MASK(p) (1U << ((p) % PINS_PER_BANK)) static int ti_gpio_intr(void *arg); static int ti_gpio_detach(device_t); #ifdef INTRNG static int ti_gpio_pic_attach(struct ti_gpio_softc *sc); static int ti_gpio_pic_detach(struct ti_gpio_softc *sc); #endif static u_int ti_first_gpio_bank(void) { switch(ti_chip()) { #ifdef SOC_OMAP4 case CHIP_OMAP_4: return (OMAP4_FIRST_GPIO_BANK); #endif #ifdef SOC_TI_AM335X case CHIP_AM335X: return (AM335X_FIRST_GPIO_BANK); #endif } return (0); } static uint32_t ti_gpio_rev(void) { switch(ti_chip()) { #ifdef SOC_OMAP4 case CHIP_OMAP_4: return (OMAP4_GPIO_REV); #endif #ifdef SOC_TI_AM335X case CHIP_AM335X: return (AM335X_GPIO_REV); #endif } return (0); } /** * Macros for driver mutex locking */ #define TI_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define TI_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define TI_GPIO_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ "ti_gpio", MTX_SPIN) #define TI_GPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define TI_GPIO_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define TI_GPIO_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED) /** * ti_gpio_read_4 - reads a 32-bit value from one of the GPIO registers * @sc: GPIO device context * @bank: The bank to read from * @off: The offset of a register from the GPIO register address range * * * RETURNS: * 32-bit value read from the register. */ static inline uint32_t ti_gpio_read_4(struct ti_gpio_softc *sc, bus_size_t off) { return (bus_read_4(sc->sc_mem_res, off)); } /** * ti_gpio_write_4 - writes a 32-bit value to one of the GPIO registers * @sc: GPIO device context * @bank: The bank to write to * @off: The offset of a register from the GPIO register address range * @val: The value to write into the register * * RETURNS: * nothing */ static inline void ti_gpio_write_4(struct ti_gpio_softc *sc, bus_size_t off, uint32_t val) { bus_write_4(sc->sc_mem_res, off, val); } static inline void ti_gpio_intr_clr(struct ti_gpio_softc *sc, uint32_t mask) { /* We clear both set of registers. */ ti_gpio_write_4(sc, TI_GPIO_IRQSTATUS_CLR_0, mask); ti_gpio_write_4(sc, TI_GPIO_IRQSTATUS_CLR_1, mask); } static inline void ti_gpio_intr_set(struct ti_gpio_softc *sc, uint32_t mask) { /* * On OMAP4 we unmask only the MPU interrupt and on AM335x we * also activate only the first interrupt. */ ti_gpio_write_4(sc, TI_GPIO_IRQSTATUS_SET_0, mask); } static inline void ti_gpio_intr_ack(struct ti_gpio_softc *sc, uint32_t mask) { /* * Acknowledge the interrupt on both registers even if we use only * the first one. */ ti_gpio_write_4(sc, TI_GPIO_IRQSTATUS_0, mask); ti_gpio_write_4(sc, TI_GPIO_IRQSTATUS_1, mask); } static inline uint32_t ti_gpio_intr_status(struct ti_gpio_softc *sc) { uint32_t reg; /* Get the status from both registers. */ reg = ti_gpio_read_4(sc, TI_GPIO_IRQSTATUS_0); reg |= ti_gpio_read_4(sc, TI_GPIO_IRQSTATUS_1); return (reg); } static device_t ti_gpio_get_bus(device_t dev) { struct ti_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } /** * ti_gpio_pin_max - Returns the maximum number of GPIO pins * @dev: gpio device handle * @maxpin: pointer to a value that upon return will contain the maximum number * of pins in the device. * * * LOCKING: * No locking required, returns static data. * * RETURNS: * Returns 0 on success otherwise an error code */ static int ti_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = PINS_PER_BANK - 1; return (0); } static int ti_gpio_valid_pin(struct ti_gpio_softc *sc, int pin) { if (pin >= sc->sc_maxpin || sc->sc_mem_res == NULL) return (EINVAL); return (0); } /** * ti_gpio_pin_getcaps - Gets the capabilties of a given pin * @dev: gpio device handle * @pin: the number of the pin * @caps: pointer to a value that upon return will contain the capabilities * * Currently all pins have the same capability, notably: * - GPIO_PIN_INPUT * - GPIO_PIN_OUTPUT * - GPIO_PIN_PULLUP * - GPIO_PIN_PULLDOWN * * LOCKING: * No locking required, returns static data. * * RETURNS: * Returns 0 on success otherwise an error code */ static int ti_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct ti_gpio_softc *sc; sc = device_get_softc(dev); if (ti_gpio_valid_pin(sc, pin) != 0) return (EINVAL); *caps = (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN); return (0); } /** * ti_gpio_pin_getflags - Gets the current flags of a given pin * @dev: gpio device handle * @pin: the number of the pin * @flags: upon return will contain the current flags of the pin * * Reads the current flags of a given pin, here we actually read the H/W * registers to determine the flags, rather than storing the value in the * setflags call. * * LOCKING: * Internally locks the context * * RETURNS: * Returns 0 on success otherwise an error code */ static int ti_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct ti_gpio_softc *sc; sc = device_get_softc(dev); if (ti_gpio_valid_pin(sc, pin) != 0) return (EINVAL); /* Get the current pin state */ TI_GPIO_LOCK(sc); TI_GPIO_GET_FLAGS(dev, pin, flags); TI_GPIO_UNLOCK(sc); return (0); } /** * ti_gpio_pin_getname - Gets the name of a given pin * @dev: gpio device handle * @pin: the number of the pin * @name: buffer to put the name in * * The driver simply calls the pins gpio_n, where 'n' is obviously the number * of the pin. * * LOCKING: * No locking required, returns static data. * * RETURNS: * Returns 0 on success otherwise an error code */ static int ti_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct ti_gpio_softc *sc; sc = device_get_softc(dev); if (ti_gpio_valid_pin(sc, pin) != 0) return (EINVAL); /* Set a very simple name */ snprintf(name, GPIOMAXNAME, "gpio_%u", pin); name[GPIOMAXNAME - 1] = '\0'; return (0); } /** * ti_gpio_pin_setflags - Sets the flags for a given pin * @dev: gpio device handle * @pin: the number of the pin * @flags: the flags to set * * The flags of the pin correspond to things like input/output mode, pull-ups, * pull-downs, etc. This driver doesn't support all flags, only the following: * - GPIO_PIN_INPUT * - GPIO_PIN_OUTPUT * - GPIO_PIN_PULLUP * - GPIO_PIN_PULLDOWN * * LOCKING: * Internally locks the context * * RETURNS: * Returns 0 on success otherwise an error code */ static int ti_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct ti_gpio_softc *sc; uint32_t oe; sc = device_get_softc(dev); if (ti_gpio_valid_pin(sc, pin) != 0) return (EINVAL); /* Set the GPIO mode and state */ TI_GPIO_LOCK(sc); if (TI_GPIO_SET_FLAGS(dev, pin, flags) != 0) { TI_GPIO_UNLOCK(sc); return (EINVAL); } /* If configuring as an output set the "output enable" bit */ oe = ti_gpio_read_4(sc, TI_GPIO_OE); if (flags & GPIO_PIN_INPUT) oe |= TI_GPIO_MASK(pin); else oe &= ~TI_GPIO_MASK(pin); ti_gpio_write_4(sc, TI_GPIO_OE, oe); TI_GPIO_UNLOCK(sc); return (0); } /** * ti_gpio_pin_set - Sets the current level on a GPIO pin * @dev: gpio device handle * @pin: the number of the pin * @value: non-zero value will drive the pin high, otherwise the pin is * driven low. * * * LOCKING: * Internally locks the context * * RETURNS: * Returns 0 on success otherwise a error code */ static int ti_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct ti_gpio_softc *sc; uint32_t reg; sc = device_get_softc(dev); if (ti_gpio_valid_pin(sc, pin) != 0) return (EINVAL); TI_GPIO_LOCK(sc); if (value == GPIO_PIN_LOW) reg = TI_GPIO_CLEARDATAOUT; else reg = TI_GPIO_SETDATAOUT; ti_gpio_write_4(sc, reg, TI_GPIO_MASK(pin)); TI_GPIO_UNLOCK(sc); return (0); } /** * ti_gpio_pin_get - Gets the current level on a GPIO pin * @dev: gpio device handle * @pin: the number of the pin * @value: pointer to a value that upond return will contain the pin value * * The pin must be configured as an input pin beforehand, otherwise this * function will fail. * * LOCKING: * Internally locks the context * * RETURNS: * Returns 0 on success otherwise a error code */ static int ti_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *value) { struct ti_gpio_softc *sc; uint32_t oe, reg, val; sc = device_get_softc(dev); if (ti_gpio_valid_pin(sc, pin) != 0) return (EINVAL); /* * Return data from output latch when set as output and from the * input register otherwise. */ TI_GPIO_LOCK(sc); oe = ti_gpio_read_4(sc, TI_GPIO_OE); if (oe & TI_GPIO_MASK(pin)) reg = TI_GPIO_DATAIN; else reg = TI_GPIO_DATAOUT; val = ti_gpio_read_4(sc, reg); *value = (val & TI_GPIO_MASK(pin)) ? 1 : 0; TI_GPIO_UNLOCK(sc); return (0); } /** * ti_gpio_pin_toggle - Toggles a given GPIO pin * @dev: gpio device handle * @pin: the number of the pin * * * LOCKING: * Internally locks the context * * RETURNS: * Returns 0 on success otherwise a error code */ static int ti_gpio_pin_toggle(device_t dev, uint32_t pin) { struct ti_gpio_softc *sc; uint32_t reg, val; sc = device_get_softc(dev); if (ti_gpio_valid_pin(sc, pin) != 0) return (EINVAL); /* Toggle the pin */ TI_GPIO_LOCK(sc); val = ti_gpio_read_4(sc, TI_GPIO_DATAOUT); if (val & TI_GPIO_MASK(pin)) reg = TI_GPIO_CLEARDATAOUT; else reg = TI_GPIO_SETDATAOUT; ti_gpio_write_4(sc, reg, TI_GPIO_MASK(pin)); TI_GPIO_UNLOCK(sc); return (0); } #ifndef INTRNG /** * ti_gpio_intr - ISR for all GPIO modules * @arg: the soft context pointer * * LOCKING: * Internally locks the context * */ static int ti_gpio_intr(void *arg) { int bank_last, irq; struct intr_event *event; struct ti_gpio_softc *sc; uint32_t reg; sc = (struct ti_gpio_softc *)arg; bank_last = -1; reg = 0; /* squelch bogus gcc warning */ reg = ti_gpio_intr_status(sc); for (irq = 0; irq < sc->sc_maxpin; irq++) { if ((reg & TI_GPIO_MASK(irq)) == 0) continue; event = sc->sc_events[irq]; if (event != NULL && !TAILQ_EMPTY(&event->ie_handlers)) intr_event_handle(event, NULL); else device_printf(sc->sc_dev, "Stray IRQ %d\n", irq); /* Ack the IRQ Status bit. */ ti_gpio_intr_ack(sc, TI_GPIO_MASK(irq)); } return (FILTER_HANDLED); } #endif static int ti_gpio_bank_init(device_t dev) { int pin; struct ti_gpio_softc *sc; uint32_t flags, reg_oe, reg_set, rev; clk_ident_t clk; sc = device_get_softc(dev); /* Enable the interface and functional clocks for the module. */ clk = ti_hwmods_get_clock(dev); if (clk == INVALID_CLK_IDENT) { device_printf(dev, "failed to get device id based on ti,hwmods\n"); return (EINVAL); } sc->sc_bank = clk - GPIO1_CLK + ti_first_gpio_bank(); ti_prcm_clk_enable(clk); /* * Read the revision number of the module. TI don't publish the * actual revision numbers, so instead the values have been * determined by experimentation. */ rev = ti_gpio_read_4(sc, TI_GPIO_REVISION); /* Check the revision. */ if (rev != ti_gpio_rev()) { device_printf(dev, "Warning: could not determine the revision " "of GPIO module (revision:0x%08x)\n", rev); return (EINVAL); } /* Disable interrupts for all pins. */ ti_gpio_intr_clr(sc, 0xffffffff); /* Init OE register based on pads configuration. */ reg_oe = 0xffffffff; reg_set = 0; for (pin = 0; pin < PINS_PER_BANK; pin++) { TI_GPIO_GET_FLAGS(dev, pin, &flags); if (flags & GPIO_PIN_OUTPUT) { reg_oe &= ~(1UL << pin); if (flags & GPIO_PIN_PULLUP) reg_set |= (1UL << pin); } } ti_gpio_write_4(sc, TI_GPIO_OE, reg_oe); if (reg_set) ti_gpio_write_4(sc, TI_GPIO_SETDATAOUT, reg_set); return (0); } /** * ti_gpio_attach - attach function for the driver * @dev: gpio device handle * * Allocates and sets up the driver context for all GPIO banks. This function * expects the memory ranges and IRQs to already be allocated to the driver. * * LOCKING: * None * * RETURNS: * Always returns 0 */ static int ti_gpio_attach(device_t dev) { struct ti_gpio_softc *sc; #ifndef INTRNG unsigned int i; #endif int err; sc = device_get_softc(dev); sc->sc_dev = dev; TI_GPIO_LOCK_INIT(sc); ti_gpio_pin_max(dev, &sc->sc_maxpin); sc->sc_maxpin++; sc->sc_mem_rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); if (!sc->sc_mem_res) { device_printf(dev, "Error: could not allocate mem resources\n"); ti_gpio_detach(dev); return (ENXIO); } sc->sc_irq_rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE); if (!sc->sc_irq_res) { device_printf(dev, "Error: could not allocate irq resources\n"); ti_gpio_detach(dev); return (ENXIO); } /* * Register our interrupt filter for each of the IRQ resources. */ if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, ti_gpio_intr, NULL, sc, &sc->sc_irq_hdl) != 0) { device_printf(dev, "WARNING: unable to register interrupt filter\n"); ti_gpio_detach(dev); return (ENXIO); } #ifdef INTRNG if (ti_gpio_pic_attach(sc) != 0) { device_printf(dev, "WARNING: unable to attach PIC\n"); ti_gpio_detach(dev); return (ENXIO); } #else /* * Initialize the interrupt settings. The default is active-low * interrupts. */ sc->sc_irq_trigger = malloc( sizeof(*sc->sc_irq_trigger) * sc->sc_maxpin, M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_irq_polarity = malloc( sizeof(*sc->sc_irq_polarity) * sc->sc_maxpin, M_DEVBUF, M_WAITOK | M_ZERO); for (i = 0; i < sc->sc_maxpin; i++) { sc->sc_irq_trigger[i] = INTR_TRIGGER_LEVEL; sc->sc_irq_polarity[i] = INTR_POLARITY_LOW; } sc->sc_events = malloc(sizeof(struct intr_event *) * sc->sc_maxpin, M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_mask_args = malloc(sizeof(struct ti_gpio_mask_arg) * sc->sc_maxpin, M_DEVBUF, M_WAITOK | M_ZERO); #endif /* We need to go through each block and ensure the clocks are running and * the module is enabled. It might be better to do this only when the * pins are configured which would result in less power used if the GPIO * pins weren't used ... */ if (sc->sc_mem_res != NULL) { /* Initialize the GPIO module. */ err = ti_gpio_bank_init(dev); if (err != 0) { ti_gpio_detach(dev); return (err); } } sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) { ti_gpio_detach(dev); return (ENXIO); } return (0); } /** * ti_gpio_detach - detach function for the driver * @dev: scm device handle * * Allocates and sets up the driver context, this simply entails creating a * bus mappings for the SCM register set. * * LOCKING: * None * * RETURNS: * Always returns 0 */ static int ti_gpio_detach(device_t dev) { struct ti_gpio_softc *sc = device_get_softc(dev); KASSERT(mtx_initialized(&sc->sc_mtx), ("gpio mutex not initialized")); /* Disable all interrupts */ if (sc->sc_mem_res != NULL) ti_gpio_intr_clr(sc, 0xffffffff); gpiobus_detach_bus(dev); #ifdef INTRNG if (sc->sc_isrcs != NULL) ti_gpio_pic_detach(sc); #else if (sc->sc_events) free(sc->sc_events, M_DEVBUF); if (sc->sc_mask_args) free(sc->sc_mask_args, M_DEVBUF); if (sc->sc_irq_polarity) free(sc->sc_irq_polarity, M_DEVBUF); if (sc->sc_irq_trigger) free(sc->sc_irq_trigger, M_DEVBUF); #endif /* Release the memory and IRQ resources. */ if (sc->sc_irq_hdl) { bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_hdl); } bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); TI_GPIO_LOCK_DESTROY(sc); return (0); } #ifdef INTRNG static inline void ti_gpio_rwreg_set(struct ti_gpio_softc *sc, uint32_t reg, uint32_t mask) { ti_gpio_write_4(sc, reg, ti_gpio_read_4(sc, reg) | mask); } static inline void ti_gpio_rwreg_clr(struct ti_gpio_softc *sc, uint32_t reg, uint32_t mask) { ti_gpio_write_4(sc, reg, ti_gpio_read_4(sc, reg) & ~mask); } static inline void ti_gpio_isrc_mask(struct ti_gpio_softc *sc, struct ti_gpio_irqsrc *tgi) { /* Writing a 0 has no effect. */ ti_gpio_intr_clr(sc, tgi->tgi_mask); } static inline void ti_gpio_isrc_unmask(struct ti_gpio_softc *sc, struct ti_gpio_irqsrc *tgi) { /* Writing a 0 has no effect. */ ti_gpio_intr_set(sc, tgi->tgi_mask); } static inline void ti_gpio_isrc_eoi(struct ti_gpio_softc *sc, struct ti_gpio_irqsrc *tgi) { /* Writing a 0 has no effect. */ ti_gpio_intr_ack(sc, tgi->tgi_mask); } static inline bool ti_gpio_isrc_is_level(struct ti_gpio_irqsrc *tgi) { return (tgi->tgi_cfgreg == TI_GPIO_LEVELDETECT0 || tgi->tgi_cfgreg == TI_GPIO_LEVELDETECT1); } static int ti_gpio_intr(void *arg) { u_int irq; uint32_t reg; struct ti_gpio_softc *sc; struct trapframe *tf; struct ti_gpio_irqsrc *tgi; sc = (struct ti_gpio_softc *)arg; tf = curthread->td_intr_frame; reg = ti_gpio_intr_status(sc); for (irq = 0; irq < sc->sc_maxpin; irq++) { tgi = &sc->sc_isrcs[irq]; if ((reg & tgi->tgi_mask) == 0) continue; if (!ti_gpio_isrc_is_level(tgi)) ti_gpio_isrc_eoi(sc, tgi); if (intr_isrc_dispatch(&tgi->tgi_isrc, tf) != 0) { ti_gpio_isrc_mask(sc, tgi); if (ti_gpio_isrc_is_level(tgi)) ti_gpio_isrc_eoi(sc, tgi); device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq); } } return (FILTER_HANDLED); } static int ti_gpio_pic_attach(struct ti_gpio_softc *sc) { int error; uint32_t irq; const char *name; sc->sc_isrcs = malloc(sizeof(*sc->sc_isrcs) * sc->sc_maxpin, M_DEVBUF, M_WAITOK | M_ZERO); name = device_get_nameunit(sc->sc_dev); for (irq = 0; irq < sc->sc_maxpin; irq++) { sc->sc_isrcs[irq].tgi_irq = irq; sc->sc_isrcs[irq].tgi_mask = TI_GPIO_MASK(irq); sc->sc_isrcs[irq].tgi_cfgreg = 0; error = intr_isrc_register(&sc->sc_isrcs[irq].tgi_isrc, sc->sc_dev, 0, "%s,%u", name, irq); if (error != 0) return (error); /* XXX deregister ISRCs */ } return (intr_pic_register(sc->sc_dev, OF_xref_from_node(ofw_bus_get_node(sc->sc_dev)))); } static int ti_gpio_pic_detach(struct ti_gpio_softc *sc) { /* * There has not been established any procedure yet * how to detach PIC from living system correctly. */ device_printf(sc->sc_dev, "%s: not implemented yet\n", __func__); return (EBUSY); } static void ti_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct ti_gpio_softc *sc = device_get_softc(dev); struct ti_gpio_irqsrc *tgi = (struct ti_gpio_irqsrc *)isrc; ti_gpio_isrc_mask(sc, tgi); } static void ti_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct ti_gpio_softc *sc = device_get_softc(dev); struct ti_gpio_irqsrc *tgi = (struct ti_gpio_irqsrc *)isrc; arm_irq_memory_barrier(tgi->tgi_irq); ti_gpio_isrc_unmask(sc, tgi); } static int ti_gpio_pic_map_fdt(struct ti_gpio_softc *sc, u_int ncells, pcell_t *cells, u_int *irqp, uint32_t *regp) { uint32_t reg; /* * The first cell is the interrupt number. * The second cell is used to specify flags: * bits[3:0] trigger type and level flags: * 1 = low-to-high edge triggered. * 2 = high-to-low edge triggered. * 4 = active high level-sensitive. * 8 = active low level-sensitive. */ if (ncells != 2 || cells[0] >= sc->sc_maxpin) return (EINVAL); /* * All interrupt types could be set for an interrupt at one moment. * At least, the combination of 'low-to-high' and 'high-to-low' edge * triggered interrupt types can make a sense. However, no combo is * supported now. */ if (cells[1] == 1) reg = TI_GPIO_RISINGDETECT; else if (cells[1] == 2) reg = TI_GPIO_FALLINGDETECT; else if (cells[1] == 4) reg = TI_GPIO_LEVELDETECT1; else if (cells[1] == 8) reg = TI_GPIO_LEVELDETECT0; else return (EINVAL); *irqp = cells[0]; if (regp != NULL) *regp = reg; return (0); } static int ti_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int error; u_int irq; struct ti_gpio_softc *sc; + struct intr_map_data_fdt *daf; if (data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); sc = device_get_softc(dev); - error = ti_gpio_pic_map_fdt(sc, data->fdt.ncells, data->fdt.cells, &irq, - NULL); + daf = (struct intr_map_data_fdt *)data; + + error = ti_gpio_pic_map_fdt(sc, daf->ncells, daf->cells, &irq, NULL); if (error == 0) *isrcp = &sc->sc_isrcs[irq].tgi_isrc; return (error); } static void ti_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct ti_gpio_softc *sc = device_get_softc(dev); struct ti_gpio_irqsrc *tgi = (struct ti_gpio_irqsrc *)isrc; if (ti_gpio_isrc_is_level(tgi)) ti_gpio_isrc_eoi(sc, tgi); } static void ti_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { ti_gpio_pic_enable_intr(dev, isrc); } static void ti_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct ti_gpio_softc *sc = device_get_softc(dev); struct ti_gpio_irqsrc *tgi = (struct ti_gpio_irqsrc *)isrc; ti_gpio_isrc_mask(sc, tgi); if (ti_gpio_isrc_is_level(tgi)) ti_gpio_isrc_eoi(sc, tgi); } static int ti_gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { u_int irq; uint32_t cfgreg; struct ti_gpio_softc *sc; struct ti_gpio_irqsrc *tgi; + struct intr_map_data_fdt *daf; if (data == NULL || data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); sc = device_get_softc(dev); tgi = (struct ti_gpio_irqsrc *)isrc; + daf = (struct intr_map_data_fdt *)data; /* Get and check config for an interrupt. */ - if (ti_gpio_pic_map_fdt(sc, data->fdt.ncells, data->fdt.cells, &irq, + if (ti_gpio_pic_map_fdt(sc, daf->ncells, daf->cells, &irq, &cfgreg) != 0 || tgi->tgi_irq != irq) return (EINVAL); /* * If this is a setup for another handler, * only check that its configuration match. */ if (isrc->isrc_handlers != 0) return (tgi->tgi_cfgreg == cfgreg ? 0 : EINVAL); TI_GPIO_LOCK(sc); ti_gpio_rwreg_clr(sc, TI_GPIO_RISINGDETECT, tgi->tgi_mask); ti_gpio_rwreg_clr(sc, TI_GPIO_FALLINGDETECT, tgi->tgi_mask); ti_gpio_rwreg_clr(sc, TI_GPIO_LEVELDETECT1, tgi->tgi_mask); ti_gpio_rwreg_clr(sc, TI_GPIO_LEVELDETECT0, tgi->tgi_mask); tgi->tgi_cfgreg = cfgreg; ti_gpio_rwreg_set(sc, cfgreg, tgi->tgi_mask); TI_GPIO_UNLOCK(sc); return (0); } static int ti_gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct ti_gpio_softc *sc = device_get_softc(dev); struct ti_gpio_irqsrc *tgi = (struct ti_gpio_irqsrc *)isrc; if (isrc->isrc_handlers == 0) { TI_GPIO_LOCK(sc); ti_gpio_rwreg_clr(sc, tgi->tgi_cfgreg, tgi->tgi_mask); tgi->tgi_cfgreg = 0; TI_GPIO_UNLOCK(sc); } return (0); } #else static uint32_t ti_gpio_intr_reg(struct ti_gpio_softc *sc, int irq) { if (ti_gpio_valid_pin(sc, irq) != 0) return (0); if (sc->sc_irq_trigger[irq] == INTR_TRIGGER_LEVEL) { if (sc->sc_irq_polarity[irq] == INTR_POLARITY_LOW) return (TI_GPIO_LEVELDETECT0); else if (sc->sc_irq_polarity[irq] == INTR_POLARITY_HIGH) return (TI_GPIO_LEVELDETECT1); } else if (sc->sc_irq_trigger[irq] == INTR_TRIGGER_EDGE) { if (sc->sc_irq_polarity[irq] == INTR_POLARITY_LOW) return (TI_GPIO_FALLINGDETECT); else if (sc->sc_irq_polarity[irq] == INTR_POLARITY_HIGH) return (TI_GPIO_RISINGDETECT); } return (0); } static void ti_gpio_mask_irq_internal(struct ti_gpio_softc *sc, int irq) { uint32_t reg, val; if (ti_gpio_valid_pin(sc, irq) != 0) return; TI_GPIO_LOCK(sc); ti_gpio_intr_clr(sc, TI_GPIO_MASK(irq)); reg = ti_gpio_intr_reg(sc, irq); if (reg != 0) { val = ti_gpio_read_4(sc, reg); val &= ~TI_GPIO_MASK(irq); ti_gpio_write_4(sc, reg, val); } TI_GPIO_UNLOCK(sc); } static void ti_gpio_unmask_irq_internal(struct ti_gpio_softc *sc, int irq) { uint32_t reg, val; if (ti_gpio_valid_pin(sc, irq) != 0) return; TI_GPIO_LOCK(sc); reg = ti_gpio_intr_reg(sc, irq); if (reg != 0) { val = ti_gpio_read_4(sc, reg); val |= TI_GPIO_MASK(irq); ti_gpio_write_4(sc, reg, val); ti_gpio_intr_set(sc, TI_GPIO_MASK(irq)); } TI_GPIO_UNLOCK(sc); } static void ti_gpio_mask_irq(void *source) { struct ti_gpio_mask_arg *arg = source; ti_gpio_mask_irq_internal(arg->softc, arg->pin); } static void ti_gpio_unmask_irq(void *source) { struct ti_gpio_mask_arg *arg = source; ti_gpio_unmask_irq_internal(arg->softc, arg->pin); } static int ti_gpio_activate_resource(device_t dev, device_t child, int type, int rid, struct resource *res) { struct ti_gpio_mask_arg mask_arg; if (type != SYS_RES_IRQ) return (ENXIO); /* Unmask the interrupt. */ mask_arg.pin = rman_get_start(res); mask_arg.softc = device_get_softc(dev); ti_gpio_unmask_irq((void *)&mask_arg); return (0); } static int ti_gpio_deactivate_resource(device_t dev, device_t child, int type, int rid, struct resource *res) { int pin; if (type != SYS_RES_IRQ) return (ENXIO); /* Mask the interrupt. */ pin = rman_get_start(res); ti_gpio_mask_irq((void *)(uintptr_t)pin); return (0); } static int ti_gpio_config_intr(device_t dev, int irq, enum intr_trigger trig, enum intr_polarity pol) { struct ti_gpio_softc *sc; uint32_t oldreg, reg, val; sc = device_get_softc(dev); if (ti_gpio_valid_pin(sc, irq) != 0) return (EINVAL); /* There is no standard trigger or polarity. */ if (trig == INTR_TRIGGER_CONFORM || pol == INTR_POLARITY_CONFORM) return (EINVAL); TI_GPIO_LOCK(sc); /* * TRM recommends add the new event before remove the old one to * avoid losing interrupts. */ oldreg = ti_gpio_intr_reg(sc, irq); sc->sc_irq_trigger[irq] = trig; sc->sc_irq_polarity[irq] = pol; reg = ti_gpio_intr_reg(sc, irq); if (reg != 0) { /* Apply the new settings. */ val = ti_gpio_read_4(sc, reg); val |= TI_GPIO_MASK(irq); ti_gpio_write_4(sc, reg, val); } if (reg != oldreg && oldreg != 0) { /* Remove the old settings. */ val = ti_gpio_read_4(sc, oldreg); val &= ~TI_GPIO_MASK(irq); ti_gpio_write_4(sc, oldreg, val); } TI_GPIO_UNLOCK(sc); return (0); } static int ti_gpio_setup_intr(device_t dev, device_t child, struct resource *ires, int flags, driver_filter_t *filt, driver_intr_t *handler, void *arg, void **cookiep) { struct ti_gpio_softc *sc; struct intr_event *event; int pin, error; sc = device_get_softc(dev); pin = rman_get_start(ires); if (ti_gpio_valid_pin(sc, pin) != 0) panic("%s: bad pin %d", __func__, pin); event = sc->sc_events[pin]; if (event == NULL) { sc->sc_mask_args[pin].softc = sc; sc->sc_mask_args[pin].pin = pin; error = intr_event_create(&event, (void *)&sc->sc_mask_args[pin], 0, pin, ti_gpio_mask_irq, ti_gpio_unmask_irq, NULL, NULL, "gpio%d pin%d:", device_get_unit(dev), pin); if (error != 0) return (error); sc->sc_events[pin] = event; } intr_event_add_handler(event, device_get_nameunit(child), filt, handler, arg, intr_priority(flags), flags, cookiep); return (0); } static int ti_gpio_teardown_intr(device_t dev, device_t child, struct resource *ires, void *cookie) { struct ti_gpio_softc *sc; int pin, err; sc = device_get_softc(dev); pin = rman_get_start(ires); if (ti_gpio_valid_pin(sc, pin) != 0) panic("%s: bad pin %d", __func__, pin); if (sc->sc_events[pin] == NULL) panic("Trying to teardown unoccupied IRQ"); err = intr_event_remove_handler(cookie); if (!err) sc->sc_events[pin] = NULL; return (err); } #endif static phandle_t ti_gpio_get_node(device_t bus, device_t dev) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } static device_method_t ti_gpio_methods[] = { DEVMETHOD(device_attach, ti_gpio_attach), DEVMETHOD(device_detach, ti_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, ti_gpio_get_bus), DEVMETHOD(gpio_pin_max, ti_gpio_pin_max), DEVMETHOD(gpio_pin_getname, ti_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, ti_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, ti_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, ti_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, ti_gpio_pin_get), DEVMETHOD(gpio_pin_set, ti_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, ti_gpio_pin_toggle), #ifdef INTRNG /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, ti_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, ti_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, ti_gpio_pic_map_intr), DEVMETHOD(pic_setup_intr, ti_gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, ti_gpio_pic_teardown_intr), DEVMETHOD(pic_post_filter, ti_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, ti_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, ti_gpio_pic_pre_ithread), #else /* Bus interface */ DEVMETHOD(bus_activate_resource, ti_gpio_activate_resource), DEVMETHOD(bus_deactivate_resource, ti_gpio_deactivate_resource), DEVMETHOD(bus_config_intr, ti_gpio_config_intr), DEVMETHOD(bus_setup_intr, ti_gpio_setup_intr), DEVMETHOD(bus_teardown_intr, ti_gpio_teardown_intr), #endif /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, ti_gpio_get_node), {0, 0}, }; driver_t ti_gpio_driver = { "gpio", ti_gpio_methods, sizeof(struct ti_gpio_softc), }; Index: user/ngie/detangle-rc/sys/arm64/arm64/gic_v3.c =================================================================== --- user/ngie/detangle-rc/sys/arm64/arm64/gic_v3.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm64/arm64/gic_v3.c (revision 299127) @@ -1,744 +1,745 @@ /*- * Copyright (c) 2015 The FreeBSD Foundation * All rights reserved. * * This software was developed by Semihalf under * the sponsorship of the FreeBSD Foundation. * * 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. */ #include __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pic_if.h" #include "gic_v3_reg.h" #include "gic_v3_var.h" /* Device and PIC methods */ static int gic_v3_bind(device_t, u_int, u_int); static void gic_v3_dispatch(device_t, struct trapframe *); static void gic_v3_eoi(device_t, u_int); static void gic_v3_mask_irq(device_t, u_int); static void gic_v3_unmask_irq(device_t, u_int); #ifdef SMP static void gic_v3_init_secondary(device_t); static void gic_v3_ipi_send(device_t, cpuset_t, u_int); #endif static device_method_t gic_v3_methods[] = { /* Device interface */ DEVMETHOD(device_detach, gic_v3_detach), /* PIC interface */ DEVMETHOD(pic_bind, gic_v3_bind), DEVMETHOD(pic_dispatch, gic_v3_dispatch), DEVMETHOD(pic_eoi, gic_v3_eoi), DEVMETHOD(pic_mask, gic_v3_mask_irq), DEVMETHOD(pic_unmask, gic_v3_unmask_irq), #ifdef SMP DEVMETHOD(pic_init_secondary, gic_v3_init_secondary), DEVMETHOD(pic_ipi_send, gic_v3_ipi_send), #endif /* End */ DEVMETHOD_END }; DEFINE_CLASS_0(gic, gic_v3_driver, gic_v3_methods, sizeof(struct gic_v3_softc)); /* * Driver-specific definitions. */ MALLOC_DEFINE(M_GIC_V3, "GICv3", GIC_V3_DEVSTR); /* * Helper functions and definitions. */ /* Destination registers, either Distributor or Re-Distributor */ enum gic_v3_xdist { DIST = 0, REDIST, }; /* Helper routines starting with gic_v3_ */ static int gic_v3_dist_init(struct gic_v3_softc *); static int gic_v3_redist_alloc(struct gic_v3_softc *); static int gic_v3_redist_find(struct gic_v3_softc *); static int gic_v3_redist_init(struct gic_v3_softc *); static int gic_v3_cpu_init(struct gic_v3_softc *); static void gic_v3_wait_for_rwp(struct gic_v3_softc *, enum gic_v3_xdist); /* A sequence of init functions for primary (boot) CPU */ typedef int (*gic_v3_initseq_t) (struct gic_v3_softc *); /* Primary CPU initialization sequence */ static gic_v3_initseq_t gic_v3_primary_init[] = { gic_v3_dist_init, gic_v3_redist_alloc, gic_v3_redist_init, gic_v3_cpu_init, NULL }; #ifdef SMP /* Secondary CPU initialization sequence */ static gic_v3_initseq_t gic_v3_secondary_init[] = { gic_v3_redist_init, gic_v3_cpu_init, NULL }; #endif /* * Device interface. */ int gic_v3_attach(device_t dev) { struct gic_v3_softc *sc; gic_v3_initseq_t *init_func; uint32_t typer; int rid; int err; size_t i; sc = device_get_softc(dev); sc->gic_registered = FALSE; sc->dev = dev; err = 0; /* Initialize mutex */ mtx_init(&sc->gic_mtx, "GICv3 lock", NULL, MTX_SPIN); /* * Allocate array of struct resource. * One entry for Distributor and all remaining for Re-Distributor. */ sc->gic_res = malloc( sizeof(*sc->gic_res) * (sc->gic_redists.nregions + 1), M_GIC_V3, M_WAITOK); /* Now allocate corresponding resources */ for (i = 0, rid = 0; i < (sc->gic_redists.nregions + 1); i++, rid++) { sc->gic_res[rid] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->gic_res[rid] == NULL) return (ENXIO); } /* * Distributor interface */ sc->gic_dist = sc->gic_res[0]; /* * Re-Dristributor interface */ /* Allocate space under region descriptions */ sc->gic_redists.regions = malloc( sizeof(*sc->gic_redists.regions) * sc->gic_redists.nregions, M_GIC_V3, M_WAITOK); /* Fill-up bus_space information for each region. */ for (i = 0, rid = 1; i < sc->gic_redists.nregions; i++, rid++) sc->gic_redists.regions[i] = sc->gic_res[rid]; /* Get the number of supported SPI interrupts */ typer = gic_d_read(sc, 4, GICD_TYPER); sc->gic_nirqs = GICD_TYPER_I_NUM(typer); if (sc->gic_nirqs > GIC_I_NUM_MAX) sc->gic_nirqs = GIC_I_NUM_MAX; /* Get the number of supported interrupt identifier bits */ sc->gic_idbits = GICD_TYPER_IDBITS(typer); if (bootverbose) { device_printf(dev, "SPIs: %u, IDs: %u\n", sc->gic_nirqs, (1 << sc->gic_idbits) - 1); } /* Train init sequence for boot CPU */ for (init_func = gic_v3_primary_init; *init_func != NULL; init_func++) { err = (*init_func)(sc); if (err != 0) return (err); } /* * Full success. * Now register PIC to the interrupts handling layer. */ arm_register_root_pic(dev, sc->gic_nirqs); sc->gic_registered = TRUE; return (0); } int gic_v3_detach(device_t dev) { struct gic_v3_softc *sc; size_t i; int rid; sc = device_get_softc(dev); if (device_is_attached(dev)) { /* * XXX: We should probably deregister PIC */ if (sc->gic_registered) panic("Trying to detach registered PIC"); } for (rid = 0; rid < (sc->gic_redists.nregions + 1); rid++) bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->gic_res[rid]); for (i = 0; i < mp_ncpus; i++) free(sc->gic_redists.pcpu[i], M_GIC_V3); free(sc->gic_res, M_GIC_V3); free(sc->gic_redists.regions, M_GIC_V3); return (0); } /* * PIC interface. */ static int gic_v3_bind(device_t dev, u_int irq, u_int cpuid) { uint64_t aff; struct gic_v3_softc *sc; sc = device_get_softc(dev); if (irq <= GIC_LAST_PPI) { /* Can't bind PPI to another CPU but it's not an error */ return (0); } else if (irq >= GIC_FIRST_SPI && irq <= GIC_LAST_SPI) { aff = CPU_AFFINITY(cpuid); gic_d_write(sc, 4, GICD_IROUTER(irq), aff); return (0); } else if (irq >= GIC_FIRST_LPI) return (lpi_migrate(dev, irq, cpuid)); return (EINVAL); } static void gic_v3_dispatch(device_t dev, struct trapframe *frame) { uint64_t active_irq; while (1) { if (CPU_MATCH_ERRATA_CAVIUM_THUNDER_1_1) { /* * Hardware: Cavium ThunderX * Chip revision: Pass 1.0 (early version) * Pass 1.1 (production) * ERRATUM: 22978, 23154 */ __asm __volatile( "nop;nop;nop;nop;nop;nop;nop;nop; \n" "mrs %0, ICC_IAR1_EL1 \n" "nop;nop;nop;nop; \n" "dsb sy \n" : "=&r" (active_irq)); } else { active_irq = gic_icc_read(IAR1); } if (__predict_false(active_irq == ICC_IAR1_EL1_SPUR)) break; if (__predict_true((active_irq >= GIC_FIRST_PPI && active_irq <= GIC_LAST_SPI) || active_irq >= GIC_FIRST_LPI)) { arm_dispatch_intr(active_irq, frame); continue; } if (active_irq <= GIC_LAST_SGI) { gic_icc_write(EOIR1, (uint64_t)active_irq); arm_dispatch_intr(active_irq, frame); continue; } } } static void gic_v3_eoi(device_t dev, u_int irq) { gic_icc_write(EOIR1, (uint64_t)irq); } static void gic_v3_mask_irq(device_t dev, u_int irq) { struct gic_v3_softc *sc; sc = device_get_softc(dev); if (irq <= GIC_LAST_PPI) { /* SGIs and PPIs in corresponding Re-Distributor */ gic_r_write(sc, 4, GICR_SGI_BASE_SIZE + GICD_ICENABLER(irq), GICD_I_MASK(irq)); gic_v3_wait_for_rwp(sc, REDIST); } else if (irq >= GIC_FIRST_SPI && irq <= GIC_LAST_SPI) { /* SPIs in distributor */ gic_r_write(sc, 4, GICD_ICENABLER(irq), GICD_I_MASK(irq)); gic_v3_wait_for_rwp(sc, DIST); } else if (irq >= GIC_FIRST_LPI) { /* LPIs */ lpi_mask_irq(dev, irq); } else panic("%s: Unsupported IRQ number %u", __func__, irq); } static void gic_v3_unmask_irq(device_t dev, u_int irq) { struct gic_v3_softc *sc; sc = device_get_softc(dev); if (irq <= GIC_LAST_PPI) { /* SGIs and PPIs in corresponding Re-Distributor */ gic_r_write(sc, 4, GICR_SGI_BASE_SIZE + GICD_ISENABLER(irq), GICD_I_MASK(irq)); gic_v3_wait_for_rwp(sc, REDIST); } else if (irq >= GIC_FIRST_SPI && irq <= GIC_LAST_SPI) { /* SPIs in distributor */ gic_d_write(sc, 4, GICD_ISENABLER(irq), GICD_I_MASK(irq)); gic_v3_wait_for_rwp(sc, DIST); } else if (irq >= GIC_FIRST_LPI) { /* LPIs */ lpi_unmask_irq(dev, irq); } else panic("%s: Unsupported IRQ number %u", __func__, irq); } #ifdef SMP static void gic_v3_init_secondary(device_t dev) { struct gic_v3_softc *sc; gic_v3_initseq_t *init_func; int err; sc = device_get_softc(dev); /* Train init sequence for boot CPU */ for (init_func = gic_v3_secondary_init; *init_func != NULL; init_func++) { err = (*init_func)(sc); if (err != 0) { device_printf(dev, "Could not initialize GIC for CPU%u\n", PCPU_GET(cpuid)); return; } } /* * Try to initialize ITS. * If there is no driver attached this routine will fail but that * does not mean failure here as only LPIs will not be functional * on the current CPU. */ if (its_init_cpu(NULL) != 0) { device_printf(dev, "Could not initialize ITS for CPU%u. " "No LPIs will arrive on this CPU\n", PCPU_GET(cpuid)); } /* * ARM64TODO: Unmask timer PPIs. To be removed when appropriate * mechanism is implemented. * Activate the timer interrupts: virtual (27), secure (29), * and non-secure (30). Use hardcoded values here as there * should be no defines for them. */ gic_v3_unmask_irq(dev, 27); gic_v3_unmask_irq(dev, 29); gic_v3_unmask_irq(dev, 30); } static void gic_v3_ipi_send(device_t dev, cpuset_t cpuset, u_int ipi) { u_int cpu; uint64_t aff, tlist; uint64_t val; uint64_t aff_mask; /* Set affinity mask to match level 3, 2 and 1 */ aff_mask = CPU_AFF1_MASK | CPU_AFF2_MASK | CPU_AFF3_MASK; /* Iterate through all CPUs in set */ while (!CPU_EMPTY(&cpuset)) { aff = tlist = 0; for (cpu = 0; cpu < mp_ncpus; cpu++) { /* Compose target list for single AFF3:AFF2:AFF1 set */ if (CPU_ISSET(cpu, &cpuset)) { if (!tlist) { /* * Save affinity of the first CPU to * send IPI to for later comparison. */ aff = CPU_AFFINITY(cpu); tlist |= (1UL << CPU_AFF0(aff)); CPU_CLR(cpu, &cpuset); } /* Check for same Affinity level 3, 2 and 1 */ if ((aff & aff_mask) == (CPU_AFFINITY(cpu) & aff_mask)) { tlist |= (1UL << CPU_AFF0(CPU_AFFINITY(cpu))); /* Clear CPU in cpuset from target list */ CPU_CLR(cpu, &cpuset); } } } if (tlist) { KASSERT((tlist & ~ICC_SGI1R_EL1_TL_MASK) == 0, ("Target list too long for GICv3 IPI")); /* Send SGI to CPUs in target list */ val = tlist; val |= (uint64_t)CPU_AFF3(aff) << ICC_SGI1R_EL1_AFF3_SHIFT; val |= (uint64_t)CPU_AFF2(aff) << ICC_SGI1R_EL1_AFF2_SHIFT; val |= (uint64_t)CPU_AFF1(aff) << ICC_SGI1R_EL1_AFF1_SHIFT; val |= (uint64_t)(ipi & ICC_SGI1R_EL1_SGIID_MASK) << ICC_SGI1R_EL1_SGIID_SHIFT; gic_icc_write(SGI1R, val); } } } #endif /* * Helper routines */ static void gic_v3_wait_for_rwp(struct gic_v3_softc *sc, enum gic_v3_xdist xdist) { struct resource *res; u_int cpuid; size_t us_left = 1000000; cpuid = PCPU_GET(cpuid); switch (xdist) { case DIST: res = sc->gic_dist; break; case REDIST: res = sc->gic_redists.pcpu[cpuid]; break; default: KASSERT(0, ("%s: Attempt to wait for unknown RWP", __func__)); return; } while ((bus_read_4(res, GICD_CTLR) & GICD_CTLR_RWP) != 0) { DELAY(1); if (us_left-- == 0) panic("GICD Register write pending for too long"); } } /* CPU interface. */ static __inline void gic_v3_cpu_priority(uint64_t mask) { /* Set prority mask */ gic_icc_write(PMR, mask & ICC_PMR_EL1_PRIO_MASK); } static int gic_v3_cpu_enable_sre(struct gic_v3_softc *sc) { uint64_t sre; u_int cpuid; cpuid = PCPU_GET(cpuid); /* * Set the SRE bit to enable access to GIC CPU interface * via system registers. */ sre = READ_SPECIALREG(icc_sre_el1); sre |= ICC_SRE_EL1_SRE; WRITE_SPECIALREG(icc_sre_el1, sre); isb(); /* * Now ensure that the bit is set. */ sre = READ_SPECIALREG(icc_sre_el1); if ((sre & ICC_SRE_EL1_SRE) == 0) { /* We are done. This was disabled in EL2 */ device_printf(sc->dev, "ERROR: CPU%u cannot enable CPU interface " "via system registers\n", cpuid); return (ENXIO); } else if (bootverbose) { device_printf(sc->dev, "CPU%u enabled CPU interface via system registers\n", cpuid); } return (0); } static int gic_v3_cpu_init(struct gic_v3_softc *sc) { int err; /* Enable access to CPU interface via system registers */ err = gic_v3_cpu_enable_sre(sc); if (err != 0) return (err); /* Priority mask to minimum - accept all interrupts */ gic_v3_cpu_priority(GIC_PRIORITY_MIN); /* Disable EOI mode */ gic_icc_clear(CTLR, ICC_CTLR_EL1_EOIMODE); /* Enable group 1 (insecure) interrups */ gic_icc_set(IGRPEN1, ICC_IGRPEN0_EL1_EN); return (0); } /* Distributor */ static int gic_v3_dist_init(struct gic_v3_softc *sc) { uint64_t aff; u_int i; /* * 1. Disable the Distributor */ gic_d_write(sc, 4, GICD_CTLR, 0); gic_v3_wait_for_rwp(sc, DIST); /* * 2. Configure the Distributor */ /* Set all global interrupts to be level triggered, active low. */ for (i = GIC_FIRST_SPI; i < sc->gic_nirqs; i += GICD_I_PER_ICFGRn) gic_d_write(sc, 4, GICD_ICFGR(i), 0x00000000); /* Set priority to all shared interrupts */ for (i = GIC_FIRST_SPI; i < sc->gic_nirqs; i += GICD_I_PER_IPRIORITYn) { /* Set highest priority */ gic_d_write(sc, 4, GICD_IPRIORITYR(i), GIC_PRIORITY_MAX); } /* * Disable all interrupts. Leave PPI and SGIs as they are enabled in * Re-Distributor registers. */ for (i = GIC_FIRST_SPI; i < sc->gic_nirqs; i += GICD_I_PER_ISENABLERn) gic_d_write(sc, 4, GICD_ICENABLER(i), 0xFFFFFFFF); gic_v3_wait_for_rwp(sc, DIST); /* * 3. Enable Distributor */ /* Enable Distributor with ARE, Group 1 */ gic_d_write(sc, 4, GICD_CTLR, GICD_CTLR_ARE_NS | GICD_CTLR_G1A | GICD_CTLR_G1); /* * 4. Route all interrupts to boot CPU. */ aff = CPU_AFFINITY(0); for (i = GIC_FIRST_SPI; i < sc->gic_nirqs; i++) gic_d_write(sc, 4, GICD_IROUTER(i), aff); return (0); } /* Re-Distributor */ static int gic_v3_redist_alloc(struct gic_v3_softc *sc) { u_int cpuid; /* Allocate struct resource for all CPU's Re-Distributor registers */ for (cpuid = 0; cpuid < mp_ncpus; cpuid++) if (CPU_ISSET(cpuid, &all_cpus) != 0) sc->gic_redists.pcpu[cpuid] = malloc(sizeof(*sc->gic_redists.pcpu[0]), M_GIC_V3, M_WAITOK); else sc->gic_redists.pcpu[cpuid] = NULL; return (0); } static int gic_v3_redist_find(struct gic_v3_softc *sc) { struct resource r_res; bus_space_handle_t r_bsh; uint64_t aff; uint64_t typer; uint32_t pidr2; u_int cpuid; size_t i; cpuid = PCPU_GET(cpuid); aff = CPU_AFFINITY(cpuid); /* Affinity in format for comparison with typer */ aff = (CPU_AFF3(aff) << 24) | (CPU_AFF2(aff) << 16) | (CPU_AFF1(aff) << 8) | CPU_AFF0(aff); if (bootverbose) { device_printf(sc->dev, "Start searching for Re-Distributor\n"); } /* Iterate through Re-Distributor regions */ for (i = 0; i < sc->gic_redists.nregions; i++) { /* Take a copy of the region's resource */ r_res = *sc->gic_redists.regions[i]; r_bsh = rman_get_bushandle(&r_res); pidr2 = bus_read_4(&r_res, GICR_PIDR2); switch (pidr2 & GICR_PIDR2_ARCH_MASK) { case GICR_PIDR2_ARCH_GICv3: /* fall through */ case GICR_PIDR2_ARCH_GICv4: break; default: device_printf(sc->dev, "No Re-Distributor found for CPU%u\n", cpuid); return (ENODEV); } do { typer = bus_read_8(&r_res, GICR_TYPER); if ((typer >> GICR_TYPER_AFF_SHIFT) == aff) { KASSERT(sc->gic_redists.pcpu[cpuid] != NULL, ("Invalid pointer to per-CPU redistributor")); /* Copy res contents to its final destination */ *sc->gic_redists.pcpu[cpuid] = r_res; if (bootverbose) { device_printf(sc->dev, "CPU%u Re-Distributor has been found\n", cpuid); } return (0); } r_bsh += (GICR_RD_BASE_SIZE + GICR_SGI_BASE_SIZE); if ((typer & GICR_TYPER_VLPIS) != 0) { r_bsh += (GICR_VLPI_BASE_SIZE + GICR_RESERVED_SIZE); } rman_set_bushandle(&r_res, r_bsh); } while ((typer & GICR_TYPER_LAST) == 0); } device_printf(sc->dev, "No Re-Distributor found for CPU%u\n", cpuid); return (ENXIO); } static int gic_v3_redist_wake(struct gic_v3_softc *sc) { uint32_t waker; size_t us_left = 1000000; waker = gic_r_read(sc, 4, GICR_WAKER); /* Wake up Re-Distributor for this CPU */ waker &= ~GICR_WAKER_PS; gic_r_write(sc, 4, GICR_WAKER, waker); /* * When clearing ProcessorSleep bit it is required to wait for * ChildrenAsleep to become zero following the processor power-on. */ while ((gic_r_read(sc, 4, GICR_WAKER) & GICR_WAKER_CA) != 0) { DELAY(1); if (us_left-- == 0) { panic("Could not wake Re-Distributor for CPU%u", PCPU_GET(cpuid)); } } if (bootverbose) { device_printf(sc->dev, "CPU%u Re-Distributor woke up\n", PCPU_GET(cpuid)); } return (0); } static int gic_v3_redist_init(struct gic_v3_softc *sc) { int err; size_t i; err = gic_v3_redist_find(sc); if (err != 0) return (err); err = gic_v3_redist_wake(sc); if (err != 0) return (err); /* Disable SPIs */ gic_r_write(sc, 4, GICR_SGI_BASE_SIZE + GICR_ICENABLER0, GICR_I_ENABLER_PPI_MASK); /* Enable SGIs */ gic_r_write(sc, 4, GICR_SGI_BASE_SIZE + GICR_ISENABLER0, GICR_I_ENABLER_SGI_MASK); /* Set priority for SGIs and PPIs */ for (i = 0; i <= GIC_LAST_PPI; i += GICR_I_PER_IPRIORITYn) { gic_r_write(sc, 4, GICR_SGI_BASE_SIZE + GICD_IPRIORITYR(i), GIC_PRIORITY_MAX); } gic_v3_wait_for_rwp(sc, REDIST); return (0); } Index: user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_fdt.c =================================================================== --- user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_fdt.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_fdt.c (revision 299127) @@ -1,311 +1,312 @@ /*- * Copyright (c) 2015 The FreeBSD Foundation * All rights reserved. * * This software was developed by Semihalf under * the sponsorship of the FreeBSD Foundation. * * 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. */ #include __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include #include #include #include #include #include #include "pic_if.h" #include "gic_v3_reg.h" #include "gic_v3_var.h" /* * FDT glue. */ static int gic_v3_fdt_probe(device_t); static int gic_v3_fdt_attach(device_t); static struct resource *gic_v3_ofw_bus_alloc_res(device_t, device_t, int, int *, rman_res_t, rman_res_t, rman_res_t, u_int); static const struct ofw_bus_devinfo *gic_v3_ofw_get_devinfo(device_t, device_t); static device_method_t gic_v3_fdt_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gic_v3_fdt_probe), DEVMETHOD(device_attach, gic_v3_fdt_attach), /* Bus interface */ DEVMETHOD(bus_alloc_resource, gic_v3_ofw_bus_alloc_res), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, gic_v3_ofw_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), /* End */ DEVMETHOD_END }; DEFINE_CLASS_1(gic, gic_v3_fdt_driver, gic_v3_fdt_methods, sizeof(struct gic_v3_softc), gic_v3_driver); static devclass_t gic_v3_fdt_devclass; EARLY_DRIVER_MODULE(gic_v3, simplebus, gic_v3_fdt_driver, gic_v3_fdt_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); EARLY_DRIVER_MODULE(gic_v3, ofwbus, gic_v3_fdt_driver, gic_v3_fdt_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); /* * Helper functions declarations. */ static int gic_v3_ofw_bus_attach(device_t); /* * Device interface. */ static int gic_v3_fdt_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "arm,gic-v3")) return (ENXIO); device_set_desc(dev, GIC_V3_DEVSTR); return (BUS_PROBE_DEFAULT); } static int gic_v3_fdt_attach(device_t dev) { struct gic_v3_softc *sc; pcell_t redist_regions; int err; sc = device_get_softc(dev); sc->dev = dev; /* * Recover number of the Re-Distributor regions. */ if (OF_getencprop(ofw_bus_get_node(dev), "#redistributor-regions", &redist_regions, sizeof(redist_regions)) <= 0) sc->gic_redists.nregions = 1; else sc->gic_redists.nregions = redist_regions; err = gic_v3_attach(dev); if (err) goto error; /* * Try to register ITS to this GIC. * GIC will act as a bus in that case. * Failure here will not affect main GIC functionality. */ if (gic_v3_ofw_bus_attach(dev) != 0) { if (bootverbose) { device_printf(dev, "Failed to attach ITS to this GIC\n"); } } return (err); error: if (bootverbose) { device_printf(dev, "Failed to attach. Error %d\n", err); } /* Failure so free resources */ gic_v3_detach(dev); return (err); } /* OFW bus interface */ struct gic_v3_ofw_devinfo { struct ofw_bus_devinfo di_dinfo; struct resource_list di_rl; }; static const struct ofw_bus_devinfo * gic_v3_ofw_get_devinfo(device_t bus __unused, device_t child) { struct gic_v3_ofw_devinfo *di; di = device_get_ivars(child); return (&di->di_dinfo); } static struct resource * gic_v3_ofw_bus_alloc_res(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct gic_v3_ofw_devinfo *di; struct resource_list_entry *rle; int ranges_len; if (RMAN_IS_DEFAULT_RANGE(start, end)) { if ((di = device_get_ivars(child)) == NULL) return (NULL); if (type != SYS_RES_MEMORY) return (NULL); /* Find defaults for this rid */ rle = resource_list_find(&di->di_rl, type, *rid); if (rle == NULL) return (NULL); start = rle->start; end = rle->end; count = rle->count; } /* * XXX: No ranges remap! * Absolute address is expected. */ if (ofw_bus_has_prop(bus, "ranges")) { ranges_len = OF_getproplen(ofw_bus_get_node(bus), "ranges"); if (ranges_len != 0) { if (bootverbose) { device_printf(child, "Ranges remap not supported\n"); } return (NULL); } } return (bus_generic_alloc_resource(bus, child, type, rid, start, end, count, flags)); } /* Helper functions */ /* * Bus capability support for GICv3. * Collects and configures device informations and finally * adds ITS device as a child of GICv3 in Newbus hierarchy. */ static int gic_v3_ofw_bus_attach(device_t dev) { struct gic_v3_ofw_devinfo *di; device_t child; phandle_t parent, node; pcell_t addr_cells, size_cells; parent = ofw_bus_get_node(dev); if (parent > 0) { addr_cells = 2; OF_getencprop(parent, "#address-cells", &addr_cells, sizeof(addr_cells)); size_cells = 2; OF_getencprop(parent, "#size-cells", &size_cells, sizeof(size_cells)); /* Iterate through all GIC subordinates */ for (node = OF_child(parent); node > 0; node = OF_peer(node)) { /* Allocate and populate devinfo. */ di = malloc(sizeof(*di), M_GIC_V3, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&di->di_dinfo, node)) { if (bootverbose) { device_printf(dev, "Could not set up devinfo for ITS\n"); } free(di, M_GIC_V3); continue; } /* Initialize and populate resource list. */ resource_list_init(&di->di_rl); ofw_bus_reg_to_rl(dev, node, addr_cells, size_cells, &di->di_rl); /* Should not have any interrupts, so don't add any */ /* Add newbus device for this FDT node */ child = device_add_child(dev, NULL, -1); if (!child) { if (bootverbose) { device_printf(dev, "Could not add child: %s\n", di->di_dinfo.obd_name); } resource_list_free(&di->di_rl); ofw_bus_gen_destroy_devinfo(&di->di_dinfo); free(di, M_GIC_V3); continue; } device_set_ivars(child, di); } } return (bus_generic_attach(dev)); } static int gic_v3_its_fdt_probe(device_t dev); static device_method_t gic_v3_its_fdt_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gic_v3_its_fdt_probe), /* End */ DEVMETHOD_END }; DEFINE_CLASS_1(its, gic_v3_its_fdt_driver, gic_v3_its_fdt_methods, sizeof(struct gic_v3_its_softc), gic_v3_its_driver); static devclass_t gic_v3_its_fdt_devclass; EARLY_DRIVER_MODULE(its, gic, gic_v3_its_fdt_driver, gic_v3_its_fdt_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); static int gic_v3_its_fdt_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, GIC_V3_ITS_COMPSTR)) return (ENXIO); device_set_desc(dev, GIC_V3_ITS_DEVSTR); return (BUS_PROBE_DEFAULT); } Index: user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_its.c =================================================================== --- user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_its.c (revision 299126) +++ user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_its.c (revision 299127) @@ -1,1788 +1,1785 @@ /*- * Copyright (c) 2015 The FreeBSD Foundation * All rights reserved. * * This software was developed by Semihalf under * the sponsorship of the FreeBSD Foundation. * * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gic_v3_reg.h" #include "gic_v3_var.h" #define GIC_V3_ITS_QUIRK_THUNDERX_PEM_BUS_OFFSET 88 #include "pic_if.h" /* Device and PIC methods */ static int gic_v3_its_attach(device_t); static device_method_t gic_v3_its_methods[] = { /* Device interface */ DEVMETHOD(device_attach, gic_v3_its_attach), /* * PIC interface */ /* MSI-X */ DEVMETHOD(pic_alloc_msix, gic_v3_its_alloc_msix), /* MSI */ DEVMETHOD(pic_alloc_msi, gic_v3_its_alloc_msi), DEVMETHOD(pic_map_msi, gic_v3_its_map_msi), /* End */ DEVMETHOD_END }; DEFINE_CLASS_0(its, gic_v3_its_driver, gic_v3_its_methods, sizeof(struct gic_v3_its_softc)); MALLOC_DEFINE(M_GIC_V3_ITS, "GICv3 ITS", GIC_V3_ITS_DEVSTR); static int its_alloc_tables(struct gic_v3_its_softc *); static void its_free_tables(struct gic_v3_its_softc *); static void its_init_commandq(struct gic_v3_its_softc *); static void its_init_cpu_collection(struct gic_v3_its_softc *); static uint32_t its_get_devid(device_t); static struct its_dev * its_device_find_locked(struct gic_v3_its_softc *, device_t, uint32_t); static int its_cmd_send(struct gic_v3_its_softc *, struct its_cmd_desc *); static void its_cmd_movi(struct gic_v3_its_softc *, struct its_dev *, struct its_col *, uint32_t); static void its_cmd_mapc(struct gic_v3_its_softc *, struct its_col *, uint8_t); static void its_cmd_mapvi(struct gic_v3_its_softc *, struct its_dev *, uint32_t, uint32_t); static void its_cmd_mapi(struct gic_v3_its_softc *, struct its_dev *, uint32_t); static void its_cmd_inv(struct gic_v3_its_softc *, struct its_dev *, uint32_t); static void its_cmd_invall(struct gic_v3_its_softc *, struct its_col *); static uint32_t its_get_devbits(device_t); static void lpi_init_conftable(struct gic_v3_its_softc *); static void lpi_bitmap_init(struct gic_v3_its_softc *); static int lpi_config_cpu(struct gic_v3_its_softc *); static void lpi_alloc_cpu_pendtables(struct gic_v3_its_softc *); const char *its_ptab_cache[] = { [GITS_BASER_CACHE_NCNB] = "(NC,NB)", [GITS_BASER_CACHE_NC] = "(NC)", [GITS_BASER_CACHE_RAWT] = "(RA,WT)", [GITS_BASER_CACHE_RAWB] = "(RA,WB)", [GITS_BASER_CACHE_WAWT] = "(WA,WT)", [GITS_BASER_CACHE_WAWB] = "(WA,WB)", [GITS_BASER_CACHE_RAWAWT] = "(RAWA,WT)", [GITS_BASER_CACHE_RAWAWB] = "(RAWA,WB)", }; const char *its_ptab_share[] = { [GITS_BASER_SHARE_NS] = "none", [GITS_BASER_SHARE_IS] = "inner", [GITS_BASER_SHARE_OS] = "outer", [GITS_BASER_SHARE_RES] = "none", }; const char *its_ptab_type[] = { [GITS_BASER_TYPE_UNIMPL] = "Unimplemented", [GITS_BASER_TYPE_DEV] = "Devices", [GITS_BASER_TYPE_VP] = "Virtual Processors", [GITS_BASER_TYPE_PP] = "Physical Processors", [GITS_BASER_TYPE_IC] = "Interrupt Collections", [GITS_BASER_TYPE_RES5] = "Reserved (5)", [GITS_BASER_TYPE_RES6] = "Reserved (6)", [GITS_BASER_TYPE_RES7] = "Reserved (7)", }; /* * Vendor specific quirks. * One needs to add appropriate entry to its_quirks[] * table if the imlementation varies from the generic ARM ITS. */ /* Cavium ThunderX PCI devid acquire function */ static uint32_t its_get_devbits_thunder(device_t); static uint32_t its_get_devid_thunder(device_t); static const struct its_quirks its_quirks[] = { { /* * Hardware: Cavium ThunderX * Chip revision: Pass 1.0, Pass 1.1 */ .cpuid = CPU_ID_RAW(CPU_IMPL_CAVIUM, CPU_PART_THUNDER, 0, 0), .cpuid_mask = CPU_IMPL_MASK | CPU_PART_MASK, .devid_func = its_get_devid_thunder, .devbits_func = its_get_devbits_thunder, }, }; static struct gic_v3_its_softc *its_sc; #define gic_its_read(sc, len, reg) \ bus_read_##len(&sc->its_res[0], reg) #define gic_its_write(sc, len, reg, val) \ bus_write_##len(&sc->its_res[0], reg, val) static int gic_v3_its_attach(device_t dev) { struct gic_v3_its_softc *sc; uint64_t gits_tmp; uint32_t gits_pidr2; int rid; int ret; sc = device_get_softc(dev); /* * XXX ARM64TODO: Avoid configuration of more than one ITS * device. To be removed when multi-PIC support is added * to FreeBSD (or at least multi-ITS is implemented). Limit * supported ITS sockets to '0' only. */ if (device_get_unit(dev) != 0) { device_printf(dev, "Only single instance of ITS is supported, exiting...\n"); return (ENXIO); } sc->its_socket = 0; /* * Initialize sleep & spin mutex for ITS */ /* Protects ITS device list and assigned LPIs bitmaps. */ mtx_init(&sc->its_dev_lock, "ITS dev lock", NULL, MTX_SPIN); /* Protects access to ITS command circular buffer. */ mtx_init(&sc->its_cmd_lock, "ITS cmd lock", NULL, MTX_SPIN); rid = 0; sc->its_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->its_res == NULL) { device_printf(dev, "Could not allocate memory\n"); return (ENXIO); } sc->dev = dev; gits_pidr2 = gic_its_read(sc, 4, GITS_PIDR2); switch (gits_pidr2 & GITS_PIDR2_ARCH_MASK) { case GITS_PIDR2_ARCH_GICv3: /* fall through */ case GITS_PIDR2_ARCH_GICv4: if (bootverbose) { device_printf(dev, "ITS found. Architecture rev. %u\n", (u_int)(gits_pidr2 & GITS_PIDR2_ARCH_MASK) >> 4); } break; default: device_printf(dev, "No ITS found in the system\n"); gic_v3_its_detach(dev); return (ENODEV); } /* 1. Initialize commands queue */ its_init_commandq(sc); /* 2. Provide memory for any private ITS tables */ ret = its_alloc_tables(sc); if (ret != 0) { gic_v3_its_detach(dev); return (ret); } /* 3. Allocate collections. One per-CPU */ for (int cpu = 0; cpu < mp_ncpus; cpu++) if (CPU_ISSET(cpu, &all_cpus) != 0) sc->its_cols[cpu] = malloc(sizeof(*sc->its_cols[0]), M_GIC_V3_ITS, (M_WAITOK | M_ZERO)); else sc->its_cols[cpu] = NULL; /* 4. Enable ITS in GITS_CTLR */ gits_tmp = gic_its_read(sc, 4, GITS_CTLR); gic_its_write(sc, 4, GITS_CTLR, gits_tmp | GITS_CTLR_EN); /* 5. Initialize LPIs configuration table */ lpi_init_conftable(sc); /* 6. LPIs bitmap init */ lpi_bitmap_init(sc); /* 7. Allocate pending tables for all CPUs */ lpi_alloc_cpu_pendtables(sc); /* 8. CPU init */ (void)its_init_cpu(sc); /* 9. Init ITS devices list */ TAILQ_INIT(&sc->its_dev_list); arm_register_msi_pic(dev); /* * XXX ARM64TODO: We need to have ITS software context * when being called by the interrupt code (mask/unmask). * This may be used only when one ITS is present in * the system and eventually should be removed. */ KASSERT(its_sc == NULL, ("Trying to assign its_sc that is already set")); its_sc = sc; return (0); } /* Will not detach but use it for convenience */ int gic_v3_its_detach(device_t dev) { device_t parent; struct gic_v3_softc *gic_sc; struct gic_v3_its_softc *sc; u_int cpuid; int rid = 0; sc = device_get_softc(dev); cpuid = PCPU_GET(cpuid); /* Release what's possible */ /* Command queue */ if ((void *)sc->its_cmdq_base != NULL) { contigfree((void *)sc->its_cmdq_base, ITS_CMDQ_SIZE, M_GIC_V3_ITS); } /* ITTs */ its_free_tables(sc); /* Collections */ for (cpuid = 0; cpuid < mp_ncpus; cpuid++) free(sc->its_cols[cpuid], M_GIC_V3_ITS); /* LPI config table */ parent = device_get_parent(sc->dev); gic_sc = device_get_softc(parent); if ((void *)gic_sc->gic_redists.lpis.conf_base != NULL) { contigfree((void *)gic_sc->gic_redists.lpis.conf_base, LPI_CONFTAB_SIZE, M_GIC_V3_ITS); } for (cpuid = 0; cpuid < mp_ncpus; cpuid++) if ((void *)gic_sc->gic_redists.lpis.pend_base[cpuid] != NULL) { contigfree( (void *)gic_sc->gic_redists.lpis.pend_base[cpuid], roundup2(LPI_PENDTAB_SIZE, PAGE_SIZE_64K), M_GIC_V3_ITS); } /* Resource... */ bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->its_res); /* XXX ARM64TODO: Reset global pointer to ITS software context */ its_sc = NULL; return (0); } static int its_alloc_tables(struct gic_v3_its_softc *sc) { uint64_t gits_baser, gits_tmp; uint64_t type, esize, cache, share, psz; size_t page_size, npages, nitspages, nidents, tn; size_t its_tbl_size; vm_offset_t ptab_vaddr; vm_paddr_t ptab_paddr; boolean_t first = TRUE; page_size = PAGE_SIZE_64K; for (tn = 0; tn < GITS_BASER_NUM; tn++) { gits_baser = gic_its_read(sc, 8, GITS_BASER(tn)); type = GITS_BASER_TYPE(gits_baser); /* Get the Table Entry size */ esize = GITS_BASER_ESIZE(gits_baser); switch (type) { case GITS_BASER_TYPE_UNIMPL: /* fall through */ case GITS_BASER_TYPE_RES5: case GITS_BASER_TYPE_RES6: case GITS_BASER_TYPE_RES7: continue; case GITS_BASER_TYPE_DEV: nidents = (1 << its_get_devbits(sc->dev)); its_tbl_size = esize * nidents; its_tbl_size = roundup2(its_tbl_size, page_size); npages = howmany(its_tbl_size, PAGE_SIZE); break; default: npages = howmany(page_size, PAGE_SIZE); break; } /* Allocate required space */ ptab_vaddr = (vm_offset_t)contigmalloc(npages * PAGE_SIZE, M_GIC_V3_ITS, (M_WAITOK | M_ZERO), 0, ~0UL, PAGE_SIZE, 0); sc->its_ptabs[tn].ptab_vaddr = ptab_vaddr; sc->its_ptabs[tn].ptab_pgsz = PAGE_SIZE; sc->its_ptabs[tn].ptab_npages = npages; ptab_paddr = vtophys(ptab_vaddr); KASSERT((ptab_paddr & GITS_BASER_PA_MASK) == ptab_paddr, ("%s: Unaligned PA for Interrupt Translation Table", device_get_name(sc->dev))); /* Set defaults: WAWB, IS */ cache = GITS_BASER_CACHE_WAWB; share = GITS_BASER_SHARE_IS; for (;;) { nitspages = howmany(its_tbl_size, page_size); switch (page_size) { case PAGE_SIZE: /* 4KB */ psz = GITS_BASER_PSZ_4K; break; case PAGE_SIZE_16K: /* 16KB */ psz = GITS_BASER_PSZ_4K; break; case PAGE_SIZE_64K: /* 64KB */ psz = GITS_BASER_PSZ_64K; break; default: device_printf(sc->dev, "Unsupported page size: %zuKB\n", (page_size / 1024)); its_free_tables(sc); return (ENXIO); } /* Clear fields under modification first */ gits_baser &= ~(GITS_BASER_VALID | GITS_BASER_CACHE_MASK | GITS_BASER_TYPE_MASK | GITS_BASER_ESIZE_MASK | GITS_BASER_PA_MASK | GITS_BASER_SHARE_MASK | GITS_BASER_PSZ_MASK | GITS_BASER_SIZE_MASK); /* Construct register value */ gits_baser |= (type << GITS_BASER_TYPE_SHIFT) | ((esize - 1) << GITS_BASER_ESIZE_SHIFT) | (cache << GITS_BASER_CACHE_SHIFT) | (share << GITS_BASER_SHARE_SHIFT) | (psz << GITS_BASER_PSZ_SHIFT) | ptab_paddr | (nitspages - 1) | GITS_BASER_VALID; gic_its_write(sc, 8, GITS_BASER(tn), gits_baser); /* * Verify. * Depending on implementation we may encounter * shareability and page size mismatch. */ gits_tmp = gic_its_read(sc, 8, GITS_BASER(tn)); if (((gits_tmp ^ gits_baser) & GITS_BASER_SHARE_MASK) != 0) { share = gits_tmp & GITS_BASER_SHARE_MASK; share >>= GITS_BASER_SHARE_SHIFT; continue; } if (((gits_tmp ^ gits_baser) & GITS_BASER_PSZ_MASK) != 0) { switch (page_size) { case PAGE_SIZE_16K: /* Drop to 4KB page */ page_size = PAGE_SIZE; continue; case PAGE_SIZE_64K: /* Drop to 16KB page */ page_size = PAGE_SIZE_16K; continue; } } /* * All possible adjustments should * be applied by now so just break the loop. */ break; } /* * Do not compare Cacheability field since * it is implementation defined. */ gits_tmp &= ~GITS_BASER_CACHE_MASK; gits_baser &= ~GITS_BASER_CACHE_MASK; if (gits_tmp != gits_baser) { device_printf(sc->dev, "Could not allocate ITS tables\n"); its_free_tables(sc); return (ENXIO); } if (bootverbose) { if (first) { device_printf(sc->dev, "Allocated ITS private tables:\n"); first = FALSE; } device_printf(sc->dev, "\tPTAB%zu for %s: PA 0x%lx," " %lu entries," " cache policy %s, %s shareable," " page size %zuKB\n", tn, its_ptab_type[type], ptab_paddr, (page_size * nitspages) / esize, its_ptab_cache[cache], its_ptab_share[share], page_size / 1024); } } return (0); } static void its_free_tables(struct gic_v3_its_softc *sc) { vm_offset_t ptab_vaddr; size_t size; size_t tn; for (tn = 0; tn < GITS_BASER_NUM; tn++) { ptab_vaddr = sc->its_ptabs[tn].ptab_vaddr; if (ptab_vaddr == 0) continue; size = sc->its_ptabs[tn].ptab_pgsz; size *= sc->its_ptabs[tn].ptab_npages; if ((void *)ptab_vaddr != NULL) contigfree((void *)ptab_vaddr, size, M_GIC_V3_ITS); /* Clear the table description */ memset(&sc->its_ptabs[tn], 0, sizeof(sc->its_ptabs[tn])); } } static void its_init_commandq(struct gic_v3_its_softc *sc) { uint64_t gits_cbaser, gits_tmp; uint64_t cache, share; vm_paddr_t cmdq_paddr; device_t dev; dev = sc->dev; /* Allocate memory for command queue */ sc->its_cmdq_base = contigmalloc(ITS_CMDQ_SIZE, M_GIC_V3_ITS, (M_WAITOK | M_ZERO), 0, ~0UL, ITS_CMDQ_SIZE, 0); /* Set command queue write pointer (command queue empty) */ sc->its_cmdq_write = sc->its_cmdq_base; /* Save command queue pointer and attributes */ cmdq_paddr = vtophys(sc->its_cmdq_base); /* Set defaults: Normal Inner WAWB, IS */ cache = GITS_CBASER_CACHE_NIWAWB; share = GITS_CBASER_SHARE_IS; gits_cbaser = (cmdq_paddr | (cache << GITS_CBASER_CACHE_SHIFT) | (share << GITS_CBASER_SHARE_SHIFT) | /* Number of 4KB pages - 1 */ ((ITS_CMDQ_SIZE / PAGE_SIZE) - 1) | /* Valid bit */ GITS_CBASER_VALID); gic_its_write(sc, 8, GITS_CBASER, gits_cbaser); gits_tmp = gic_its_read(sc, 8, GITS_CBASER); if (((gits_tmp ^ gits_cbaser) & GITS_CBASER_SHARE_MASK) != 0) { if (bootverbose) { device_printf(dev, "Will use cache flushing for commands queue\n"); } /* Command queue needs cache flushing */ sc->its_flags |= ITS_FLAGS_CMDQ_FLUSH; } gic_its_write(sc, 8, GITS_CWRITER, 0x0); } int its_init_cpu(struct gic_v3_its_softc *sc) { device_t parent; struct gic_v3_softc *gic_sc; /* * NULL in place of the softc pointer means that * this function was called during GICv3 secondary initialization. */ if (sc == NULL) { if (its_sc != NULL && device_is_attached(its_sc->dev)) { /* * XXX ARM64TODO: This is part of the workaround that * saves ITS software context for further use in * mask/unmask and here. This should be removed as soon * as the upper layer is capable of passing the ITS * context to this function. */ sc = its_sc; } else return (ENXIO); /* Skip if running secondary init on a wrong socket */ if (sc->its_socket != CPU_CURRENT_SOCKET) return (ENXIO); } /* * Check for LPIs support on this Re-Distributor. */ parent = device_get_parent(sc->dev); gic_sc = device_get_softc(parent); if ((gic_r_read(gic_sc, 4, GICR_TYPER) & GICR_TYPER_PLPIS) == 0) { if (bootverbose) { device_printf(sc->dev, "LPIs not supported on CPU%u\n", PCPU_GET(cpuid)); } return (ENXIO); } /* Configure LPIs for this CPU */ lpi_config_cpu(sc); /* Initialize collections */ its_init_cpu_collection(sc); return (0); } static void its_init_cpu_collection(struct gic_v3_its_softc *sc) { device_t parent; struct gic_v3_softc *gic_sc; uint64_t typer; uint64_t target; vm_offset_t redist_base; u_int cpuid; cpuid = PCPU_GET(cpuid); parent = device_get_parent(sc->dev); gic_sc = device_get_softc(parent); typer = gic_its_read(sc, 8, GITS_TYPER); if ((typer & GITS_TYPER_PTA) != 0) { redist_base = rman_get_bushandle(gic_sc->gic_redists.pcpu[cpuid]); /* * Target Address correspond to the base physical * address of Re-Distributors. */ target = vtophys(redist_base); } else { /* Target Address correspond to unique processor numbers */ typer = gic_r_read(gic_sc, 8, GICR_TYPER); target = GICR_TYPER_CPUNUM(typer); } sc->its_cols[cpuid]->col_target = target; sc->its_cols[cpuid]->col_id = cpuid; its_cmd_mapc(sc, sc->its_cols[cpuid], 1); its_cmd_invall(sc, sc->its_cols[cpuid]); } static void lpi_init_conftable(struct gic_v3_its_softc *sc) { device_t parent; struct gic_v3_softc *gic_sc; vm_offset_t conf_base; uint8_t prio_default; parent = device_get_parent(sc->dev); gic_sc = device_get_softc(parent); /* * LPI Configuration Table settings. * Notice that Configuration Table is shared among all * Re-Distributors, so this is going to be created just once. */ conf_base = (vm_offset_t)contigmalloc(LPI_CONFTAB_SIZE, M_GIC_V3_ITS, (M_WAITOK | M_ZERO), 0, ~0UL, PAGE_SIZE_64K, 0); if (bootverbose) { device_printf(sc->dev, "LPI Configuration Table at PA: 0x%lx\n", vtophys(conf_base)); } /* * Let the default priority be aligned with all other * interrupts assuming that each interrupt is assigned * MAX priority at startup. MAX priority on the other * hand cannot be higher than 0xFC for LPIs. */ prio_default = GIC_PRIORITY_MAX; /* Write each settings byte to LPI configuration table */ memset((void *)conf_base, (prio_default & LPI_CONF_PRIO_MASK) | LPI_CONF_GROUP1, LPI_CONFTAB_SIZE); cpu_dcache_wb_range((vm_offset_t)conf_base, roundup2(LPI_CONFTAB_SIZE, PAGE_SIZE_64K)); gic_sc->gic_redists.lpis.conf_base = conf_base; } static void lpi_alloc_cpu_pendtables(struct gic_v3_its_softc *sc) { device_t parent; struct gic_v3_softc *gic_sc; vm_offset_t pend_base; u_int cpuid; parent = device_get_parent(sc->dev); gic_sc = device_get_softc(parent); /* * LPI Pending Table settings. * This has to be done for each Re-Distributor, hence for each CPU. */ for (cpuid = 0; cpuid < mp_ncpus; cpuid++) { /* Limit allocation to active CPUs only */ if (CPU_ISSET(cpuid, &all_cpus) == 0) continue; pend_base = (vm_offset_t)contigmalloc( roundup2(LPI_PENDTAB_SIZE, PAGE_SIZE_64K), M_GIC_V3_ITS, (M_WAITOK | M_ZERO), 0, ~0UL, PAGE_SIZE_64K, 0); /* Clean D-cache so that ITS can see zeroed pages */ cpu_dcache_wb_range((vm_offset_t)pend_base, roundup2(LPI_PENDTAB_SIZE, PAGE_SIZE_64K)); if (bootverbose) { device_printf(sc->dev, "LPI Pending Table for CPU%u at PA: 0x%lx\n", cpuid, vtophys(pend_base)); } gic_sc->gic_redists.lpis.pend_base[cpuid] = pend_base; } /* Ensure visibility of pend_base addresses on other CPUs */ wmb(); } static int lpi_config_cpu(struct gic_v3_its_softc *sc) { device_t parent; struct gic_v3_softc *gic_sc; vm_offset_t conf_base, pend_base; uint64_t gicr_xbaser, gicr_temp; uint64_t cache, share, idbits; uint32_t gicr_ctlr; u_int cpuid; parent = device_get_parent(sc->dev); gic_sc = device_get_softc(parent); cpuid = PCPU_GET(cpuid); /* Ensure data observability on a current CPU */ rmb(); conf_base = gic_sc->gic_redists.lpis.conf_base; pend_base = gic_sc->gic_redists.lpis.pend_base[cpuid]; /* Disable LPIs */ gicr_ctlr = gic_r_read(gic_sc, 4, GICR_CTLR); gicr_ctlr &= ~GICR_CTLR_LPI_ENABLE; gic_r_write(gic_sc, 4, GICR_CTLR, gicr_ctlr); /* Perform full system barrier */ dsb(sy); /* * Set GICR_PROPBASER */ /* * Find out how many bits do we need for LPI identifiers. * Remark 1.: Even though we have (LPI_CONFTAB_SIZE / 8) LPIs * the notified LPI ID still starts from 8192 * (GIC_FIRST_LPI). * Remark 2.: This could be done on compilation time but there * seems to be no sufficient macro. */ idbits = flsl(LPI_CONFTAB_SIZE + GIC_FIRST_LPI) - 1; /* Set defaults: Normal Inner WAWB, IS */ cache = GICR_PROPBASER_CACHE_NIWAWB; share = GICR_PROPBASER_SHARE_IS; gicr_xbaser = vtophys(conf_base) | ((idbits - 1) & GICR_PROPBASER_IDBITS_MASK) | (cache << GICR_PROPBASER_CACHE_SHIFT) | (share << GICR_PROPBASER_SHARE_SHIFT); gic_r_write(gic_sc, 8, GICR_PROPBASER, gicr_xbaser); gicr_temp = gic_r_read(gic_sc, 8, GICR_PROPBASER); if (((gicr_xbaser ^ gicr_temp) & GICR_PROPBASER_SHARE_MASK) != 0) { if (bootverbose) { device_printf(sc->dev, "Will use cache flushing for LPI " "Configuration Table\n"); } gic_sc->gic_redists.lpis.flags |= LPI_FLAGS_CONF_FLUSH; } /* * Set GICR_PENDBASER */ /* Set defaults: Normal Inner WAWB, IS */ cache = GICR_PENDBASER_CACHE_NIWAWB; share = GICR_PENDBASER_SHARE_IS; gicr_xbaser = vtophys(pend_base) | (cache << GICR_PENDBASER_CACHE_SHIFT) | (share << GICR_PENDBASER_SHARE_SHIFT); gic_r_write(gic_sc, 8, GICR_PENDBASER, gicr_xbaser); /* Enable LPIs */ gicr_ctlr = gic_r_read(gic_sc, 4, GICR_CTLR); gicr_ctlr |= GICR_CTLR_LPI_ENABLE; gic_r_write(gic_sc, 4, GICR_CTLR, gicr_ctlr); dsb(sy); return (0); } static void lpi_bitmap_init(struct gic_v3_its_softc *sc) { device_t parent; struct gic_v3_softc *gic_sc; uint32_t lpi_id_num; size_t lpi_chunks_num; size_t bits_in_chunk; parent = device_get_parent(sc->dev); gic_sc = device_get_softc(parent); lpi_id_num = (1 << gic_sc->gic_idbits) - 1; /* Substract IDs dedicated for SGIs, PPIs and SPIs */ lpi_id_num -= GIC_FIRST_LPI; sc->its_lpi_maxid = lpi_id_num; bits_in_chunk = sizeof(*sc->its_lpi_bitmap) * NBBY; /* * Round up to the number of bits in chunk. * We will need to take care to avoid using invalid LPI IDs later. */ lpi_id_num = roundup2(lpi_id_num, bits_in_chunk); lpi_chunks_num = lpi_id_num / bits_in_chunk; sc->its_lpi_bitmap = contigmalloc((lpi_chunks_num * sizeof(*sc->its_lpi_bitmap)), M_GIC_V3_ITS, (M_WAITOK | M_ZERO), 0, ~0UL, sizeof(*sc->its_lpi_bitmap), 0); } static int lpi_alloc_chunk(struct gic_v3_its_softc *sc, struct lpi_chunk *lpic, u_int nvecs) { u_int *col_ids; int fclr; /* First cleared bit */ - uint8_t *bitmap; + bitstr_t *bitmap; size_t nb, i; col_ids = malloc(sizeof(*col_ids) * nvecs, M_GIC_V3_ITS, (M_NOWAIT | M_ZERO)); if (col_ids == NULL) return (ENOMEM); mtx_lock_spin(&sc->its_dev_lock); - bitmap = (uint8_t *)sc->its_lpi_bitmap; + bitmap = sc->its_lpi_bitmap; fclr = 0; retry: /* Check other bits - sloooow */ for (i = 0, nb = fclr; i < nvecs; i++, nb++) { if (nb > sc->its_lpi_maxid) { mtx_unlock_spin(&sc->its_dev_lock); free(col_ids, M_GIC_V3_ITS); return (EINVAL); } if (isset(bitmap, nb)) { /* To little free bits in this area. Move on. */ fclr = nb + 1; goto retry; } } /* This area is free. Take it. */ bit_nset(bitmap, fclr, fclr + nvecs - 1); lpic->lpi_base = fclr + GIC_FIRST_LPI; lpic->lpi_num = nvecs; lpic->lpi_free = lpic->lpi_num; lpic->lpi_col_ids = col_ids; for (i = 0; i < lpic->lpi_num; i++) { /* * Initially all interrupts go to CPU0 but can be moved * to another CPU by bus_bind_intr() or interrupts shuffling. */ lpic->lpi_col_ids[i] = 0; } mtx_unlock_spin(&sc->its_dev_lock); return (0); } static void lpi_free_chunk(struct gic_v3_its_softc *sc, struct lpi_chunk *lpic) { int start, end; - uint8_t *bitmap; - bitmap = (uint8_t *)sc->its_lpi_bitmap; - KASSERT((lpic->lpi_free == lpic->lpi_num), ("Trying to free LPI chunk that is still in use.\n")); mtx_lock_spin(&sc->its_dev_lock); /* First bit of this chunk in a global bitmap */ start = lpic->lpi_base - GIC_FIRST_LPI; /* and last bit of this chunk... */ end = start + lpic->lpi_num - 1; /* Finally free this chunk */ - bit_nclear(bitmap, start, end); + bit_nclear(sc->its_lpi_bitmap, start, end); mtx_unlock_spin(&sc->its_dev_lock); free(lpic->lpi_col_ids, M_GIC_V3_ITS); lpic->lpi_col_ids = NULL; } static void lpi_configure(struct gic_v3_its_softc *sc, struct its_dev *its_dev, uint32_t lpinum, boolean_t unmask) { device_t parent; struct gic_v3_softc *gic_sc; uint8_t *conf_byte; parent = device_get_parent(sc->dev); gic_sc = device_get_softc(parent); conf_byte = (uint8_t *)gic_sc->gic_redists.lpis.conf_base; conf_byte += (lpinum - GIC_FIRST_LPI); if (unmask) *conf_byte |= LPI_CONF_ENABLE; else *conf_byte &= ~LPI_CONF_ENABLE; if ((gic_sc->gic_redists.lpis.flags & LPI_FLAGS_CONF_FLUSH) != 0) { /* Clean D-cache under configuration byte */ cpu_dcache_wb_range((vm_offset_t)conf_byte, sizeof(*conf_byte)); } else { /* DSB inner shareable, store */ dsb(ishst); } its_cmd_inv(sc, its_dev, lpinum); } static void lpi_map_to_device(struct gic_v3_its_softc *sc, struct its_dev *its_dev, uint32_t id, uint32_t pid) { if ((pid < its_dev->lpis.lpi_base) || (pid >= (its_dev->lpis.lpi_base + its_dev->lpis.lpi_num))) panic("Trying to map ivalid LPI %u for the device\n", pid); its_cmd_mapvi(sc, its_dev, id, pid); } static void lpi_xmask_irq(device_t parent, uint32_t irq, boolean_t unmask) { struct its_dev *its_dev; TAILQ_FOREACH(its_dev, &its_sc->its_dev_list, entry) { if (irq >= its_dev->lpis.lpi_base && irq < (its_dev->lpis.lpi_base + its_dev->lpis.lpi_num)) { lpi_configure(its_sc, its_dev, irq, unmask); return; } } panic("Trying to %s not existing LPI: %u\n", (unmask == TRUE) ? "unmask" : "mask", irq); } int lpi_migrate(device_t parent, uint32_t irq, u_int cpuid) { struct gic_v3_its_softc *sc; struct its_dev *its_dev; struct its_col *col; sc = its_sc; mtx_lock_spin(&sc->its_dev_lock); its_dev = its_device_find_locked(sc, NULL, irq); mtx_unlock_spin(&sc->its_dev_lock); if (its_dev == NULL) { /* Cannot migrate not configured LPI */ return (ENXIO); } /* Find local device's interrupt identifier */ irq = irq - its_dev->lpis.lpi_base; /* Move interrupt to another collection */ col = sc->its_cols[cpuid]; its_cmd_movi(sc, its_dev, col, irq); its_dev->lpis.lpi_col_ids[irq] = cpuid; return (0); } void lpi_unmask_irq(device_t parent, uint32_t irq) { lpi_xmask_irq(parent, irq, 1); } void lpi_mask_irq(device_t parent, uint32_t irq) { lpi_xmask_irq(parent, irq, 0); } /* * Commands handling. */ static __inline void cmd_format_command(struct its_cmd *cmd, uint8_t cmd_type) { /* Command field: DW0 [7:0] */ cmd->cmd_dword[0] &= ~CMD_COMMAND_MASK; cmd->cmd_dword[0] |= cmd_type; } static __inline void cmd_format_devid(struct its_cmd *cmd, uint32_t devid) { /* Device ID field: DW0 [63:32] */ cmd->cmd_dword[0] &= ~CMD_DEVID_MASK; cmd->cmd_dword[0] |= ((uint64_t)devid << CMD_DEVID_SHIFT); } static __inline void cmd_format_size(struct its_cmd *cmd, uint16_t size) { /* Size field: DW1 [4:0] */ cmd->cmd_dword[1] &= ~CMD_SIZE_MASK; cmd->cmd_dword[1] |= (size & CMD_SIZE_MASK); } static __inline void cmd_format_id(struct its_cmd *cmd, uint32_t id) { /* ID field: DW1 [31:0] */ cmd->cmd_dword[1] &= ~CMD_ID_MASK; cmd->cmd_dword[1] |= id; } static __inline void cmd_format_pid(struct its_cmd *cmd, uint32_t pid) { /* Physical ID field: DW1 [63:32] */ cmd->cmd_dword[1] &= ~CMD_PID_MASK; cmd->cmd_dword[1] |= ((uint64_t)pid << CMD_PID_SHIFT); } static __inline void cmd_format_col(struct its_cmd *cmd, uint16_t col_id) { /* Collection field: DW2 [16:0] */ cmd->cmd_dword[2] &= ~CMD_COL_MASK; cmd->cmd_dword[2] |= col_id; } static __inline void cmd_format_target(struct its_cmd *cmd, uint64_t target) { /* Target Address field: DW2 [47:16] */ cmd->cmd_dword[2] &= ~CMD_TARGET_MASK; cmd->cmd_dword[2] |= (target & CMD_TARGET_MASK); } static __inline void cmd_format_itt(struct its_cmd *cmd, uint64_t itt) { /* ITT Address field: DW2 [47:8] */ cmd->cmd_dword[2] &= ~CMD_ITT_MASK; cmd->cmd_dword[2] |= (itt & CMD_ITT_MASK); } static __inline void cmd_format_valid(struct its_cmd *cmd, uint8_t valid) { /* Valid field: DW2 [63] */ cmd->cmd_dword[2] &= ~CMD_VALID_MASK; cmd->cmd_dword[2] |= ((uint64_t)valid << CMD_VALID_SHIFT); } static __inline void cmd_fix_endian(struct its_cmd *cmd) { size_t i; for (i = 0; i < nitems(cmd->cmd_dword); i++) cmd->cmd_dword[i] = htole64(cmd->cmd_dword[i]); } static void its_cmd_movi(struct gic_v3_its_softc *sc, struct its_dev *its_dev, struct its_col *col, uint32_t id) { struct its_cmd_desc desc; desc.cmd_type = ITS_CMD_MOVI; desc.cmd_desc_movi.its_dev = its_dev; desc.cmd_desc_movi.col = col; desc.cmd_desc_movi.id = id; its_cmd_send(sc, &desc); } static void its_cmd_mapc(struct gic_v3_its_softc *sc, struct its_col *col, uint8_t valid) { struct its_cmd_desc desc; desc.cmd_type = ITS_CMD_MAPC; desc.cmd_desc_mapc.col = col; /* * Valid bit set - map the collection. * Valid bit cleared - unmap the collection. */ desc.cmd_desc_mapc.valid = valid; its_cmd_send(sc, &desc); } static void its_cmd_mapvi(struct gic_v3_its_softc *sc, struct its_dev *its_dev, uint32_t id, uint32_t pid) { struct its_cmd_desc desc; struct its_col *col; u_int col_id; col_id = its_dev->lpis.lpi_col_ids[id]; col = sc->its_cols[col_id]; desc.cmd_type = ITS_CMD_MAPVI; desc.cmd_desc_mapvi.its_dev = its_dev; desc.cmd_desc_mapvi.col = col; desc.cmd_desc_mapvi.id = id; desc.cmd_desc_mapvi.pid = pid; its_cmd_send(sc, &desc); } static void __unused its_cmd_mapi(struct gic_v3_its_softc *sc, struct its_dev *its_dev, uint32_t pid) { struct its_cmd_desc desc; struct its_col *col; u_int col_id; uint32_t id; KASSERT(pid >= its_dev->lpis.lpi_base, ("%s: invalid pid: %d for the ITS device", __func__, pid)); id = pid - its_dev->lpis.lpi_base; col_id = its_dev->lpis.lpi_col_ids[id]; col = sc->its_cols[col_id]; desc.cmd_type = ITS_CMD_MAPI; desc.cmd_desc_mapi.its_dev = its_dev; desc.cmd_desc_mapi.col = col; desc.cmd_desc_mapi.pid = pid; its_cmd_send(sc, &desc); } static void its_cmd_mapd(struct gic_v3_its_softc *sc, struct its_dev *its_dev, uint8_t valid) { struct its_cmd_desc desc; desc.cmd_type = ITS_CMD_MAPD; desc.cmd_desc_mapd.its_dev = its_dev; desc.cmd_desc_mapd.valid = valid; its_cmd_send(sc, &desc); } static void its_cmd_inv(struct gic_v3_its_softc *sc, struct its_dev *its_dev, uint32_t pid) { struct its_cmd_desc desc; struct its_col *col; u_int col_id; uint32_t id; KASSERT(pid >= its_dev->lpis.lpi_base, ("%s: invalid pid: %d for the ITS device", __func__, pid)); id = pid - its_dev->lpis.lpi_base; col_id = its_dev->lpis.lpi_col_ids[id]; col = sc->its_cols[col_id]; desc.cmd_type = ITS_CMD_INV; desc.cmd_desc_inv.pid = pid - its_dev->lpis.lpi_base; desc.cmd_desc_inv.its_dev = its_dev; desc.cmd_desc_inv.col = col; its_cmd_send(sc, &desc); } static void its_cmd_invall(struct gic_v3_its_softc *sc, struct its_col *col) { struct its_cmd_desc desc; desc.cmd_type = ITS_CMD_INVALL; desc.cmd_desc_invall.col = col; its_cmd_send(sc, &desc); } /* * Helper routines for commands processing. */ static __inline boolean_t its_cmd_queue_full(struct gic_v3_its_softc *sc) { size_t read_idx, write_idx; write_idx = (size_t)(sc->its_cmdq_write - sc->its_cmdq_base); read_idx = gic_its_read(sc, 4, GITS_CREADR) / sizeof(struct its_cmd); /* * The queue is full when the write offset points * at the command before the current read offset. */ if (((write_idx + 1) % ITS_CMDQ_NENTRIES) == read_idx) return (TRUE); return (FALSE); } static __inline void its_cmd_sync(struct gic_v3_its_softc *sc, struct its_cmd *cmd) { if ((sc->its_flags & ITS_FLAGS_CMDQ_FLUSH) != 0) { /* Clean D-cache under command. */ cpu_dcache_wb_range((vm_offset_t)cmd, sizeof(*cmd)); } else { /* DSB inner shareable, store */ dsb(ishst); } } static struct its_cmd * its_cmd_alloc_locked(struct gic_v3_its_softc *sc) { struct its_cmd *cmd; size_t us_left; /* * XXX ARM64TODO: This is obviously a significant delay. * The reason for that is that currently the time frames for * the command to complete (and therefore free the descriptor) * are not known. */ us_left = 1000000; mtx_assert(&sc->its_cmd_lock, MA_OWNED); while (its_cmd_queue_full(sc)) { if (us_left-- == 0) { /* Timeout while waiting for free command */ device_printf(sc->dev, "Timeout while waiting for free command\n"); return (NULL); } DELAY(1); } cmd = sc->its_cmdq_write; sc->its_cmdq_write++; if (sc->its_cmdq_write == (sc->its_cmdq_base + ITS_CMDQ_NENTRIES)) { /* Wrap the queue */ sc->its_cmdq_write = sc->its_cmdq_base; } return (cmd); } static uint64_t its_cmd_prepare(struct its_cmd *cmd, struct its_cmd_desc *desc) { uint64_t target; uint8_t cmd_type; u_int size; boolean_t error; error = FALSE; cmd_type = desc->cmd_type; target = ITS_TARGET_NONE; switch (cmd_type) { case ITS_CMD_MOVI: /* Move interrupt ID to another collection */ target = desc->cmd_desc_movi.col->col_target; cmd_format_command(cmd, ITS_CMD_MOVI); cmd_format_id(cmd, desc->cmd_desc_movi.id); cmd_format_col(cmd, desc->cmd_desc_movi.col->col_id); cmd_format_devid(cmd, desc->cmd_desc_movi.its_dev->devid); break; case ITS_CMD_SYNC: /* Wait for previous commands completion */ target = desc->cmd_desc_sync.col->col_target; cmd_format_command(cmd, ITS_CMD_SYNC); cmd_format_target(cmd, target); break; case ITS_CMD_MAPD: /* Assign ITT to device */ cmd_format_command(cmd, ITS_CMD_MAPD); cmd_format_itt(cmd, vtophys(desc->cmd_desc_mapd.its_dev->itt)); /* * Size describes number of bits to encode interrupt IDs * supported by the device minus one. * When V (valid) bit is zero, this field should be written * as zero. */ if (desc->cmd_desc_mapd.valid != 0) { size = fls(desc->cmd_desc_mapd.its_dev->lpis.lpi_num); size = MAX(1, size) - 1; } else size = 0; cmd_format_size(cmd, size); cmd_format_devid(cmd, desc->cmd_desc_mapd.its_dev->devid); cmd_format_valid(cmd, desc->cmd_desc_mapd.valid); break; case ITS_CMD_MAPC: /* Map collection to Re-Distributor */ target = desc->cmd_desc_mapc.col->col_target; cmd_format_command(cmd, ITS_CMD_MAPC); cmd_format_col(cmd, desc->cmd_desc_mapc.col->col_id); cmd_format_valid(cmd, desc->cmd_desc_mapc.valid); cmd_format_target(cmd, target); break; case ITS_CMD_MAPVI: target = desc->cmd_desc_mapvi.col->col_target; cmd_format_command(cmd, ITS_CMD_MAPVI); cmd_format_devid(cmd, desc->cmd_desc_mapvi.its_dev->devid); cmd_format_id(cmd, desc->cmd_desc_mapvi.id); cmd_format_pid(cmd, desc->cmd_desc_mapvi.pid); cmd_format_col(cmd, desc->cmd_desc_mapvi.col->col_id); break; case ITS_CMD_MAPI: target = desc->cmd_desc_mapi.col->col_target; cmd_format_command(cmd, ITS_CMD_MAPI); cmd_format_devid(cmd, desc->cmd_desc_mapi.its_dev->devid); cmd_format_id(cmd, desc->cmd_desc_mapi.pid); cmd_format_col(cmd, desc->cmd_desc_mapi.col->col_id); break; case ITS_CMD_INV: target = desc->cmd_desc_inv.col->col_target; cmd_format_command(cmd, ITS_CMD_INV); cmd_format_devid(cmd, desc->cmd_desc_inv.its_dev->devid); cmd_format_id(cmd, desc->cmd_desc_inv.pid); break; case ITS_CMD_INVALL: cmd_format_command(cmd, ITS_CMD_INVALL); cmd_format_col(cmd, desc->cmd_desc_invall.col->col_id); break; default: error = TRUE; break; } if (!error) cmd_fix_endian(cmd); return (target); } static __inline uint64_t its_cmd_cwriter_offset(struct gic_v3_its_softc *sc, struct its_cmd *cmd) { uint64_t off; off = (cmd - sc->its_cmdq_base) * sizeof(*cmd); return (off); } static void its_cmd_wait_completion(struct gic_v3_its_softc *sc, struct its_cmd *cmd_first, struct its_cmd *cmd_last) { uint64_t first, last, read; size_t us_left; /* * XXX ARM64TODO: This is obviously a significant delay. * The reason for that is that currently the time frames for * the command to complete are not known. */ us_left = 1000000; first = its_cmd_cwriter_offset(sc, cmd_first); last = its_cmd_cwriter_offset(sc, cmd_last); for (;;) { read = gic_its_read(sc, 8, GITS_CREADR); if (read < first || read >= last) break; if (us_left-- == 0) { /* This means timeout */ device_printf(sc->dev, "Timeout while waiting for CMD completion.\n"); return; } DELAY(1); } } static int its_cmd_send(struct gic_v3_its_softc *sc, struct its_cmd_desc *desc) { struct its_cmd *cmd, *cmd_sync, *cmd_write; struct its_col col_sync; struct its_cmd_desc desc_sync; uint64_t target, cwriter; mtx_lock_spin(&sc->its_cmd_lock); cmd = its_cmd_alloc_locked(sc); if (cmd == NULL) { device_printf(sc->dev, "could not allocate ITS command\n"); mtx_unlock_spin(&sc->its_cmd_lock); return (EBUSY); } target = its_cmd_prepare(cmd, desc); its_cmd_sync(sc, cmd); if (target != ITS_TARGET_NONE) { cmd_sync = its_cmd_alloc_locked(sc); if (cmd_sync == NULL) goto end; desc_sync.cmd_type = ITS_CMD_SYNC; col_sync.col_target = target; desc_sync.cmd_desc_sync.col = &col_sync; its_cmd_prepare(cmd_sync, &desc_sync); its_cmd_sync(sc, cmd_sync); } end: /* Update GITS_CWRITER */ cwriter = its_cmd_cwriter_offset(sc, sc->its_cmdq_write); gic_its_write(sc, 8, GITS_CWRITER, cwriter); cmd_write = sc->its_cmdq_write; mtx_unlock_spin(&sc->its_cmd_lock); its_cmd_wait_completion(sc, cmd, cmd_write); return (0); } /* Find ITS device descriptor by pci_dev or irq number */ static struct its_dev * its_device_find_locked(struct gic_v3_its_softc *sc, device_t pci_dev, uint32_t irq) { struct its_dev *its_dev; struct lpi_chunk *lpis; mtx_assert(&sc->its_dev_lock, MA_OWNED); KASSERT((pci_dev == NULL || irq == 0), ("%s: Can't search by both pci_dev and irq number", __func__)); /* Find existing device if any */ TAILQ_FOREACH(its_dev, &sc->its_dev_list, entry) { if (pci_dev != NULL) { if (its_dev->pci_dev == pci_dev) return (its_dev); } else if (irq != 0) { lpis = &its_dev->lpis; if ((irq >= lpis->lpi_base) && (irq < (lpis->lpi_base + lpis->lpi_num))) return (its_dev); } } return (NULL); } static struct its_dev * its_device_alloc(struct gic_v3_its_softc *sc, device_t pci_dev, u_int nvecs) { struct its_dev *newdev; uint64_t typer; uint32_t devid; size_t esize; int err; mtx_lock_spin(&sc->its_dev_lock); /* Find existing device if any */ newdev = its_device_find_locked(sc, pci_dev, 0); mtx_unlock_spin(&sc->its_dev_lock); if (newdev != NULL) return (newdev); devid = its_get_devid(pci_dev); /* There was no previously created device. Create one now */ newdev = malloc(sizeof(*newdev), M_GIC_V3_ITS, (M_NOWAIT | M_ZERO)); if (newdev == NULL) return (NULL); newdev->pci_dev = pci_dev; newdev->devid = devid; err = lpi_alloc_chunk(sc, &newdev->lpis, nvecs); if (err != 0) { free(newdev, M_GIC_V3_ITS); return (NULL); } /* Get ITT entry size */ typer = gic_its_read(sc, 8, GITS_TYPER); esize = GITS_TYPER_ITTES(typer); /* * Allocate ITT for this device. * PA has to be 256 B aligned. At least two entries for device. */ newdev->itt = (vm_offset_t)contigmalloc( roundup2(roundup2(nvecs, 2) * esize, 0x100), M_GIC_V3_ITS, (M_NOWAIT | M_ZERO), 0, ~0UL, 0x100, 0); if (newdev->itt == 0) { lpi_free_chunk(sc, &newdev->lpis); free(newdev, M_GIC_V3_ITS); return (NULL); } mtx_lock_spin(&sc->its_dev_lock); TAILQ_INSERT_TAIL(&sc->its_dev_list, newdev, entry); mtx_unlock_spin(&sc->its_dev_lock); /* Map device to its ITT */ its_cmd_mapd(sc, newdev, 1); return (newdev); } static __inline void its_device_asign_lpi_locked(struct gic_v3_its_softc *sc, struct its_dev *its_dev, u_int *irq) { mtx_assert(&sc->its_dev_lock, MA_OWNED); if (its_dev->lpis.lpi_free == 0) { panic("Requesting more LPIs than allocated for this device. " "LPI num: %u, free %u", its_dev->lpis.lpi_num, its_dev->lpis.lpi_free); } *irq = its_dev->lpis.lpi_base + (its_dev->lpis.lpi_num - its_dev->lpis.lpi_free); its_dev->lpis.lpi_free--; } /* * ITS quirks. * Add vendor specific PCI devid function here. */ static uint32_t its_get_devid_thunder(device_t pci_dev) { int bsf; int pem; uint32_t bus; bus = pci_get_bus(pci_dev); bsf = pci_get_rid(pci_dev); /* Check if accessing internal PCIe (low bus numbers) */ if (bus < GIC_V3_ITS_QUIRK_THUNDERX_PEM_BUS_OFFSET) { return ((pci_get_domain(pci_dev) << PCI_RID_DOMAIN_SHIFT) | bsf); /* PEM otherwise */ } else { /* PEM (PCIe MAC/root complex) number is equal to domain */ pem = pci_get_domain(pci_dev); /* * Set appropriate device ID (passed by the HW along with * the transaction to memory) for different root complex * numbers using hard-coded domain portion for each group. */ if (pem < 3) return ((0x1 << PCI_RID_DOMAIN_SHIFT) | bsf); if (pem < 6) return ((0x3 << PCI_RID_DOMAIN_SHIFT) | bsf); if (pem < 9) return ((0x9 << PCI_RID_DOMAIN_SHIFT) | bsf); if (pem < 12) return ((0xB << PCI_RID_DOMAIN_SHIFT) | bsf); } return (0); } static uint32_t its_get_devbits_thunder(device_t dev) { uint32_t devid_bits; /* * GITS_TYPER[17:13] of ThunderX reports that device IDs * are to be 21 bits in length. * The entry size of the ITS table can be read from GITS_BASERn[52:48] * and on ThunderX is supposed to be 8 bytes in length (for device * table). Finally the page size that is to be used by ITS to access * this table will be set to 64KB. * * This gives 0x200000 entries of size 0x8 bytes covered by 256 pages * each of which 64KB in size. The number of pages (minus 1) should * then be written to GITS_BASERn[7:0]. In that case this value would * be 0xFF but on ThunderX the maximum value that HW accepts is 0xFD. * * Set arbitrary number of device ID bits to 20 in order to limit * the number of entries in ITS device table to 0x100000 and hence * the table size to 8MB. */ devid_bits = 20; if (bootverbose) { device_printf(dev, "Limiting number of Device ID bits implemented to %d\n", devid_bits); } return (devid_bits); } static __inline uint32_t its_get_devbits_default(device_t dev) { uint64_t gits_typer; struct gic_v3_its_softc *sc; sc = device_get_softc(dev); gits_typer = gic_its_read(sc, 8, GITS_TYPER); return (GITS_TYPER_DEVB(gits_typer)); } static uint32_t its_get_devbits(device_t dev) { const struct its_quirks *quirk; size_t i; for (i = 0; i < nitems(its_quirks); i++) { quirk = &its_quirks[i]; if (CPU_MATCH_RAW(quirk->cpuid_mask, quirk->cpuid)) { if (quirk->devbits_func != NULL) return ((*quirk->devbits_func)(dev)); } } return (its_get_devbits_default(dev)); } static __inline uint32_t its_get_devid_default(device_t pci_dev) { return (PCI_DEVID_GENERIC(pci_dev)); } static uint32_t its_get_devid(device_t pci_dev) { const struct its_quirks *quirk; size_t i; for (i = 0; i < nitems(its_quirks); i++) { quirk = &its_quirks[i]; if (CPU_MATCH_RAW(quirk->cpuid_mask, quirk->cpuid)) { if (quirk->devid_func != NULL) return ((*quirk->devid_func)(pci_dev)); } } return (its_get_devid_default(pci_dev)); } /* * Message signalled interrupts handling. */ /* * XXX ARM64TODO: Watch out for "irq" type. * * In theory GIC can handle up to (2^32 - 1) interrupt IDs whereas * we pass "irq" pointer of type integer. This is obviously wrong but * is determined by the way as PCI layer wants it to be done. */ int gic_v3_its_alloc_msix(device_t dev, device_t pci_dev, int *irq) { struct gic_v3_its_softc *sc; struct its_dev *its_dev; u_int nvecs; sc = device_get_softc(dev); nvecs = PCI_MSIX_NUM(pci_dev); /* * Allocate device as seen by ITS if not already available. * Notice that MSI-X interrupts are allocated on one-by-one basis. */ its_dev = its_device_alloc(sc, pci_dev, nvecs); if (its_dev == NULL) return (ENOMEM); mtx_lock_spin(&sc->its_dev_lock); its_device_asign_lpi_locked(sc, its_dev, irq); mtx_unlock_spin(&sc->its_dev_lock); return (0); } int gic_v3_its_alloc_msi(device_t dev, device_t pci_dev, int count, int *irqs) { struct gic_v3_its_softc *sc; struct its_dev *its_dev; sc = device_get_softc(dev); /* Allocate device as seen by ITS if not already available. */ its_dev = its_device_alloc(sc, pci_dev, count); if (its_dev == NULL) return (ENOMEM); mtx_lock_spin(&sc->its_dev_lock); for (; count > 0; count--) { its_device_asign_lpi_locked(sc, its_dev, irqs); irqs++; } mtx_unlock_spin(&sc->its_dev_lock); return (0); } int gic_v3_its_map_msi(device_t dev, device_t pci_dev, int irq, uint64_t *addr, uint32_t *data) { struct gic_v3_its_softc *sc; bus_space_handle_t its_bsh; struct its_dev *its_dev; uint64_t its_pa; uint32_t id; sc = device_get_softc(dev); /* Verify that this device is allocated and owns this LPI */ mtx_lock_spin(&sc->its_dev_lock); its_dev = its_device_find_locked(sc, pci_dev, 0); mtx_unlock_spin(&sc->its_dev_lock); if (its_dev == NULL) return (EINVAL); id = irq - its_dev->lpis.lpi_base; lpi_map_to_device(sc, its_dev, id, irq); its_bsh = rman_get_bushandle(&sc->its_res[0]); its_pa = vtophys(its_bsh); *addr = (its_pa + GITS_TRANSLATER); *data = id; return (0); } Index: user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_var.h =================================================================== --- user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_var.h (revision 299126) +++ user/ngie/detangle-rc/sys/arm64/arm64/gic_v3_var.h (revision 299127) @@ -1,328 +1,328 @@ /*- * Copyright (c) 2015 The FreeBSD Foundation * All rights reserved. * * This software was developed by Semihalf under * the sponsorship of the FreeBSD Foundation. * * 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. * * $FreeBSD$ */ #ifndef _GIC_V3_VAR_H_ #define _GIC_V3_VAR_H_ #define GIC_V3_DEVSTR "ARM Generic Interrupt Controller v3.0" DECLARE_CLASS(gic_v3_driver); #define LPI_FLAGS_CONF_FLUSH (1UL << 0) #define LPI_CONFTAB_SIZE PAGE_SIZE_64K /* 1 bit per LPI + 1 KB more for the obligatory PPI, SGI, SPI stuff */ #define LPI_PENDTAB_SIZE ((LPI_CONFTAB_SIZE / 8) + 0x400) struct redist_lpis { vm_offset_t conf_base; vm_offset_t pend_base[MAXCPU]; uint64_t flags; }; struct gic_redists { /* * Re-Distributor region description. * We will have few of those depending * on the #redistributor-regions property in FDT. */ struct resource ** regions; /* Number of Re-Distributor regions */ u_int nregions; /* Per-CPU Re-Distributor handler */ struct resource * pcpu[MAXCPU]; /* LPIs data */ struct redist_lpis lpis; }; struct gic_v3_softc { device_t dev; struct resource ** gic_res; struct mtx gic_mtx; /* Distributor */ struct resource * gic_dist; /* Re-Distributors */ struct gic_redists gic_redists; u_int gic_nirqs; u_int gic_idbits; boolean_t gic_registered; }; MALLOC_DECLARE(M_GIC_V3); /* Device methods */ int gic_v3_attach(device_t dev); int gic_v3_detach(device_t dev); /* * ITS */ #define GIC_V3_ITS_DEVSTR "ARM GIC Interrupt Translation Service" #define GIC_V3_ITS_COMPSTR "arm,gic-v3-its" DECLARE_CLASS(gic_v3_its_driver); /* LPI chunk owned by ITS device */ struct lpi_chunk { u_int lpi_base; u_int lpi_num; u_int lpi_free; /* First free LPI in set */ u_int *lpi_col_ids; }; /* ITS device */ struct its_dev { TAILQ_ENTRY(its_dev) entry; /* PCI device */ device_t pci_dev; /* Device ID (i.e. PCI device ID) */ uint32_t devid; /* List of assigned LPIs */ struct lpi_chunk lpis; /* Virtual address of ITT */ vm_offset_t itt; }; TAILQ_HEAD(its_dev_list, its_dev); /* ITS private table description */ struct its_ptab { vm_offset_t ptab_vaddr; /* Virtual Address of table */ size_t ptab_pgsz; /* Page size */ size_t ptab_npages; /* Number of pages */ }; /* ITS collection description. */ struct its_col { uint64_t col_target; /* Target Re-Distributor */ uint64_t col_id; /* Collection ID */ }; /* ITS command. Each command is 32 bytes long */ struct its_cmd { uint64_t cmd_dword[4]; /* ITS command double word */ }; /* ITS commands encoding */ #define ITS_CMD_MOVI (0x01) #define ITS_CMD_SYNC (0x05) #define ITS_CMD_MAPD (0x08) #define ITS_CMD_MAPC (0x09) #define ITS_CMD_MAPVI (0x0a) #define ITS_CMD_MAPI (0x0b) #define ITS_CMD_INV (0x0c) #define ITS_CMD_INVALL (0x0d) /* Command */ #define CMD_COMMAND_MASK (0xFFUL) /* PCI device ID */ #define CMD_DEVID_SHIFT (32) #define CMD_DEVID_MASK (0xFFFFFFFFUL << CMD_DEVID_SHIFT) /* Size of IRQ ID bitfield */ #define CMD_SIZE_MASK (0xFFUL) /* Virtual LPI ID */ #define CMD_ID_MASK (0xFFFFFFFFUL) /* Physical LPI ID */ #define CMD_PID_SHIFT (32) #define CMD_PID_MASK (0xFFFFFFFFUL << CMD_PID_SHIFT) /* Collection */ #define CMD_COL_MASK (0xFFFFUL) /* Target (CPU or Re-Distributor) */ #define CMD_TARGET_SHIFT (16) #define CMD_TARGET_MASK (0xFFFFFFFFUL << CMD_TARGET_SHIFT) /* Interrupt Translation Table address */ #define CMD_ITT_MASK (0xFFFFFFFFFF00UL) /* Valid command bit */ #define CMD_VALID_SHIFT (63) #define CMD_VALID_MASK (1UL << CMD_VALID_SHIFT) /* * ITS command descriptor. * Idea for command description passing taken from Linux. */ struct its_cmd_desc { uint8_t cmd_type; union { struct { struct its_dev *its_dev; struct its_col *col; uint32_t id; } cmd_desc_movi; struct { struct its_col *col; } cmd_desc_sync; struct { struct its_col *col; uint8_t valid; } cmd_desc_mapc; struct { struct its_dev *its_dev; struct its_col *col; uint32_t pid; uint32_t id; } cmd_desc_mapvi; struct { struct its_dev *its_dev; struct its_col *col; uint32_t pid; } cmd_desc_mapi; struct { struct its_dev *its_dev; uint8_t valid; } cmd_desc_mapd; struct { struct its_dev *its_dev; struct its_col *col; uint32_t pid; } cmd_desc_inv; struct { struct its_col *col; } cmd_desc_invall; }; }; #define ITS_CMDQ_SIZE PAGE_SIZE_64K #define ITS_CMDQ_NENTRIES (ITS_CMDQ_SIZE / sizeof(struct its_cmd)) #define ITS_FLAGS_CMDQ_FLUSH (1UL << 0) #define ITS_TARGET_NONE 0xFBADBEEF struct gic_v3_its_softc { device_t dev; struct resource * its_res; struct its_cmd * its_cmdq_base; /* ITS command queue base */ struct its_cmd * its_cmdq_write; /* ITS command queue write ptr */ struct its_ptab its_ptabs[GITS_BASER_NUM];/* ITS private tables */ struct its_col * its_cols[MAXCPU];/* Per-CPU collections */ uint64_t its_flags; struct its_dev_list its_dev_list; - unsigned long * its_lpi_bitmap; + bitstr_t * its_lpi_bitmap; uint32_t its_lpi_maxid; struct mtx its_dev_lock; struct mtx its_cmd_lock; uint32_t its_socket; /* Socket number ITS is attached to */ }; /* Stuff that is specific to the vendor's implementation */ typedef uint32_t (*its_devbits_func_t)(device_t); typedef uint32_t (*its_devid_func_t)(device_t); struct its_quirks { uint64_t cpuid; uint64_t cpuid_mask; its_devid_func_t devid_func; its_devbits_func_t devbits_func; }; extern devclass_t gic_v3_its_devclass; int gic_v3_its_detach(device_t); int gic_v3_its_alloc_msix(device_t, device_t, int *); int gic_v3_its_alloc_msi(device_t, device_t, int, int *); int gic_v3_its_map_msi(device_t, device_t, int, uint64_t *, uint32_t *); int its_init_cpu(struct gic_v3_its_softc *); int lpi_migrate(device_t, uint32_t, u_int); void lpi_unmask_irq(device_t, uint32_t); void lpi_mask_irq(device_t, uint32_t); /* * GIC Distributor accessors. * Notice that only GIC sofc can be passed. */ #define gic_d_read(sc, len, reg) \ ({ \ bus_read_##len(sc->gic_dist, reg); \ }) #define gic_d_write(sc, len, reg, val) \ ({ \ bus_write_##len(sc->gic_dist, reg, val);\ }) /* GIC Re-Distributor accessors (per-CPU) */ #define gic_r_read(sc, len, reg) \ ({ \ u_int cpu = PCPU_GET(cpuid); \ \ bus_read_##len( \ sc->gic_redists.pcpu[cpu], \ reg); \ }) #define gic_r_write(sc, len, reg, val) \ ({ \ u_int cpu = PCPU_GET(cpuid); \ \ bus_write_##len( \ sc->gic_redists.pcpu[cpu], \ reg, val); \ }) #define PCI_DEVID_GENERIC(pci_dev) \ ({ \ ((pci_get_domain(pci_dev) << PCI_RID_DOMAIN_SHIFT) | \ (pci_get_bus(pci_dev) << PCI_RID_BUS_SHIFT) | \ (pci_get_slot(pci_dev) << PCI_RID_SLOT_SHIFT) | \ (pci_get_function(pci_dev) << PCI_RID_FUNC_SHIFT)); \ }) /* * Request number of maximum MSI-X vectors for this device. * Device can ask for less vectors than maximum supported but not more. */ #define PCI_MSIX_NUM(pci_dev) \ ({ \ struct pci_devinfo *dinfo; \ pcicfgregs *cfg; \ \ dinfo = device_get_ivars(pci_dev); \ cfg = &dinfo->cfg; \ \ cfg->msix.msix_msgnum; \ }) #endif /* _GIC_V3_VAR_H_ */ Index: user/ngie/detangle-rc/sys/cddl/contrib/opensolaris/uts/common/sys/dtrace.h =================================================================== --- user/ngie/detangle-rc/sys/cddl/contrib/opensolaris/uts/common/sys/dtrace.h (revision 299126) +++ user/ngie/detangle-rc/sys/cddl/contrib/opensolaris/uts/common/sys/dtrace.h (revision 299127) @@ -1,2492 +1,2504 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. * Copyright (c) 2013 by Delphix. All rights reserved. */ #ifndef _SYS_DTRACE_H #define _SYS_DTRACE_H #pragma ident "%Z%%M% %I% %E% SMI" #ifdef __cplusplus extern "C" { #endif /* * DTrace Dynamic Tracing Software: Kernel Interfaces * * Note: The contents of this file are private to the implementation of the * Solaris system and DTrace subsystem and are subject to change at any time * without notice. Applications and drivers using these interfaces will fail * to run on future releases. These interfaces should not be used for any * purpose except those expressly outlined in dtrace(7D) and libdtrace(3LIB). * Please refer to the "Solaris Dynamic Tracing Guide" for more information. */ #ifndef _ASM #include #include #include #ifdef illumos #include #else #include #include #include #include #include typedef int model_t; #endif #include #ifdef illumos #include #include #else #include #endif /* * DTrace Universal Constants and Typedefs */ #define DTRACE_CPUALL -1 /* all CPUs */ #define DTRACE_IDNONE 0 /* invalid probe identifier */ #define DTRACE_EPIDNONE 0 /* invalid enabled probe identifier */ #define DTRACE_AGGIDNONE 0 /* invalid aggregation identifier */ #define DTRACE_AGGVARIDNONE 0 /* invalid aggregation variable ID */ #define DTRACE_CACHEIDNONE 0 /* invalid predicate cache */ #define DTRACE_PROVNONE 0 /* invalid provider identifier */ #define DTRACE_METAPROVNONE 0 /* invalid meta-provider identifier */ #define DTRACE_ARGNONE -1 /* invalid argument index */ #define DTRACE_PROVNAMELEN 64 #define DTRACE_MODNAMELEN 64 #define DTRACE_FUNCNAMELEN 192 #define DTRACE_NAMELEN 64 #define DTRACE_FULLNAMELEN (DTRACE_PROVNAMELEN + DTRACE_MODNAMELEN + \ DTRACE_FUNCNAMELEN + DTRACE_NAMELEN + 4) #define DTRACE_ARGTYPELEN 128 typedef uint32_t dtrace_id_t; /* probe identifier */ typedef uint32_t dtrace_epid_t; /* enabled probe identifier */ typedef uint32_t dtrace_aggid_t; /* aggregation identifier */ typedef int64_t dtrace_aggvarid_t; /* aggregation variable identifier */ typedef uint16_t dtrace_actkind_t; /* action kind */ typedef int64_t dtrace_optval_t; /* option value */ typedef uint32_t dtrace_cacheid_t; /* predicate cache identifier */ typedef enum dtrace_probespec { DTRACE_PROBESPEC_NONE = -1, DTRACE_PROBESPEC_PROVIDER = 0, DTRACE_PROBESPEC_MOD, DTRACE_PROBESPEC_FUNC, DTRACE_PROBESPEC_NAME } dtrace_probespec_t; /* * DTrace Intermediate Format (DIF) * * The following definitions describe the DTrace Intermediate Format (DIF), a * a RISC-like instruction set and program encoding used to represent * predicates and actions that can be bound to DTrace probes. The constants * below defining the number of available registers are suggested minimums; the * compiler should use DTRACEIOC_CONF to dynamically obtain the number of * registers provided by the current DTrace implementation. */ #define DIF_VERSION_1 1 /* DIF version 1: Solaris 10 Beta */ #define DIF_VERSION_2 2 /* DIF version 2: Solaris 10 FCS */ #define DIF_VERSION DIF_VERSION_2 /* latest DIF instruction set version */ #define DIF_DIR_NREGS 8 /* number of DIF integer registers */ #define DIF_DTR_NREGS 8 /* number of DIF tuple registers */ #define DIF_OP_OR 1 /* or r1, r2, rd */ #define DIF_OP_XOR 2 /* xor r1, r2, rd */ #define DIF_OP_AND 3 /* and r1, r2, rd */ #define DIF_OP_SLL 4 /* sll r1, r2, rd */ #define DIF_OP_SRL 5 /* srl r1, r2, rd */ #define DIF_OP_SUB 6 /* sub r1, r2, rd */ #define DIF_OP_ADD 7 /* add r1, r2, rd */ #define DIF_OP_MUL 8 /* mul r1, r2, rd */ #define DIF_OP_SDIV 9 /* sdiv r1, r2, rd */ #define DIF_OP_UDIV 10 /* udiv r1, r2, rd */ #define DIF_OP_SREM 11 /* srem r1, r2, rd */ #define DIF_OP_UREM 12 /* urem r1, r2, rd */ #define DIF_OP_NOT 13 /* not r1, rd */ #define DIF_OP_MOV 14 /* mov r1, rd */ #define DIF_OP_CMP 15 /* cmp r1, r2 */ #define DIF_OP_TST 16 /* tst r1 */ #define DIF_OP_BA 17 /* ba label */ #define DIF_OP_BE 18 /* be label */ #define DIF_OP_BNE 19 /* bne label */ #define DIF_OP_BG 20 /* bg label */ #define DIF_OP_BGU 21 /* bgu label */ #define DIF_OP_BGE 22 /* bge label */ #define DIF_OP_BGEU 23 /* bgeu label */ #define DIF_OP_BL 24 /* bl label */ #define DIF_OP_BLU 25 /* blu label */ #define DIF_OP_BLE 26 /* ble label */ #define DIF_OP_BLEU 27 /* bleu label */ #define DIF_OP_LDSB 28 /* ldsb [r1], rd */ #define DIF_OP_LDSH 29 /* ldsh [r1], rd */ #define DIF_OP_LDSW 30 /* ldsw [r1], rd */ #define DIF_OP_LDUB 31 /* ldub [r1], rd */ #define DIF_OP_LDUH 32 /* lduh [r1], rd */ #define DIF_OP_LDUW 33 /* lduw [r1], rd */ #define DIF_OP_LDX 34 /* ldx [r1], rd */ #define DIF_OP_RET 35 /* ret rd */ #define DIF_OP_NOP 36 /* nop */ #define DIF_OP_SETX 37 /* setx intindex, rd */ #define DIF_OP_SETS 38 /* sets strindex, rd */ #define DIF_OP_SCMP 39 /* scmp r1, r2 */ #define DIF_OP_LDGA 40 /* ldga var, ri, rd */ #define DIF_OP_LDGS 41 /* ldgs var, rd */ #define DIF_OP_STGS 42 /* stgs var, rs */ #define DIF_OP_LDTA 43 /* ldta var, ri, rd */ #define DIF_OP_LDTS 44 /* ldts var, rd */ #define DIF_OP_STTS 45 /* stts var, rs */ #define DIF_OP_SRA 46 /* sra r1, r2, rd */ #define DIF_OP_CALL 47 /* call subr, rd */ #define DIF_OP_PUSHTR 48 /* pushtr type, rs, rr */ #define DIF_OP_PUSHTV 49 /* pushtv type, rs, rv */ #define DIF_OP_POPTS 50 /* popts */ #define DIF_OP_FLUSHTS 51 /* flushts */ #define DIF_OP_LDGAA 52 /* ldgaa var, rd */ #define DIF_OP_LDTAA 53 /* ldtaa var, rd */ #define DIF_OP_STGAA 54 /* stgaa var, rs */ #define DIF_OP_STTAA 55 /* sttaa var, rs */ #define DIF_OP_LDLS 56 /* ldls var, rd */ #define DIF_OP_STLS 57 /* stls var, rs */ #define DIF_OP_ALLOCS 58 /* allocs r1, rd */ #define DIF_OP_COPYS 59 /* copys r1, r2, rd */ #define DIF_OP_STB 60 /* stb r1, [rd] */ #define DIF_OP_STH 61 /* sth r1, [rd] */ #define DIF_OP_STW 62 /* stw r1, [rd] */ #define DIF_OP_STX 63 /* stx r1, [rd] */ #define DIF_OP_ULDSB 64 /* uldsb [r1], rd */ #define DIF_OP_ULDSH 65 /* uldsh [r1], rd */ #define DIF_OP_ULDSW 66 /* uldsw [r1], rd */ #define DIF_OP_ULDUB 67 /* uldub [r1], rd */ #define DIF_OP_ULDUH 68 /* ulduh [r1], rd */ #define DIF_OP_ULDUW 69 /* ulduw [r1], rd */ #define DIF_OP_ULDX 70 /* uldx [r1], rd */ #define DIF_OP_RLDSB 71 /* rldsb [r1], rd */ #define DIF_OP_RLDSH 72 /* rldsh [r1], rd */ #define DIF_OP_RLDSW 73 /* rldsw [r1], rd */ #define DIF_OP_RLDUB 74 /* rldub [r1], rd */ #define DIF_OP_RLDUH 75 /* rlduh [r1], rd */ #define DIF_OP_RLDUW 76 /* rlduw [r1], rd */ #define DIF_OP_RLDX 77 /* rldx [r1], rd */ #define DIF_OP_XLATE 78 /* xlate xlrindex, rd */ #define DIF_OP_XLARG 79 /* xlarg xlrindex, rd */ #define DIF_INTOFF_MAX 0xffff /* highest integer table offset */ #define DIF_STROFF_MAX 0xffff /* highest string table offset */ #define DIF_REGISTER_MAX 0xff /* highest register number */ #define DIF_VARIABLE_MAX 0xffff /* highest variable identifier */ #define DIF_SUBROUTINE_MAX 0xffff /* highest subroutine code */ #define DIF_VAR_ARRAY_MIN 0x0000 /* lowest numbered array variable */ #define DIF_VAR_ARRAY_UBASE 0x0080 /* lowest user-defined array */ #define DIF_VAR_ARRAY_MAX 0x00ff /* highest numbered array variable */ #define DIF_VAR_OTHER_MIN 0x0100 /* lowest numbered scalar or assc */ #define DIF_VAR_OTHER_UBASE 0x0500 /* lowest user-defined scalar or assc */ #define DIF_VAR_OTHER_MAX 0xffff /* highest numbered scalar or assc */ #define DIF_VAR_ARGS 0x0000 /* arguments array */ #define DIF_VAR_REGS 0x0001 /* registers array */ #define DIF_VAR_UREGS 0x0002 /* user registers array */ #define DIF_VAR_CURTHREAD 0x0100 /* thread pointer */ #define DIF_VAR_TIMESTAMP 0x0101 /* timestamp */ #define DIF_VAR_VTIMESTAMP 0x0102 /* virtual timestamp */ #define DIF_VAR_IPL 0x0103 /* interrupt priority level */ #define DIF_VAR_EPID 0x0104 /* enabled probe ID */ #define DIF_VAR_ID 0x0105 /* probe ID */ #define DIF_VAR_ARG0 0x0106 /* first argument */ #define DIF_VAR_ARG1 0x0107 /* second argument */ #define DIF_VAR_ARG2 0x0108 /* third argument */ #define DIF_VAR_ARG3 0x0109 /* fourth argument */ #define DIF_VAR_ARG4 0x010a /* fifth argument */ #define DIF_VAR_ARG5 0x010b /* sixth argument */ #define DIF_VAR_ARG6 0x010c /* seventh argument */ #define DIF_VAR_ARG7 0x010d /* eighth argument */ #define DIF_VAR_ARG8 0x010e /* ninth argument */ #define DIF_VAR_ARG9 0x010f /* tenth argument */ #define DIF_VAR_STACKDEPTH 0x0110 /* stack depth */ #define DIF_VAR_CALLER 0x0111 /* caller */ #define DIF_VAR_PROBEPROV 0x0112 /* probe provider */ #define DIF_VAR_PROBEMOD 0x0113 /* probe module */ #define DIF_VAR_PROBEFUNC 0x0114 /* probe function */ #define DIF_VAR_PROBENAME 0x0115 /* probe name */ #define DIF_VAR_PID 0x0116 /* process ID */ #define DIF_VAR_TID 0x0117 /* (per-process) thread ID */ #define DIF_VAR_EXECNAME 0x0118 /* name of executable */ #define DIF_VAR_ZONENAME 0x0119 /* zone name associated with process */ #define DIF_VAR_WALLTIMESTAMP 0x011a /* wall-clock timestamp */ #define DIF_VAR_USTACKDEPTH 0x011b /* user-land stack depth */ #define DIF_VAR_UCALLER 0x011c /* user-level caller */ #define DIF_VAR_PPID 0x011d /* parent process ID */ #define DIF_VAR_UID 0x011e /* process user ID */ #define DIF_VAR_GID 0x011f /* process group ID */ #define DIF_VAR_ERRNO 0x0120 /* thread errno */ #define DIF_VAR_EXECARGS 0x0121 /* process arguments */ #ifndef illumos #define DIF_VAR_CPU 0x0200 #endif #define DIF_SUBR_RAND 0 #define DIF_SUBR_MUTEX_OWNED 1 #define DIF_SUBR_MUTEX_OWNER 2 #define DIF_SUBR_MUTEX_TYPE_ADAPTIVE 3 #define DIF_SUBR_MUTEX_TYPE_SPIN 4 #define DIF_SUBR_RW_READ_HELD 5 #define DIF_SUBR_RW_WRITE_HELD 6 #define DIF_SUBR_RW_ISWRITER 7 #define DIF_SUBR_COPYIN 8 #define DIF_SUBR_COPYINSTR 9 #define DIF_SUBR_SPECULATION 10 #define DIF_SUBR_PROGENYOF 11 #define DIF_SUBR_STRLEN 12 #define DIF_SUBR_COPYOUT 13 #define DIF_SUBR_COPYOUTSTR 14 #define DIF_SUBR_ALLOCA 15 #define DIF_SUBR_BCOPY 16 #define DIF_SUBR_COPYINTO 17 #define DIF_SUBR_MSGDSIZE 18 #define DIF_SUBR_MSGSIZE 19 #define DIF_SUBR_GETMAJOR 20 #define DIF_SUBR_GETMINOR 21 #define DIF_SUBR_DDI_PATHNAME 22 #define DIF_SUBR_STRJOIN 23 #define DIF_SUBR_LLTOSTR 24 #define DIF_SUBR_BASENAME 25 #define DIF_SUBR_DIRNAME 26 #define DIF_SUBR_CLEANPATH 27 #define DIF_SUBR_STRCHR 28 #define DIF_SUBR_STRRCHR 29 #define DIF_SUBR_STRSTR 30 #define DIF_SUBR_STRTOK 31 #define DIF_SUBR_SUBSTR 32 #define DIF_SUBR_INDEX 33 #define DIF_SUBR_RINDEX 34 #define DIF_SUBR_HTONS 35 #define DIF_SUBR_HTONL 36 #define DIF_SUBR_HTONLL 37 #define DIF_SUBR_NTOHS 38 #define DIF_SUBR_NTOHL 39 #define DIF_SUBR_NTOHLL 40 #define DIF_SUBR_INET_NTOP 41 #define DIF_SUBR_INET_NTOA 42 #define DIF_SUBR_INET_NTOA6 43 #define DIF_SUBR_TOUPPER 44 #define DIF_SUBR_TOLOWER 45 #define DIF_SUBR_MEMREF 46 #define DIF_SUBR_TYPEREF 47 #define DIF_SUBR_SX_SHARED_HELD 48 #define DIF_SUBR_SX_EXCLUSIVE_HELD 49 #define DIF_SUBR_SX_ISEXCLUSIVE 50 #define DIF_SUBR_MEMSTR 51 #define DIF_SUBR_GETF 52 #define DIF_SUBR_JSON 53 #define DIF_SUBR_STRTOLL 54 #define DIF_SUBR_MAX 54 /* max subroutine value */ typedef uint32_t dif_instr_t; #define DIF_INSTR_OP(i) (((i) >> 24) & 0xff) #define DIF_INSTR_R1(i) (((i) >> 16) & 0xff) #define DIF_INSTR_R2(i) (((i) >> 8) & 0xff) #define DIF_INSTR_RD(i) ((i) & 0xff) #define DIF_INSTR_RS(i) ((i) & 0xff) #define DIF_INSTR_LABEL(i) ((i) & 0xffffff) #define DIF_INSTR_VAR(i) (((i) >> 8) & 0xffff) #define DIF_INSTR_INTEGER(i) (((i) >> 8) & 0xffff) #define DIF_INSTR_STRING(i) (((i) >> 8) & 0xffff) #define DIF_INSTR_SUBR(i) (((i) >> 8) & 0xffff) #define DIF_INSTR_TYPE(i) (((i) >> 16) & 0xff) #define DIF_INSTR_XLREF(i) (((i) >> 8) & 0xffff) #define DIF_INSTR_FMT(op, r1, r2, d) \ (((op) << 24) | ((r1) << 16) | ((r2) << 8) | (d)) #define DIF_INSTR_NOT(r1, d) (DIF_INSTR_FMT(DIF_OP_NOT, r1, 0, d)) #define DIF_INSTR_MOV(r1, d) (DIF_INSTR_FMT(DIF_OP_MOV, r1, 0, d)) #define DIF_INSTR_CMP(op, r1, r2) (DIF_INSTR_FMT(op, r1, r2, 0)) #define DIF_INSTR_TST(r1) (DIF_INSTR_FMT(DIF_OP_TST, r1, 0, 0)) #define DIF_INSTR_BRANCH(op, label) (((op) << 24) | (label)) #define DIF_INSTR_LOAD(op, r1, d) (DIF_INSTR_FMT(op, r1, 0, d)) #define DIF_INSTR_STORE(op, r1, d) (DIF_INSTR_FMT(op, r1, 0, d)) #define DIF_INSTR_SETX(i, d) ((DIF_OP_SETX << 24) | ((i) << 8) | (d)) #define DIF_INSTR_SETS(s, d) ((DIF_OP_SETS << 24) | ((s) << 8) | (d)) #define DIF_INSTR_RET(d) (DIF_INSTR_FMT(DIF_OP_RET, 0, 0, d)) #define DIF_INSTR_NOP (DIF_OP_NOP << 24) #define DIF_INSTR_LDA(op, v, r, d) (DIF_INSTR_FMT(op, v, r, d)) #define DIF_INSTR_LDV(op, v, d) (((op) << 24) | ((v) << 8) | (d)) #define DIF_INSTR_STV(op, v, rs) (((op) << 24) | ((v) << 8) | (rs)) #define DIF_INSTR_CALL(s, d) ((DIF_OP_CALL << 24) | ((s) << 8) | (d)) #define DIF_INSTR_PUSHTS(op, t, r2, rs) (DIF_INSTR_FMT(op, t, r2, rs)) #define DIF_INSTR_POPTS (DIF_OP_POPTS << 24) #define DIF_INSTR_FLUSHTS (DIF_OP_FLUSHTS << 24) #define DIF_INSTR_ALLOCS(r1, d) (DIF_INSTR_FMT(DIF_OP_ALLOCS, r1, 0, d)) #define DIF_INSTR_COPYS(r1, r2, d) (DIF_INSTR_FMT(DIF_OP_COPYS, r1, r2, d)) #define DIF_INSTR_XLATE(op, r, d) (((op) << 24) | ((r) << 8) | (d)) #define DIF_REG_R0 0 /* %r0 is always set to zero */ /* * A DTrace Intermediate Format Type (DIF Type) is used to represent the types * of variables, function and associative array arguments, and the return type * for each DIF object (shown below). It contains a description of the type, * its size in bytes, and a module identifier. */ typedef struct dtrace_diftype { uint8_t dtdt_kind; /* type kind (see below) */ uint8_t dtdt_ckind; /* type kind in CTF */ uint8_t dtdt_flags; /* type flags (see below) */ uint8_t dtdt_pad; /* reserved for future use */ uint32_t dtdt_size; /* type size in bytes (unless string) */ } dtrace_diftype_t; #define DIF_TYPE_CTF 0 /* type is a CTF type */ #define DIF_TYPE_STRING 1 /* type is a D string */ #define DIF_TF_BYREF 0x1 /* type is passed by reference */ #define DIF_TF_BYUREF 0x2 /* user type is passed by reference */ /* * A DTrace Intermediate Format variable record is used to describe each of the * variables referenced by a given DIF object. It contains an integer variable * identifier along with variable scope and properties, as shown below. The * size of this structure must be sizeof (int) aligned. */ typedef struct dtrace_difv { uint32_t dtdv_name; /* variable name index in dtdo_strtab */ uint32_t dtdv_id; /* variable reference identifier */ uint8_t dtdv_kind; /* variable kind (see below) */ uint8_t dtdv_scope; /* variable scope (see below) */ uint16_t dtdv_flags; /* variable flags (see below) */ dtrace_diftype_t dtdv_type; /* variable type (see above) */ } dtrace_difv_t; #define DIFV_KIND_ARRAY 0 /* variable is an array of quantities */ #define DIFV_KIND_SCALAR 1 /* variable is a scalar quantity */ #define DIFV_SCOPE_GLOBAL 0 /* variable has global scope */ #define DIFV_SCOPE_THREAD 1 /* variable has thread scope */ #define DIFV_SCOPE_LOCAL 2 /* variable has local scope */ #define DIFV_F_REF 0x1 /* variable is referenced by DIFO */ #define DIFV_F_MOD 0x2 /* variable is written by DIFO */ /* * DTrace Actions * * The upper byte determines the class of the action; the low bytes determines * the specific action within that class. The classes of actions are as * follows: * * [ no class ] <= May record process- or kernel-related data * DTRACEACT_PROC <= Only records process-related data * DTRACEACT_PROC_DESTRUCTIVE <= Potentially destructive to processes * DTRACEACT_KERNEL <= Only records kernel-related data * DTRACEACT_KERNEL_DESTRUCTIVE <= Potentially destructive to the kernel * DTRACEACT_SPECULATIVE <= Speculation-related action * DTRACEACT_AGGREGATION <= Aggregating action */ #define DTRACEACT_NONE 0 /* no action */ #define DTRACEACT_DIFEXPR 1 /* action is DIF expression */ #define DTRACEACT_EXIT 2 /* exit() action */ #define DTRACEACT_PRINTF 3 /* printf() action */ #define DTRACEACT_PRINTA 4 /* printa() action */ #define DTRACEACT_LIBACT 5 /* library-controlled action */ #define DTRACEACT_TRACEMEM 6 /* tracemem() action */ #define DTRACEACT_TRACEMEM_DYNSIZE 7 /* dynamic tracemem() size */ #define DTRACEACT_PRINTM 8 /* printm() action (BSD) */ #define DTRACEACT_PRINTT 9 /* printt() action (BSD) */ #define DTRACEACT_PROC 0x0100 #define DTRACEACT_USTACK (DTRACEACT_PROC + 1) #define DTRACEACT_JSTACK (DTRACEACT_PROC + 2) #define DTRACEACT_USYM (DTRACEACT_PROC + 3) #define DTRACEACT_UMOD (DTRACEACT_PROC + 4) #define DTRACEACT_UADDR (DTRACEACT_PROC + 5) #define DTRACEACT_PROC_DESTRUCTIVE 0x0200 #define DTRACEACT_STOP (DTRACEACT_PROC_DESTRUCTIVE + 1) #define DTRACEACT_RAISE (DTRACEACT_PROC_DESTRUCTIVE + 2) #define DTRACEACT_SYSTEM (DTRACEACT_PROC_DESTRUCTIVE + 3) #define DTRACEACT_FREOPEN (DTRACEACT_PROC_DESTRUCTIVE + 4) #define DTRACEACT_PROC_CONTROL 0x0300 #define DTRACEACT_KERNEL 0x0400 #define DTRACEACT_STACK (DTRACEACT_KERNEL + 1) #define DTRACEACT_SYM (DTRACEACT_KERNEL + 2) #define DTRACEACT_MOD (DTRACEACT_KERNEL + 3) #define DTRACEACT_KERNEL_DESTRUCTIVE 0x0500 #define DTRACEACT_BREAKPOINT (DTRACEACT_KERNEL_DESTRUCTIVE + 1) #define DTRACEACT_PANIC (DTRACEACT_KERNEL_DESTRUCTIVE + 2) #define DTRACEACT_CHILL (DTRACEACT_KERNEL_DESTRUCTIVE + 3) #define DTRACEACT_SPECULATIVE 0x0600 #define DTRACEACT_SPECULATE (DTRACEACT_SPECULATIVE + 1) #define DTRACEACT_COMMIT (DTRACEACT_SPECULATIVE + 2) #define DTRACEACT_DISCARD (DTRACEACT_SPECULATIVE + 3) #define DTRACEACT_CLASS(x) ((x) & 0xff00) #define DTRACEACT_ISDESTRUCTIVE(x) \ (DTRACEACT_CLASS(x) == DTRACEACT_PROC_DESTRUCTIVE || \ DTRACEACT_CLASS(x) == DTRACEACT_KERNEL_DESTRUCTIVE) #define DTRACEACT_ISSPECULATIVE(x) \ (DTRACEACT_CLASS(x) == DTRACEACT_SPECULATIVE) #define DTRACEACT_ISPRINTFLIKE(x) \ ((x) == DTRACEACT_PRINTF || (x) == DTRACEACT_PRINTA || \ (x) == DTRACEACT_SYSTEM || (x) == DTRACEACT_FREOPEN) /* * DTrace Aggregating Actions * * These are functions f(x) for which the following is true: * * f(f(x_0) U f(x_1) U ... U f(x_n)) = f(x_0 U x_1 U ... U x_n) * * where x_n is a set of arbitrary data. Aggregating actions are in their own * DTrace action class, DTTRACEACT_AGGREGATION. The macros provided here allow * for easier processing of the aggregation argument and data payload for a few * aggregating actions (notably: quantize(), lquantize(), and ustack()). */ #define DTRACEACT_AGGREGATION 0x0700 #define DTRACEAGG_COUNT (DTRACEACT_AGGREGATION + 1) #define DTRACEAGG_MIN (DTRACEACT_AGGREGATION + 2) #define DTRACEAGG_MAX (DTRACEACT_AGGREGATION + 3) #define DTRACEAGG_AVG (DTRACEACT_AGGREGATION + 4) #define DTRACEAGG_SUM (DTRACEACT_AGGREGATION + 5) #define DTRACEAGG_STDDEV (DTRACEACT_AGGREGATION + 6) #define DTRACEAGG_QUANTIZE (DTRACEACT_AGGREGATION + 7) #define DTRACEAGG_LQUANTIZE (DTRACEACT_AGGREGATION + 8) #define DTRACEAGG_LLQUANTIZE (DTRACEACT_AGGREGATION + 9) #define DTRACEACT_ISAGG(x) \ (DTRACEACT_CLASS(x) == DTRACEACT_AGGREGATION) #define DTRACE_QUANTIZE_NBUCKETS \ (((sizeof (uint64_t) * NBBY) - 1) * 2 + 1) #define DTRACE_QUANTIZE_ZEROBUCKET ((sizeof (uint64_t) * NBBY) - 1) #define DTRACE_QUANTIZE_BUCKETVAL(buck) \ (int64_t)((buck) < DTRACE_QUANTIZE_ZEROBUCKET ? \ -(1LL << (DTRACE_QUANTIZE_ZEROBUCKET - 1 - (buck))) : \ (buck) == DTRACE_QUANTIZE_ZEROBUCKET ? 0 : \ 1LL << ((buck) - DTRACE_QUANTIZE_ZEROBUCKET - 1)) #define DTRACE_LQUANTIZE_STEPSHIFT 48 #define DTRACE_LQUANTIZE_STEPMASK ((uint64_t)UINT16_MAX << 48) #define DTRACE_LQUANTIZE_LEVELSHIFT 32 #define DTRACE_LQUANTIZE_LEVELMASK ((uint64_t)UINT16_MAX << 32) #define DTRACE_LQUANTIZE_BASESHIFT 0 #define DTRACE_LQUANTIZE_BASEMASK UINT32_MAX #define DTRACE_LQUANTIZE_STEP(x) \ (uint16_t)(((x) & DTRACE_LQUANTIZE_STEPMASK) >> \ DTRACE_LQUANTIZE_STEPSHIFT) #define DTRACE_LQUANTIZE_LEVELS(x) \ (uint16_t)(((x) & DTRACE_LQUANTIZE_LEVELMASK) >> \ DTRACE_LQUANTIZE_LEVELSHIFT) #define DTRACE_LQUANTIZE_BASE(x) \ (int32_t)(((x) & DTRACE_LQUANTIZE_BASEMASK) >> \ DTRACE_LQUANTIZE_BASESHIFT) #define DTRACE_LLQUANTIZE_FACTORSHIFT 48 #define DTRACE_LLQUANTIZE_FACTORMASK ((uint64_t)UINT16_MAX << 48) #define DTRACE_LLQUANTIZE_LOWSHIFT 32 #define DTRACE_LLQUANTIZE_LOWMASK ((uint64_t)UINT16_MAX << 32) #define DTRACE_LLQUANTIZE_HIGHSHIFT 16 #define DTRACE_LLQUANTIZE_HIGHMASK ((uint64_t)UINT16_MAX << 16) #define DTRACE_LLQUANTIZE_NSTEPSHIFT 0 #define DTRACE_LLQUANTIZE_NSTEPMASK UINT16_MAX #define DTRACE_LLQUANTIZE_FACTOR(x) \ (uint16_t)(((x) & DTRACE_LLQUANTIZE_FACTORMASK) >> \ DTRACE_LLQUANTIZE_FACTORSHIFT) #define DTRACE_LLQUANTIZE_LOW(x) \ (uint16_t)(((x) & DTRACE_LLQUANTIZE_LOWMASK) >> \ DTRACE_LLQUANTIZE_LOWSHIFT) #define DTRACE_LLQUANTIZE_HIGH(x) \ (uint16_t)(((x) & DTRACE_LLQUANTIZE_HIGHMASK) >> \ DTRACE_LLQUANTIZE_HIGHSHIFT) #define DTRACE_LLQUANTIZE_NSTEP(x) \ (uint16_t)(((x) & DTRACE_LLQUANTIZE_NSTEPMASK) >> \ DTRACE_LLQUANTIZE_NSTEPSHIFT) #define DTRACE_USTACK_NFRAMES(x) (uint32_t)((x) & UINT32_MAX) #define DTRACE_USTACK_STRSIZE(x) (uint32_t)((x) >> 32) #define DTRACE_USTACK_ARG(x, y) \ ((((uint64_t)(y)) << 32) | ((x) & UINT32_MAX)) #ifndef _LP64 #if BYTE_ORDER == _BIG_ENDIAN #define DTRACE_PTR(type, name) uint32_t name##pad; type *name #else #define DTRACE_PTR(type, name) type *name; uint32_t name##pad #endif #else #define DTRACE_PTR(type, name) type *name #endif /* * DTrace Object Format (DOF) * * DTrace programs can be persistently encoded in the DOF format so that they * may be embedded in other programs (for example, in an ELF file) or in the * dtrace driver configuration file for use in anonymous tracing. The DOF * format is versioned and extensible so that it can be revised and so that * internal data structures can be modified or extended compatibly. All DOF * structures use fixed-size types, so the 32-bit and 64-bit representations * are identical and consumers can use either data model transparently. * * The file layout is structured as follows: * * +---------------+-------------------+----- ... ----+---- ... ------+ * | dof_hdr_t | dof_sec_t[ ... ] | loadable | non-loadable | * | (file header) | (section headers) | section data | section data | * +---------------+-------------------+----- ... ----+---- ... ------+ * |<------------ dof_hdr.dofh_loadsz --------------->| | * |<------------ dof_hdr.dofh_filesz ------------------------------->| * * The file header stores meta-data including a magic number, data model for * the instrumentation, data encoding, and properties of the DIF code within. * The header describes its own size and the size of the section headers. By * convention, an array of section headers follows the file header, and then * the data for all loadable sections and unloadable sections. This permits * consumer code to easily download the headers and all loadable data into the * DTrace driver in one contiguous chunk, omitting other extraneous sections. * * The section headers describe the size, offset, alignment, and section type * for each section. Sections are described using a set of #defines that tell * the consumer what kind of data is expected. Sections can contain links to * other sections by storing a dof_secidx_t, an index into the section header * array, inside of the section data structures. The section header includes * an entry size so that sections with data arrays can grow their structures. * * The DOF data itself can contain many snippets of DIF (i.e. >1 DIFOs), which * are represented themselves as a collection of related DOF sections. This * permits us to change the set of sections associated with a DIFO over time, * and also permits us to encode DIFOs that contain different sets of sections. * When a DOF section wants to refer to a DIFO, it stores the dof_secidx_t of a * section of type DOF_SECT_DIFOHDR. This section's data is then an array of * dof_secidx_t's which in turn denote the sections associated with this DIFO. * * This loose coupling of the file structure (header and sections) to the * structure of the DTrace program itself (ECB descriptions, action * descriptions, and DIFOs) permits activities such as relocation processing * to occur in a single pass without having to understand D program structure. * * Finally, strings are always stored in ELF-style string tables along with a * string table section index and string table offset. Therefore strings in * DOF are always arbitrary-length and not bound to the current implementation. */ #define DOF_ID_SIZE 16 /* total size of dofh_ident[] in bytes */ typedef struct dof_hdr { uint8_t dofh_ident[DOF_ID_SIZE]; /* identification bytes (see below) */ uint32_t dofh_flags; /* file attribute flags (if any) */ uint32_t dofh_hdrsize; /* size of file header in bytes */ uint32_t dofh_secsize; /* size of section header in bytes */ uint32_t dofh_secnum; /* number of section headers */ uint64_t dofh_secoff; /* file offset of section headers */ uint64_t dofh_loadsz; /* file size of loadable portion */ uint64_t dofh_filesz; /* file size of entire DOF file */ uint64_t dofh_pad; /* reserved for future use */ } dof_hdr_t; #define DOF_ID_MAG0 0 /* first byte of magic number */ #define DOF_ID_MAG1 1 /* second byte of magic number */ #define DOF_ID_MAG2 2 /* third byte of magic number */ #define DOF_ID_MAG3 3 /* fourth byte of magic number */ #define DOF_ID_MODEL 4 /* DOF data model (see below) */ #define DOF_ID_ENCODING 5 /* DOF data encoding (see below) */ #define DOF_ID_VERSION 6 /* DOF file format major version (see below) */ #define DOF_ID_DIFVERS 7 /* DIF instruction set version */ #define DOF_ID_DIFIREG 8 /* DIF integer registers used by compiler */ #define DOF_ID_DIFTREG 9 /* DIF tuple registers used by compiler */ #define DOF_ID_PAD 10 /* start of padding bytes (all zeroes) */ #define DOF_MAG_MAG0 0x7F /* DOF_ID_MAG[0-3] */ #define DOF_MAG_MAG1 'D' #define DOF_MAG_MAG2 'O' #define DOF_MAG_MAG3 'F' #define DOF_MAG_STRING "\177DOF" #define DOF_MAG_STRLEN 4 #define DOF_MODEL_NONE 0 /* DOF_ID_MODEL */ #define DOF_MODEL_ILP32 1 #define DOF_MODEL_LP64 2 #ifdef _LP64 #define DOF_MODEL_NATIVE DOF_MODEL_LP64 #else #define DOF_MODEL_NATIVE DOF_MODEL_ILP32 #endif #define DOF_ENCODE_NONE 0 /* DOF_ID_ENCODING */ #define DOF_ENCODE_LSB 1 #define DOF_ENCODE_MSB 2 #if BYTE_ORDER == _BIG_ENDIAN #define DOF_ENCODE_NATIVE DOF_ENCODE_MSB #else #define DOF_ENCODE_NATIVE DOF_ENCODE_LSB #endif #define DOF_VERSION_1 1 /* DOF version 1: Solaris 10 FCS */ #define DOF_VERSION_2 2 /* DOF version 2: Solaris Express 6/06 */ #define DOF_VERSION DOF_VERSION_2 /* Latest DOF version */ #define DOF_FL_VALID 0 /* mask of all valid dofh_flags bits */ typedef uint32_t dof_secidx_t; /* section header table index type */ typedef uint32_t dof_stridx_t; /* string table index type */ #define DOF_SECIDX_NONE (-1U) /* null value for section indices */ #define DOF_STRIDX_NONE (-1U) /* null value for string indices */ typedef struct dof_sec { uint32_t dofs_type; /* section type (see below) */ uint32_t dofs_align; /* section data memory alignment */ uint32_t dofs_flags; /* section flags (if any) */ uint32_t dofs_entsize; /* size of section entry (if table) */ uint64_t dofs_offset; /* offset of section data within file */ uint64_t dofs_size; /* size of section data in bytes */ } dof_sec_t; #define DOF_SECT_NONE 0 /* null section */ #define DOF_SECT_COMMENTS 1 /* compiler comments */ #define DOF_SECT_SOURCE 2 /* D program source code */ #define DOF_SECT_ECBDESC 3 /* dof_ecbdesc_t */ #define DOF_SECT_PROBEDESC 4 /* dof_probedesc_t */ #define DOF_SECT_ACTDESC 5 /* dof_actdesc_t array */ #define DOF_SECT_DIFOHDR 6 /* dof_difohdr_t (variable length) */ #define DOF_SECT_DIF 7 /* uint32_t array of byte code */ #define DOF_SECT_STRTAB 8 /* string table */ #define DOF_SECT_VARTAB 9 /* dtrace_difv_t array */ #define DOF_SECT_RELTAB 10 /* dof_relodesc_t array */ #define DOF_SECT_TYPTAB 11 /* dtrace_diftype_t array */ #define DOF_SECT_URELHDR 12 /* dof_relohdr_t (user relocations) */ #define DOF_SECT_KRELHDR 13 /* dof_relohdr_t (kernel relocations) */ #define DOF_SECT_OPTDESC 14 /* dof_optdesc_t array */ #define DOF_SECT_PROVIDER 15 /* dof_provider_t */ #define DOF_SECT_PROBES 16 /* dof_probe_t array */ #define DOF_SECT_PRARGS 17 /* uint8_t array (probe arg mappings) */ #define DOF_SECT_PROFFS 18 /* uint32_t array (probe arg offsets) */ #define DOF_SECT_INTTAB 19 /* uint64_t array */ #define DOF_SECT_UTSNAME 20 /* struct utsname */ #define DOF_SECT_XLTAB 21 /* dof_xlref_t array */ #define DOF_SECT_XLMEMBERS 22 /* dof_xlmember_t array */ #define DOF_SECT_XLIMPORT 23 /* dof_xlator_t */ #define DOF_SECT_XLEXPORT 24 /* dof_xlator_t */ #define DOF_SECT_PREXPORT 25 /* dof_secidx_t array (exported objs) */ #define DOF_SECT_PRENOFFS 26 /* uint32_t array (enabled offsets) */ #define DOF_SECF_LOAD 1 /* section should be loaded */ #define DOF_SEC_ISLOADABLE(x) \ (((x) == DOF_SECT_ECBDESC) || ((x) == DOF_SECT_PROBEDESC) || \ ((x) == DOF_SECT_ACTDESC) || ((x) == DOF_SECT_DIFOHDR) || \ ((x) == DOF_SECT_DIF) || ((x) == DOF_SECT_STRTAB) || \ ((x) == DOF_SECT_VARTAB) || ((x) == DOF_SECT_RELTAB) || \ ((x) == DOF_SECT_TYPTAB) || ((x) == DOF_SECT_URELHDR) || \ ((x) == DOF_SECT_KRELHDR) || ((x) == DOF_SECT_OPTDESC) || \ ((x) == DOF_SECT_PROVIDER) || ((x) == DOF_SECT_PROBES) || \ ((x) == DOF_SECT_PRARGS) || ((x) == DOF_SECT_PROFFS) || \ ((x) == DOF_SECT_INTTAB) || ((x) == DOF_SECT_XLTAB) || \ ((x) == DOF_SECT_XLMEMBERS) || ((x) == DOF_SECT_XLIMPORT) || \ ((x) == DOF_SECT_XLIMPORT) || ((x) == DOF_SECT_XLEXPORT) || \ ((x) == DOF_SECT_PREXPORT) || ((x) == DOF_SECT_PRENOFFS)) typedef struct dof_ecbdesc { dof_secidx_t dofe_probes; /* link to DOF_SECT_PROBEDESC */ dof_secidx_t dofe_pred; /* link to DOF_SECT_DIFOHDR */ dof_secidx_t dofe_actions; /* link to DOF_SECT_ACTDESC */ uint32_t dofe_pad; /* reserved for future use */ uint64_t dofe_uarg; /* user-supplied library argument */ } dof_ecbdesc_t; typedef struct dof_probedesc { dof_secidx_t dofp_strtab; /* link to DOF_SECT_STRTAB section */ dof_stridx_t dofp_provider; /* provider string */ dof_stridx_t dofp_mod; /* module string */ dof_stridx_t dofp_func; /* function string */ dof_stridx_t dofp_name; /* name string */ uint32_t dofp_id; /* probe identifier (or zero) */ } dof_probedesc_t; typedef struct dof_actdesc { dof_secidx_t dofa_difo; /* link to DOF_SECT_DIFOHDR */ dof_secidx_t dofa_strtab; /* link to DOF_SECT_STRTAB section */ uint32_t dofa_kind; /* action kind (DTRACEACT_* constant) */ uint32_t dofa_ntuple; /* number of subsequent tuple actions */ uint64_t dofa_arg; /* kind-specific argument */ uint64_t dofa_uarg; /* user-supplied argument */ } dof_actdesc_t; typedef struct dof_difohdr { dtrace_diftype_t dofd_rtype; /* return type for this fragment */ dof_secidx_t dofd_links[1]; /* variable length array of indices */ } dof_difohdr_t; typedef struct dof_relohdr { dof_secidx_t dofr_strtab; /* link to DOF_SECT_STRTAB for names */ dof_secidx_t dofr_relsec; /* link to DOF_SECT_RELTAB for relos */ dof_secidx_t dofr_tgtsec; /* link to section we are relocating */ } dof_relohdr_t; typedef struct dof_relodesc { dof_stridx_t dofr_name; /* string name of relocation symbol */ uint32_t dofr_type; /* relo type (DOF_RELO_* constant) */ uint64_t dofr_offset; /* byte offset for relocation */ uint64_t dofr_data; /* additional type-specific data */ } dof_relodesc_t; #define DOF_RELO_NONE 0 /* empty relocation entry */ #define DOF_RELO_SETX 1 /* relocate setx value */ typedef struct dof_optdesc { uint32_t dofo_option; /* option identifier */ dof_secidx_t dofo_strtab; /* string table, if string option */ uint64_t dofo_value; /* option value or string index */ } dof_optdesc_t; typedef uint32_t dof_attr_t; /* encoded stability attributes */ #define DOF_ATTR(n, d, c) (((n) << 24) | ((d) << 16) | ((c) << 8)) #define DOF_ATTR_NAME(a) (((a) >> 24) & 0xff) #define DOF_ATTR_DATA(a) (((a) >> 16) & 0xff) #define DOF_ATTR_CLASS(a) (((a) >> 8) & 0xff) typedef struct dof_provider { dof_secidx_t dofpv_strtab; /* link to DOF_SECT_STRTAB section */ dof_secidx_t dofpv_probes; /* link to DOF_SECT_PROBES section */ dof_secidx_t dofpv_prargs; /* link to DOF_SECT_PRARGS section */ dof_secidx_t dofpv_proffs; /* link to DOF_SECT_PROFFS section */ dof_stridx_t dofpv_name; /* provider name string */ dof_attr_t dofpv_provattr; /* provider attributes */ dof_attr_t dofpv_modattr; /* module attributes */ dof_attr_t dofpv_funcattr; /* function attributes */ dof_attr_t dofpv_nameattr; /* name attributes */ dof_attr_t dofpv_argsattr; /* args attributes */ dof_secidx_t dofpv_prenoffs; /* link to DOF_SECT_PRENOFFS section */ } dof_provider_t; typedef struct dof_probe { uint64_t dofpr_addr; /* probe base address or offset */ dof_stridx_t dofpr_func; /* probe function string */ dof_stridx_t dofpr_name; /* probe name string */ dof_stridx_t dofpr_nargv; /* native argument type strings */ dof_stridx_t dofpr_xargv; /* translated argument type strings */ uint32_t dofpr_argidx; /* index of first argument mapping */ uint32_t dofpr_offidx; /* index of first offset entry */ uint8_t dofpr_nargc; /* native argument count */ uint8_t dofpr_xargc; /* translated argument count */ uint16_t dofpr_noffs; /* number of offset entries for probe */ uint32_t dofpr_enoffidx; /* index of first is-enabled offset */ uint16_t dofpr_nenoffs; /* number of is-enabled offsets */ uint16_t dofpr_pad1; /* reserved for future use */ uint32_t dofpr_pad2; /* reserved for future use */ } dof_probe_t; typedef struct dof_xlator { dof_secidx_t dofxl_members; /* link to DOF_SECT_XLMEMBERS section */ dof_secidx_t dofxl_strtab; /* link to DOF_SECT_STRTAB section */ dof_stridx_t dofxl_argv; /* input parameter type strings */ uint32_t dofxl_argc; /* input parameter list length */ dof_stridx_t dofxl_type; /* output type string name */ dof_attr_t dofxl_attr; /* output stability attributes */ } dof_xlator_t; typedef struct dof_xlmember { dof_secidx_t dofxm_difo; /* member link to DOF_SECT_DIFOHDR */ dof_stridx_t dofxm_name; /* member name */ dtrace_diftype_t dofxm_type; /* member type */ } dof_xlmember_t; typedef struct dof_xlref { dof_secidx_t dofxr_xlator; /* link to DOF_SECT_XLATORS section */ uint32_t dofxr_member; /* index of referenced dof_xlmember */ uint32_t dofxr_argn; /* index of argument for DIF_OP_XLARG */ } dof_xlref_t; /* * DTrace Intermediate Format Object (DIFO) * * A DIFO is used to store the compiled DIF for a D expression, its return * type, and its string and variable tables. The string table is a single * buffer of character data into which sets instructions and variable * references can reference strings using a byte offset. The variable table * is an array of dtrace_difv_t structures that describe the name and type of * each variable and the id used in the DIF code. This structure is described * above in the DIF section of this header file. The DIFO is used at both * user-level (in the library) and in the kernel, but the structure is never * passed between the two: the DOF structures form the only interface. As a * result, the definition can change depending on the presence of _KERNEL. */ typedef struct dtrace_difo { dif_instr_t *dtdo_buf; /* instruction buffer */ uint64_t *dtdo_inttab; /* integer table (optional) */ char *dtdo_strtab; /* string table (optional) */ dtrace_difv_t *dtdo_vartab; /* variable table (optional) */ uint_t dtdo_len; /* length of instruction buffer */ uint_t dtdo_intlen; /* length of integer table */ uint_t dtdo_strlen; /* length of string table */ uint_t dtdo_varlen; /* length of variable table */ dtrace_diftype_t dtdo_rtype; /* return type */ uint_t dtdo_refcnt; /* owner reference count */ uint_t dtdo_destructive; /* invokes destructive subroutines */ #ifndef _KERNEL dof_relodesc_t *dtdo_kreltab; /* kernel relocations */ dof_relodesc_t *dtdo_ureltab; /* user relocations */ struct dt_node **dtdo_xlmtab; /* translator references */ uint_t dtdo_krelen; /* length of krelo table */ uint_t dtdo_urelen; /* length of urelo table */ uint_t dtdo_xlmlen; /* length of translator table */ #endif } dtrace_difo_t; /* * DTrace Enabling Description Structures * * When DTrace is tracking the description of a DTrace enabling entity (probe, * predicate, action, ECB, record, etc.), it does so in a description * structure. These structures all end in "desc", and are used at both * user-level and in the kernel -- but (with the exception of * dtrace_probedesc_t) they are never passed between them. Typically, * user-level will use the description structures when assembling an enabling. * It will then distill those description structures into a DOF object (see * above), and send it into the kernel. The kernel will again use the * description structures to create a description of the enabling as it reads * the DOF. When the description is complete, the enabling will be actually * created -- turning it into the structures that represent the enabling * instead of merely describing it. Not surprisingly, the description * structures bear a strong resemblance to the DOF structures that act as their * conduit. */ struct dtrace_predicate; typedef struct dtrace_probedesc { dtrace_id_t dtpd_id; /* probe identifier */ char dtpd_provider[DTRACE_PROVNAMELEN]; /* probe provider name */ char dtpd_mod[DTRACE_MODNAMELEN]; /* probe module name */ char dtpd_func[DTRACE_FUNCNAMELEN]; /* probe function name */ char dtpd_name[DTRACE_NAMELEN]; /* probe name */ } dtrace_probedesc_t; typedef struct dtrace_repldesc { dtrace_probedesc_t dtrpd_match; /* probe descr. to match */ dtrace_probedesc_t dtrpd_create; /* probe descr. to create */ } dtrace_repldesc_t; typedef struct dtrace_preddesc { dtrace_difo_t *dtpdd_difo; /* pointer to DIF object */ struct dtrace_predicate *dtpdd_predicate; /* pointer to predicate */ } dtrace_preddesc_t; typedef struct dtrace_actdesc { dtrace_difo_t *dtad_difo; /* pointer to DIF object */ struct dtrace_actdesc *dtad_next; /* next action */ dtrace_actkind_t dtad_kind; /* kind of action */ uint32_t dtad_ntuple; /* number in tuple */ uint64_t dtad_arg; /* action argument */ uint64_t dtad_uarg; /* user argument */ int dtad_refcnt; /* reference count */ } dtrace_actdesc_t; typedef struct dtrace_ecbdesc { dtrace_actdesc_t *dted_action; /* action description(s) */ dtrace_preddesc_t dted_pred; /* predicate description */ dtrace_probedesc_t dted_probe; /* probe description */ uint64_t dted_uarg; /* library argument */ int dted_refcnt; /* reference count */ } dtrace_ecbdesc_t; /* * DTrace Metadata Description Structures * * DTrace separates the trace data stream from the metadata stream. The only * metadata tokens placed in the data stream are the dtrace_rechdr_t (EPID + * timestamp) or (in the case of aggregations) aggregation identifiers. To * determine the structure of the data, DTrace consumers pass the token to the * kernel, and receive in return a corresponding description of the enabled * probe (via the dtrace_eprobedesc structure) or the aggregation (via the * dtrace_aggdesc structure). Both of these structures are expressed in terms * of record descriptions (via the dtrace_recdesc structure) that describe the * exact structure of the data. Some record descriptions may also contain a * format identifier; this additional bit of metadata can be retrieved from the * kernel, for which a format description is returned via the dtrace_fmtdesc * structure. Note that all four of these structures must be bitness-neutral * to allow for a 32-bit DTrace consumer on a 64-bit kernel. */ typedef struct dtrace_recdesc { dtrace_actkind_t dtrd_action; /* kind of action */ uint32_t dtrd_size; /* size of record */ uint32_t dtrd_offset; /* offset in ECB's data */ uint16_t dtrd_alignment; /* required alignment */ uint16_t dtrd_format; /* format, if any */ uint64_t dtrd_arg; /* action argument */ uint64_t dtrd_uarg; /* user argument */ } dtrace_recdesc_t; typedef struct dtrace_eprobedesc { dtrace_epid_t dtepd_epid; /* enabled probe ID */ dtrace_id_t dtepd_probeid; /* probe ID */ uint64_t dtepd_uarg; /* library argument */ uint32_t dtepd_size; /* total size */ int dtepd_nrecs; /* number of records */ dtrace_recdesc_t dtepd_rec[1]; /* records themselves */ } dtrace_eprobedesc_t; typedef struct dtrace_aggdesc { DTRACE_PTR(char, dtagd_name); /* not filled in by kernel */ dtrace_aggvarid_t dtagd_varid; /* not filled in by kernel */ int dtagd_flags; /* not filled in by kernel */ dtrace_aggid_t dtagd_id; /* aggregation ID */ dtrace_epid_t dtagd_epid; /* enabled probe ID */ uint32_t dtagd_size; /* size in bytes */ int dtagd_nrecs; /* number of records */ uint32_t dtagd_pad; /* explicit padding */ dtrace_recdesc_t dtagd_rec[1]; /* record descriptions */ } dtrace_aggdesc_t; typedef struct dtrace_fmtdesc { DTRACE_PTR(char, dtfd_string); /* format string */ int dtfd_length; /* length of format string */ uint16_t dtfd_format; /* format identifier */ } dtrace_fmtdesc_t; #define DTRACE_SIZEOF_EPROBEDESC(desc) \ (sizeof (dtrace_eprobedesc_t) + ((desc)->dtepd_nrecs ? \ (((desc)->dtepd_nrecs - 1) * sizeof (dtrace_recdesc_t)) : 0)) #define DTRACE_SIZEOF_AGGDESC(desc) \ (sizeof (dtrace_aggdesc_t) + ((desc)->dtagd_nrecs ? \ (((desc)->dtagd_nrecs - 1) * sizeof (dtrace_recdesc_t)) : 0)) /* * DTrace Option Interface * * Run-time DTrace options are set and retrieved via DOF_SECT_OPTDESC sections * in a DOF image. The dof_optdesc structure contains an option identifier and * an option value. The valid option identifiers are found below; the mapping * between option identifiers and option identifying strings is maintained at * user-level. Note that the value of DTRACEOPT_UNSET is such that all of the * following are potentially valid option values: all positive integers, zero * and negative one. Some options (notably "bufpolicy" and "bufresize") take * predefined tokens as their values; these are defined with * DTRACEOPT_{option}_{token}. */ #define DTRACEOPT_BUFSIZE 0 /* buffer size */ #define DTRACEOPT_BUFPOLICY 1 /* buffer policy */ #define DTRACEOPT_DYNVARSIZE 2 /* dynamic variable size */ #define DTRACEOPT_AGGSIZE 3 /* aggregation size */ #define DTRACEOPT_SPECSIZE 4 /* speculation size */ #define DTRACEOPT_NSPEC 5 /* number of speculations */ #define DTRACEOPT_STRSIZE 6 /* string size */ #define DTRACEOPT_CLEANRATE 7 /* dynvar cleaning rate */ #define DTRACEOPT_CPU 8 /* CPU to trace */ #define DTRACEOPT_BUFRESIZE 9 /* buffer resizing policy */ #define DTRACEOPT_GRABANON 10 /* grab anonymous state, if any */ #define DTRACEOPT_FLOWINDENT 11 /* indent function entry/return */ #define DTRACEOPT_QUIET 12 /* only output explicitly traced data */ #define DTRACEOPT_STACKFRAMES 13 /* number of stack frames */ #define DTRACEOPT_USTACKFRAMES 14 /* number of user stack frames */ #define DTRACEOPT_AGGRATE 15 /* aggregation snapshot rate */ #define DTRACEOPT_SWITCHRATE 16 /* buffer switching rate */ #define DTRACEOPT_STATUSRATE 17 /* status rate */ #define DTRACEOPT_DESTRUCTIVE 18 /* destructive actions allowed */ #define DTRACEOPT_STACKINDENT 19 /* output indent for stack traces */ #define DTRACEOPT_RAWBYTES 20 /* always print bytes in raw form */ #define DTRACEOPT_JSTACKFRAMES 21 /* number of jstack() frames */ #define DTRACEOPT_JSTACKSTRSIZE 22 /* size of jstack() string table */ #define DTRACEOPT_AGGSORTKEY 23 /* sort aggregations by key */ #define DTRACEOPT_AGGSORTREV 24 /* reverse-sort aggregations */ #define DTRACEOPT_AGGSORTPOS 25 /* agg. position to sort on */ #define DTRACEOPT_AGGSORTKEYPOS 26 /* agg. key position to sort on */ #define DTRACEOPT_TEMPORAL 27 /* temporally ordered output */ #define DTRACEOPT_AGGHIST 28 /* histogram aggregation output */ #define DTRACEOPT_AGGPACK 29 /* packed aggregation output */ #define DTRACEOPT_AGGZOOM 30 /* zoomed aggregation scaling */ #define DTRACEOPT_ZONE 31 /* zone in which to enable probes */ #define DTRACEOPT_MAX 32 /* number of options */ #define DTRACEOPT_UNSET (dtrace_optval_t)-2 /* unset option */ #define DTRACEOPT_BUFPOLICY_RING 0 /* ring buffer */ #define DTRACEOPT_BUFPOLICY_FILL 1 /* fill buffer, then stop */ #define DTRACEOPT_BUFPOLICY_SWITCH 2 /* switch buffers */ #define DTRACEOPT_BUFRESIZE_AUTO 0 /* automatic resizing */ #define DTRACEOPT_BUFRESIZE_MANUAL 1 /* manual resizing */ /* * DTrace Buffer Interface * * In order to get a snapshot of the principal or aggregation buffer, * user-level passes a buffer description to the kernel with the dtrace_bufdesc * structure. This describes which CPU user-level is interested in, and * where user-level wishes the kernel to snapshot the buffer to (the * dtbd_data field). The kernel uses the same structure to pass back some * information regarding the buffer: the size of data actually copied out, the * number of drops, the number of errors, the offset of the oldest record, * and the time of the snapshot. * * If the buffer policy is a "switch" policy, taking a snapshot of the * principal buffer has the additional effect of switching the active and * inactive buffers. Taking a snapshot of the aggregation buffer _always_ has * the additional effect of switching the active and inactive buffers. */ typedef struct dtrace_bufdesc { uint64_t dtbd_size; /* size of buffer */ uint32_t dtbd_cpu; /* CPU or DTRACE_CPUALL */ uint32_t dtbd_errors; /* number of errors */ uint64_t dtbd_drops; /* number of drops */ DTRACE_PTR(char, dtbd_data); /* data */ uint64_t dtbd_oldest; /* offset of oldest record */ uint64_t dtbd_timestamp; /* hrtime of snapshot */ } dtrace_bufdesc_t; /* * Each record in the buffer (dtbd_data) begins with a header that includes * the epid and a timestamp. The timestamp is split into two 4-byte parts * so that we do not require 8-byte alignment. */ typedef struct dtrace_rechdr { dtrace_epid_t dtrh_epid; /* enabled probe id */ uint32_t dtrh_timestamp_hi; /* high bits of hrtime_t */ uint32_t dtrh_timestamp_lo; /* low bits of hrtime_t */ } dtrace_rechdr_t; #define DTRACE_RECORD_LOAD_TIMESTAMP(dtrh) \ ((dtrh)->dtrh_timestamp_lo + \ ((uint64_t)(dtrh)->dtrh_timestamp_hi << 32)) #define DTRACE_RECORD_STORE_TIMESTAMP(dtrh, hrtime) { \ (dtrh)->dtrh_timestamp_lo = (uint32_t)hrtime; \ (dtrh)->dtrh_timestamp_hi = hrtime >> 32; \ } /* * DTrace Status * * The status of DTrace is relayed via the dtrace_status structure. This * structure contains members to count drops other than the capacity drops * available via the buffer interface (see above). This consists of dynamic * drops (including capacity dynamic drops, rinsing drops and dirty drops), and * speculative drops (including capacity speculative drops, drops due to busy * speculative buffers and drops due to unavailable speculative buffers). * Additionally, the status structure contains a field to indicate the number * of "fill"-policy buffers have been filled and a boolean field to indicate * that exit() has been called. If the dtst_exiting field is non-zero, no * further data will be generated until tracing is stopped (at which time any * enablings of the END action will be processed); if user-level sees that * this field is non-zero, tracing should be stopped as soon as possible. */ typedef struct dtrace_status { uint64_t dtst_dyndrops; /* dynamic drops */ uint64_t dtst_dyndrops_rinsing; /* dyn drops due to rinsing */ uint64_t dtst_dyndrops_dirty; /* dyn drops due to dirty */ uint64_t dtst_specdrops; /* speculative drops */ uint64_t dtst_specdrops_busy; /* spec drops due to busy */ uint64_t dtst_specdrops_unavail; /* spec drops due to unavail */ uint64_t dtst_errors; /* total errors */ uint64_t dtst_filled; /* number of filled bufs */ uint64_t dtst_stkstroverflows; /* stack string tab overflows */ uint64_t dtst_dblerrors; /* errors in ERROR probes */ char dtst_killed; /* non-zero if killed */ char dtst_exiting; /* non-zero if exit() called */ char dtst_pad[6]; /* pad out to 64-bit align */ } dtrace_status_t; /* * DTrace Configuration * * User-level may need to understand some elements of the kernel DTrace * configuration in order to generate correct DIF. This information is * conveyed via the dtrace_conf structure. */ typedef struct dtrace_conf { uint_t dtc_difversion; /* supported DIF version */ uint_t dtc_difintregs; /* # of DIF integer registers */ uint_t dtc_diftupregs; /* # of DIF tuple registers */ uint_t dtc_ctfmodel; /* CTF data model */ uint_t dtc_pad[8]; /* reserved for future use */ } dtrace_conf_t; /* * DTrace Faults * * The constants below DTRACEFLT_LIBRARY indicate probe processing faults; * constants at or above DTRACEFLT_LIBRARY indicate faults in probe * postprocessing at user-level. Probe processing faults induce an ERROR * probe and are replicated in unistd.d to allow users' ERROR probes to decode * the error condition using thse symbolic labels. */ #define DTRACEFLT_UNKNOWN 0 /* Unknown fault */ #define DTRACEFLT_BADADDR 1 /* Bad address */ #define DTRACEFLT_BADALIGN 2 /* Bad alignment */ #define DTRACEFLT_ILLOP 3 /* Illegal operation */ #define DTRACEFLT_DIVZERO 4 /* Divide-by-zero */ #define DTRACEFLT_NOSCRATCH 5 /* Out of scratch space */ #define DTRACEFLT_KPRIV 6 /* Illegal kernel access */ #define DTRACEFLT_UPRIV 7 /* Illegal user access */ #define DTRACEFLT_TUPOFLOW 8 /* Tuple stack overflow */ #define DTRACEFLT_BADSTACK 9 /* Bad stack */ #define DTRACEFLT_LIBRARY 1000 /* Library-level fault */ /* * DTrace Argument Types * * Because it would waste both space and time, argument types do not reside * with the probe. In order to determine argument types for args[X] * variables, the D compiler queries for argument types on a probe-by-probe * basis. (This optimizes for the common case that arguments are either not * used or used in an untyped fashion.) Typed arguments are specified with a * string of the type name in the dtragd_native member of the argument * description structure. Typed arguments may be further translated to types * of greater stability; the provider indicates such a translated argument by * filling in the dtargd_xlate member with the string of the translated type. * Finally, the provider may indicate which argument value a given argument * maps to by setting the dtargd_mapping member -- allowing a single argument * to map to multiple args[X] variables. */ typedef struct dtrace_argdesc { dtrace_id_t dtargd_id; /* probe identifier */ int dtargd_ndx; /* arg number (-1 iff none) */ int dtargd_mapping; /* value mapping */ char dtargd_native[DTRACE_ARGTYPELEN]; /* native type name */ char dtargd_xlate[DTRACE_ARGTYPELEN]; /* translated type name */ } dtrace_argdesc_t; /* * DTrace Stability Attributes * * Each DTrace provider advertises the name and data stability of each of its * probe description components, as well as its architectural dependencies. * The D compiler can query the provider attributes (dtrace_pattr_t below) in * order to compute the properties of an input program and report them. */ typedef uint8_t dtrace_stability_t; /* stability code (see attributes(5)) */ typedef uint8_t dtrace_class_t; /* architectural dependency class */ #define DTRACE_STABILITY_INTERNAL 0 /* private to DTrace itself */ #define DTRACE_STABILITY_PRIVATE 1 /* private to Sun (see docs) */ #define DTRACE_STABILITY_OBSOLETE 2 /* scheduled for removal */ #define DTRACE_STABILITY_EXTERNAL 3 /* not controlled by Sun */ #define DTRACE_STABILITY_UNSTABLE 4 /* new or rapidly changing */ #define DTRACE_STABILITY_EVOLVING 5 /* less rapidly changing */ #define DTRACE_STABILITY_STABLE 6 /* mature interface from Sun */ #define DTRACE_STABILITY_STANDARD 7 /* industry standard */ #define DTRACE_STABILITY_MAX 7 /* maximum valid stability */ #define DTRACE_CLASS_UNKNOWN 0 /* unknown architectural dependency */ #define DTRACE_CLASS_CPU 1 /* CPU-module-specific */ #define DTRACE_CLASS_PLATFORM 2 /* platform-specific (uname -i) */ #define DTRACE_CLASS_GROUP 3 /* hardware-group-specific (uname -m) */ #define DTRACE_CLASS_ISA 4 /* ISA-specific (uname -p) */ #define DTRACE_CLASS_COMMON 5 /* common to all systems */ #define DTRACE_CLASS_MAX 5 /* maximum valid class */ #define DTRACE_PRIV_NONE 0x0000 #define DTRACE_PRIV_KERNEL 0x0001 #define DTRACE_PRIV_USER 0x0002 #define DTRACE_PRIV_PROC 0x0004 #define DTRACE_PRIV_OWNER 0x0008 #define DTRACE_PRIV_ZONEOWNER 0x0010 #define DTRACE_PRIV_ALL \ (DTRACE_PRIV_KERNEL | DTRACE_PRIV_USER | \ DTRACE_PRIV_PROC | DTRACE_PRIV_OWNER | DTRACE_PRIV_ZONEOWNER) typedef struct dtrace_ppriv { uint32_t dtpp_flags; /* privilege flags */ uid_t dtpp_uid; /* user ID */ zoneid_t dtpp_zoneid; /* zone ID */ } dtrace_ppriv_t; typedef struct dtrace_attribute { dtrace_stability_t dtat_name; /* entity name stability */ dtrace_stability_t dtat_data; /* entity data stability */ dtrace_class_t dtat_class; /* entity data dependency */ } dtrace_attribute_t; typedef struct dtrace_pattr { dtrace_attribute_t dtpa_provider; /* provider attributes */ dtrace_attribute_t dtpa_mod; /* module attributes */ dtrace_attribute_t dtpa_func; /* function attributes */ dtrace_attribute_t dtpa_name; /* name attributes */ dtrace_attribute_t dtpa_args; /* args[] attributes */ } dtrace_pattr_t; typedef struct dtrace_providerdesc { char dtvd_name[DTRACE_PROVNAMELEN]; /* provider name */ dtrace_pattr_t dtvd_attr; /* stability attributes */ dtrace_ppriv_t dtvd_priv; /* privileges required */ } dtrace_providerdesc_t; /* * DTrace Pseudodevice Interface * * DTrace is controlled through ioctl(2)'s to the in-kernel dtrace:dtrace * pseudodevice driver. These ioctls comprise the user-kernel interface to * DTrace. */ #ifdef illumos #define DTRACEIOC (('d' << 24) | ('t' << 16) | ('r' << 8)) #define DTRACEIOC_PROVIDER (DTRACEIOC | 1) /* provider query */ #define DTRACEIOC_PROBES (DTRACEIOC | 2) /* probe query */ #define DTRACEIOC_BUFSNAP (DTRACEIOC | 4) /* snapshot buffer */ #define DTRACEIOC_PROBEMATCH (DTRACEIOC | 5) /* match probes */ #define DTRACEIOC_ENABLE (DTRACEIOC | 6) /* enable probes */ #define DTRACEIOC_AGGSNAP (DTRACEIOC | 7) /* snapshot agg. */ #define DTRACEIOC_EPROBE (DTRACEIOC | 8) /* get eprobe desc. */ #define DTRACEIOC_PROBEARG (DTRACEIOC | 9) /* get probe arg */ #define DTRACEIOC_CONF (DTRACEIOC | 10) /* get config. */ #define DTRACEIOC_STATUS (DTRACEIOC | 11) /* get status */ #define DTRACEIOC_GO (DTRACEIOC | 12) /* start tracing */ #define DTRACEIOC_STOP (DTRACEIOC | 13) /* stop tracing */ #define DTRACEIOC_AGGDESC (DTRACEIOC | 15) /* get agg. desc. */ #define DTRACEIOC_FORMAT (DTRACEIOC | 16) /* get format str */ #define DTRACEIOC_DOFGET (DTRACEIOC | 17) /* get DOF */ #define DTRACEIOC_REPLICATE (DTRACEIOC | 18) /* replicate enab */ #else #define DTRACEIOC_PROVIDER _IOWR('x',1,dtrace_providerdesc_t) /* provider query */ #define DTRACEIOC_PROBES _IOWR('x',2,dtrace_probedesc_t) /* probe query */ #define DTRACEIOC_BUFSNAP _IOW('x',4,dtrace_bufdesc_t *) /* snapshot buffer */ #define DTRACEIOC_PROBEMATCH _IOWR('x',5,dtrace_probedesc_t) /* match probes */ typedef struct { void *dof; /* DOF userland address written to driver. */ int n_matched; /* # matches returned by driver. */ } dtrace_enable_io_t; #define DTRACEIOC_ENABLE _IOWR('x',6,dtrace_enable_io_t) /* enable probes */ #define DTRACEIOC_AGGSNAP _IOW('x',7,dtrace_bufdesc_t *) /* snapshot agg. */ #define DTRACEIOC_EPROBE _IOW('x',8,dtrace_eprobedesc_t) /* get eprobe desc. */ #define DTRACEIOC_PROBEARG _IOWR('x',9,dtrace_argdesc_t) /* get probe arg */ #define DTRACEIOC_CONF _IOR('x',10,dtrace_conf_t) /* get config. */ #define DTRACEIOC_STATUS _IOR('x',11,dtrace_status_t) /* get status */ #define DTRACEIOC_GO _IOR('x',12,processorid_t) /* start tracing */ #define DTRACEIOC_STOP _IOWR('x',13,processorid_t) /* stop tracing */ #define DTRACEIOC_AGGDESC _IOW('x',15,dtrace_aggdesc_t *) /* get agg. desc. */ #define DTRACEIOC_FORMAT _IOWR('x',16,dtrace_fmtdesc_t) /* get format str */ #define DTRACEIOC_DOFGET _IOW('x',17,dof_hdr_t *) /* get DOF */ #define DTRACEIOC_REPLICATE _IOW('x',18,dtrace_repldesc_t) /* replicate enab */ #endif /* * DTrace Helpers * * In general, DTrace establishes probes in processes and takes actions on * processes without knowing their specific user-level structures. Instead of * existing in the framework, process-specific knowledge is contained by the * enabling D program -- which can apply process-specific knowledge by making * appropriate use of DTrace primitives like copyin() and copyinstr() to * operate on user-level data. However, there may exist some specific probes * of particular semantic relevance that the application developer may wish to * explicitly export. For example, an application may wish to export a probe * at the point that it begins and ends certain well-defined transactions. In * addition to providing probes, programs may wish to offer assistance for * certain actions. For example, in highly dynamic environments (e.g., Java), * it may be difficult to obtain a stack trace in terms of meaningful symbol * names (the translation from instruction addresses to corresponding symbol * names may only be possible in situ); these environments may wish to define * a series of actions to be applied in situ to obtain a meaningful stack * trace. * * These two mechanisms -- user-level statically defined tracing and assisting * DTrace actions -- are provided via DTrace _helpers_. Helpers are specified * via DOF, but unlike enabling DOF, helper DOF may contain definitions of * providers, probes and their arguments. If a helper wishes to provide * action assistance, probe descriptions and corresponding DIF actions may be * specified in the helper DOF. For such helper actions, however, the probe * description describes the specific helper: all DTrace helpers have the * provider name "dtrace" and the module name "helper", and the name of the * helper is contained in the function name (for example, the ustack() helper * is named "ustack"). Any helper-specific name may be contained in the name * (for example, if a helper were to have a constructor, it might be named * "dtrace:helper::init"). Helper actions are only called when the * action that they are helping is taken. Helper actions may only return DIF * expressions, and may only call the following subroutines: * * alloca() <= Allocates memory out of the consumer's scratch space * bcopy() <= Copies memory to scratch space * copyin() <= Copies memory from user-level into consumer's scratch * copyinto() <= Copies memory into a specific location in scratch * copyinstr() <= Copies a string into a specific location in scratch * * Helper actions may only access the following built-in variables: * * curthread <= Current kthread_t pointer * tid <= Current thread identifier * pid <= Current process identifier * ppid <= Parent process identifier * uid <= Current user ID * gid <= Current group ID * execname <= Current executable name * zonename <= Current zone name * * Helper actions may not manipulate or allocate dynamic variables, but they * may have clause-local and statically-allocated global variables. The * helper action variable state is specific to the helper action -- variables * used by the helper action may not be accessed outside of the helper * action, and the helper action may not access variables that like outside * of it. Helper actions may not load from kernel memory at-large; they are * restricting to loading current user state (via copyin() and variants) and * scratch space. As with probe enablings, helper actions are executed in * program order. The result of the helper action is the result of the last * executing helper expression. * * Helpers -- composed of either providers/probes or probes/actions (or both) * -- are added by opening the "helper" minor node, and issuing an ioctl(2) * (DTRACEHIOC_ADDDOF) that specifies the dof_helper_t structure. This * encapsulates the name and base address of the user-level library or * executable publishing the helpers and probes as well as the DOF that * contains the definitions of those helpers and probes. * * The DTRACEHIOC_ADD and DTRACEHIOC_REMOVE are left in place for legacy * helpers and should no longer be used. No other ioctls are valid on the * helper minor node. */ #ifdef illumos #define DTRACEHIOC (('d' << 24) | ('t' << 16) | ('h' << 8)) #define DTRACEHIOC_ADD (DTRACEHIOC | 1) /* add helper */ #define DTRACEHIOC_REMOVE (DTRACEHIOC | 2) /* remove helper */ #define DTRACEHIOC_ADDDOF (DTRACEHIOC | 3) /* add helper DOF */ #else #define DTRACEHIOC_ADD _IOWR('z', 1, dof_hdr_t)/* add helper */ #define DTRACEHIOC_REMOVE _IOW('z', 2, int) /* remove helper */ #define DTRACEHIOC_ADDDOF _IOWR('z', 3, dof_helper_t)/* add helper DOF */ #endif typedef struct dof_helper { char dofhp_mod[DTRACE_MODNAMELEN]; /* executable or library name */ uint64_t dofhp_addr; /* base address of object */ uint64_t dofhp_dof; /* address of helper DOF */ #ifdef __FreeBSD__ pid_t dofhp_pid; /* target process ID */ int dofhp_gen; #endif } dof_helper_t; #define DTRACEMNR_DTRACE "dtrace" /* node for DTrace ops */ #define DTRACEMNR_HELPER "helper" /* node for helpers */ #define DTRACEMNRN_DTRACE 0 /* minor for DTrace ops */ #define DTRACEMNRN_HELPER 1 /* minor for helpers */ #define DTRACEMNRN_CLONE 2 /* first clone minor */ #ifdef _KERNEL /* * DTrace Provider API * * The following functions are implemented by the DTrace framework and are * used to implement separate in-kernel DTrace providers. Common functions * are provided in uts/common/os/dtrace.c. ISA-dependent subroutines are * defined in uts//dtrace/dtrace_asm.s or uts//dtrace/dtrace_isa.c. * * The provider API has two halves: the API that the providers consume from * DTrace, and the API that providers make available to DTrace. * * 1 Framework-to-Provider API * * 1.1 Overview * * The Framework-to-Provider API is represented by the dtrace_pops structure * that the provider passes to the framework when registering itself. This * structure consists of the following members: * * dtps_provide() <-- Provide all probes, all modules * dtps_provide_module() <-- Provide all probes in specified module * dtps_enable() <-- Enable specified probe * dtps_disable() <-- Disable specified probe * dtps_suspend() <-- Suspend specified probe * dtps_resume() <-- Resume specified probe * dtps_getargdesc() <-- Get the argument description for args[X] * dtps_getargval() <-- Get the value for an argX or args[X] variable * dtps_usermode() <-- Find out if the probe was fired in user mode * dtps_destroy() <-- Destroy all state associated with this probe * * 1.2 void dtps_provide(void *arg, const dtrace_probedesc_t *spec) * * 1.2.1 Overview * * Called to indicate that the provider should provide all probes. If the * specified description is non-NULL, dtps_provide() is being called because * no probe matched a specified probe -- if the provider has the ability to * create custom probes, it may wish to create a probe that matches the * specified description. * * 1.2.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is a pointer to a probe description that the provider may * wish to consider when creating custom probes. The provider is expected to * call back into the DTrace framework via dtrace_probe_create() to create * any necessary probes. dtps_provide() may be called even if the provider * has made available all probes; the provider should check the return value * of dtrace_probe_create() to handle this case. Note that the provider need * not implement both dtps_provide() and dtps_provide_module(); see * "Arguments and Notes" for dtrace_register(), below. * * 1.2.3 Return value * * None. * * 1.2.4 Caller's context * * dtps_provide() is typically called from open() or ioctl() context, but may * be called from other contexts as well. The DTrace framework is locked in * such a way that providers may not register or unregister. This means that * the provider may not call any DTrace API that affects its registration with * the framework, including dtrace_register(), dtrace_unregister(), * dtrace_invalidate(), and dtrace_condense(). However, the context is such * that the provider may (and indeed, is expected to) call probe-related * DTrace routines, including dtrace_probe_create(), dtrace_probe_lookup(), * and dtrace_probe_arg(). * * 1.3 void dtps_provide_module(void *arg, modctl_t *mp) * * 1.3.1 Overview * * Called to indicate that the provider should provide all probes in the * specified module. * * 1.3.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is a pointer to a modctl structure that indicates the * module for which probes should be created. * * 1.3.3 Return value * * None. * * 1.3.4 Caller's context * * dtps_provide_module() may be called from open() or ioctl() context, but * may also be called from a module loading context. mod_lock is held, and * the DTrace framework is locked in such a way that providers may not * register or unregister. This means that the provider may not call any * DTrace API that affects its registration with the framework, including * dtrace_register(), dtrace_unregister(), dtrace_invalidate(), and * dtrace_condense(). However, the context is such that the provider may (and * indeed, is expected to) call probe-related DTrace routines, including * dtrace_probe_create(), dtrace_probe_lookup(), and dtrace_probe_arg(). Note * that the provider need not implement both dtps_provide() and * dtps_provide_module(); see "Arguments and Notes" for dtrace_register(), * below. * * 1.4 void dtps_enable(void *arg, dtrace_id_t id, void *parg) * * 1.4.1 Overview * * Called to enable the specified probe. * * 1.4.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is the identifier of the probe to be enabled. The third * argument is the probe argument as passed to dtrace_probe_create(). * dtps_enable() will be called when a probe transitions from not being * enabled at all to having one or more ECB. The number of ECBs associated * with the probe may change without subsequent calls into the provider. * When the number of ECBs drops to zero, the provider will be explicitly * told to disable the probe via dtps_disable(). dtrace_probe() should never * be called for a probe identifier that hasn't been explicitly enabled via * dtps_enable(). * * 1.4.3 Return value * * None. * * 1.4.4 Caller's context * * The DTrace framework is locked in such a way that it may not be called * back into at all. cpu_lock is held. mod_lock is not held and may not * be acquired. * * 1.5 void dtps_disable(void *arg, dtrace_id_t id, void *parg) * * 1.5.1 Overview * * Called to disable the specified probe. * * 1.5.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is the identifier of the probe to be disabled. The third * argument is the probe argument as passed to dtrace_probe_create(). * dtps_disable() will be called when a probe transitions from being enabled * to having zero ECBs. dtrace_probe() should never be called for a probe * identifier that has been explicitly enabled via dtps_disable(). * * 1.5.3 Return value * * None. * * 1.5.4 Caller's context * * The DTrace framework is locked in such a way that it may not be called * back into at all. cpu_lock is held. mod_lock is not held and may not * be acquired. * * 1.6 void dtps_suspend(void *arg, dtrace_id_t id, void *parg) * * 1.6.1 Overview * * Called to suspend the specified enabled probe. This entry point is for * providers that may need to suspend some or all of their probes when CPUs * are being powered on or when the boot monitor is being entered for a * prolonged period of time. * * 1.6.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is the identifier of the probe to be suspended. The * third argument is the probe argument as passed to dtrace_probe_create(). * dtps_suspend will only be called on an enabled probe. Providers that * provide a dtps_suspend entry point will want to take roughly the action * that it takes for dtps_disable. * * 1.6.3 Return value * * None. * * 1.6.4 Caller's context * * Interrupts are disabled. The DTrace framework is in a state such that the * specified probe cannot be disabled or destroyed for the duration of * dtps_suspend(). As interrupts are disabled, the provider is afforded * little latitude; the provider is expected to do no more than a store to * memory. * * 1.7 void dtps_resume(void *arg, dtrace_id_t id, void *parg) * * 1.7.1 Overview * * Called to resume the specified enabled probe. This entry point is for * providers that may need to resume some or all of their probes after the * completion of an event that induced a call to dtps_suspend(). * * 1.7.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is the identifier of the probe to be resumed. The * third argument is the probe argument as passed to dtrace_probe_create(). * dtps_resume will only be called on an enabled probe. Providers that * provide a dtps_resume entry point will want to take roughly the action * that it takes for dtps_enable. * * 1.7.3 Return value * * None. * * 1.7.4 Caller's context * * Interrupts are disabled. The DTrace framework is in a state such that the * specified probe cannot be disabled or destroyed for the duration of * dtps_resume(). As interrupts are disabled, the provider is afforded * little latitude; the provider is expected to do no more than a store to * memory. * * 1.8 void dtps_getargdesc(void *arg, dtrace_id_t id, void *parg, * dtrace_argdesc_t *desc) * * 1.8.1 Overview * * Called to retrieve the argument description for an args[X] variable. * * 1.8.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is the identifier of the current probe. The third * argument is the probe argument as passed to dtrace_probe_create(). The * fourth argument is a pointer to the argument description. This * description is both an input and output parameter: it contains the * index of the desired argument in the dtargd_ndx field, and expects * the other fields to be filled in upon return. If there is no argument * corresponding to the specified index, the dtargd_ndx field should be set * to DTRACE_ARGNONE. * * 1.8.3 Return value * * None. The dtargd_ndx, dtargd_native, dtargd_xlate and dtargd_mapping * members of the dtrace_argdesc_t structure are all output values. * * 1.8.4 Caller's context * * dtps_getargdesc() is called from ioctl() context. mod_lock is held, and * the DTrace framework is locked in such a way that providers may not * register or unregister. This means that the provider may not call any * DTrace API that affects its registration with the framework, including * dtrace_register(), dtrace_unregister(), dtrace_invalidate(), and * dtrace_condense(). * * 1.9 uint64_t dtps_getargval(void *arg, dtrace_id_t id, void *parg, * int argno, int aframes) * * 1.9.1 Overview * * Called to retrieve a value for an argX or args[X] variable. * * 1.9.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is the identifier of the current probe. The third * argument is the probe argument as passed to dtrace_probe_create(). The * fourth argument is the number of the argument (the X in the example in * 1.9.1). The fifth argument is the number of stack frames that were used * to get from the actual place in the code that fired the probe to * dtrace_probe() itself, the so-called artificial frames. This argument may * be used to descend an appropriate number of frames to find the correct * values. If this entry point is left NULL, the dtrace_getarg() built-in * function is used. * * 1.9.3 Return value * * The value of the argument. * * 1.9.4 Caller's context * * This is called from within dtrace_probe() meaning that interrupts * are disabled. No locks should be taken within this entry point. * * 1.10 int dtps_usermode(void *arg, dtrace_id_t id, void *parg) * * 1.10.1 Overview * * Called to determine if the probe was fired in a user context. * * 1.10.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is the identifier of the current probe. The third * argument is the probe argument as passed to dtrace_probe_create(). This * entry point must not be left NULL for providers whose probes allow for * mixed mode tracing, that is to say those probes that can fire during * kernel- _or_ user-mode execution * * 1.10.3 Return value * * A bitwise OR that encapsulates both the mode (either DTRACE_MODE_KERNEL * or DTRACE_MODE_USER) and the policy when the privilege of the enabling * is insufficient for that mode (a combination of DTRACE_MODE_NOPRIV_DROP, * DTRACE_MODE_NOPRIV_RESTRICT, and DTRACE_MODE_LIMITEDPRIV_RESTRICT). If * DTRACE_MODE_NOPRIV_DROP bit is set, insufficient privilege will result * in the probe firing being silently ignored for the enabling; if the * DTRACE_NODE_NOPRIV_RESTRICT bit is set, insufficient privilege will not * prevent probe processing for the enabling, but restrictions will be in * place that induce a UPRIV fault upon attempt to examine probe arguments * or current process state. If the DTRACE_MODE_LIMITEDPRIV_RESTRICT bit * is set, similar restrictions will be placed upon operation if the * privilege is sufficient to process the enabling, but does not otherwise * entitle the enabling to all zones. The DTRACE_MODE_NOPRIV_DROP and * DTRACE_MODE_NOPRIV_RESTRICT are mutually exclusive (and one of these * two policies must be specified), but either may be combined (or not) * with DTRACE_MODE_LIMITEDPRIV_RESTRICT. * * 1.10.4 Caller's context * * This is called from within dtrace_probe() meaning that interrupts * are disabled. No locks should be taken within this entry point. * * 1.11 void dtps_destroy(void *arg, dtrace_id_t id, void *parg) * * 1.11.1 Overview * * Called to destroy the specified probe. * * 1.11.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_register(). The * second argument is the identifier of the probe to be destroyed. The third * argument is the probe argument as passed to dtrace_probe_create(). The * provider should free all state associated with the probe. The framework * guarantees that dtps_destroy() is only called for probes that have either * been disabled via dtps_disable() or were never enabled via dtps_enable(). * Once dtps_disable() has been called for a probe, no further call will be * made specifying the probe. * * 1.11.3 Return value * * None. * * 1.11.4 Caller's context * * The DTrace framework is locked in such a way that it may not be called * back into at all. mod_lock is held. cpu_lock is not held, and may not be * acquired. * * * 2 Provider-to-Framework API * * 2.1 Overview * * The Provider-to-Framework API provides the mechanism for the provider to * register itself with the DTrace framework, to create probes, to lookup * probes and (most importantly) to fire probes. The Provider-to-Framework * consists of: * * dtrace_register() <-- Register a provider with the DTrace framework * dtrace_unregister() <-- Remove a provider's DTrace registration * dtrace_invalidate() <-- Invalidate the specified provider * dtrace_condense() <-- Remove a provider's unenabled probes * dtrace_attached() <-- Indicates whether or not DTrace has attached * dtrace_probe_create() <-- Create a DTrace probe * dtrace_probe_lookup() <-- Lookup a DTrace probe based on its name * dtrace_probe_arg() <-- Return the probe argument for a specific probe * dtrace_probe() <-- Fire the specified probe * * 2.2 int dtrace_register(const char *name, const dtrace_pattr_t *pap, * uint32_t priv, cred_t *cr, const dtrace_pops_t *pops, void *arg, * dtrace_provider_id_t *idp) * * 2.2.1 Overview * * dtrace_register() registers the calling provider with the DTrace * framework. It should generally be called by DTrace providers in their * attach(9E) entry point. * * 2.2.2 Arguments and Notes * * The first argument is the name of the provider. The second argument is a * pointer to the stability attributes for the provider. The third argument * is the privilege flags for the provider, and must be some combination of: * * DTRACE_PRIV_NONE <= All users may enable probes from this provider * * DTRACE_PRIV_PROC <= Any user with privilege of PRIV_DTRACE_PROC may * enable probes from this provider * * DTRACE_PRIV_USER <= Any user with privilege of PRIV_DTRACE_USER may * enable probes from this provider * * DTRACE_PRIV_KERNEL <= Any user with privilege of PRIV_DTRACE_KERNEL * may enable probes from this provider * * DTRACE_PRIV_OWNER <= This flag places an additional constraint on * the privilege requirements above. These probes * require either (a) a user ID matching the user * ID of the cred passed in the fourth argument * or (b) the PRIV_PROC_OWNER privilege. * * DTRACE_PRIV_ZONEOWNER<= This flag places an additional constraint on * the privilege requirements above. These probes * require either (a) a zone ID matching the zone * ID of the cred passed in the fourth argument * or (b) the PRIV_PROC_ZONE privilege. * * Note that these flags designate the _visibility_ of the probes, not * the conditions under which they may or may not fire. * * The fourth argument is the credential that is associated with the * provider. This argument should be NULL if the privilege flags don't * include DTRACE_PRIV_OWNER or DTRACE_PRIV_ZONEOWNER. If non-NULL, the * framework stashes the uid and zoneid represented by this credential * for use at probe-time, in implicit predicates. These limit visibility * of the probes to users and/or zones which have sufficient privilege to * access them. * * The fifth argument is a DTrace provider operations vector, which provides * the implementation for the Framework-to-Provider API. (See Section 1, * above.) This must be non-NULL, and each member must be non-NULL. The * exceptions to this are (1) the dtps_provide() and dtps_provide_module() * members (if the provider so desires, _one_ of these members may be left * NULL -- denoting that the provider only implements the other) and (2) * the dtps_suspend() and dtps_resume() members, which must either both be * NULL or both be non-NULL. * * The sixth argument is a cookie to be specified as the first argument for * each function in the Framework-to-Provider API. This argument may have * any value. * * The final argument is a pointer to dtrace_provider_id_t. If * dtrace_register() successfully completes, the provider identifier will be * stored in the memory pointed to be this argument. This argument must be * non-NULL. * * 2.2.3 Return value * * On success, dtrace_register() returns 0 and stores the new provider's * identifier into the memory pointed to by the idp argument. On failure, * dtrace_register() returns an errno: * * EINVAL The arguments passed to dtrace_register() were somehow invalid. * This may because a parameter that must be non-NULL was NULL, * because the name was invalid (either empty or an illegal * provider name) or because the attributes were invalid. * * No other failure code is returned. * * 2.2.4 Caller's context * * dtrace_register() may induce calls to dtrace_provide(); the provider must * hold no locks across dtrace_register() that may also be acquired by * dtrace_provide(). cpu_lock and mod_lock must not be held. * * 2.3 int dtrace_unregister(dtrace_provider_t id) * * 2.3.1 Overview * * Unregisters the specified provider from the DTrace framework. It should * generally be called by DTrace providers in their detach(9E) entry point. * * 2.3.2 Arguments and Notes * * The only argument is the provider identifier, as returned from a * successful call to dtrace_register(). As a result of calling * dtrace_unregister(), the DTrace framework will call back into the provider * via the dtps_destroy() entry point. Once dtrace_unregister() successfully * completes, however, the DTrace framework will no longer make calls through * the Framework-to-Provider API. * * 2.3.3 Return value * * On success, dtrace_unregister returns 0. On failure, dtrace_unregister() * returns an errno: * * EBUSY There are currently processes that have the DTrace pseudodevice * open, or there exists an anonymous enabling that hasn't yet * been claimed. * * No other failure code is returned. * * 2.3.4 Caller's context * * Because a call to dtrace_unregister() may induce calls through the * Framework-to-Provider API, the caller may not hold any lock across * dtrace_register() that is also acquired in any of the Framework-to- * Provider API functions. Additionally, mod_lock may not be held. * * 2.4 void dtrace_invalidate(dtrace_provider_id_t id) * * 2.4.1 Overview * * Invalidates the specified provider. All subsequent probe lookups for the * specified provider will fail, but its probes will not be removed. * * 2.4.2 Arguments and note * * The only argument is the provider identifier, as returned from a * successful call to dtrace_register(). In general, a provider's probes * always remain valid; dtrace_invalidate() is a mechanism for invalidating * an entire provider, regardless of whether or not probes are enabled or * not. Note that dtrace_invalidate() will _not_ prevent already enabled * probes from firing -- it will merely prevent any new enablings of the * provider's probes. * * 2.5 int dtrace_condense(dtrace_provider_id_t id) * * 2.5.1 Overview * * Removes all the unenabled probes for the given provider. This function is * not unlike dtrace_unregister(), except that it doesn't remove the * provider just as many of its associated probes as it can. * * 2.5.2 Arguments and Notes * * As with dtrace_unregister(), the sole argument is the provider identifier * as returned from a successful call to dtrace_register(). As a result of * calling dtrace_condense(), the DTrace framework will call back into the * given provider's dtps_destroy() entry point for each of the provider's * unenabled probes. * * 2.5.3 Return value * * Currently, dtrace_condense() always returns 0. However, consumers of this * function should check the return value as appropriate; its behavior may * change in the future. * * 2.5.4 Caller's context * * As with dtrace_unregister(), the caller may not hold any lock across * dtrace_condense() that is also acquired in the provider's entry points. * Also, mod_lock may not be held. * * 2.6 int dtrace_attached() * * 2.6.1 Overview * * Indicates whether or not DTrace has attached. * * 2.6.2 Arguments and Notes * * For most providers, DTrace makes initial contact beyond registration. * That is, once a provider has registered with DTrace, it waits to hear * from DTrace to create probes. However, some providers may wish to * proactively create probes without first being told by DTrace to do so. * If providers wish to do this, they must first call dtrace_attached() to * determine if DTrace itself has attached. If dtrace_attached() returns 0, * the provider must not make any other Provider-to-Framework API call. * * 2.6.3 Return value * * dtrace_attached() returns 1 if DTrace has attached, 0 otherwise. * * 2.7 int dtrace_probe_create(dtrace_provider_t id, const char *mod, * const char *func, const char *name, int aframes, void *arg) * * 2.7.1 Overview * * Creates a probe with specified module name, function name, and name. * * 2.7.2 Arguments and Notes * * The first argument is the provider identifier, as returned from a * successful call to dtrace_register(). The second, third, and fourth * arguments are the module name, function name, and probe name, * respectively. Of these, module name and function name may both be NULL * (in which case the probe is considered to be unanchored), or they may both * be non-NULL. The name must be non-NULL, and must point to a non-empty * string. * * The fifth argument is the number of artificial stack frames that will be * found on the stack when dtrace_probe() is called for the new probe. These * artificial frames will be automatically be pruned should the stack() or * stackdepth() functions be called as part of one of the probe's ECBs. If * the parameter doesn't add an artificial frame, this parameter should be * zero. * * The final argument is a probe argument that will be passed back to the * provider when a probe-specific operation is called. (e.g., via * dtps_enable(), dtps_disable(), etc.) * * Note that it is up to the provider to be sure that the probe that it * creates does not already exist -- if the provider is unsure of the probe's * existence, it should assure its absence with dtrace_probe_lookup() before * calling dtrace_probe_create(). * * 2.7.3 Return value * * dtrace_probe_create() always succeeds, and always returns the identifier * of the newly-created probe. * * 2.7.4 Caller's context * * While dtrace_probe_create() is generally expected to be called from * dtps_provide() and/or dtps_provide_module(), it may be called from other * non-DTrace contexts. Neither cpu_lock nor mod_lock may be held. * * 2.8 dtrace_id_t dtrace_probe_lookup(dtrace_provider_t id, const char *mod, * const char *func, const char *name) * * 2.8.1 Overview * * Looks up a probe based on provdider and one or more of module name, * function name and probe name. * * 2.8.2 Arguments and Notes * * The first argument is the provider identifier, as returned from a * successful call to dtrace_register(). The second, third, and fourth * arguments are the module name, function name, and probe name, * respectively. Any of these may be NULL; dtrace_probe_lookup() will return * the identifier of the first probe that is provided by the specified * provider and matches all of the non-NULL matching criteria. * dtrace_probe_lookup() is generally used by a provider to be check the * existence of a probe before creating it with dtrace_probe_create(). * * 2.8.3 Return value * * If the probe exists, returns its identifier. If the probe does not exist, * return DTRACE_IDNONE. * * 2.8.4 Caller's context * * While dtrace_probe_lookup() is generally expected to be called from * dtps_provide() and/or dtps_provide_module(), it may also be called from * other non-DTrace contexts. Neither cpu_lock nor mod_lock may be held. * * 2.9 void *dtrace_probe_arg(dtrace_provider_t id, dtrace_id_t probe) * * 2.9.1 Overview * * Returns the probe argument associated with the specified probe. * * 2.9.2 Arguments and Notes * * The first argument is the provider identifier, as returned from a * successful call to dtrace_register(). The second argument is a probe * identifier, as returned from dtrace_probe_lookup() or * dtrace_probe_create(). This is useful if a probe has multiple * provider-specific components to it: the provider can create the probe * once with provider-specific state, and then add to the state by looking * up the probe based on probe identifier. * * 2.9.3 Return value * * Returns the argument associated with the specified probe. If the * specified probe does not exist, or if the specified probe is not provided * by the specified provider, NULL is returned. * * 2.9.4 Caller's context * * While dtrace_probe_arg() is generally expected to be called from * dtps_provide() and/or dtps_provide_module(), it may also be called from * other non-DTrace contexts. Neither cpu_lock nor mod_lock may be held. * * 2.10 void dtrace_probe(dtrace_id_t probe, uintptr_t arg0, uintptr_t arg1, * uintptr_t arg2, uintptr_t arg3, uintptr_t arg4) * * 2.10.1 Overview * * The epicenter of DTrace: fires the specified probes with the specified * arguments. * * 2.10.2 Arguments and Notes * * The first argument is a probe identifier as returned by * dtrace_probe_create() or dtrace_probe_lookup(). The second through sixth * arguments are the values to which the D variables "arg0" through "arg4" * will be mapped. * * dtrace_probe() should be called whenever the specified probe has fired -- * however the provider defines it. * * 2.10.3 Return value * * None. * * 2.10.4 Caller's context * * dtrace_probe() may be called in virtually any context: kernel, user, * interrupt, high-level interrupt, with arbitrary adaptive locks held, with * dispatcher locks held, with interrupts disabled, etc. The only latitude * that must be afforded to DTrace is the ability to make calls within * itself (and to its in-kernel subroutines) and the ability to access * arbitrary (but mapped) memory. On some platforms, this constrains * context. For example, on UltraSPARC, dtrace_probe() cannot be called * from any context in which TL is greater than zero. dtrace_probe() may * also not be called from any routine which may be called by dtrace_probe() * -- which includes functions in the DTrace framework and some in-kernel * DTrace subroutines. All such functions "dtrace_"; providers that * instrument the kernel arbitrarily should be sure to not instrument these * routines. */ typedef struct dtrace_pops { void (*dtps_provide)(void *arg, dtrace_probedesc_t *spec); void (*dtps_provide_module)(void *arg, modctl_t *mp); void (*dtps_enable)(void *arg, dtrace_id_t id, void *parg); void (*dtps_disable)(void *arg, dtrace_id_t id, void *parg); void (*dtps_suspend)(void *arg, dtrace_id_t id, void *parg); void (*dtps_resume)(void *arg, dtrace_id_t id, void *parg); void (*dtps_getargdesc)(void *arg, dtrace_id_t id, void *parg, dtrace_argdesc_t *desc); uint64_t (*dtps_getargval)(void *arg, dtrace_id_t id, void *parg, int argno, int aframes); int (*dtps_usermode)(void *arg, dtrace_id_t id, void *parg); void (*dtps_destroy)(void *arg, dtrace_id_t id, void *parg); } dtrace_pops_t; #define DTRACE_MODE_KERNEL 0x01 #define DTRACE_MODE_USER 0x02 #define DTRACE_MODE_NOPRIV_DROP 0x10 #define DTRACE_MODE_NOPRIV_RESTRICT 0x20 #define DTRACE_MODE_LIMITEDPRIV_RESTRICT 0x40 typedef uintptr_t dtrace_provider_id_t; extern int dtrace_register(const char *, const dtrace_pattr_t *, uint32_t, cred_t *, const dtrace_pops_t *, void *, dtrace_provider_id_t *); extern int dtrace_unregister(dtrace_provider_id_t); extern int dtrace_condense(dtrace_provider_id_t); extern void dtrace_invalidate(dtrace_provider_id_t); extern dtrace_id_t dtrace_probe_lookup(dtrace_provider_id_t, char *, char *, char *); extern dtrace_id_t dtrace_probe_create(dtrace_provider_id_t, const char *, const char *, const char *, int, void *); extern void *dtrace_probe_arg(dtrace_provider_id_t, dtrace_id_t); extern void dtrace_probe(dtrace_id_t, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4); /* * DTrace Meta Provider API * * The following functions are implemented by the DTrace framework and are * used to implement meta providers. Meta providers plug into the DTrace * framework and are used to instantiate new providers on the fly. At * present, there is only one type of meta provider and only one meta * provider may be registered with the DTrace framework at a time. The * sole meta provider type provides user-land static tracing facilities * by taking meta probe descriptions and adding a corresponding provider * into the DTrace framework. * * 1 Framework-to-Provider * * 1.1 Overview * * The Framework-to-Provider API is represented by the dtrace_mops structure * that the meta provider passes to the framework when registering itself as * a meta provider. This structure consists of the following members: * * dtms_create_probe() <-- Add a new probe to a created provider * dtms_provide_pid() <-- Create a new provider for a given process * dtms_remove_pid() <-- Remove a previously created provider * * 1.2 void dtms_create_probe(void *arg, void *parg, * dtrace_helper_probedesc_t *probedesc); * * 1.2.1 Overview * * Called by the DTrace framework to create a new probe in a provider * created by this meta provider. * * 1.2.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_meta_register(). * The second argument is the provider cookie for the associated provider; * this is obtained from the return value of dtms_provide_pid(). The third * argument is the helper probe description. * * 1.2.3 Return value * * None * * 1.2.4 Caller's context * * dtms_create_probe() is called from either ioctl() or module load context * in the context of a newly-created provider (that is, a provider that * is a result of a call to dtms_provide_pid()). The DTrace framework is * locked in such a way that meta providers may not register or unregister, * such that no other thread can call into a meta provider operation and that * atomicity is assured with respect to meta provider operations across * dtms_provide_pid() and subsequent calls to dtms_create_probe(). * The context is thus effectively single-threaded with respect to the meta * provider, and that the meta provider cannot call dtrace_meta_register() * or dtrace_meta_unregister(). However, the context is such that the * provider may (and is expected to) call provider-related DTrace provider * APIs including dtrace_probe_create(). * * 1.3 void *dtms_provide_pid(void *arg, dtrace_meta_provider_t *mprov, * pid_t pid) * * 1.3.1 Overview * * Called by the DTrace framework to instantiate a new provider given the * description of the provider and probes in the mprov argument. The * meta provider should call dtrace_register() to insert the new provider * into the DTrace framework. * * 1.3.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_meta_register(). * The second argument is a pointer to a structure describing the new * helper provider. The third argument is the process identifier for * process associated with this new provider. Note that the name of the * provider as passed to dtrace_register() should be the contatenation of * the dtmpb_provname member of the mprov argument and the processs * identifier as a string. * * 1.3.3 Return value * * The cookie for the provider that the meta provider creates. This is * the same value that it passed to dtrace_register(). * * 1.3.4 Caller's context * * dtms_provide_pid() is called from either ioctl() or module load context. * The DTrace framework is locked in such a way that meta providers may not * register or unregister. This means that the meta provider cannot call * dtrace_meta_register() or dtrace_meta_unregister(). However, the context * is such that the provider may -- and is expected to -- call * provider-related DTrace provider APIs including dtrace_register(). * * 1.4 void dtms_remove_pid(void *arg, dtrace_meta_provider_t *mprov, * pid_t pid) * * 1.4.1 Overview * * Called by the DTrace framework to remove a provider that had previously * been instantiated via the dtms_provide_pid() entry point. The meta * provider need not remove the provider immediately, but this entry * point indicates that the provider should be removed as soon as possible * using the dtrace_unregister() API. * * 1.4.2 Arguments and notes * * The first argument is the cookie as passed to dtrace_meta_register(). * The second argument is a pointer to a structure describing the helper * provider. The third argument is the process identifier for process * associated with this new provider. * * 1.4.3 Return value * * None * * 1.4.4 Caller's context * * dtms_remove_pid() is called from either ioctl() or exit() context. * The DTrace framework is locked in such a way that meta providers may not * register or unregister. This means that the meta provider cannot call * dtrace_meta_register() or dtrace_meta_unregister(). However, the context * is such that the provider may -- and is expected to -- call * provider-related DTrace provider APIs including dtrace_unregister(). */ typedef struct dtrace_helper_probedesc { char *dthpb_mod; /* probe module */ char *dthpb_func; /* probe function */ char *dthpb_name; /* probe name */ uint64_t dthpb_base; /* base address */ uint32_t *dthpb_offs; /* offsets array */ uint32_t *dthpb_enoffs; /* is-enabled offsets array */ uint32_t dthpb_noffs; /* offsets count */ uint32_t dthpb_nenoffs; /* is-enabled offsets count */ uint8_t *dthpb_args; /* argument mapping array */ uint8_t dthpb_xargc; /* translated argument count */ uint8_t dthpb_nargc; /* native argument count */ char *dthpb_xtypes; /* translated types strings */ char *dthpb_ntypes; /* native types strings */ } dtrace_helper_probedesc_t; typedef struct dtrace_helper_provdesc { char *dthpv_provname; /* provider name */ dtrace_pattr_t dthpv_pattr; /* stability attributes */ } dtrace_helper_provdesc_t; typedef struct dtrace_mops { void (*dtms_create_probe)(void *, void *, dtrace_helper_probedesc_t *); void *(*dtms_provide_pid)(void *, dtrace_helper_provdesc_t *, pid_t); void (*dtms_remove_pid)(void *, dtrace_helper_provdesc_t *, pid_t); } dtrace_mops_t; typedef uintptr_t dtrace_meta_provider_id_t; extern int dtrace_meta_register(const char *, const dtrace_mops_t *, void *, dtrace_meta_provider_id_t *); extern int dtrace_meta_unregister(dtrace_meta_provider_id_t); /* * DTrace Kernel Hooks * * The following functions are implemented by the base kernel and form a set of * hooks used by the DTrace framework. DTrace hooks are implemented in either * uts/common/os/dtrace_subr.c, an ISA-specific assembly file, or in a * uts//os/dtrace_subr.c corresponding to each hardware platform. */ typedef enum dtrace_vtime_state { DTRACE_VTIME_INACTIVE = 0, /* No DTrace, no TNF */ DTRACE_VTIME_ACTIVE, /* DTrace virtual time, no TNF */ DTRACE_VTIME_INACTIVE_TNF, /* No DTrace, TNF active */ DTRACE_VTIME_ACTIVE_TNF /* DTrace virtual time _and_ TNF */ } dtrace_vtime_state_t; #ifdef illumos extern dtrace_vtime_state_t dtrace_vtime_active; #endif extern void dtrace_vtime_switch(kthread_t *next); extern void dtrace_vtime_enable_tnf(void); extern void dtrace_vtime_disable_tnf(void); extern void dtrace_vtime_enable(void); extern void dtrace_vtime_disable(void); struct regs; struct reg; #ifdef illumos extern int (*dtrace_pid_probe_ptr)(struct reg *); extern int (*dtrace_return_probe_ptr)(struct reg *); extern void (*dtrace_fasttrap_fork_ptr)(proc_t *, proc_t *); extern void (*dtrace_fasttrap_exec_ptr)(proc_t *); extern void (*dtrace_fasttrap_exit_ptr)(proc_t *); extern void dtrace_fasttrap_fork(proc_t *, proc_t *); #endif typedef uintptr_t dtrace_icookie_t; typedef void (*dtrace_xcall_t)(void *); extern dtrace_icookie_t dtrace_interrupt_disable(void); extern void dtrace_interrupt_enable(dtrace_icookie_t); extern void dtrace_membar_producer(void); extern void dtrace_membar_consumer(void); extern void (*dtrace_cpu_init)(processorid_t); #ifdef illumos extern void (*dtrace_modload)(modctl_t *); extern void (*dtrace_modunload)(modctl_t *); #endif extern void (*dtrace_helpers_cleanup)(void); extern void (*dtrace_helpers_fork)(proc_t *parent, proc_t *child); extern void (*dtrace_cpustart_init)(void); extern void (*dtrace_cpustart_fini)(void); extern void (*dtrace_closef)(void); extern void (*dtrace_debugger_init)(void); extern void (*dtrace_debugger_fini)(void); extern dtrace_cacheid_t dtrace_predcache_id; #ifdef illumos extern hrtime_t dtrace_gethrtime(void); #else void dtrace_debug_printf(const char *, ...) __printflike(1, 2); #endif extern void dtrace_sync(void); extern void dtrace_toxic_ranges(void (*)(uintptr_t, uintptr_t)); extern void dtrace_xcall(processorid_t, dtrace_xcall_t, void *); extern void dtrace_vpanic(const char *, __va_list); extern void dtrace_panic(const char *, ...); extern int dtrace_safe_defer_signal(void); extern void dtrace_safe_synchronous_signal(void); extern int dtrace_mach_aframes(void); #if defined(__i386) || defined(__amd64) extern int dtrace_instr_size(uchar_t *instr); extern int dtrace_instr_size_isa(uchar_t *, model_t, int *); extern void dtrace_invop_callsite(void); #endif extern void dtrace_invop_add(int (*)(uintptr_t, struct trapframe *, uintptr_t)); extern void dtrace_invop_remove(int (*)(uintptr_t, struct trapframe *, uintptr_t)); #ifdef __sparc extern int dtrace_blksuword32(uintptr_t, uint32_t *, int); extern void dtrace_getfsr(uint64_t *); #endif #ifndef illumos extern void dtrace_helpers_duplicate(proc_t *, proc_t *); extern void dtrace_helpers_destroy(proc_t *); #endif #define DTRACE_CPUFLAG_ISSET(flag) \ (cpu_core[curcpu].cpuc_dtrace_flags & (flag)) #define DTRACE_CPUFLAG_SET(flag) \ (cpu_core[curcpu].cpuc_dtrace_flags |= (flag)) #define DTRACE_CPUFLAG_CLEAR(flag) \ (cpu_core[curcpu].cpuc_dtrace_flags &= ~(flag)) #endif /* _KERNEL */ #endif /* _ASM */ #if defined(__i386) || defined(__amd64) #define DTRACE_INVOP_PUSHL_EBP 1 #define DTRACE_INVOP_PUSHQ_RBP DTRACE_INVOP_PUSHL_EBP #define DTRACE_INVOP_POPL_EBP 2 #define DTRACE_INVOP_POPQ_RBP DTRACE_INVOP_POPL_EBP #define DTRACE_INVOP_LEAVE 3 #define DTRACE_INVOP_NOP 4 #define DTRACE_INVOP_RET 5 #elif defined(__powerpc__) #define DTRACE_INVOP_RET 1 #define DTRACE_INVOP_BCTR 2 #define DTRACE_INVOP_BLR 3 #define DTRACE_INVOP_JUMP 4 #define DTRACE_INVOP_MFLR_R0 5 #define DTRACE_INVOP_NOP 6 #elif defined(__arm__) #define DTRACE_INVOP_SHIFT 4 #define DTRACE_INVOP_MASK ((1 << DTRACE_INVOP_SHIFT) - 1) #define DTRACE_INVOP_DATA(x) ((x) >> DTRACE_INVOP_SHIFT) #define DTRACE_INVOP_PUSHM 1 #define DTRACE_INVOP_POPM 2 #define DTRACE_INVOP_B 3 #elif defined(__aarch64__) #define INSN_SIZE 4 #define B_MASK 0xff000000 #define B_DATA_MASK 0x00ffffff #define B_INSTR 0x14000000 #define RET_INSTR 0xd65f03c0 #define LDP_STP_MASK 0xffc00000 #define STP_32 0x29800000 #define STP_64 0xa9800000 #define LDP_32 0x28c00000 #define LDP_64 0xa8c00000 #define LDP_STP_PREIND (1 << 24) #define LDP_STP_DIR (1 << 22) /* Load instruction */ #define ARG1_SHIFT 0 #define ARG1_MASK 0x1f #define ARG2_SHIFT 10 #define ARG2_MASK 0x1f #define OFFSET_SHIFT 15 #define OFFSET_SIZE 7 #define OFFSET_MASK ((1 << OFFSET_SIZE) - 1) #define DTRACE_INVOP_PUSHM 1 #define DTRACE_INVOP_RET 2 #define DTRACE_INVOP_B 3 +#elif defined(__mips__) + +#define INSN_SIZE 4 + +/* Load/Store double RA to/from SP */ +#define LDSD_RA_SP_MASK 0xffff0000 +#define LDSD_DATA_MASK 0x0000ffff +#define SD_RA_SP 0xffbf0000 +#define LD_RA_SP 0xdfbf0000 + +#define DTRACE_INVOP_SD 1 +#define DTRACE_INVOP_LD 2 #endif #ifdef __cplusplus } #endif #endif /* _SYS_DTRACE_H */ Index: user/ngie/detangle-rc/sys/cddl/contrib/opensolaris =================================================================== --- user/ngie/detangle-rc/sys/cddl/contrib/opensolaris (revision 299126) +++ user/ngie/detangle-rc/sys/cddl/contrib/opensolaris (revision 299127) Property changes on: user/ngie/detangle-rc/sys/cddl/contrib/opensolaris ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sys/cddl/contrib/opensolaris:r299100-299126 Index: user/ngie/detangle-rc/sys/cddl/dev/dtrace/mips/dtrace_asm.S =================================================================== --- user/ngie/detangle-rc/sys/cddl/dev/dtrace/mips/dtrace_asm.S (revision 299126) +++ user/ngie/detangle-rc/sys/cddl/dev/dtrace/mips/dtrace_asm.S (revision 299127) @@ -1,259 +1,236 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * $FreeBSD$ */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #define _ASM #define _LOCORE -#define LOCORE #include #include #include #include #include #include #include "assym.s" .set noreorder # Noreorder is default style! /* * Primitives */ .text /* void dtrace_membar_producer(void) */ LEAF(dtrace_membar_producer) j ra nop END(dtrace_membar_producer) /* void dtrace_membar_consumer(void) */ LEAF(dtrace_membar_consumer) j ra nop END(dtrace_membar_consumer) /* dtrace_icookie_t dtrace_interrupt_disable(void) */ LEAF(dtrace_interrupt_disable) mfc0 t0, MIPS_COP_0_STATUS move v0, t0 and v0, v0, MIPS_SR_INT_IE and t0, t0, ~MIPS_SR_INT_IE mtc0 t0, MIPS_COP_0_STATUS j ra nop END(dtrace_interrupt_disable) /* void dtrace_interrupt_enable(dtrace_icookie_t cookie) */ LEAF(dtrace_interrupt_enable) mfc0 t0, MIPS_COP_0_STATUS beqz a0, not_enabled or t0, t0, MIPS_SR_INT_IE mtc0 t0, MIPS_COP_0_STATUS not_enabled: j ra nop END(dtrace_interrupt_enable) /* uint32_t dtrace_cas32(uint32_t *target, uint32_t cmp, uint32_t new) */ LEAF(dtrace_cas32) 1: move t1, a2 ll t0, 0(a0) bne t0, a1, 2f nop sc t1, 0(a0) beqz t1, 1b nop 2: move v0, t0 j ra nop END(dtrace_cas32) /* void * dtrace_casptr(void *target, void *cmp, void *new) */ LEAF(dtrace_casptr) 1: move t1, a2 PTR_LL t0, 0(a0) bne t0, a1, 2f nop PTR_SC t1, 0(a0) beqz t1, 1b nop 2: move v0, t0 j ra nop END(dtrace_casptr) /* uintptr_t dtrace_fulword(void *addr) */ LEAF(dtrace_fulword) END(dtrace_fulword) /* uint8_t dtrace_fuword8_nocheck(void *addr) */ LEAF(dtrace_fuword8_nocheck) lbu v0, 0(a0) j ra nop END(dtrace_fuword8_nocheck) /* uint16_t dtrace_fuword16_nocheck(void *addr) */ LEAF(dtrace_fuword16_nocheck) lhu v0, 0(a0) j ra nop END(dtrace_fuword16_nocheck) /* uint32_t dtrace_fuword32_nocheck(void *addr) */ LEAF(dtrace_fuword32_nocheck) lw v0, 0(a0) j ra nop END(dtrace_fuword32_nocheck) /* uint64_t dtrace_fuword64_nocheck(void *addr) */ LEAF(dtrace_fuword64_nocheck) #if defined(__mips_n64) || defined(__mips_n32) ld v0, 0(a0) #else lw v1,4(a0) lw v0,0(a0) #endif j ra nop END(dtrace_fuword64_nocheck) /* XXX: unoptimized void dtrace_copy(uintptr_t src, uintptr_t dest, size_t size) */ LEAF(dtrace_copy) 1: beqz a2, 2f nop lbu t0, 0(a0) sb t0, 0(a1) PTR_ADDU a0, a0, 1 PTR_ADDU a1, a1, 1 INT_SUBU a2, a2, 1 j 1b nop 2: j ra nop END(dtrace_copy) /* XXX: Unoptimized. Check for flags on page boundaries only(?) void dtrace_copystr(uintptr_t uaddr, uintptr_t kaddr, size_t size, volatile uint16_t *flags) */ LEAF(dtrace_copystr) 1: lbu t0, 0(a0) sb t0, 0(a1) PTR_ADDU a0, a0, 1 PTR_ADDU a1, a1, 1 INT_SUBU a2, a2, 1 beqz t0, 2f nop lhu t1, (a3) and t1, t1, CPU_DTRACE_BADADDR bnez t1, 2f nop bnez a2, 1b nop 2: j ra nop END(dtrace_copystr) - -/* -void dtrace_invop_init(void) -*/ -LEAF(dtrace_invop_init) - /* XXX: impement it properly */ - PTR_LA t0, dtrace_invop_jump_addr - /* dla t1, dtrace_invop_start */ - PTR_S zero, 0(t0) - j ra - nop -END(dtrace_invop_init) - -/* -void dtrace_invop_uninit(void) -*/ -LEAF(dtrace_invop_uninit) - PTR_LA t0, dtrace_invop_jump_addr - PTR_S zero, 0(t0) - j ra - nop -END(dtrace_invop_uninit) /* uintptr_t dtrace_caller(int aframes) */ LEAF(dtrace_caller) li v0, -1 j ra nop END(dtrace_caller) Index: user/ngie/detangle-rc/sys/cddl/dev/dtrace/mips/dtrace_subr.c =================================================================== --- user/ngie/detangle-rc/sys/cddl/dev/dtrace/mips/dtrace_subr.c (revision 299126) +++ user/ngie/detangle-rc/sys/cddl/dev/dtrace/mips/dtrace_subr.c (revision 299127) @@ -1,201 +1,284 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * $FreeBSD$ * */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #define DELAYBRANCH(x) ((int)(x) < 0) - + +extern int (*dtrace_invop_jump_addr)(struct trapframe *); extern dtrace_id_t dtrace_probeid_error; int dtrace_invop(uintptr_t, struct trapframe *, uintptr_t); typedef struct dtrace_invop_hdlr { - int (*dtih_func)(uintptr_t, uintptr_t *, uintptr_t); + int (*dtih_func)(uintptr_t, struct trapframe *, uintptr_t); struct dtrace_invop_hdlr *dtih_next; } dtrace_invop_hdlr_t; dtrace_invop_hdlr_t *dtrace_invop_hdlr; int dtrace_invop(uintptr_t addr, struct trapframe *stack, uintptr_t eax) { dtrace_invop_hdlr_t *hdlr; int rval; for (hdlr = dtrace_invop_hdlr; hdlr != NULL; hdlr = hdlr->dtih_next) if ((rval = hdlr->dtih_func(addr, stack, eax)) != 0) return (rval); return (0); } +void +dtrace_invop_add(int (*func)(uintptr_t, struct trapframe *, uintptr_t)) +{ + dtrace_invop_hdlr_t *hdlr; + hdlr = kmem_alloc(sizeof (dtrace_invop_hdlr_t), KM_SLEEP); + hdlr->dtih_func = func; + hdlr->dtih_next = dtrace_invop_hdlr; + dtrace_invop_hdlr = hdlr; +} + +void +dtrace_invop_remove(int (*func)(uintptr_t, struct trapframe *, uintptr_t)) +{ + dtrace_invop_hdlr_t *hdlr, *prev; + + hdlr = dtrace_invop_hdlr; + prev = NULL; + + for (;;) { + if (hdlr == NULL) + panic("attempt to remove non-existent invop handler"); + + if (hdlr->dtih_func == func) + break; + + prev = hdlr; + hdlr = hdlr->dtih_next; + } + + if (prev == NULL) { + ASSERT(dtrace_invop_hdlr == hdlr); + dtrace_invop_hdlr = hdlr->dtih_next; + } else { + ASSERT(dtrace_invop_hdlr != hdlr); + prev->dtih_next = hdlr->dtih_next; + } + + kmem_free(hdlr, 0); +} + /*ARGSUSED*/ void dtrace_toxic_ranges(void (*func)(uintptr_t base, uintptr_t limit)) { /* * No toxic regions? */ } void dtrace_xcall(processorid_t cpu, dtrace_xcall_t func, void *arg) { cpuset_t cpus; if (cpu == DTRACE_CPUALL) cpus = all_cpus; else CPU_SETOF(cpu, &cpus); smp_rendezvous_cpus(cpus, smp_no_rendevous_barrier, func, smp_no_rendevous_barrier, arg); } static void dtrace_sync_func(void) { } void dtrace_sync(void) { dtrace_xcall(DTRACE_CPUALL, (dtrace_xcall_t)dtrace_sync_func, NULL); } /* * DTrace needs a high resolution time function which can * be called from a probe context and guaranteed not to have * instrumented with probes itself. * * Returns nanoseconds since boot. */ uint64_t dtrace_gethrtime() { struct timespec curtime; nanouptime(&curtime); return (curtime.tv_sec * 1000000000UL + curtime.tv_nsec); } uint64_t dtrace_gethrestime(void) { struct timespec curtime; getnanotime(&curtime); return (curtime.tv_sec * 1000000000UL + curtime.tv_nsec); } /* Function to handle DTrace traps during probes. See amd64/amd64/trap.c */ int dtrace_trap(struct trapframe *frame, u_int type) { /* * A trap can occur while DTrace executes a probe. Before * executing the probe, DTrace blocks re-scheduling and sets * a flag in its per-cpu flags to indicate that it doesn't * want to fault. On returning from the probe, the no-fault * flag is cleared and finally re-scheduling is enabled. * * Check if DTrace has enabled 'no-fault' mode: */ if ((cpu_core[curcpu].cpuc_dtrace_flags & CPU_DTRACE_NOFAULT) != 0) { /* * There are only a couple of trap types that are expected. * All the rest will be handled in the usual way. */ switch (type) { /* Page fault. */ case T_TLB_ST_MISS: case T_ADDR_ERR_ST: case T_TLB_LD_MISS: case T_ADDR_ERR_LD: case T_BUS_ERR_IFETCH: /* Flag a bad address. */ cpu_core[curcpu].cpuc_dtrace_flags |= CPU_DTRACE_BADADDR; cpu_core[curcpu].cpuc_dtrace_illval = frame->badvaddr; /* * Offset the instruction pointer to the instruction * following the one causing the fault. */ if (DELAYBRANCH(frame->cause)) /* Check BD bit */ { /* XXX: check MipsEmulateBranch on MIPS64 frame->pc = MipsEmulateBranch(frame, frame->pc, 0, 0); */ panic("%s: delay slot at %jx, badvaddr = %jx\n", __func__, (intmax_t)frame->pc, (intmax_t)frame->badvaddr); } else frame->pc += sizeof(int); return (1); default: /* Handle all other traps in the usual way. */ break; } } /* Handle the trap in the usual way. */ return (0); } void dtrace_probe_error(dtrace_state_t *state, dtrace_epid_t epid, int which, int fault, int fltoffs, uintptr_t illval) { dtrace_probe(dtrace_probeid_error, (uint64_t)(uintptr_t)state, (uintptr_t)epid, (uintptr_t)which, (uintptr_t)fault, (uintptr_t)fltoffs); +} + +static int +dtrace_invop_start(struct trapframe *frame) +{ + register_t *sp; + int16_t offs; + int invop; + + invop = dtrace_invop(frame->pc, frame, frame->pc); + offs = (invop & LDSD_DATA_MASK); + sp = (register_t *)((uint8_t *)frame->sp + offs); + + switch (invop & LDSD_RA_SP_MASK) { + case LD_RA_SP: + frame->ra = *sp; + frame->pc += INSN_SIZE; + break; + case SD_RA_SP: + *(sp) = frame->ra; + frame->pc += INSN_SIZE; + break; + default: + printf("%s: 0x%x undefined\n", __func__, invop); + return (-1); + }; + + return (0); +} + +void +dtrace_invop_init(void) +{ + + dtrace_invop_jump_addr = dtrace_invop_start; +} + +void +dtrace_invop_uninit(void) +{ + + dtrace_invop_jump_addr = 0; } Index: user/ngie/detangle-rc/sys/cddl/dev/fbt/mips/fbt_isa.c =================================================================== --- user/ngie/detangle-rc/sys/cddl/dev/fbt/mips/fbt_isa.c (nonexistent) +++ user/ngie/detangle-rc/sys/cddl/dev/fbt/mips/fbt_isa.c (revision 299127) @@ -0,0 +1,165 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + * + * Portions Copyright 2006-2008 John Birrell jb@freebsd.org + * Portions Copyright 2013 Justin Hibbits jhibbits@freebsd.org + * Portions Copyright 2013 Howard Su howardsu@freebsd.org + * Portions Copyright 2015-2016 Ruslan Bukin + * + * $FreeBSD$ + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include + +#include +#include + +#include "fbt.h" + +#define FBT_PATCHVAL (MIPS_BREAK_INSTR) +#define FBT_ENTRY "entry" +#define FBT_RETURN "return" + +int +fbt_invop(uintptr_t addr, struct trapframe *frame, uintptr_t rval) +{ + solaris_cpu_t *cpu; + fbt_probe_t *fbt; + + cpu = &solaris_cpu[curcpu]; + fbt = fbt_probetab[FBT_ADDR2NDX(addr)]; + + for (; fbt != NULL; fbt = fbt->fbtp_hashnext) { + if ((uintptr_t)fbt->fbtp_patchpoint == addr) { + cpu->cpu_dtrace_caller = addr; + + dtrace_probe(fbt->fbtp_id, frame->a0, + frame->a1, frame->a2, + frame->a3, frame->a4); + + cpu->cpu_dtrace_caller = 0; + return (fbt->fbtp_savedval); + } + } + + return (0); +} + +void +fbt_patch_tracepoint(fbt_probe_t *fbt, fbt_patchval_t val) +{ + + *fbt->fbtp_patchpoint = val; + mips_icache_sync_range((vm_offset_t)fbt->fbtp_patchpoint, 4); +} + +int +fbt_provide_module_function(linker_file_t lf, int symindx, + linker_symval_t *symval, void *opaque) +{ + fbt_probe_t *fbt, *retfbt; + uint32_t *target, *start; + uint32_t *instr, *limit; + const char *name; + char *modname; + + modname = opaque; + name = symval->name; + + /* Check if function is excluded from instrumentation */ + if (fbt_excluded(name)) + return (0); + + instr = (uint32_t *)(symval->value); + limit = (uint32_t *)(symval->value + symval->size); + + /* Look for store double to ra register */ + for (; instr < limit; instr++) { + if ((*instr & LDSD_RA_SP_MASK) == SD_RA_SP) + break; + } + + if (instr >= limit) + return (0); + + fbt = malloc(sizeof (fbt_probe_t), M_FBT, M_WAITOK | M_ZERO); + fbt->fbtp_name = name; + fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, + name, FBT_ENTRY, 3, fbt); + fbt->fbtp_patchpoint = instr; + fbt->fbtp_ctl = lf; + fbt->fbtp_loadcnt = lf->loadcnt; + fbt->fbtp_savedval = *instr; + fbt->fbtp_patchval = FBT_PATCHVAL; + fbt->fbtp_rval = DTRACE_INVOP_SD; + fbt->fbtp_symindx = symindx; + + fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; + fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; + + lf->fbt_nentries++; + + retfbt = NULL; +again: + for (; instr < limit; instr++) { + if ((*instr & LDSD_RA_SP_MASK) == LD_RA_SP) { + break; + } + } + + if (instr >= limit) + return (0); + + /* + * We have a winner! + */ + fbt = malloc(sizeof (fbt_probe_t), M_FBT, M_WAITOK | M_ZERO); + fbt->fbtp_name = name; + if (retfbt == NULL) { + fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, + name, FBT_RETURN, 3, fbt); + } else { + retfbt->fbtp_next = fbt; + fbt->fbtp_id = retfbt->fbtp_id; + } + retfbt = fbt; + + fbt->fbtp_patchpoint = instr; + fbt->fbtp_ctl = lf; + fbt->fbtp_loadcnt = lf->loadcnt; + fbt->fbtp_symindx = symindx; + fbt->fbtp_rval = DTRACE_INVOP_LD; + fbt->fbtp_savedval = *instr; + fbt->fbtp_patchval = FBT_PATCHVAL; + fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; + fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; + + lf->fbt_nentries++; + + instr++; + goto again; +} Property changes on: user/ngie/detangle-rc/sys/cddl/dev/fbt/mips/fbt_isa.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: user/ngie/detangle-rc/sys/cddl/dev/fbt/mips/fbt_isa.h =================================================================== --- user/ngie/detangle-rc/sys/cddl/dev/fbt/mips/fbt_isa.h (nonexistent) +++ user/ngie/detangle-rc/sys/cddl/dev/fbt/mips/fbt_isa.h (revision 299127) @@ -0,0 +1,30 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + * + * $FreeBSD$ + * + */ + +#ifndef _FBT_ISA_H_ +#define _FBT_ISA_H_ + +typedef uint32_t fbt_patchval_t; + +#endif Property changes on: user/ngie/detangle-rc/sys/cddl/dev/fbt/mips/fbt_isa.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: user/ngie/detangle-rc/sys/conf/files.mips =================================================================== --- user/ngie/detangle-rc/sys/conf/files.mips (revision 299126) +++ user/ngie/detangle-rc/sys/conf/files.mips (revision 299127) @@ -1,97 +1,103 @@ # This file tells config what files go into building a kernel, # files marked standard are always included. # # $FreeBSD$ # # Arch dependent files mips/mips/autoconf.c standard mips/mips/bus_space_generic.c standard mips/mips/busdma_machdep.c standard mips/mips/cache.c standard mips/mips/cache_mipsNN.c standard mips/mips/cpu.c standard mips/mips/db_disasm.c optional ddb mips/mips/db_interface.c optional ddb mips/mips/db_trace.c optional ddb mips/mips/dump_machdep.c standard mips/mips/elf_machdep.c standard mips/mips/exception.S standard mips/mips/fp.S standard mips/mips/freebsd32_machdep.c optional compat_freebsd32 mips/mips/gdb_machdep.c standard mips/mips/in_cksum.c optional inet mips/mips/libkern_machdep.c standard mips/mips/locore.S standard no-obj mips/mips/machdep.c standard mips/mips/mem.c optional mem mips/mips/minidump_machdep.c standard mips/mips/mp_machdep.c optional smp mips/mips/mpboot.S optional smp mips/mips/nexus.c standard mips/mips/ofw_machdep.c optional fdt mips/mips/pm_machdep.c standard mips/mips/pmap.c standard mips/mips/ptrace_machdep.c standard mips/mips/sc_machdep.c standard mips/mips/stack_machdep.c optional ddb | stack mips/mips/stdatomic.c standard \ compile-with "${NORMAL_C:N-Wmissing-prototypes}" mips/mips/support.S standard mips/mips/bcopy.S standard mips/mips/swtch.S standard mips/mips/sys_machdep.c standard mips/mips/tlb.c standard mips/mips/trap.c standard mips/mips/uio_machdep.c standard mips/mips/uma_machdep.c standard mips/mips/vm_machdep.c standard # misc opt-in bits kern/kern_clocksource.c standard kern/link_elf_obj.c standard kern/subr_busdma_bufalloc.c standard kern/subr_dummy_vdso_tc.c standard kern/subr_sfbuf.c optional mips | mipsel | mipsn32 # gcc/clang runtime libkern/ffsl.c standard libkern/ffsll.c standard libkern/fls.c standard libkern/flsl.c standard libkern/flsll.c standard libkern/memmove.c standard libkern/cmpdi2.c optional mips | mipsel libkern/ucmpdi2.c optional mips | mipsel libkern/ashldi3.c standard libkern/ashrdi3.c standard # cfe support dev/cfe/cfe_api.c optional cfe dev/cfe/cfe_console.c optional cfe_console dev/cfe/cfe_env.c optional cfe_env # syscons support dev/fb/fb.c optional sc dev/syscons/scgfbrndr.c optional sc dev/syscons/scterm-teken.c optional sc dev/syscons/scvtb.c optional sc mips/mips/sc_machdep.c optional sc # FDT support dev/uart/uart_cpu_fdt.c optional uart fdt # crypto support -- use generic crypto/blowfish/bf_enc.c optional crypto | ipsec crypto/des/des_enc.c optional crypto | ipsec | netsmb # AP common nvram interface MIPS specific, but maybe should be more generic dev/nvram2env/nvram2env.c optional nvram2env # hwpmc support dev/hwpmc/hwpmc_mips.c optional hwpmc dev/hwpmc/hwpmc_mips24k.c optional hwpmc_mips24k dev/hwpmc/hwpmc_mips74k.c optional hwpmc_mips74k # ofw support dev/ofw/ofwpci.c optional fdt pci + +# DTrace +cddl/compat/opensolaris/kern/opensolaris_atomic.c optional zfs | dtrace compile-with "${CDDL_C}" +cddl/dev/dtrace/mips/dtrace_asm.S optional dtrace compile-with "${DTRACE_S}" +cddl/dev/dtrace/mips/dtrace_subr.c optional dtrace compile-with "${DTRACE_C}" +cddl/dev/fbt/mips/fbt_isa.c optional dtrace_fbt | dtraceall compile-with "${FBT_C}" Index: user/ngie/detangle-rc/sys/conf/options.arm =================================================================== --- user/ngie/detangle-rc/sys/conf/options.arm (revision 299126) +++ user/ngie/detangle-rc/sys/conf/options.arm (revision 299127) @@ -1,77 +1,78 @@ #$FreeBSD$ ARMV6 opt_global.h ARM_CACHE_LOCK_ENABLE opt_global.h ARM_KERN_DIRECTMAP opt_vm.h ARM_L2_PIPT opt_global.h ARM_MANY_BOARD opt_global.h NKPT2PG opt_pmap.h ARM_WANT_TP_ADDRESS opt_global.h COUNTS_PER_SEC opt_timer.h CPU_ARM9 opt_global.h CPU_ARM9E opt_global.h CPU_ARM1176 opt_global.h CPU_CORTEXA opt_global.h CPU_KRAIT opt_global.h CPU_FA526 opt_global.h CPU_MV_PJ4B opt_global.h CPU_XSCALE_81342 opt_global.h CPU_XSCALE_IXP425 opt_global.h CPU_XSCALE_IXP435 opt_global.h CPU_XSCALE_PXA2X0 opt_global.h DEV_GIC opt_global.h DEV_PMU opt_global.h EFI opt_platform.h FLASHADDR opt_global.h GIC_DEFAULT_ICFGR_INIT opt_global.h INTRNG opt_global.h IPI_IRQ_START opt_smp.h IPI_IRQ_END opt_smp.h FREEBSD_BOOT_LOADER opt_global.h IXP4XX_FLASH_SIZE opt_global.h KERNBASE opt_global.h KERNVIRTADDR opt_global.h LINUX_BOOT_ABI opt_global.h LOADERRAMADDR opt_global.h MULTIDELAY opt_global.h PHYSADDR opt_global.h PLATFORM opt_global.h PLATFORM_SMP opt_global.h SOCDEV_PA opt_global.h SOCDEV_VA opt_global.h PV_STATS opt_pmap.h QEMU_WORKAROUNDS opt_global.h SOC_ALLWINNER_A10 opt_global.h SOC_ALLWINNER_A20 opt_global.h SOC_ALLWINNER_A31 opt_global.h SOC_ALLWINNER_A31S opt_global.h +SOC_ALLWINNER_A83T opt_global.h SOC_BCM2835 opt_global.h SOC_BCM2836 opt_global.h SOC_IMX51 opt_global.h SOC_IMX53 opt_global.h SOC_IMX6 opt_global.h SOC_MV_ARMADAXP opt_global.h SOC_MV_ARMADA38X opt_global.h SOC_MV_DISCOVERY opt_global.h SOC_MV_DOVE opt_global.h SOC_MV_FREY opt_global.h SOC_MV_KIRKWOOD opt_global.h SOC_MV_LOKIPLUS opt_global.h SOC_MV_ORION opt_global.h SOC_OMAP3 opt_global.h SOC_OMAP4 opt_global.h SOC_TI_AM335X opt_global.h SOC_TEGRA2 opt_global.h XSCALE_CACHE_READ_WRITE_ALLOCATE opt_global.h XSACLE_DISABLE_CCNT opt_timer.h VERBOSE_INIT_ARM opt_global.h VM_MAXUSER_ADDRESS opt_global.h AT91_ATE_USE_RMII opt_at91.h AT91_MACB_USE_RMII opt_at91.h AT91_MCI_ALLOW_OVERCLOCK opt_at91.h AT91_MCI_HAS_4WIRE opt_at91.h AT91_MCI_SLOT_B opt_at91.h GFB_DEBUG opt_gfb.h GFB_NO_FONT_LOADING opt_gfb.h GFB_NO_MODE_CHANGE opt_gfb.h AT91C_MAIN_CLOCK opt_at91.h VFP opt_global.h Index: user/ngie/detangle-rc/sys/dev/acpica/acpi_thermal.c =================================================================== --- user/ngie/detangle-rc/sys/dev/acpica/acpi_thermal.c (revision 299126) +++ user/ngie/detangle-rc/sys/dev/acpica/acpi_thermal.c (revision 299127) @@ -1,1214 +1,1214 @@ /*- * Copyright (c) 2000, 2001 Michael Smith * Copyright (c) 2000 BSDi * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_THERMAL ACPI_MODULE_NAME("THERMAL") #define TZ_ZEROC 2732 #define TZ_KELVTOC(x) (((x) - TZ_ZEROC) / 10), abs(((x) - TZ_ZEROC) % 10) #define TZ_NOTIFY_TEMPERATURE 0x80 /* Temperature changed. */ #define TZ_NOTIFY_LEVELS 0x81 /* Cooling levels changed. */ #define TZ_NOTIFY_DEVICES 0x82 /* Device lists changed. */ #define TZ_NOTIFY_CRITICAL 0xcc /* Fake notify that _CRT/_HOT reached. */ /* Check for temperature changes every 10 seconds by default */ #define TZ_POLLRATE 10 /* Make sure the reported temperature is valid for this number of polls. */ #define TZ_VALIDCHECKS 3 /* Notify the user we will be shutting down in one more poll cycle. */ #define TZ_NOTIFYCOUNT (TZ_VALIDCHECKS - 1) /* ACPI spec defines this */ #define TZ_NUMLEVELS 10 struct acpi_tz_zone { int ac[TZ_NUMLEVELS]; ACPI_BUFFER al[TZ_NUMLEVELS]; int crt; int hot; ACPI_BUFFER psl; int psv; int tc1; int tc2; int tsp; int tzp; }; struct acpi_tz_softc { device_t tz_dev; ACPI_HANDLE tz_handle; /*Thermal zone handle*/ int tz_temperature; /*Current temperature*/ int tz_active; /*Current active cooling*/ #define TZ_ACTIVE_NONE -1 #define TZ_ACTIVE_UNKNOWN -2 int tz_requested; /*Minimum active cooling*/ int tz_thflags; /*Current temp-related flags*/ #define TZ_THFLAG_NONE 0 #define TZ_THFLAG_PSV (1<<0) #define TZ_THFLAG_HOT (1<<2) #define TZ_THFLAG_CRT (1<<3) int tz_flags; #define TZ_FLAG_NO_SCP (1<<0) /*No _SCP method*/ #define TZ_FLAG_GETPROFILE (1<<1) /*Get power_profile in timeout*/ #define TZ_FLAG_GETSETTINGS (1<<2) /*Get devs/setpoints*/ struct timespec tz_cooling_started; /*Current cooling starting time*/ struct sysctl_ctx_list tz_sysctl_ctx; struct sysctl_oid *tz_sysctl_tree; eventhandler_tag tz_event; struct acpi_tz_zone tz_zone; /*Thermal zone parameters*/ int tz_validchecks; int tz_insane_tmp_notified; /* passive cooling */ struct proc *tz_cooling_proc; int tz_cooling_proc_running; int tz_cooling_enabled; int tz_cooling_active; int tz_cooling_updated; int tz_cooling_saved_freq; }; #define TZ_ACTIVE_LEVEL(act) ((act) >= 0 ? (act) : TZ_NUMLEVELS) #define CPUFREQ_MAX_LEVELS 64 /* XXX cpufreq should export this */ static int acpi_tz_probe(device_t dev); static int acpi_tz_attach(device_t dev); static int acpi_tz_establish(struct acpi_tz_softc *sc); static void acpi_tz_monitor(void *Context); static void acpi_tz_switch_cooler_off(ACPI_OBJECT *obj, void *arg); static void acpi_tz_switch_cooler_on(ACPI_OBJECT *obj, void *arg); static void acpi_tz_getparam(struct acpi_tz_softc *sc, char *node, int *data); static void acpi_tz_sanity(struct acpi_tz_softc *sc, int *val, char *what); static int acpi_tz_active_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_tz_cooling_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_tz_temp_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_tz_passive_sysctl(SYSCTL_HANDLER_ARGS); static void acpi_tz_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context); static void acpi_tz_signal(struct acpi_tz_softc *sc, int flags); static void acpi_tz_timeout(struct acpi_tz_softc *sc, int flags); static void acpi_tz_power_profile(void *arg); static void acpi_tz_thread(void *arg); static int acpi_tz_cooling_is_available(struct acpi_tz_softc *sc); static int acpi_tz_cooling_thread_start(struct acpi_tz_softc *sc); static device_method_t acpi_tz_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_tz_probe), DEVMETHOD(device_attach, acpi_tz_attach), DEVMETHOD_END }; static driver_t acpi_tz_driver = { "acpi_tz", acpi_tz_methods, sizeof(struct acpi_tz_softc), }; static char *acpi_tz_tmp_name = "_TMP"; static devclass_t acpi_tz_devclass; DRIVER_MODULE(acpi_tz, acpi, acpi_tz_driver, acpi_tz_devclass, 0, 0); MODULE_DEPEND(acpi_tz, acpi, 1, 1, 1); static struct sysctl_ctx_list acpi_tz_sysctl_ctx; static struct sysctl_oid *acpi_tz_sysctl_tree; /* Minimum cooling run time */ static int acpi_tz_min_runtime; static int acpi_tz_polling_rate = TZ_POLLRATE; static int acpi_tz_override; /* Timezone polling thread */ static struct proc *acpi_tz_proc; ACPI_LOCK_DECL(thermal, "ACPI thermal zone"); static int acpi_tz_cooling_unit = -1; static int acpi_tz_probe(device_t dev) { int result; if (acpi_get_type(dev) == ACPI_TYPE_THERMAL && !acpi_disabled("thermal")) { device_set_desc(dev, "Thermal Zone"); result = -10; } else result = ENXIO; return (result); } static int acpi_tz_attach(device_t dev) { struct acpi_tz_softc *sc; struct acpi_softc *acpi_sc; int error; char oidname[8]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->tz_dev = dev; sc->tz_handle = acpi_get_handle(dev); sc->tz_requested = TZ_ACTIVE_NONE; sc->tz_active = TZ_ACTIVE_UNKNOWN; sc->tz_thflags = TZ_THFLAG_NONE; sc->tz_cooling_proc = NULL; sc->tz_cooling_proc_running = FALSE; sc->tz_cooling_active = FALSE; sc->tz_cooling_updated = FALSE; sc->tz_cooling_enabled = FALSE; /* * Parse the current state of the thermal zone and build control * structures. We don't need to worry about interference with the * control thread since we haven't fully attached this device yet. */ if ((error = acpi_tz_establish(sc)) != 0) return (error); /* * Register for any Notify events sent to this zone. */ AcpiInstallNotifyHandler(sc->tz_handle, ACPI_DEVICE_NOTIFY, acpi_tz_notify_handler, sc); /* * Create our sysctl nodes. * * XXX we need a mechanism for adding nodes under ACPI. */ if (device_get_unit(dev) == 0) { acpi_sc = acpi_device_get_parent_softc(dev); sysctl_ctx_init(&acpi_tz_sysctl_ctx); acpi_tz_sysctl_tree = SYSCTL_ADD_NODE(&acpi_tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "thermal", CTLFLAG_RD, 0, ""); SYSCTL_ADD_INT(&acpi_tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, "min_runtime", CTLFLAG_RW, &acpi_tz_min_runtime, 0, "minimum cooling run time in sec"); SYSCTL_ADD_INT(&acpi_tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, "polling_rate", CTLFLAG_RW, &acpi_tz_polling_rate, 0, "monitor polling interval in seconds"); SYSCTL_ADD_INT(&acpi_tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, "user_override", CTLFLAG_RW, &acpi_tz_override, 0, "allow override of thermal settings"); } sysctl_ctx_init(&sc->tz_sysctl_ctx); sprintf(oidname, "tz%d", device_get_unit(dev)); sc->tz_sysctl_tree = SYSCTL_ADD_NODE(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, oidname, CTLFLAG_RD, 0, ""); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, &sc->tz_temperature, 0, sysctl_handle_int, "IK", "current thermal zone temperature"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "active", CTLTYPE_INT | CTLFLAG_RW, sc, 0, acpi_tz_active_sysctl, "I", "cooling is active"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "passive_cooling", CTLTYPE_INT | CTLFLAG_RW, sc, 0, acpi_tz_cooling_sysctl, "I", "enable passive (speed reduction) cooling"); SYSCTL_ADD_INT(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "thermal_flags", CTLFLAG_RD, &sc->tz_thflags, 0, "thermal zone flags"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_PSV", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.psv), acpi_tz_temp_sysctl, "IK", "passive cooling temp setpoint"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_HOT", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.hot), acpi_tz_temp_sysctl, "IK", "too hot temp setpoint (suspend now)"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_CRT", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.crt), acpi_tz_temp_sysctl, "IK", "critical temp setpoint (shutdown now)"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_ACx", CTLTYPE_INT | CTLFLAG_RD, &sc->tz_zone.ac, sizeof(sc->tz_zone.ac), sysctl_handle_opaque, "IK", ""); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_TC1", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.tc1), acpi_tz_passive_sysctl, "I", "thermal constant 1 for passive cooling"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_TC2", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.tc2), acpi_tz_passive_sysctl, "I", "thermal constant 2 for passive cooling"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_TSP", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.tsp), acpi_tz_passive_sysctl, "I", "thermal sampling period for passive cooling"); /* * Create thread to service all of the thermal zones. Register * our power profile event handler. */ sc->tz_event = EVENTHANDLER_REGISTER(power_profile_change, acpi_tz_power_profile, sc, 0); if (acpi_tz_proc == NULL) { error = kproc_create(acpi_tz_thread, NULL, &acpi_tz_proc, RFHIGHPID, 0, "acpi_thermal"); if (error != 0) { device_printf(sc->tz_dev, "could not create thread - %d", error); goto out; } } /* * Create a thread to handle passive cooling for 1st zone which * has _PSV, _TSP, _TC1 and _TC2. Users can enable it for other * zones manually for now. * * XXX We enable only one zone to avoid multiple zones conflict * with each other since cpufreq currently sets all CPUs to the * given frequency whereas it's possible for different thermal * zones to specify independent settings for multiple CPUs. */ if (acpi_tz_cooling_unit < 0 && acpi_tz_cooling_is_available(sc)) sc->tz_cooling_enabled = TRUE; if (sc->tz_cooling_enabled) { error = acpi_tz_cooling_thread_start(sc); if (error != 0) { sc->tz_cooling_enabled = FALSE; goto out; } acpi_tz_cooling_unit = device_get_unit(dev); } /* * Flag the event handler for a manual invocation by our timeout. * We defer it like this so that the rest of the subsystem has time * to come up. Don't bother evaluating/printing the temperature at * this point; on many systems it'll be bogus until the EC is running. */ sc->tz_flags |= TZ_FLAG_GETPROFILE; out: if (error != 0) { EVENTHANDLER_DEREGISTER(power_profile_change, sc->tz_event); AcpiRemoveNotifyHandler(sc->tz_handle, ACPI_DEVICE_NOTIFY, acpi_tz_notify_handler); sysctl_ctx_free(&sc->tz_sysctl_ctx); } return_VALUE (error); } /* * Parse the current state of this thermal zone and set up to use it. * * Note that we may have previous state, which will have to be discarded. */ static int acpi_tz_establish(struct acpi_tz_softc *sc) { ACPI_OBJECT *obj; int i; char nbuf[8]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Erase any existing state. */ for (i = 0; i < TZ_NUMLEVELS; i++) if (sc->tz_zone.al[i].Pointer != NULL) AcpiOsFree(sc->tz_zone.al[i].Pointer); if (sc->tz_zone.psl.Pointer != NULL) AcpiOsFree(sc->tz_zone.psl.Pointer); /* * XXX: We initialize only ACPI_BUFFER to avoid race condition * with passive cooling thread which refers psv, tc1, tc2 and tsp. */ bzero(sc->tz_zone.ac, sizeof(sc->tz_zone.ac)); bzero(sc->tz_zone.al, sizeof(sc->tz_zone.al)); bzero(&sc->tz_zone.psl, sizeof(sc->tz_zone.psl)); /* Evaluate thermal zone parameters. */ for (i = 0; i < TZ_NUMLEVELS; i++) { sprintf(nbuf, "_AC%d", i); acpi_tz_getparam(sc, nbuf, &sc->tz_zone.ac[i]); sprintf(nbuf, "_AL%d", i); sc->tz_zone.al[i].Length = ACPI_ALLOCATE_BUFFER; sc->tz_zone.al[i].Pointer = NULL; AcpiEvaluateObject(sc->tz_handle, nbuf, NULL, &sc->tz_zone.al[i]); obj = (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer; if (obj != NULL) { /* Should be a package containing a list of power objects */ if (obj->Type != ACPI_TYPE_PACKAGE) { device_printf(sc->tz_dev, "%s has unknown type %d, rejecting\n", nbuf, obj->Type); return_VALUE (ENXIO); } } } acpi_tz_getparam(sc, "_CRT", &sc->tz_zone.crt); acpi_tz_getparam(sc, "_HOT", &sc->tz_zone.hot); sc->tz_zone.psl.Length = ACPI_ALLOCATE_BUFFER; sc->tz_zone.psl.Pointer = NULL; AcpiEvaluateObject(sc->tz_handle, "_PSL", NULL, &sc->tz_zone.psl); acpi_tz_getparam(sc, "_PSV", &sc->tz_zone.psv); acpi_tz_getparam(sc, "_TC1", &sc->tz_zone.tc1); acpi_tz_getparam(sc, "_TC2", &sc->tz_zone.tc2); acpi_tz_getparam(sc, "_TSP", &sc->tz_zone.tsp); acpi_tz_getparam(sc, "_TZP", &sc->tz_zone.tzp); /* * Sanity-check the values we've been given. * * XXX what do we do about systems that give us the same value for * more than one of these setpoints? */ acpi_tz_sanity(sc, &sc->tz_zone.crt, "_CRT"); acpi_tz_sanity(sc, &sc->tz_zone.hot, "_HOT"); acpi_tz_sanity(sc, &sc->tz_zone.psv, "_PSV"); for (i = 0; i < TZ_NUMLEVELS; i++) acpi_tz_sanity(sc, &sc->tz_zone.ac[i], "_ACx"); return_VALUE (0); } static char *aclevel_string[] = { "NONE", "_AC0", "_AC1", "_AC2", "_AC3", "_AC4", "_AC5", "_AC6", "_AC7", "_AC8", "_AC9" }; static __inline const char * acpi_tz_aclevel_string(int active) { if (active < -1 || active >= TZ_NUMLEVELS) return (aclevel_string[0]); return (aclevel_string[active + 1]); } /* * Get the current temperature. */ static int acpi_tz_get_temperature(struct acpi_tz_softc *sc) { int temp; ACPI_STATUS status; ACPI_FUNCTION_NAME ("acpi_tz_get_temperature"); /* Evaluate the thermal zone's _TMP method. */ status = acpi_GetInteger(sc->tz_handle, acpi_tz_tmp_name, &temp); if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "error fetching current temperature -- %s\n", AcpiFormatException(status)); return (FALSE); } /* Check it for validity. */ acpi_tz_sanity(sc, &temp, acpi_tz_tmp_name); if (temp == -1) return (FALSE); ACPI_DEBUG_PRINT((ACPI_DB_VALUES, "got %d.%dC\n", TZ_KELVTOC(temp))); sc->tz_temperature = temp; return (TRUE); } /* * Evaluate the condition of a thermal zone, take appropriate actions. */ static void acpi_tz_monitor(void *Context) { struct acpi_tz_softc *sc; struct timespec curtime; int temp; int i; int newactive, newflags; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_tz_softc *)Context; /* Get the current temperature. */ if (!acpi_tz_get_temperature(sc)) { /* XXX disable zone? go to max cooling? */ return_VOID; } temp = sc->tz_temperature; /* * Work out what we ought to be doing right now. * * Note that the _ACx levels sort from hot to cold. */ newactive = TZ_ACTIVE_NONE; for (i = TZ_NUMLEVELS - 1; i >= 0; i--) { if (sc->tz_zone.ac[i] != -1 && temp >= sc->tz_zone.ac[i]) newactive = i; } /* * We are going to get _ACx level down (colder side), but give a guaranteed * minimum cooling run time if requested. */ if (acpi_tz_min_runtime > 0 && sc->tz_active != TZ_ACTIVE_NONE && sc->tz_active != TZ_ACTIVE_UNKNOWN && (newactive == TZ_ACTIVE_NONE || newactive > sc->tz_active)) { getnanotime(&curtime); timespecsub(&curtime, &sc->tz_cooling_started); if (curtime.tv_sec < acpi_tz_min_runtime) newactive = sc->tz_active; } /* Handle user override of active mode */ if (sc->tz_requested != TZ_ACTIVE_NONE && (newactive == TZ_ACTIVE_NONE || sc->tz_requested < newactive)) newactive = sc->tz_requested; /* update temperature-related flags */ newflags = TZ_THFLAG_NONE; if (sc->tz_zone.psv != -1 && temp >= sc->tz_zone.psv) newflags |= TZ_THFLAG_PSV; if (sc->tz_zone.hot != -1 && temp >= sc->tz_zone.hot) newflags |= TZ_THFLAG_HOT; if (sc->tz_zone.crt != -1 && temp >= sc->tz_zone.crt) newflags |= TZ_THFLAG_CRT; /* If the active cooling state has changed, we have to switch things. */ if (sc->tz_active == TZ_ACTIVE_UNKNOWN) { /* * We don't know which cooling device is on or off, * so stop them all, because we now know which * should be on (if any). */ for (i = 0; i < TZ_NUMLEVELS; i++) { if (sc->tz_zone.al[i].Pointer != NULL) { acpi_ForeachPackageObject( (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer, acpi_tz_switch_cooler_off, sc); } } /* now we know that all devices are off */ sc->tz_active = TZ_ACTIVE_NONE; } if (newactive != sc->tz_active) { /* Turn off unneeded cooling devices that are on, if any are */ for (i = TZ_ACTIVE_LEVEL(sc->tz_active); i < TZ_ACTIVE_LEVEL(newactive); i++) { acpi_ForeachPackageObject( (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer, acpi_tz_switch_cooler_off, sc); } /* Turn on cooling devices that are required, if any are */ for (i = TZ_ACTIVE_LEVEL(sc->tz_active) - 1; i >= TZ_ACTIVE_LEVEL(newactive); i--) { acpi_ForeachPackageObject( (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer, acpi_tz_switch_cooler_on, sc); } ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "switched from %s to %s: %d.%dC\n", acpi_tz_aclevel_string(sc->tz_active), acpi_tz_aclevel_string(newactive), TZ_KELVTOC(temp)); sc->tz_active = newactive; getnanotime(&sc->tz_cooling_started); } /* XXX (de)activate any passive cooling that may be required. */ /* * If the temperature is at _HOT or _CRT, increment our event count. * If it has occurred enough times, shutdown the system. This is * needed because some systems will report an invalid high temperature * for one poll cycle. It is suspected this is due to the embedded * controller timing out. A typical value is 138C for one cycle on * a system that is otherwise 65C. * * If we're almost at that threshold, notify the user through devd(8). */ if ((newflags & (TZ_THFLAG_HOT | TZ_THFLAG_CRT)) != 0) { sc->tz_validchecks++; if (sc->tz_validchecks == TZ_VALIDCHECKS) { device_printf(sc->tz_dev, "WARNING - current temperature (%d.%dC) exceeds safe limits\n", TZ_KELVTOC(sc->tz_temperature)); shutdown_nice(RB_POWEROFF); } else if (sc->tz_validchecks == TZ_NOTIFYCOUNT) acpi_UserNotify("Thermal", sc->tz_handle, TZ_NOTIFY_CRITICAL); } else { sc->tz_validchecks = 0; } sc->tz_thflags = newflags; return_VOID; } /* * Given an object, verify that it's a reference to a device of some sort, * and try to switch it off. */ static void acpi_tz_switch_cooler_off(ACPI_OBJECT *obj, void *arg) { ACPI_HANDLE cooler; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); cooler = acpi_GetReference(NULL, obj); if (cooler == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't get handle\n")); return_VOID; } ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to turn %s off\n", acpi_name(cooler))); acpi_pwr_switch_consumer(cooler, ACPI_STATE_D3); return_VOID; } /* * Given an object, verify that it's a reference to a device of some sort, * and try to switch it on. * * XXX replication of off/on function code is bad. */ static void acpi_tz_switch_cooler_on(ACPI_OBJECT *obj, void *arg) { struct acpi_tz_softc *sc = (struct acpi_tz_softc *)arg; ACPI_HANDLE cooler; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); cooler = acpi_GetReference(NULL, obj); if (cooler == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't get handle\n")); return_VOID; } ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to turn %s on\n", acpi_name(cooler))); status = acpi_pwr_switch_consumer(cooler, ACPI_STATE_D0); if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "failed to activate %s - %s\n", acpi_name(cooler), AcpiFormatException(status)); } return_VOID; } /* * Read/debug-print a parameter, default it to -1. */ static void acpi_tz_getparam(struct acpi_tz_softc *sc, char *node, int *data) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(acpi_GetInteger(sc->tz_handle, node, data))) { *data = -1; } else { ACPI_DEBUG_PRINT((ACPI_DB_VALUES, "%s.%s = %d\n", acpi_name(sc->tz_handle), node, *data)); } return_VOID; } /* * Sanity-check a temperature value. Assume that setpoints * should be between 0C and 200C. */ static void acpi_tz_sanity(struct acpi_tz_softc *sc, int *val, char *what) { if (*val != -1 && (*val < TZ_ZEROC || *val > TZ_ZEROC + 2000)) { /* * If the value we are checking is _TMP, warn the user only * once. This avoids spamming messages if, for instance, the * sensor is broken and always returns an invalid temperature. * * This is only done for _TMP; other values always emit a * warning. */ if (what != acpi_tz_tmp_name || !sc->tz_insane_tmp_notified) { device_printf(sc->tz_dev, "%s value is absurd, ignored (%d.%dC)\n", what, TZ_KELVTOC(*val)); /* Don't warn the user again if the read value doesn't improve. */ if (what == acpi_tz_tmp_name) sc->tz_insane_tmp_notified = 1; } *val = -1; return; } /* This value is correct. Warn if it's incorrect again. */ if (what == acpi_tz_tmp_name) sc->tz_insane_tmp_notified = 0; } /* * Respond to a sysctl on the active state node. */ static int acpi_tz_active_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_tz_softc *sc; int active; int error; sc = (struct acpi_tz_softc *)oidp->oid_arg1; active = sc->tz_active; error = sysctl_handle_int(oidp, &active, 0, req); /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); if (active < -1 || active >= TZ_NUMLEVELS) return (EINVAL); /* Set new preferred level and re-switch */ sc->tz_requested = active; acpi_tz_signal(sc, 0); return (0); } static int acpi_tz_cooling_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_tz_softc *sc; int enabled, error; sc = (struct acpi_tz_softc *)oidp->oid_arg1; enabled = sc->tz_cooling_enabled; error = sysctl_handle_int(oidp, &enabled, 0, req); /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); if (enabled != TRUE && enabled != FALSE) return (EINVAL); if (enabled) { if (acpi_tz_cooling_is_available(sc)) error = acpi_tz_cooling_thread_start(sc); else error = ENODEV; if (error) enabled = FALSE; } sc->tz_cooling_enabled = enabled; return (error); } static int acpi_tz_temp_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_tz_softc *sc; int temp, *temp_ptr; int error; sc = oidp->oid_arg1; - temp_ptr = (int *)((uintptr_t)sc + oidp->oid_arg2); + temp_ptr = (int *)(void *)(uintptr_t)((uintptr_t)sc + oidp->oid_arg2); temp = *temp_ptr; error = sysctl_handle_int(oidp, &temp, 0, req); /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); /* Only allow changing settings if override is set. */ if (!acpi_tz_override) return (EPERM); /* Check user-supplied value for sanity. */ acpi_tz_sanity(sc, &temp, "user-supplied temp"); if (temp == -1) return (EINVAL); *temp_ptr = temp; return (0); } static int acpi_tz_passive_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_tz_softc *sc; int val, *val_ptr; int error; sc = oidp->oid_arg1; - val_ptr = (int *)((uintptr_t)sc + oidp->oid_arg2); + val_ptr = (int *)(void *)(uintptr_t)((uintptr_t)sc + oidp->oid_arg2); val = *val_ptr; error = sysctl_handle_int(oidp, &val, 0, req); /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); /* Only allow changing settings if override is set. */ if (!acpi_tz_override) return (EPERM); *val_ptr = val; return (0); } static void acpi_tz_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_tz_softc *sc = (struct acpi_tz_softc *)context; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); switch (notify) { case TZ_NOTIFY_TEMPERATURE: /* Temperature change occurred */ acpi_tz_signal(sc, 0); break; case TZ_NOTIFY_DEVICES: case TZ_NOTIFY_LEVELS: /* Zone devices/setpoints changed */ acpi_tz_signal(sc, TZ_FLAG_GETSETTINGS); break; default: ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "unknown Notify event 0x%x\n", notify); break; } acpi_UserNotify("Thermal", h, notify); return_VOID; } static void acpi_tz_signal(struct acpi_tz_softc *sc, int flags) { ACPI_LOCK(thermal); sc->tz_flags |= flags; ACPI_UNLOCK(thermal); wakeup(&acpi_tz_proc); } /* * Notifies can be generated asynchronously but have also been seen to be * triggered by other thermal methods. One system generates a notify of * 0x81 when the fan is turned on or off. Another generates it when _SCP * is called. To handle these situations, we check the zone via * acpi_tz_monitor() before evaluating changes to setpoints or the cooling * policy. */ static void acpi_tz_timeout(struct acpi_tz_softc *sc, int flags) { /* Check the current temperature and take action based on it */ acpi_tz_monitor(sc); /* If requested, get the power profile settings. */ if (flags & TZ_FLAG_GETPROFILE) acpi_tz_power_profile(sc); /* * If requested, check for new devices/setpoints. After finding them, * check if we need to switch fans based on the new values. */ if (flags & TZ_FLAG_GETSETTINGS) { acpi_tz_establish(sc); acpi_tz_monitor(sc); } /* XXX passive cooling actions? */ } /* * System power profile may have changed; fetch and notify the * thermal zone accordingly. * * Since this can be called from an arbitrary eventhandler, it needs * to get the ACPI lock itself. */ static void acpi_tz_power_profile(void *arg) { ACPI_STATUS status; struct acpi_tz_softc *sc = (struct acpi_tz_softc *)arg; int state; state = power_profile_get_state(); if (state != POWER_PROFILE_PERFORMANCE && state != POWER_PROFILE_ECONOMY) return; /* check that we haven't decided there's no _SCP method */ if ((sc->tz_flags & TZ_FLAG_NO_SCP) == 0) { /* Call _SCP to set the new profile */ status = acpi_SetInteger(sc->tz_handle, "_SCP", (state == POWER_PROFILE_PERFORMANCE) ? 0 : 1); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "can't evaluate %s._SCP - %s\n", acpi_name(sc->tz_handle), AcpiFormatException(status)); sc->tz_flags |= TZ_FLAG_NO_SCP; } else { /* We have to re-evaluate the entire zone now */ acpi_tz_signal(sc, TZ_FLAG_GETSETTINGS); } } } /* * Thermal zone monitor thread. */ static void acpi_tz_thread(void *arg) { device_t *devs; int devcount, i; int flags; struct acpi_tz_softc **sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); devs = NULL; devcount = 0; sc = NULL; for (;;) { /* If the number of devices has changed, re-evaluate. */ if (devclass_get_count(acpi_tz_devclass) != devcount) { if (devs != NULL) { free(devs, M_TEMP); free(sc, M_TEMP); } devclass_get_devices(acpi_tz_devclass, &devs, &devcount); sc = malloc(sizeof(struct acpi_tz_softc *) * devcount, M_TEMP, M_WAITOK | M_ZERO); for (i = 0; i < devcount; i++) sc[i] = device_get_softc(devs[i]); } /* Check for temperature events and act on them. */ for (i = 0; i < devcount; i++) { ACPI_LOCK(thermal); flags = sc[i]->tz_flags; sc[i]->tz_flags &= TZ_FLAG_NO_SCP; ACPI_UNLOCK(thermal); acpi_tz_timeout(sc[i], flags); } /* If more work to do, don't go to sleep yet. */ ACPI_LOCK(thermal); for (i = 0; i < devcount; i++) { if (sc[i]->tz_flags & ~TZ_FLAG_NO_SCP) break; } /* * If we have no more work, sleep for a while, setting PDROP so that * the mutex will not be reacquired. Otherwise, drop the mutex and * loop to handle more events. */ if (i == devcount) msleep(&acpi_tz_proc, &thermal_mutex, PZERO | PDROP, "tzpoll", hz * acpi_tz_polling_rate); else ACPI_UNLOCK(thermal); } } static int acpi_tz_cpufreq_restore(struct acpi_tz_softc *sc) { device_t dev; int error; if (!sc->tz_cooling_updated) return (0); if ((dev = devclass_get_device(devclass_find("cpufreq"), 0)) == NULL) return (ENXIO); ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "temperature %d.%dC: resuming previous clock speed (%d MHz)\n", TZ_KELVTOC(sc->tz_temperature), sc->tz_cooling_saved_freq); error = CPUFREQ_SET(dev, NULL, CPUFREQ_PRIO_KERN); if (error == 0) sc->tz_cooling_updated = FALSE; return (error); } static int acpi_tz_cpufreq_update(struct acpi_tz_softc *sc, int req) { device_t dev; struct cf_level *levels; int num_levels, error, freq, desired_freq, perf, i; levels = malloc(CPUFREQ_MAX_LEVELS * sizeof(*levels), M_TEMP, M_NOWAIT); if (levels == NULL) return (ENOMEM); /* * Find the main device, cpufreq0. We don't yet support independent * CPU frequency control on SMP. */ if ((dev = devclass_get_device(devclass_find("cpufreq"), 0)) == NULL) { error = ENXIO; goto out; } /* Get the current frequency. */ error = CPUFREQ_GET(dev, &levels[0]); if (error) goto out; freq = levels[0].total_set.freq; /* Get the current available frequency levels. */ num_levels = CPUFREQ_MAX_LEVELS; error = CPUFREQ_LEVELS(dev, levels, &num_levels); if (error) { if (error == E2BIG) printf("cpufreq: need to increase CPUFREQ_MAX_LEVELS\n"); goto out; } /* Calculate the desired frequency as a percent of the max frequency. */ perf = 100 * freq / levels[0].total_set.freq - req; if (perf < 0) perf = 0; else if (perf > 100) perf = 100; desired_freq = levels[0].total_set.freq * perf / 100; if (desired_freq < freq) { /* Find the closest available frequency, rounding down. */ for (i = 0; i < num_levels; i++) if (levels[i].total_set.freq <= desired_freq) break; /* If we didn't find a relevant setting, use the lowest. */ if (i == num_levels) i--; } else { /* If we didn't decrease frequency yet, don't increase it. */ if (!sc->tz_cooling_updated) { sc->tz_cooling_active = FALSE; goto out; } /* Use saved cpu frequency as maximum value. */ if (desired_freq > sc->tz_cooling_saved_freq) desired_freq = sc->tz_cooling_saved_freq; /* Find the closest available frequency, rounding up. */ for (i = num_levels - 1; i >= 0; i--) if (levels[i].total_set.freq >= desired_freq) break; /* If we didn't find a relevant setting, use the highest. */ if (i == -1) i++; /* If we're going to the highest frequency, restore the old setting. */ if (i == 0 || desired_freq == sc->tz_cooling_saved_freq) { error = acpi_tz_cpufreq_restore(sc); if (error == 0) sc->tz_cooling_active = FALSE; goto out; } } /* If we are going to a new frequency, activate it. */ if (levels[i].total_set.freq != freq) { ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "temperature %d.%dC: %screasing clock speed " "from %d MHz to %d MHz\n", TZ_KELVTOC(sc->tz_temperature), (freq > levels[i].total_set.freq) ? "de" : "in", freq, levels[i].total_set.freq); error = CPUFREQ_SET(dev, &levels[i], CPUFREQ_PRIO_KERN); if (error == 0 && !sc->tz_cooling_updated) { sc->tz_cooling_saved_freq = freq; sc->tz_cooling_updated = TRUE; } } out: if (levels) free(levels, M_TEMP); return (error); } /* * Passive cooling thread; monitors current temperature according to the * cooling interval and calculates whether to scale back CPU frequency. */ static void acpi_tz_cooling_thread(void *arg) { struct acpi_tz_softc *sc; int error, perf, curr_temp, prev_temp; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_tz_softc *)arg; prev_temp = sc->tz_temperature; while (sc->tz_cooling_enabled) { if (sc->tz_cooling_active) (void)acpi_tz_get_temperature(sc); curr_temp = sc->tz_temperature; if (curr_temp >= sc->tz_zone.psv) sc->tz_cooling_active = TRUE; if (sc->tz_cooling_active) { perf = sc->tz_zone.tc1 * (curr_temp - prev_temp) + sc->tz_zone.tc2 * (curr_temp - sc->tz_zone.psv); perf /= 10; if (perf != 0) { error = acpi_tz_cpufreq_update(sc, perf); /* * If error and not simply a higher priority setting was * active, disable cooling. */ if (error != 0 && error != EPERM) { device_printf(sc->tz_dev, "failed to set new freq, disabling passive cooling\n"); sc->tz_cooling_enabled = FALSE; } } } prev_temp = curr_temp; tsleep(&sc->tz_cooling_proc, PZERO, "cooling", hz * sc->tz_zone.tsp / 10); } if (sc->tz_cooling_active) { acpi_tz_cpufreq_restore(sc); sc->tz_cooling_active = FALSE; } sc->tz_cooling_proc = NULL; ACPI_LOCK(thermal); sc->tz_cooling_proc_running = FALSE; ACPI_UNLOCK(thermal); kproc_exit(0); } /* * TODO: We ignore _PSL (list of cooling devices) since cpufreq enumerates * all CPUs for us. However, it's possible in the future _PSL will * reference non-CPU devices so we may want to support it then. */ static int acpi_tz_cooling_is_available(struct acpi_tz_softc *sc) { return (sc->tz_zone.tc1 != -1 && sc->tz_zone.tc2 != -1 && sc->tz_zone.tsp != -1 && sc->tz_zone.tsp != 0 && sc->tz_zone.psv != -1); } static int acpi_tz_cooling_thread_start(struct acpi_tz_softc *sc) { int error; ACPI_LOCK(thermal); if (sc->tz_cooling_proc_running) { ACPI_UNLOCK(thermal); return (0); } sc->tz_cooling_proc_running = TRUE; ACPI_UNLOCK(thermal); error = 0; if (sc->tz_cooling_proc == NULL) { error = kproc_create(acpi_tz_cooling_thread, sc, &sc->tz_cooling_proc, RFHIGHPID, 0, "acpi_cooling%d", device_get_unit(sc->tz_dev)); if (error != 0) { device_printf(sc->tz_dev, "could not create thread - %d", error); ACPI_LOCK(thermal); sc->tz_cooling_proc_running = FALSE; ACPI_UNLOCK(thermal); } } return (error); } Index: user/ngie/detangle-rc/sys/dev/bhnd/bcma/bcma_subr.c =================================================================== --- user/ngie/detangle-rc/sys/dev/bhnd/bcma/bcma_subr.c (revision 299126) +++ user/ngie/detangle-rc/sys/dev/bhnd/bcma/bcma_subr.c (revision 299127) @@ -1,279 +1,281 @@ /*- * Copyright (c) 2015 Landon Fuller * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "bcmavar.h" /** * Allocate and initialize new core config structure. * * @param core_index Core index on the bus. * @param core_unit Core unit number. * @param vendor Core designer. * @param device Core identifier (e.g. part number). * @param hwrev Core revision. */ struct bcma_corecfg * bcma_alloc_corecfg(u_int core_index, int core_unit, uint16_t vendor, uint16_t device, uint8_t hwrev) { struct bcma_corecfg *cfg; cfg = malloc(sizeof(*cfg), M_BHND, M_NOWAIT); if (cfg == NULL) return NULL; cfg->core_info = (struct bhnd_core_info) { .vendor = vendor, .device = device, .hwrev = hwrev, .core_idx = core_index, .unit = core_unit }; STAILQ_INIT(&cfg->master_ports); cfg->num_master_ports = 0; STAILQ_INIT(&cfg->dev_ports); cfg->num_dev_ports = 0; STAILQ_INIT(&cfg->bridge_ports); cfg->num_bridge_ports = 0; STAILQ_INIT(&cfg->wrapper_ports); cfg->num_wrapper_ports = 0; return (cfg); } /** * Deallocate the given core config and any associated resources. * * @param corecfg Core info to be deallocated. */ void bcma_free_corecfg(struct bcma_corecfg *corecfg) { struct bcma_mport *mport, *mnext; struct bcma_sport *sport, *snext; STAILQ_FOREACH_SAFE(mport, &corecfg->master_ports, mp_link, mnext) { free(mport, M_BHND); } STAILQ_FOREACH_SAFE(sport, &corecfg->dev_ports, sp_link, snext) { bcma_free_sport(sport); } STAILQ_FOREACH_SAFE(sport, &corecfg->bridge_ports, sp_link, snext) { bcma_free_sport(sport); } STAILQ_FOREACH_SAFE(sport, &corecfg->wrapper_ports, sp_link, snext) { bcma_free_sport(sport); } free(corecfg, M_BHND); } /** * Return the @p cfg port list for @p type. * * @param cfg The core configuration. * @param type The requested port type. */ struct bcma_sport_list * bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, bhnd_port_type type) { switch (type) { case BHND_PORT_DEVICE: return (&cfg->dev_ports); break; case BHND_PORT_BRIDGE: return (&cfg->bridge_ports); break; case BHND_PORT_AGENT: return (&cfg->wrapper_ports); break; + default: + return (NULL); } } /** * Populate the resource list and bcma_map RIDs using the maps defined on * @p ports. * * @param bus The requesting bus device. * @param dinfo The device info instance to be initialized. * @param ports The set of ports to be enumerated */ static void bcma_dinfo_init_resource_info(device_t bus, struct bcma_devinfo *dinfo, struct bcma_sport_list *ports) { struct bcma_map *map; struct bcma_sport *port; bhnd_addr_t end; STAILQ_FOREACH(port, ports, sp_link) { STAILQ_FOREACH(map, &port->sp_maps, m_link) { /* * Create the corresponding device resource list entry. * * We necessarily skip registration if the region's * device memory range is not representable via * rman_res_t. * * When rman_res_t is migrated to uintmax_t, any * range should be representable. */ end = map->m_base + map->m_size; if (map->m_base <= RM_MAX_END && end <= RM_MAX_END) { map->m_rid = resource_list_add_next( &dinfo->resources, SYS_RES_MEMORY, map->m_base, end, map->m_size); } else if (bootverbose) { device_printf(bus, "core%u %s%u.%u: region %llx-%llx extends " "beyond supported addressable range\n", dinfo->corecfg->core_info.core_idx, bhnd_port_type_name(port->sp_type), port->sp_num, map->m_region_num, (unsigned long long) map->m_base, (unsigned long long) end); } } } } /** * Allocate and initialize new device info structure, assuming ownership * of the provided core configuration. * * @param bus The requesting bus device. * @param corecfg Device core configuration. */ struct bcma_devinfo * bcma_alloc_dinfo(device_t bus, struct bcma_corecfg *corecfg) { struct bcma_devinfo *dinfo; dinfo = malloc(sizeof(struct bcma_devinfo), M_BHND, M_NOWAIT); if (dinfo == NULL) return NULL; dinfo->corecfg = corecfg; dinfo->res_agent = NULL; dinfo->rid_agent = -1; resource_list_init(&dinfo->resources); /* The device ports must always be initialized first to ensure that * rid 0 maps to the first device port */ bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->dev_ports); bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->bridge_ports); bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->wrapper_ports); return dinfo; } /** * Deallocate the given device info structure and any associated resources. * * @param bus The requesting bus device. * @param dinfo Device info to be deallocated. */ void bcma_free_dinfo(device_t bus, struct bcma_devinfo *dinfo) { bcma_free_corecfg(dinfo->corecfg); resource_list_free(&dinfo->resources); /* Release agent resource, if any */ if (dinfo->res_agent != NULL) { bhnd_release_resource(bus, SYS_RES_MEMORY, dinfo->rid_agent, dinfo->res_agent); } free(dinfo, M_BHND); } /** * Allocate and initialize new slave port descriptor. * * @param port_num Per-core port number. * @param port_type Port type. */ struct bcma_sport * bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type) { struct bcma_sport *sport; sport = malloc(sizeof(struct bcma_sport), M_BHND, M_NOWAIT); if (sport == NULL) return NULL; sport->sp_num = port_num; sport->sp_type = port_type; sport->sp_num_maps = 0; STAILQ_INIT(&sport->sp_maps); return sport; } /** * Deallocate all resources associated with the given port descriptor. * * @param sport Port descriptor to be deallocated. */ void bcma_free_sport(struct bcma_sport *sport) { struct bcma_map *map, *mapnext; STAILQ_FOREACH_SAFE(map, &sport->sp_maps, m_link, mapnext) { free(map, M_BHND); } free(sport, M_BHND); } Index: user/ngie/detangle-rc/sys/dev/bhnd/bhnd_subr.c =================================================================== --- user/ngie/detangle-rc/sys/dev/bhnd/bhnd_subr.c (revision 299126) +++ user/ngie/detangle-rc/sys/dev/bhnd/bhnd_subr.c (revision 299127) @@ -1,893 +1,895 @@ /*- * Copyright (c) 2015 Landon Fuller * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "bhndreg.h" #include "bhndvar.h" /* BHND core device description table. */ static const struct bhnd_core_desc { uint16_t vendor; uint16_t device; bhnd_devclass_t class; const char *desc; } bhnd_core_descs[] = { #define BHND_CDESC(_mfg, _cid, _cls, _desc) \ { BHND_MFGID_ ## _mfg, BHND_COREID_ ## _cid, \ BHND_DEVCLASS_ ## _cls, _desc } BHND_CDESC(BCM, CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, ILINE20, OTHER, "iLine20 HPNA"), BHND_CDESC(BCM, SRAM, RAM, "SRAM"), BHND_CDESC(BCM, SDRAM, RAM, "SDRAM"), BHND_CDESC(BCM, PCI, PCI, "PCI Bridge"), BHND_CDESC(BCM, MIPS, CPU, "MIPS Core"), BHND_CDESC(BCM, ENET, ENET_MAC, "Fast Ethernet MAC"), BHND_CDESC(BCM, CODEC, OTHER, "V.90 Modem Codec"), BHND_CDESC(BCM, USB, OTHER, "USB 1.1 Device/Host Controller"), BHND_CDESC(BCM, ADSL, OTHER, "ADSL Core"), BHND_CDESC(BCM, ILINE100, OTHER, "iLine100 HPNA"), BHND_CDESC(BCM, IPSEC, OTHER, "IPsec Accelerator"), BHND_CDESC(BCM, UTOPIA, OTHER, "UTOPIA ATM Core"), BHND_CDESC(BCM, PCMCIA, PCCARD, "PCMCIA Bridge"), BHND_CDESC(BCM, SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, MEMC, MEMC, "MEMC SDRAM Controller"), BHND_CDESC(BCM, OFDM, OTHER, "OFDM PHY"), BHND_CDESC(BCM, EXTIF, OTHER, "External Interface"), BHND_CDESC(BCM, D11, WLAN, "802.11 MAC/PHY/Radio"), BHND_CDESC(BCM, APHY, WLAN_PHY, "802.11a PHY"), BHND_CDESC(BCM, BPHY, WLAN_PHY, "802.11b PHY"), BHND_CDESC(BCM, GPHY, WLAN_PHY, "802.11g PHY"), BHND_CDESC(BCM, MIPS33, CPU, "MIPS 3302 Core"), BHND_CDESC(BCM, USB11H, OTHER, "USB 1.1 Host Controller"), BHND_CDESC(BCM, USB11D, OTHER, "USB 1.1 Device Core"), BHND_CDESC(BCM, USB20H, OTHER, "USB 2.0 Host Controller"), BHND_CDESC(BCM, USB20D, OTHER, "USB 2.0 Device Core"), BHND_CDESC(BCM, SDIOH, OTHER, "SDIO Host Controller"), BHND_CDESC(BCM, ROBO, OTHER, "RoboSwitch"), BHND_CDESC(BCM, ATA100, OTHER, "Parallel ATA Controller"), BHND_CDESC(BCM, SATAXOR, OTHER, "SATA DMA/XOR Controller"), BHND_CDESC(BCM, GIGETH, ENET_MAC, "Gigabit Ethernet MAC"), BHND_CDESC(BCM, PCIE, PCIE, "PCIe Bridge"), BHND_CDESC(BCM, NPHY, WLAN_PHY, "802.11n 2x2 PHY"), BHND_CDESC(BCM, SRAMC, MEMC, "SRAM Controller"), BHND_CDESC(BCM, MINIMAC, OTHER, "MINI MAC/PHY"), BHND_CDESC(BCM, ARM11, CPU, "ARM1176 CPU"), BHND_CDESC(BCM, ARM7S, CPU, "ARM7TDMI-S CPU"), BHND_CDESC(BCM, LPPHY, WLAN_PHY, "802.11a/b/g PHY"), BHND_CDESC(BCM, PMU, PMU, "PMU"), BHND_CDESC(BCM, SSNPHY, WLAN_PHY, "802.11n Single-Stream PHY"), BHND_CDESC(BCM, SDIOD, OTHER, "SDIO Device Core"), BHND_CDESC(BCM, ARMCM3, CPU, "ARM Cortex-M3 CPU"), BHND_CDESC(BCM, HTPHY, WLAN_PHY, "802.11n 4x4 PHY"), BHND_CDESC(BCM, MIPS74K, CPU, "MIPS74k CPU"), BHND_CDESC(BCM, GMAC, ENET_MAC, "Gigabit MAC core"), BHND_CDESC(BCM, DMEMC, MEMC, "DDR1/DDR2 Memory Controller"), BHND_CDESC(BCM, PCIERC, OTHER, "PCIe Root Complex"), BHND_CDESC(BCM, OCP, SOC_BRIDGE, "OCP to OCP Bridge"), BHND_CDESC(BCM, SC, OTHER, "Shared Common Core"), BHND_CDESC(BCM, AHB, SOC_BRIDGE, "OCP to AHB Bridge"), BHND_CDESC(BCM, SPIH, OTHER, "SPI Host Controller"), BHND_CDESC(BCM, I2S, OTHER, "I2S Digital Audio Interface"), BHND_CDESC(BCM, DMEMS, MEMC, "SDR/DDR1 Memory Controller"), BHND_CDESC(BCM, UBUS_SHIM, OTHER, "BCM6362/UBUS WLAN SHIM"), BHND_CDESC(BCM, PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(ARM, APB_BRIDGE, SOC_BRIDGE, "BP135 AMBA3 AXI to APB Bridge"), BHND_CDESC(ARM, PL301, SOC_ROUTER, "PL301 AMBA3 Interconnect"), BHND_CDESC(ARM, EROM, EROM, "PL366 Device Enumeration ROM"), BHND_CDESC(ARM, OOB_ROUTER, OTHER, "PL367 OOB Interrupt Router"), BHND_CDESC(ARM, AXI_UNMAPPED, OTHER, "Unmapped Address Ranges"), BHND_CDESC(BCM, 4706_CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, NS_PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(BCM, NS_DMA, OTHER, "DMA engine"), BHND_CDESC(BCM, NS_SDIO, OTHER, "SDIO 3.0 Host Controller"), BHND_CDESC(BCM, NS_USB20H, OTHER, "USB 2.0 Host Controller"), BHND_CDESC(BCM, NS_USB30H, OTHER, "USB 3.0 Host Controller"), BHND_CDESC(BCM, NS_A9JTAG, OTHER, "ARM Cortex A9 JTAG Interface"), BHND_CDESC(BCM, NS_DDR23_MEMC, MEMC, "Denali DDR2/DD3 Memory Controller"), BHND_CDESC(BCM, NS_ROM, NVRAM, "System ROM"), BHND_CDESC(BCM, NS_NAND, NVRAM, "NAND Flash Controller"), BHND_CDESC(BCM, NS_QSPI, NVRAM, "QSPI Flash Controller"), BHND_CDESC(BCM, NS_CC_B, CC_B, "ChipCommon B Auxiliary I/O Controller"), BHND_CDESC(BCM, 4706_SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, IHOST_ARMCA9, CPU, "ARM Cortex A9 CPU"), BHND_CDESC(BCM, 4706_GMAC_CMN, ENET, "Gigabit MAC (Common)"), BHND_CDESC(BCM, 4706_GMAC, ENET_MAC, "Gigabit MAC"), BHND_CDESC(BCM, AMEMC, MEMC, "Denali DDR1/DDR2 Memory Controller"), #undef BHND_CDESC /* Derived from inspection of the BCM4331 cores that provide PrimeCell * IDs. Due to lack of documentation, the surmised device name/purpose * provided here may be incorrect. */ { BHND_MFGID_ARM, BHND_PRIMEID_EROM, BHND_DEVCLASS_OTHER, "PL364 Device Enumeration ROM" }, { BHND_MFGID_ARM, BHND_PRIMEID_SWRAP, BHND_DEVCLASS_OTHER, "PL368 Device Management Interface" }, { BHND_MFGID_ARM, BHND_PRIMEID_MWRAP, BHND_DEVCLASS_OTHER, "PL369 Device Management Interface" }, { 0, 0, 0, NULL } }; /** * Return the name for a given JEP106 manufacturer ID. * * @param vendor A JEP106 Manufacturer ID, including the non-standard ARM 4-bit * JEP106 continuation code. */ const char * bhnd_vendor_name(uint16_t vendor) { switch (vendor) { case BHND_MFGID_ARM: return "ARM"; case BHND_MFGID_BCM: return "Broadcom"; case BHND_MFGID_MIPS: return "MIPS"; default: return "unknown"; } } /** * Return the name of a port type. */ const char * bhnd_port_type_name(bhnd_port_type port_type) { switch (port_type) { case BHND_PORT_DEVICE: return ("device"); case BHND_PORT_BRIDGE: return ("bridge"); case BHND_PORT_AGENT: return ("agent"); + default: + return "unknown"; } } static const struct bhnd_core_desc * bhnd_find_core_desc(uint16_t vendor, uint16_t device) { for (u_int i = 0; bhnd_core_descs[i].desc != NULL; i++) { if (bhnd_core_descs[i].vendor != vendor) continue; if (bhnd_core_descs[i].device != device) continue; return (&bhnd_core_descs[i]); } return (NULL); } /** * Return a human-readable name for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID * @param device The core identifier. */ const char * bhnd_find_core_name(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return ("unknown"); return desc->desc; } /** * Return the device class for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID * @param device The core identifier. */ bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return (BHND_DEVCLASS_OTHER); return desc->class; } /** * Return a human-readable name for a BHND core. * * @param ci The core's info record. */ const char * bhnd_core_name(const struct bhnd_core_info *ci) { return bhnd_find_core_name(ci->vendor, ci->device); } /** * Return the device class for a BHND core. * * @param ci The core's info record. */ bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci) { return bhnd_find_core_class(ci->vendor, ci->device); } /** * Initialize a core info record with data from from a bhnd-attached @p dev. * * @param dev A bhnd device. * @param core The record to be initialized. */ struct bhnd_core_info bhnd_get_core_info(device_t dev) { return (struct bhnd_core_info) { .vendor = bhnd_get_vendor(dev), .device = bhnd_get_device(dev), .hwrev = bhnd_get_hwrev(dev), .core_idx = bhnd_get_core_index(dev), .unit = bhnd_get_core_unit(dev) }; } /** * Find a @p class child device with @p unit on @p dev. * * @param parent The bhnd-compatible bus to be searched. * @param class The device class to match on. * @param unit The device unit number; specify -1 to return the first match * regardless of unit number. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_find_child(device_t dev, bhnd_devclass_t class, int unit) { struct bhnd_core_match md = { .vendor = BHND_MFGID_INVALID, .device = BHND_COREID_INVALID, .hwrev.start = BHND_HWREV_INVALID, .hwrev.end = BHND_HWREV_INVALID, .class = class, .unit = unit }; return bhnd_match_child(dev, &md); } /** * Find the first child device on @p dev that matches @p desc. * * @param parent The bhnd-compatible bus to be searched. * @param desc A match descriptor. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_match_child(device_t dev, const struct bhnd_core_match *desc) { device_t *devlistp; device_t match; int devcnt; int error; error = device_get_children(dev, &devlistp, &devcnt); if (error != 0) return (NULL); match = NULL; for (int i = 0; i < devcnt; i++) { device_t dev = devlistp[i]; if (bhnd_device_matches(dev, desc)) { match = dev; goto done; } } done: free(devlistp, M_TEMP); return match; } /** * Find the first core in @p cores that matches @p desc. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_match_core(const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc) { for (u_int i = 0; i < num_cores; i++) { if (bhnd_core_matches(&cores[i], desc)) return &cores[i]; } return (NULL); } /** * Find the first core in @p cores with the given @p class. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_find_core(const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class) { struct bhnd_core_match md = { .vendor = BHND_MFGID_INVALID, .device = BHND_COREID_INVALID, .hwrev.start = BHND_HWREV_INVALID, .hwrev.end = BHND_HWREV_INVALID, .class = class, .unit = -1 }; return bhnd_match_core(cores, num_cores, &md); } /** * Return true if the @p core matches @p desc. * * @param core A bhnd core descriptor. * @param desc A match descriptor to compare against @p core. * * @retval true if @p core matches @p match * @retval false if @p core does not match @p match. */ bool bhnd_core_matches(const struct bhnd_core_info *core, const struct bhnd_core_match *desc) { if (desc->vendor != BHND_MFGID_INVALID && desc->vendor != core->vendor) return (false); if (desc->device != BHND_COREID_INVALID && desc->device != core->device) return (false); if (desc->unit != -1 && desc->unit != core->unit) return (false); if (!bhnd_hwrev_matches(core->hwrev, &desc->hwrev)) return (false); if (desc->hwrev.end != BHND_HWREV_INVALID && desc->hwrev.end < core->hwrev) return (false); if (desc->class != BHND_DEVCLASS_INVALID && desc->class != bhnd_core_class(core)) return (false); return true; } /** * Return true if the @p hwrev matches @p desc. * * @param hwrev A bhnd hardware revision. * @param desc A match descriptor to compare against @p core. * * @retval true if @p hwrev matches @p match * @retval false if @p hwrev does not match @p match. */ bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc) { if (desc->start != BHND_HWREV_INVALID && desc->start > hwrev) return false; if (desc->end != BHND_HWREV_INVALID && desc->end < hwrev) return false; return true; } /** * Return true if the @p dev matches @p desc. * * @param dev A bhnd device. * @param desc A match descriptor to compare against @p dev. * * @retval true if @p dev matches @p match * @retval false if @p dev does not match @p match. */ bool bhnd_device_matches(device_t dev, const struct bhnd_core_match *desc) { struct bhnd_core_info ci = { .vendor = bhnd_get_vendor(dev), .device = bhnd_get_device(dev), .unit = bhnd_get_core_unit(dev), .hwrev = bhnd_get_hwrev(dev) }; return bhnd_core_matches(&ci, desc); } /** * Search @p table for an entry matching @p dev. * * @param dev A bhnd device to match against @p table. * @param table The device table to search. * @param entry_size The @p table entry size, in bytes. * * @retval bhnd_device the first matching device, if any. * @retval NULL if no matching device is found in @p table. */ const struct bhnd_device * bhnd_device_lookup(device_t dev, const struct bhnd_device *table, size_t entry_size) { const struct bhnd_device *entry; for (entry = table; entry->desc != NULL; entry = (const struct bhnd_device *) ((const char *) entry + entry_size)) { /* match core info */ if (!bhnd_device_matches(dev, &entry->core)) continue; /* match device flags */ if (entry->device_flags & BHND_DF_HOSTB) { if (!bhnd_is_hostb_device(dev)) continue; } /* device found */ return (entry); } /* not found */ return (NULL); } /** * Scan @p table for all quirk flags applicable to @p dev. * * @param dev A bhnd device to match against @p table. * @param table The device table to search. * @param entry_size The @p table entry size, in bytes. * * @return returns all matching quirk flags. */ uint32_t bhnd_device_quirks(device_t dev, const struct bhnd_device *table, size_t entry_size) { const struct bhnd_device *dent; const struct bhnd_device_quirk *qtable, *qent; uint32_t quirks; uint16_t hwrev; hwrev = bhnd_get_hwrev(dev); quirks = 0; /* Find the quirk table */ if ((dent = bhnd_device_lookup(dev, table, entry_size)) == NULL) { /* This is almost certainly a (caller) implementation bug */ device_printf(dev, "quirk lookup did not match any device\n"); return (0); } /* Quirks aren't a mandatory field */ if ((qtable = dent->quirks_table) == NULL) return (0); /* Collect matching quirk entries */ for (qent = qtable; !BHND_DEVICE_QUIRK_IS_END(qent); qent++) { if (bhnd_hwrev_matches(hwrev, &qent->hwrev)) quirks |= qent->quirks; } return (quirks); } /** * Allocate bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device requesting ownership of the resources. * @param rs A standard bus resource specification. This will be updated * with the allocated resource's RIDs. * @param res On success, the allocated bhnd resources. * * @retval 0 success * @retval non-zero if allocation of any non-RF_OPTIONAL resource fails, * all allocated resources will be released and a regular * unix error code will be returned. */ int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res) { /* Initialize output array */ for (u_int i = 0; rs[i].type != -1; i++) res[i] = NULL; for (u_int i = 0; rs[i].type != -1; i++) { res[i] = bhnd_alloc_resource_any(dev, rs[i].type, &rs[i].rid, rs[i].flags); /* Clean up all allocations on failure */ if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) { bhnd_release_resources(dev, rs, res); return (ENXIO); } } return (0); }; /** * Release bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device that owns the resources. * @param rs A standard bus resource specification previously initialized * by @p bhnd_alloc_resources. * @param res The bhnd resources to be released. */ void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res) { for (u_int i = 0; rs[i].type != -1; i++) { if (res[i] == NULL) continue; bhnd_release_resource(dev, rs[i].type, rs[i].rid, res[i]); res[i] = NULL; } } /** * Parse the CHIPC_ID_* fields from the ChipCommon CHIPC_ID * register, returning its bhnd_chipid representation. * * @param idreg The CHIPC_ID register value. * @param enum_addr The enumeration address to include in the result. * * @warning * On early siba(4) devices, the ChipCommon core does not provide * a valid CHIPC_ID_NUMCORE field. On these ChipCommon revisions * (see CHIPC_NCORES_MIN_HWREV()), this function will parse and return * an invalid `ncores` value. */ struct bhnd_chipid bhnd_parse_chipid(uint32_t idreg, bhnd_addr_t enum_addr) { struct bhnd_chipid result; /* Fetch the basic chip info */ result.chip_id = CHIPC_GET_ATTR(idreg, ID_CHIP); result.chip_pkg = CHIPC_GET_ATTR(idreg, ID_PKG); result.chip_rev = CHIPC_GET_ATTR(idreg, ID_REV); result.chip_type = CHIPC_GET_ATTR(idreg, ID_BUS); result.ncores = CHIPC_GET_ATTR(idreg, ID_NUMCORE); result.enum_addr = enum_addr; return (result); } /** * Allocate the resource defined by @p rs via @p dev, use it * to read the ChipCommon ID register relative to @p chipc_offset, * then release the resource. * * @param dev The device owning @p rs. * @param rs A resource spec that encompasses the ChipCommon register block. * @param chipc_offset The offset of the ChipCommon registers within @p rs. * @param[out] result the chip identification data. * * @retval 0 success * @retval non-zero if the ChipCommon identification data could not be read. */ int bhnd_read_chipid(device_t dev, struct resource_spec *rs, bus_size_t chipc_offset, struct bhnd_chipid *result) { struct resource *res; uint32_t reg; int error, rid, rtype; /* Allocate the ChipCommon window resource and fetch the chipid data */ rid = rs->rid; rtype = rs->type; res = bus_alloc_resource_any(dev, rtype, &rid, RF_ACTIVE); if (res == NULL) { device_printf(dev, "failed to allocate bhnd chipc resource\n"); return (ENXIO); } /* Fetch the basic chip info */ reg = bus_read_4(res, chipc_offset + CHIPC_ID); *result = bhnd_parse_chipid(reg, 0x0); /* Fetch the enum base address */ error = 0; switch (result->chip_type) { case BHND_CHIPTYPE_SIBA: result->enum_addr = BHND_DEFAULT_CHIPC_ADDR; break; case BHND_CHIPTYPE_BCMA: case BHND_CHIPTYPE_BCMA_ALT: result->enum_addr = bus_read_4(res, chipc_offset + CHIPC_EROMPTR); break; case BHND_CHIPTYPE_UBUS: device_printf(dev, "unsupported ubus/bcm63xx chip type"); error = ENODEV; goto cleanup; default: device_printf(dev, "unknown chip type %hhu\n", result->chip_type); error = ENODEV; goto cleanup; } cleanup: /* Clean up */ bus_release_resource(dev, rtype, rid, res); return (error); } /** * Using the bhnd(4) bus-level core information and a custom core name, * populate @p dev's device description. * * @param dev A bhnd-bus attached device. * @param dev_name The core's name (e.g. "SDIO Device Core") */ void bhnd_set_custom_core_desc(device_t dev, const char *dev_name) { const char *vendor_name; char *desc; vendor_name = bhnd_get_vendor_name(dev); asprintf(&desc, M_BHND, "%s %s, rev %hhu", vendor_name, dev_name, bhnd_get_hwrev(dev)); if (desc != NULL) { device_set_desc_copy(dev, desc); free(desc, M_BHND); } else { device_set_desc(dev, dev_name); } } /** * Using the bhnd(4) bus-level core information, populate @p dev's device * description. * * @param dev A bhnd-bus attached device. */ void bhnd_set_default_core_desc(device_t dev) { bhnd_set_custom_core_desc(dev, bhnd_get_device_name(dev)); } /** * Helper function for implementing BHND_BUS_IS_HOSTB_DEVICE(). * * If a parent device is available, this implementation delegates the * request to the BHND_BUS_IS_HOSTB_DEVICE() method on the parent of @p dev. * * If no parent device is available (i.e. on a the bus root), false * is returned. */ bool bhnd_bus_generic_is_hostb_device(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_IS_HOSTB_DEVICE(device_get_parent(dev), child)); return (false); } /** * Helper function for implementing BHND_BUS_IS_HW_DISABLED(). * * If a parent device is available, this implementation delegates the * request to the BHND_BUS_IS_HW_DISABLED() method on the parent of @p dev. * * If no parent device is available (i.e. on a the bus root), the hardware * is assumed to be usable and false is returned. */ bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), child)); return (false); } /** * Helper function for implementing BHND_BUS_GET_CHIPID(). * * This implementation delegates the request to the BHND_BUS_GET_CHIPID() * method on the parent of @p dev. If no parent exists, the implementation * will panic. */ const struct bhnd_chipid * bhnd_bus_generic_get_chipid(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_GET_CHIPID(device_get_parent(dev), child)); panic("missing BHND_BUS_GET_CHIPID()"); } /** * Helper function for implementing BHND_BUS_ALLOC_RESOURCE(). * * This implementation of BHND_BUS_ALLOC_RESOURCE() delegates allocation * of the underlying resource to BUS_ALLOC_RESOURCE(), and activation * to @p dev's BHND_BUS_ACTIVATE_RESOURCE(). */ struct bhnd_resource * bhnd_bus_generic_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct bhnd_resource *br; struct resource *res; int error; br = NULL; res = NULL; /* Allocate the real bus resource (without activating it) */ res = BUS_ALLOC_RESOURCE(dev, child, type, rid, start, end, count, (flags & ~RF_ACTIVE)); if (res == NULL) return (NULL); /* Allocate our bhnd resource wrapper. */ br = malloc(sizeof(struct bhnd_resource), M_BHND, M_NOWAIT); if (br == NULL) goto failed; br->direct = false; br->res = res; /* Attempt activation */ if (flags & RF_ACTIVE) { error = BHND_BUS_ACTIVATE_RESOURCE(dev, child, type, *rid, br); if (error) goto failed; } return (br); failed: if (res != NULL) BUS_RELEASE_RESOURCE(dev, child, type, *rid, res); free(br, M_BHND); return (NULL); } /** * Helper function for implementing BHND_BUS_RELEASE_RESOURCE(). * * This implementation of BHND_BUS_RELEASE_RESOURCE() delegates release of * the backing resource to BUS_RELEASE_RESOURCE(). */ int bhnd_bus_generic_release_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; if ((error = BUS_RELEASE_RESOURCE(dev, child, type, rid, r->res))) return (error); free(r, M_BHND); return (0); } /** * Helper function for implementing BHND_BUS_ACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() simply calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bhnd_bus_generic_activate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { /* Try to delegate to the parent */ if (device_get_parent(dev) != NULL) return (BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r)); return (EINVAL); }; /** * Helper function for implementing BHND_BUS_DEACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() simply calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bhnd_bus_generic_deactivate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { if (device_get_parent(dev) != NULL) return (BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r)); return (EINVAL); }; Index: user/ngie/detangle-rc/sys/dev/bwn/if_bwn.c =================================================================== --- user/ngie/detangle-rc/sys/dev/bwn/if_bwn.c (revision 299126) +++ user/ngie/detangle-rc/sys/dev/bwn/if_bwn.c (revision 299127) @@ -1,6882 +1,6982 @@ /*- * Copyright (c) 2009-2010 Weongyo Jeong * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ #include __FBSDID("$FreeBSD$"); /* * The Broadcom Wireless LAN controller driver. */ #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 #include #include #include #include #include static SYSCTL_NODE(_hw, OID_AUTO, bwn, CTLFLAG_RD, 0, "Broadcom driver parameters"); /* * Tunable & sysctl variables. */ #ifdef BWN_DEBUG static int bwn_debug = 0; SYSCTL_INT(_hw_bwn, OID_AUTO, debug, CTLFLAG_RWTUN, &bwn_debug, 0, "Broadcom debugging printfs"); #endif static int bwn_bfp = 0; /* use "Bad Frames Preemption" */ SYSCTL_INT(_hw_bwn, OID_AUTO, bfp, CTLFLAG_RW, &bwn_bfp, 0, "uses Bad Frames Preemption"); static int bwn_bluetooth = 1; SYSCTL_INT(_hw_bwn, OID_AUTO, bluetooth, CTLFLAG_RW, &bwn_bluetooth, 0, "turns on Bluetooth Coexistence"); static int bwn_hwpctl = 0; SYSCTL_INT(_hw_bwn, OID_AUTO, hwpctl, CTLFLAG_RW, &bwn_hwpctl, 0, "uses H/W power control"); static int bwn_msi_disable = 0; /* MSI disabled */ TUNABLE_INT("hw.bwn.msi_disable", &bwn_msi_disable); static int bwn_usedma = 1; SYSCTL_INT(_hw_bwn, OID_AUTO, usedma, CTLFLAG_RD, &bwn_usedma, 0, "uses DMA"); TUNABLE_INT("hw.bwn.usedma", &bwn_usedma); static int bwn_wme = 1; SYSCTL_INT(_hw_bwn, OID_AUTO, wme, CTLFLAG_RW, &bwn_wme, 0, "uses WME support"); static void bwn_attach_pre(struct bwn_softc *); static int bwn_attach_post(struct bwn_softc *); static void bwn_sprom_bugfixes(device_t); static int bwn_init(struct bwn_softc *); static void bwn_parent(struct ieee80211com *); static void bwn_start(struct bwn_softc *); static int bwn_transmit(struct ieee80211com *, struct mbuf *); static int bwn_attach_core(struct bwn_mac *); static int bwn_phy_getinfo(struct bwn_mac *, int); static int bwn_chiptest(struct bwn_mac *); static int bwn_setup_channels(struct bwn_mac *, int, int); static void bwn_shm_ctlword(struct bwn_mac *, uint16_t, uint16_t); static void bwn_addchannels(struct ieee80211_channel [], int, int *, const struct bwn_channelinfo *, int); static int bwn_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void bwn_updateslot(struct ieee80211com *); static void bwn_update_promisc(struct ieee80211com *); static void bwn_wme_init(struct bwn_mac *); static int bwn_wme_update(struct ieee80211com *); static void bwn_wme_clear(struct bwn_softc *); static void bwn_wme_load(struct bwn_mac *); static void bwn_wme_loadparams(struct bwn_mac *, const struct wmeParams *, uint16_t); static void bwn_scan_start(struct ieee80211com *); static void bwn_scan_end(struct ieee80211com *); static void bwn_set_channel(struct ieee80211com *); static struct ieee80211vap *bwn_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void bwn_vap_delete(struct ieee80211vap *); static void bwn_stop(struct bwn_softc *); static int bwn_core_init(struct bwn_mac *); static void bwn_core_start(struct bwn_mac *); static void bwn_core_exit(struct bwn_mac *); static void bwn_bt_disable(struct bwn_mac *); static int bwn_chip_init(struct bwn_mac *); static void bwn_set_txretry(struct bwn_mac *, int, int); static void bwn_rate_init(struct bwn_mac *); static void bwn_set_phytxctl(struct bwn_mac *); static void bwn_spu_setdelay(struct bwn_mac *, int); static void bwn_bt_enable(struct bwn_mac *); static void bwn_set_macaddr(struct bwn_mac *); static void bwn_crypt_init(struct bwn_mac *); static void bwn_chip_exit(struct bwn_mac *); static int bwn_fw_fillinfo(struct bwn_mac *); static int bwn_fw_loaducode(struct bwn_mac *); static int bwn_gpio_init(struct bwn_mac *); static int bwn_fw_loadinitvals(struct bwn_mac *); static int bwn_phy_init(struct bwn_mac *); static void bwn_set_txantenna(struct bwn_mac *, int); static void bwn_set_opmode(struct bwn_mac *); static void bwn_rate_write(struct bwn_mac *, uint16_t, int); static uint8_t bwn_plcp_getcck(const uint8_t); static uint8_t bwn_plcp_getofdm(const uint8_t); static void bwn_pio_init(struct bwn_mac *); static uint16_t bwn_pio_idx2base(struct bwn_mac *, int); static void bwn_pio_set_txqueue(struct bwn_mac *, struct bwn_pio_txqueue *, int); static void bwn_pio_setupqueue_rx(struct bwn_mac *, struct bwn_pio_rxqueue *, int); static void bwn_destroy_queue_tx(struct bwn_pio_txqueue *); static uint16_t bwn_pio_read_2(struct bwn_mac *, struct bwn_pio_txqueue *, uint16_t); static void bwn_pio_cancel_tx_packets(struct bwn_pio_txqueue *); static int bwn_pio_rx(struct bwn_pio_rxqueue *); static uint8_t bwn_pio_rxeof(struct bwn_pio_rxqueue *); static void bwn_pio_handle_txeof(struct bwn_mac *, const struct bwn_txstatus *); static uint16_t bwn_pio_rx_read_2(struct bwn_pio_rxqueue *, uint16_t); static uint32_t bwn_pio_rx_read_4(struct bwn_pio_rxqueue *, uint16_t); static void bwn_pio_rx_write_2(struct bwn_pio_rxqueue *, uint16_t, uint16_t); static void bwn_pio_rx_write_4(struct bwn_pio_rxqueue *, uint16_t, uint32_t); static int bwn_pio_tx_start(struct bwn_mac *, struct ieee80211_node *, struct mbuf *); static struct bwn_pio_txqueue *bwn_pio_select(struct bwn_mac *, uint8_t); static uint32_t bwn_pio_write_multi_4(struct bwn_mac *, struct bwn_pio_txqueue *, uint32_t, const void *, int); static void bwn_pio_write_4(struct bwn_mac *, struct bwn_pio_txqueue *, uint16_t, uint32_t); static uint16_t bwn_pio_write_multi_2(struct bwn_mac *, struct bwn_pio_txqueue *, uint16_t, const void *, int); static uint16_t bwn_pio_write_mbuf_2(struct bwn_mac *, struct bwn_pio_txqueue *, uint16_t, struct mbuf *); static struct bwn_pio_txqueue *bwn_pio_parse_cookie(struct bwn_mac *, uint16_t, struct bwn_pio_txpkt **); static void bwn_dma_init(struct bwn_mac *); static void bwn_dma_rxdirectfifo(struct bwn_mac *, int, uint8_t); static int bwn_dma_mask2type(uint64_t); static uint64_t bwn_dma_mask(struct bwn_mac *); static uint16_t bwn_dma_base(int, int); static void bwn_dma_ringfree(struct bwn_dma_ring **); static void bwn_dma_32_getdesc(struct bwn_dma_ring *, int, struct bwn_dmadesc_generic **, struct bwn_dmadesc_meta **); static void bwn_dma_32_setdesc(struct bwn_dma_ring *, struct bwn_dmadesc_generic *, bus_addr_t, uint16_t, int, int, int); static void bwn_dma_32_start_transfer(struct bwn_dma_ring *, int); static void bwn_dma_32_suspend(struct bwn_dma_ring *); static void bwn_dma_32_resume(struct bwn_dma_ring *); static int bwn_dma_32_get_curslot(struct bwn_dma_ring *); static void bwn_dma_32_set_curslot(struct bwn_dma_ring *, int); static void bwn_dma_64_getdesc(struct bwn_dma_ring *, int, struct bwn_dmadesc_generic **, struct bwn_dmadesc_meta **); static void bwn_dma_64_setdesc(struct bwn_dma_ring *, struct bwn_dmadesc_generic *, bus_addr_t, uint16_t, int, int, int); static void bwn_dma_64_start_transfer(struct bwn_dma_ring *, int); static void bwn_dma_64_suspend(struct bwn_dma_ring *); static void bwn_dma_64_resume(struct bwn_dma_ring *); static int bwn_dma_64_get_curslot(struct bwn_dma_ring *); static void bwn_dma_64_set_curslot(struct bwn_dma_ring *, int); static int bwn_dma_allocringmemory(struct bwn_dma_ring *); static void bwn_dma_setup(struct bwn_dma_ring *); static void bwn_dma_free_ringmemory(struct bwn_dma_ring *); static void bwn_dma_cleanup(struct bwn_dma_ring *); static void bwn_dma_free_descbufs(struct bwn_dma_ring *); static int bwn_dma_tx_reset(struct bwn_mac *, uint16_t, int); static void bwn_dma_rx(struct bwn_dma_ring *); static int bwn_dma_rx_reset(struct bwn_mac *, uint16_t, int); static void bwn_dma_free_descbuf(struct bwn_dma_ring *, struct bwn_dmadesc_meta *); static void bwn_dma_set_redzone(struct bwn_dma_ring *, struct mbuf *); static int bwn_dma_gettype(struct bwn_mac *); static void bwn_dma_ring_addr(void *, bus_dma_segment_t *, int, int); static int bwn_dma_freeslot(struct bwn_dma_ring *); static int bwn_dma_nextslot(struct bwn_dma_ring *, int); static void bwn_dma_rxeof(struct bwn_dma_ring *, int *); static int bwn_dma_newbuf(struct bwn_dma_ring *, struct bwn_dmadesc_generic *, struct bwn_dmadesc_meta *, int); static void bwn_dma_buf_addr(void *, bus_dma_segment_t *, int, bus_size_t, int); static uint8_t bwn_dma_check_redzone(struct bwn_dma_ring *, struct mbuf *); static void bwn_dma_handle_txeof(struct bwn_mac *, const struct bwn_txstatus *); static int bwn_dma_tx_start(struct bwn_mac *, struct ieee80211_node *, struct mbuf *); static int bwn_dma_getslot(struct bwn_dma_ring *); static struct bwn_dma_ring *bwn_dma_select(struct bwn_mac *, uint8_t); static int bwn_dma_attach(struct bwn_mac *); static struct bwn_dma_ring *bwn_dma_ringsetup(struct bwn_mac *, int, int, int); static struct bwn_dma_ring *bwn_dma_parse_cookie(struct bwn_mac *, const struct bwn_txstatus *, uint16_t, int *); static void bwn_dma_free(struct bwn_mac *); static int bwn_fw_gets(struct bwn_mac *, enum bwn_fwtype); static int bwn_fw_get(struct bwn_mac *, enum bwn_fwtype, const char *, struct bwn_fwfile *); static void bwn_release_firmware(struct bwn_mac *); static void bwn_do_release_fw(struct bwn_fwfile *); static uint16_t bwn_fwcaps_read(struct bwn_mac *); static int bwn_fwinitvals_write(struct bwn_mac *, const struct bwn_fwinitvals *, size_t, size_t); static uint16_t bwn_ant2phy(int); static void bwn_mac_write_bssid(struct bwn_mac *); static void bwn_mac_setfilter(struct bwn_mac *, uint16_t, const uint8_t *); static void bwn_key_dowrite(struct bwn_mac *, uint8_t, uint8_t, const uint8_t *, size_t, const uint8_t *); static void bwn_key_macwrite(struct bwn_mac *, uint8_t, const uint8_t *); static void bwn_key_write(struct bwn_mac *, uint8_t, uint8_t, const uint8_t *); static void bwn_phy_exit(struct bwn_mac *); static void bwn_core_stop(struct bwn_mac *); static int bwn_switch_band(struct bwn_softc *, struct ieee80211_channel *); static void bwn_phy_reset(struct bwn_mac *); static int bwn_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void bwn_set_pretbtt(struct bwn_mac *); static int bwn_intr(void *); static void bwn_intrtask(void *, int); static void bwn_restart(struct bwn_mac *, const char *); static void bwn_intr_ucode_debug(struct bwn_mac *); static void bwn_intr_tbtt_indication(struct bwn_mac *); static void bwn_intr_atim_end(struct bwn_mac *); static void bwn_intr_beacon(struct bwn_mac *); static void bwn_intr_pmq(struct bwn_mac *); static void bwn_intr_noise(struct bwn_mac *); static void bwn_intr_txeof(struct bwn_mac *); static void bwn_hwreset(void *, int); static void bwn_handle_fwpanic(struct bwn_mac *); static void bwn_load_beacon0(struct bwn_mac *); static void bwn_load_beacon1(struct bwn_mac *); static uint32_t bwn_jssi_read(struct bwn_mac *); static void bwn_noise_gensample(struct bwn_mac *); static void bwn_handle_txeof(struct bwn_mac *, const struct bwn_txstatus *); static void bwn_rxeof(struct bwn_mac *, struct mbuf *, const void *); static void bwn_phy_txpower_check(struct bwn_mac *, uint32_t); static int bwn_tx_start(struct bwn_softc *, struct ieee80211_node *, struct mbuf *); static int bwn_tx_isfull(struct bwn_softc *, struct mbuf *); static int bwn_set_txhdr(struct bwn_mac *, struct ieee80211_node *, struct mbuf *, struct bwn_txhdr *, uint16_t); static void bwn_plcp_genhdr(struct bwn_plcp4 *, const uint16_t, const uint8_t); static uint8_t bwn_antenna_sanitize(struct bwn_mac *, uint8_t); static uint8_t bwn_get_fbrate(uint8_t); static void bwn_txpwr(void *, int); static void bwn_tasks(void *); static void bwn_task_15s(struct bwn_mac *); static void bwn_task_30s(struct bwn_mac *); static void bwn_task_60s(struct bwn_mac *); static int bwn_plcp_get_ofdmrate(struct bwn_mac *, struct bwn_plcp6 *, uint8_t); static int bwn_plcp_get_cckrate(struct bwn_mac *, struct bwn_plcp6 *); static void bwn_rx_radiotap(struct bwn_mac *, struct mbuf *, const struct bwn_rxhdr4 *, struct bwn_plcp6 *, int, int, int); static void bwn_tsf_read(struct bwn_mac *, uint64_t *); static void bwn_set_slot_time(struct bwn_mac *, uint16_t); static void bwn_watchdog(void *); static void bwn_dma_stop(struct bwn_mac *); static void bwn_pio_stop(struct bwn_mac *); static void bwn_dma_ringstop(struct bwn_dma_ring **); static void bwn_led_attach(struct bwn_mac *); static void bwn_led_newstate(struct bwn_mac *, enum ieee80211_state); static void bwn_led_event(struct bwn_mac *, int); static void bwn_led_blink_start(struct bwn_mac *, int, int); static void bwn_led_blink_next(void *); static void bwn_led_blink_end(void *); static void bwn_rfswitch(void *); static void bwn_rf_turnon(struct bwn_mac *); static void bwn_rf_turnoff(struct bwn_mac *); static void bwn_sysctl_node(struct bwn_softc *); static struct resource_spec bwn_res_spec_legacy[] = { { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec bwn_res_spec_msi[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; static const struct bwn_channelinfo bwn_chantable_bg = { .channels = { { 2412, 1, 30 }, { 2417, 2, 30 }, { 2422, 3, 30 }, { 2427, 4, 30 }, { 2432, 5, 30 }, { 2437, 6, 30 }, { 2442, 7, 30 }, { 2447, 8, 30 }, { 2452, 9, 30 }, { 2457, 10, 30 }, { 2462, 11, 30 }, { 2467, 12, 30 }, { 2472, 13, 30 }, { 2484, 14, 30 } }, .nchannels = 14 }; static const struct bwn_channelinfo bwn_chantable_a = { .channels = { { 5170, 34, 30 }, { 5180, 36, 30 }, { 5190, 38, 30 }, { 5200, 40, 30 }, { 5210, 42, 30 }, { 5220, 44, 30 }, { 5230, 46, 30 }, { 5240, 48, 30 }, { 5260, 52, 30 }, { 5280, 56, 30 }, { 5300, 60, 30 }, { 5320, 64, 30 }, { 5500, 100, 30 }, { 5520, 104, 30 }, { 5540, 108, 30 }, { 5560, 112, 30 }, { 5580, 116, 30 }, { 5600, 120, 30 }, { 5620, 124, 30 }, { 5640, 128, 30 }, { 5660, 132, 30 }, { 5680, 136, 30 }, { 5700, 140, 30 }, { 5745, 149, 30 }, { 5765, 153, 30 }, { 5785, 157, 30 }, { 5805, 161, 30 }, { 5825, 165, 30 }, { 5920, 184, 30 }, { 5940, 188, 30 }, { 5960, 192, 30 }, { 5980, 196, 30 }, { 6000, 200, 30 }, { 6020, 204, 30 }, { 6040, 208, 30 }, { 6060, 212, 30 }, { 6080, 216, 30 } }, .nchannels = 37 }; static const struct bwn_channelinfo bwn_chantable_n = { .channels = { { 5160, 32, 30 }, { 5170, 34, 30 }, { 5180, 36, 30 }, { 5190, 38, 30 }, { 5200, 40, 30 }, { 5210, 42, 30 }, { 5220, 44, 30 }, { 5230, 46, 30 }, { 5240, 48, 30 }, { 5250, 50, 30 }, { 5260, 52, 30 }, { 5270, 54, 30 }, { 5280, 56, 30 }, { 5290, 58, 30 }, { 5300, 60, 30 }, { 5310, 62, 30 }, { 5320, 64, 30 }, { 5330, 66, 30 }, { 5340, 68, 30 }, { 5350, 70, 30 }, { 5360, 72, 30 }, { 5370, 74, 30 }, { 5380, 76, 30 }, { 5390, 78, 30 }, { 5400, 80, 30 }, { 5410, 82, 30 }, { 5420, 84, 30 }, { 5430, 86, 30 }, { 5440, 88, 30 }, { 5450, 90, 30 }, { 5460, 92, 30 }, { 5470, 94, 30 }, { 5480, 96, 30 }, { 5490, 98, 30 }, { 5500, 100, 30 }, { 5510, 102, 30 }, { 5520, 104, 30 }, { 5530, 106, 30 }, { 5540, 108, 30 }, { 5550, 110, 30 }, { 5560, 112, 30 }, { 5570, 114, 30 }, { 5580, 116, 30 }, { 5590, 118, 30 }, { 5600, 120, 30 }, { 5610, 122, 30 }, { 5620, 124, 30 }, { 5630, 126, 30 }, { 5640, 128, 30 }, { 5650, 130, 30 }, { 5660, 132, 30 }, { 5670, 134, 30 }, { 5680, 136, 30 }, { 5690, 138, 30 }, { 5700, 140, 30 }, { 5710, 142, 30 }, { 5720, 144, 30 }, { 5725, 145, 30 }, { 5730, 146, 30 }, { 5735, 147, 30 }, { 5740, 148, 30 }, { 5745, 149, 30 }, { 5750, 150, 30 }, { 5755, 151, 30 }, { 5760, 152, 30 }, { 5765, 153, 30 }, { 5770, 154, 30 }, { 5775, 155, 30 }, { 5780, 156, 30 }, { 5785, 157, 30 }, { 5790, 158, 30 }, { 5795, 159, 30 }, { 5800, 160, 30 }, { 5805, 161, 30 }, { 5810, 162, 30 }, { 5815, 163, 30 }, { 5820, 164, 30 }, { 5825, 165, 30 }, { 5830, 166, 30 }, { 5840, 168, 30 }, { 5850, 170, 30 }, { 5860, 172, 30 }, { 5870, 174, 30 }, { 5880, 176, 30 }, { 5890, 178, 30 }, { 5900, 180, 30 }, { 5910, 182, 30 }, { 5920, 184, 30 }, { 5930, 186, 30 }, { 5940, 188, 30 }, { 5950, 190, 30 }, { 5960, 192, 30 }, { 5970, 194, 30 }, { 5980, 196, 30 }, { 5990, 198, 30 }, { 6000, 200, 30 }, { 6010, 202, 30 }, { 6020, 204, 30 }, { 6030, 206, 30 }, { 6040, 208, 30 }, { 6050, 210, 30 }, { 6060, 212, 30 }, { 6070, 214, 30 }, { 6080, 216, 30 }, { 6090, 218, 30 }, { 6100, 220, 30 }, { 6110, 222, 30 }, { 6120, 224, 30 }, { 6130, 226, 30 }, { 6140, 228, 30 } }, .nchannels = 110 }; #define VENDOR_LED_ACT(vendor) \ { \ .vid = PCI_VENDOR_##vendor, \ .led_act = { BWN_VENDOR_LED_ACT_##vendor } \ } static const struct { uint16_t vid; uint8_t led_act[BWN_LED_MAX]; } bwn_vendor_led_act[] = { VENDOR_LED_ACT(COMPAQ), VENDOR_LED_ACT(ASUSTEK) }; static const uint8_t bwn_default_led_act[BWN_LED_MAX] = { BWN_VENDOR_LED_ACT_DEFAULT }; #undef VENDOR_LED_ACT static const struct { int on_dur; int off_dur; } bwn_led_duration[109] = { [0] = { 400, 100 }, [2] = { 150, 75 }, [4] = { 90, 45 }, [11] = { 66, 34 }, [12] = { 53, 26 }, [18] = { 42, 21 }, [22] = { 35, 17 }, [24] = { 32, 16 }, [36] = { 21, 10 }, [48] = { 16, 8 }, [72] = { 11, 5 }, [96] = { 9, 4 }, [108] = { 7, 3 } }; static const uint16_t bwn_wme_shm_offsets[] = { [0] = BWN_WME_BESTEFFORT, [1] = BWN_WME_BACKGROUND, [2] = BWN_WME_VOICE, [3] = BWN_WME_VIDEO, }; static const struct siba_devid bwn_devs[] = { SIBA_DEV(BROADCOM, 80211, 5, "Revision 5"), SIBA_DEV(BROADCOM, 80211, 6, "Revision 6"), SIBA_DEV(BROADCOM, 80211, 7, "Revision 7"), SIBA_DEV(BROADCOM, 80211, 9, "Revision 9"), SIBA_DEV(BROADCOM, 80211, 10, "Revision 10"), SIBA_DEV(BROADCOM, 80211, 11, "Revision 11"), SIBA_DEV(BROADCOM, 80211, 13, "Revision 13"), SIBA_DEV(BROADCOM, 80211, 15, "Revision 15"), SIBA_DEV(BROADCOM, 80211, 16, "Revision 16") }; static int bwn_probe(device_t dev) { int i; for (i = 0; i < nitems(bwn_devs); i++) { if (siba_get_vendor(dev) == bwn_devs[i].sd_vendor && siba_get_device(dev) == bwn_devs[i].sd_device && siba_get_revid(dev) == bwn_devs[i].sd_rev) return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int bwn_attach(device_t dev) { struct bwn_mac *mac; struct bwn_softc *sc = device_get_softc(dev); int error, i, msic, reg; sc->sc_dev = dev; #ifdef BWN_DEBUG sc->sc_debug = bwn_debug; #endif if ((sc->sc_flags & BWN_FLAG_ATTACHED) == 0) { bwn_attach_pre(sc); bwn_sprom_bugfixes(dev); sc->sc_flags |= BWN_FLAG_ATTACHED; } if (!TAILQ_EMPTY(&sc->sc_maclist)) { if (siba_get_pci_device(dev) != 0x4313 && siba_get_pci_device(dev) != 0x431a && siba_get_pci_device(dev) != 0x4321) { device_printf(sc->sc_dev, "skip 802.11 cores\n"); return (ENODEV); } } mac = malloc(sizeof(*mac), M_DEVBUF, M_WAITOK | M_ZERO); mac->mac_sc = sc; mac->mac_status = BWN_MAC_STATUS_UNINIT; if (bwn_bfp != 0) mac->mac_flags |= BWN_MAC_FLAG_BADFRAME_PREEMP; TASK_INIT(&mac->mac_hwreset, 0, bwn_hwreset, mac); TASK_INIT(&mac->mac_intrtask, 0, bwn_intrtask, mac); TASK_INIT(&mac->mac_txpower, 0, bwn_txpwr, mac); error = bwn_attach_core(mac); if (error) goto fail0; bwn_led_attach(mac); device_printf(sc->sc_dev, "WLAN (chipid %#x rev %u) " "PHY (analog %d type %d rev %d) RADIO (manuf %#x ver %#x rev %d)\n", siba_get_chipid(sc->sc_dev), siba_get_revid(sc->sc_dev), mac->mac_phy.analog, mac->mac_phy.type, mac->mac_phy.rev, mac->mac_phy.rf_manuf, mac->mac_phy.rf_ver, mac->mac_phy.rf_rev); if (mac->mac_flags & BWN_MAC_FLAG_DMA) device_printf(sc->sc_dev, "DMA (%d bits)\n", mac->mac_method.dma.dmatype); else device_printf(sc->sc_dev, "PIO\n"); /* * setup PCI resources and interrupt. */ if (pci_find_cap(dev, PCIY_EXPRESS, ®) == 0) { msic = pci_msi_count(dev); if (bootverbose) device_printf(sc->sc_dev, "MSI count : %d\n", msic); } else msic = 0; mac->mac_intr_spec = bwn_res_spec_legacy; if (msic == BWN_MSI_MESSAGES && bwn_msi_disable == 0) { if (pci_alloc_msi(dev, &msic) == 0) { device_printf(sc->sc_dev, "Using %d MSI messages\n", msic); mac->mac_intr_spec = bwn_res_spec_msi; mac->mac_msi = 1; } } error = bus_alloc_resources(dev, mac->mac_intr_spec, mac->mac_res_irq); if (error) { device_printf(sc->sc_dev, "couldn't allocate IRQ resources (%d)\n", error); goto fail1; } if (mac->mac_msi == 0) error = bus_setup_intr(dev, mac->mac_res_irq[0], INTR_TYPE_NET | INTR_MPSAFE, bwn_intr, NULL, mac, &mac->mac_intrhand[0]); else { for (i = 0; i < BWN_MSI_MESSAGES; i++) { error = bus_setup_intr(dev, mac->mac_res_irq[i], INTR_TYPE_NET | INTR_MPSAFE, bwn_intr, NULL, mac, &mac->mac_intrhand[i]); if (error != 0) { device_printf(sc->sc_dev, "couldn't setup interrupt (%d)\n", error); break; } } } TAILQ_INSERT_TAIL(&sc->sc_maclist, mac, mac_list); /* * calls attach-post routine */ if ((sc->sc_flags & BWN_FLAG_ATTACHED) != 0) bwn_attach_post(sc); return (0); fail1: if (msic == BWN_MSI_MESSAGES && bwn_msi_disable == 0) pci_release_msi(dev); fail0: free(mac, M_DEVBUF); return (error); } static int bwn_is_valid_ether_addr(uint8_t *addr) { char zero_addr[6] = { 0, 0, 0, 0, 0, 0 }; if ((addr[0] & 1) || (!bcmp(addr, zero_addr, ETHER_ADDR_LEN))) return (FALSE); return (TRUE); } static int bwn_attach_post(struct bwn_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; ic->ic_softc = sc; ic->ic_name = device_get_nameunit(sc->sc_dev); /* XXX not right but it's not used anywhere important */ ic->ic_phytype = IEEE80211_T_OFDM; ic->ic_opmode = IEEE80211_M_STA; ic->ic_caps = IEEE80211_C_STA /* station mode supported */ | IEEE80211_C_MONITOR /* monitor mode */ | IEEE80211_C_AHDEMO /* adhoc demo mode */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_WME /* WME/WMM supported */ | IEEE80211_C_WPA /* capable of WPA1+WPA2 */ | IEEE80211_C_BGSCAN /* capable of bg scanning */ | IEEE80211_C_TXPMGT /* capable of txpow mgt */ ; ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS; /* s/w bmiss */ IEEE80211_ADDR_COPY(ic->ic_macaddr, bwn_is_valid_ether_addr(siba_sprom_get_mac_80211a(sc->sc_dev)) ? siba_sprom_get_mac_80211a(sc->sc_dev) : siba_sprom_get_mac_80211bg(sc->sc_dev)); /* call MI attach routine. */ ieee80211_ifattach(ic); ic->ic_headroom = sizeof(struct bwn_txhdr); /* override default methods */ ic->ic_raw_xmit = bwn_raw_xmit; ic->ic_updateslot = bwn_updateslot; ic->ic_update_promisc = bwn_update_promisc; ic->ic_wme.wme_update = bwn_wme_update; ic->ic_scan_start = bwn_scan_start; ic->ic_scan_end = bwn_scan_end; ic->ic_set_channel = bwn_set_channel; ic->ic_vap_create = bwn_vap_create; ic->ic_vap_delete = bwn_vap_delete; ic->ic_transmit = bwn_transmit; ic->ic_parent = bwn_parent; ieee80211_radiotap_attach(ic, &sc->sc_tx_th.wt_ihdr, sizeof(sc->sc_tx_th), BWN_TX_RADIOTAP_PRESENT, &sc->sc_rx_th.wr_ihdr, sizeof(sc->sc_rx_th), BWN_RX_RADIOTAP_PRESENT); bwn_sysctl_node(sc); if (bootverbose) ieee80211_announce(ic); return (0); } static void bwn_phy_detach(struct bwn_mac *mac) { if (mac->mac_phy.detach != NULL) mac->mac_phy.detach(mac); } static int bwn_detach(device_t dev) { struct bwn_softc *sc = device_get_softc(dev); struct bwn_mac *mac = sc->sc_curmac; struct ieee80211com *ic = &sc->sc_ic; int i; sc->sc_flags |= BWN_FLAG_INVALID; if (device_is_attached(sc->sc_dev)) { BWN_LOCK(sc); bwn_stop(sc); BWN_UNLOCK(sc); bwn_dma_free(mac); callout_drain(&sc->sc_led_blink_ch); callout_drain(&sc->sc_rfswitch_ch); callout_drain(&sc->sc_task_ch); callout_drain(&sc->sc_watchdog_ch); bwn_phy_detach(mac); ieee80211_draintask(ic, &mac->mac_hwreset); ieee80211_draintask(ic, &mac->mac_txpower); ieee80211_ifdetach(ic); } taskqueue_drain(sc->sc_tq, &mac->mac_intrtask); taskqueue_free(sc->sc_tq); for (i = 0; i < BWN_MSI_MESSAGES; i++) { if (mac->mac_intrhand[i] != NULL) { bus_teardown_intr(dev, mac->mac_res_irq[i], mac->mac_intrhand[i]); mac->mac_intrhand[i] = NULL; } } bus_release_resources(dev, mac->mac_intr_spec, mac->mac_res_irq); if (mac->mac_msi != 0) pci_release_msi(dev); mbufq_drain(&sc->sc_snd); BWN_LOCK_DESTROY(sc); return (0); } static void bwn_attach_pre(struct bwn_softc *sc) { BWN_LOCK_INIT(sc); TAILQ_INIT(&sc->sc_maclist); callout_init_mtx(&sc->sc_rfswitch_ch, &sc->sc_mtx, 0); callout_init_mtx(&sc->sc_task_ch, &sc->sc_mtx, 0); callout_init_mtx(&sc->sc_watchdog_ch, &sc->sc_mtx, 0); mbufq_init(&sc->sc_snd, ifqmaxlen); sc->sc_tq = taskqueue_create_fast("bwn_taskq", M_NOWAIT, taskqueue_thread_enqueue, &sc->sc_tq); taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq", device_get_nameunit(sc->sc_dev)); } static void bwn_sprom_bugfixes(device_t dev) { #define BWN_ISDEV(_vendor, _device, _subvendor, _subdevice) \ ((siba_get_pci_vendor(dev) == PCI_VENDOR_##_vendor) && \ (siba_get_pci_device(dev) == _device) && \ (siba_get_pci_subvendor(dev) == PCI_VENDOR_##_subvendor) && \ (siba_get_pci_subdevice(dev) == _subdevice)) if (siba_get_pci_subvendor(dev) == PCI_VENDOR_APPLE && siba_get_pci_subdevice(dev) == 0x4e && siba_get_pci_revid(dev) > 0x40) siba_sprom_set_bf_lo(dev, siba_sprom_get_bf_lo(dev) | BWN_BFL_PACTRL); if (siba_get_pci_subvendor(dev) == SIBA_BOARDVENDOR_DELL && siba_get_chipid(dev) == 0x4301 && siba_get_pci_revid(dev) == 0x74) siba_sprom_set_bf_lo(dev, siba_sprom_get_bf_lo(dev) | BWN_BFL_BTCOEXIST); if (siba_get_type(dev) == SIBA_TYPE_PCI) { if (BWN_ISDEV(BROADCOM, 0x4318, ASUSTEK, 0x100f) || BWN_ISDEV(BROADCOM, 0x4320, DELL, 0x0003) || BWN_ISDEV(BROADCOM, 0x4320, HP, 0x12f8) || BWN_ISDEV(BROADCOM, 0x4320, LINKSYS, 0x0013) || BWN_ISDEV(BROADCOM, 0x4320, LINKSYS, 0x0014) || BWN_ISDEV(BROADCOM, 0x4320, LINKSYS, 0x0015) || BWN_ISDEV(BROADCOM, 0x4320, MOTOROLA, 0x7010)) siba_sprom_set_bf_lo(dev, siba_sprom_get_bf_lo(dev) & ~BWN_BFL_BTCOEXIST); } #undef BWN_ISDEV } static void bwn_parent(struct ieee80211com *ic) { struct bwn_softc *sc = ic->ic_softc; int startall = 0; BWN_LOCK(sc); if (ic->ic_nrunning > 0) { if ((sc->sc_flags & BWN_FLAG_RUNNING) == 0) { bwn_init(sc); startall = 1; } else bwn_update_promisc(ic); } else if (sc->sc_flags & BWN_FLAG_RUNNING) bwn_stop(sc); BWN_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } static int bwn_transmit(struct ieee80211com *ic, struct mbuf *m) { struct bwn_softc *sc = ic->ic_softc; int error; BWN_LOCK(sc); if ((sc->sc_flags & BWN_FLAG_RUNNING) == 0) { BWN_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { BWN_UNLOCK(sc); return (error); } bwn_start(sc); BWN_UNLOCK(sc); return (0); } static void bwn_start(struct bwn_softc *sc) { struct bwn_mac *mac = sc->sc_curmac; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct ieee80211_key *k; struct mbuf *m; BWN_ASSERT_LOCKED(sc); if ((sc->sc_flags & BWN_FLAG_RUNNING) == 0 || mac == NULL || mac->mac_status < BWN_MAC_STATUS_STARTED) return; while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { if (bwn_tx_isfull(sc, m)) break; ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; if (ni == NULL) { device_printf(sc->sc_dev, "unexpected NULL ni\n"); m_freem(m); counter_u64_add(sc->sc_ic.ic_oerrors, 1); continue; } wh = mtod(m, struct ieee80211_frame *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m); if (k == NULL) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); m_freem(m); continue; } } wh = NULL; /* Catch any invalid use */ if (bwn_tx_start(sc, ni, m) != 0) { if (ni != NULL) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); } continue; } sc->sc_watchdog_timer = 5; } } static int bwn_tx_isfull(struct bwn_softc *sc, struct mbuf *m) { struct bwn_dma_ring *dr; struct bwn_mac *mac = sc->sc_curmac; struct bwn_pio_txqueue *tq; int pktlen = roundup(m->m_pkthdr.len + BWN_HDRSIZE(mac), 4); BWN_ASSERT_LOCKED(sc); if (mac->mac_flags & BWN_MAC_FLAG_DMA) { dr = bwn_dma_select(mac, M_WME_GETAC(m)); if (dr->dr_stop == 1 || bwn_dma_freeslot(dr) < BWN_TX_SLOTS_PER_FRAME) { dr->dr_stop = 1; goto full; } } else { tq = bwn_pio_select(mac, M_WME_GETAC(m)); if (tq->tq_free == 0 || pktlen > tq->tq_size || pktlen > (tq->tq_size - tq->tq_used)) goto full; } return (0); full: mbufq_prepend(&sc->sc_snd, m); return (1); } static int bwn_tx_start(struct bwn_softc *sc, struct ieee80211_node *ni, struct mbuf *m) { struct bwn_mac *mac = sc->sc_curmac; int error; BWN_ASSERT_LOCKED(sc); if (m->m_pkthdr.len < IEEE80211_MIN_LEN || mac == NULL) { m_freem(m); return (ENXIO); } error = (mac->mac_flags & BWN_MAC_FLAG_DMA) ? bwn_dma_tx_start(mac, ni, m) : bwn_pio_tx_start(mac, ni, m); if (error) { m_freem(m); return (error); } return (0); } static int bwn_pio_tx_start(struct bwn_mac *mac, struct ieee80211_node *ni, struct mbuf *m) { struct bwn_pio_txpkt *tp; struct bwn_pio_txqueue *tq = bwn_pio_select(mac, M_WME_GETAC(m)); struct bwn_softc *sc = mac->mac_sc; struct bwn_txhdr txhdr; struct mbuf *m_new; uint32_t ctl32; int error; uint16_t ctl16; BWN_ASSERT_LOCKED(sc); /* XXX TODO send packets after DTIM */ KASSERT(!TAILQ_EMPTY(&tq->tq_pktlist), ("%s: fail", __func__)); tp = TAILQ_FIRST(&tq->tq_pktlist); tp->tp_ni = ni; tp->tp_m = m; error = bwn_set_txhdr(mac, ni, m, &txhdr, BWN_PIO_COOKIE(tq, tp)); if (error) { device_printf(sc->sc_dev, "tx fail\n"); return (error); } TAILQ_REMOVE(&tq->tq_pktlist, tp, tp_list); tq->tq_used += roundup(m->m_pkthdr.len + BWN_HDRSIZE(mac), 4); tq->tq_free--; if (siba_get_revid(sc->sc_dev) >= 8) { /* * XXX please removes m_defrag(9) */ m_new = m_defrag(m, M_NOWAIT); if (m_new == NULL) { device_printf(sc->sc_dev, "%s: can't defrag TX buffer\n", __func__); return (ENOBUFS); } if (m_new->m_next != NULL) device_printf(sc->sc_dev, "TODO: fragmented packets for PIO\n"); tp->tp_m = m_new; /* send HEADER */ ctl32 = bwn_pio_write_multi_4(mac, tq, (BWN_PIO_READ_4(mac, tq, BWN_PIO8_TXCTL) | BWN_PIO8_TXCTL_FRAMEREADY) & ~BWN_PIO8_TXCTL_EOF, (const uint8_t *)&txhdr, BWN_HDRSIZE(mac)); /* send BODY */ ctl32 = bwn_pio_write_multi_4(mac, tq, ctl32, mtod(m_new, const void *), m_new->m_pkthdr.len); bwn_pio_write_4(mac, tq, BWN_PIO_TXCTL, ctl32 | BWN_PIO8_TXCTL_EOF); } else { ctl16 = bwn_pio_write_multi_2(mac, tq, (bwn_pio_read_2(mac, tq, BWN_PIO_TXCTL) | BWN_PIO_TXCTL_FRAMEREADY) & ~BWN_PIO_TXCTL_EOF, (const uint8_t *)&txhdr, BWN_HDRSIZE(mac)); ctl16 = bwn_pio_write_mbuf_2(mac, tq, ctl16, m); BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl16 | BWN_PIO_TXCTL_EOF); } return (0); } static struct bwn_pio_txqueue * bwn_pio_select(struct bwn_mac *mac, uint8_t prio) { if ((mac->mac_flags & BWN_MAC_FLAG_WME) == 0) return (&mac->mac_method.pio.wme[WME_AC_BE]); switch (prio) { case 0: return (&mac->mac_method.pio.wme[WME_AC_BE]); case 1: return (&mac->mac_method.pio.wme[WME_AC_BK]); case 2: return (&mac->mac_method.pio.wme[WME_AC_VI]); case 3: return (&mac->mac_method.pio.wme[WME_AC_VO]); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (NULL); } static int bwn_dma_tx_start(struct bwn_mac *mac, struct ieee80211_node *ni, struct mbuf *m) { #define BWN_GET_TXHDRCACHE(slot) \ &(txhdr_cache[(slot / BWN_TX_SLOTS_PER_FRAME) * BWN_HDRSIZE(mac)]) struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dma_ring *dr = bwn_dma_select(mac, M_WME_GETAC(m)); struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *mt; struct bwn_softc *sc = mac->mac_sc; uint8_t *txhdr_cache = (uint8_t *)dr->dr_txhdr_cache; int error, slot, backup[2] = { dr->dr_curslot, dr->dr_usedslot }; BWN_ASSERT_LOCKED(sc); KASSERT(!dr->dr_stop, ("%s:%d: fail", __func__, __LINE__)); /* XXX send after DTIM */ slot = bwn_dma_getslot(dr); dr->getdesc(dr, slot, &desc, &mt); KASSERT(mt->mt_txtype == BWN_DMADESC_METATYPE_HEADER, ("%s:%d: fail", __func__, __LINE__)); error = bwn_set_txhdr(dr->dr_mac, ni, m, (struct bwn_txhdr *)BWN_GET_TXHDRCACHE(slot), BWN_DMA_COOKIE(dr, slot)); if (error) goto fail; error = bus_dmamap_load(dr->dr_txring_dtag, mt->mt_dmap, BWN_GET_TXHDRCACHE(slot), BWN_HDRSIZE(mac), bwn_dma_ring_addr, &mt->mt_paddr, BUS_DMA_NOWAIT); if (error) { device_printf(sc->sc_dev, "%s: can't load TX buffer (1) %d\n", __func__, error); goto fail; } bus_dmamap_sync(dr->dr_txring_dtag, mt->mt_dmap, BUS_DMASYNC_PREWRITE); dr->setdesc(dr, desc, mt->mt_paddr, BWN_HDRSIZE(mac), 1, 0, 0); bus_dmamap_sync(dr->dr_ring_dtag, dr->dr_ring_dmap, BUS_DMASYNC_PREWRITE); slot = bwn_dma_getslot(dr); dr->getdesc(dr, slot, &desc, &mt); KASSERT(mt->mt_txtype == BWN_DMADESC_METATYPE_BODY && mt->mt_islast == 1, ("%s:%d: fail", __func__, __LINE__)); mt->mt_m = m; mt->mt_ni = ni; error = bus_dmamap_load_mbuf(dma->txbuf_dtag, mt->mt_dmap, m, bwn_dma_buf_addr, &mt->mt_paddr, BUS_DMA_NOWAIT); if (error && error != EFBIG) { device_printf(sc->sc_dev, "%s: can't load TX buffer (1) %d\n", __func__, error); goto fail; } if (error) { /* error == EFBIG */ struct mbuf *m_new; m_new = m_defrag(m, M_NOWAIT); if (m_new == NULL) { device_printf(sc->sc_dev, "%s: can't defrag TX buffer\n", __func__); error = ENOBUFS; goto fail; } else { m = m_new; } mt->mt_m = m; error = bus_dmamap_load_mbuf(dma->txbuf_dtag, mt->mt_dmap, m, bwn_dma_buf_addr, &mt->mt_paddr, BUS_DMA_NOWAIT); if (error) { device_printf(sc->sc_dev, "%s: can't load TX buffer (2) %d\n", __func__, error); goto fail; } } bus_dmamap_sync(dma->txbuf_dtag, mt->mt_dmap, BUS_DMASYNC_PREWRITE); dr->setdesc(dr, desc, mt->mt_paddr, m->m_pkthdr.len, 0, 1, 1); bus_dmamap_sync(dr->dr_ring_dtag, dr->dr_ring_dmap, BUS_DMASYNC_PREWRITE); /* XXX send after DTIM */ dr->start_transfer(dr, bwn_dma_nextslot(dr, slot)); return (0); fail: dr->dr_curslot = backup[0]; dr->dr_usedslot = backup[1]; return (error); #undef BWN_GET_TXHDRCACHE } static void bwn_watchdog(void *arg) { struct bwn_softc *sc = arg; if (sc->sc_watchdog_timer != 0 && --sc->sc_watchdog_timer == 0) { device_printf(sc->sc_dev, "device timeout\n"); counter_u64_add(sc->sc_ic.ic_oerrors, 1); } callout_schedule(&sc->sc_watchdog_ch, hz); } static int bwn_attach_core(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int error, have_bg = 0, have_a = 0; uint32_t high; KASSERT(siba_get_revid(sc->sc_dev) >= 5, ("unsupported revision %d", siba_get_revid(sc->sc_dev))); siba_powerup(sc->sc_dev, 0); high = siba_read_4(sc->sc_dev, SIBA_TGSHIGH); bwn_reset_core(mac, (high & BWN_TGSHIGH_HAVE_2GHZ) ? BWN_TGSLOW_SUPPORT_G : 0); error = bwn_phy_getinfo(mac, high); if (error) goto fail; have_a = (high & BWN_TGSHIGH_HAVE_5GHZ) ? 1 : 0; have_bg = (high & BWN_TGSHIGH_HAVE_2GHZ) ? 1 : 0; if (siba_get_pci_device(sc->sc_dev) != 0x4312 && siba_get_pci_device(sc->sc_dev) != 0x4319 && siba_get_pci_device(sc->sc_dev) != 0x4324) { have_a = have_bg = 0; if (mac->mac_phy.type == BWN_PHYTYPE_A) have_a = 1; else if (mac->mac_phy.type == BWN_PHYTYPE_G || mac->mac_phy.type == BWN_PHYTYPE_N || mac->mac_phy.type == BWN_PHYTYPE_LP) have_bg = 1; else KASSERT(0 == 1, ("%s: unknown phy type (%d)", __func__, mac->mac_phy.type)); } /* XXX turns off PHY A because it's not supported */ if (mac->mac_phy.type != BWN_PHYTYPE_LP && mac->mac_phy.type != BWN_PHYTYPE_N) { have_a = 0; have_bg = 1; } if (mac->mac_phy.type == BWN_PHYTYPE_G) { mac->mac_phy.attach = bwn_phy_g_attach; mac->mac_phy.detach = bwn_phy_g_detach; mac->mac_phy.prepare_hw = bwn_phy_g_prepare_hw; mac->mac_phy.init_pre = bwn_phy_g_init_pre; mac->mac_phy.init = bwn_phy_g_init; mac->mac_phy.exit = bwn_phy_g_exit; mac->mac_phy.phy_read = bwn_phy_g_read; mac->mac_phy.phy_write = bwn_phy_g_write; mac->mac_phy.rf_read = bwn_phy_g_rf_read; mac->mac_phy.rf_write = bwn_phy_g_rf_write; mac->mac_phy.use_hwpctl = bwn_phy_g_hwpctl; mac->mac_phy.rf_onoff = bwn_phy_g_rf_onoff; mac->mac_phy.switch_analog = bwn_phy_switch_analog; mac->mac_phy.switch_channel = bwn_phy_g_switch_channel; mac->mac_phy.get_default_chan = bwn_phy_g_get_default_chan; mac->mac_phy.set_antenna = bwn_phy_g_set_antenna; mac->mac_phy.set_im = bwn_phy_g_im; mac->mac_phy.recalc_txpwr = bwn_phy_g_recalc_txpwr; mac->mac_phy.set_txpwr = bwn_phy_g_set_txpwr; mac->mac_phy.task_15s = bwn_phy_g_task_15s; mac->mac_phy.task_60s = bwn_phy_g_task_60s; } else if (mac->mac_phy.type == BWN_PHYTYPE_LP) { mac->mac_phy.init_pre = bwn_phy_lp_init_pre; mac->mac_phy.init = bwn_phy_lp_init; mac->mac_phy.phy_read = bwn_phy_lp_read; mac->mac_phy.phy_write = bwn_phy_lp_write; mac->mac_phy.phy_maskset = bwn_phy_lp_maskset; mac->mac_phy.rf_read = bwn_phy_lp_rf_read; mac->mac_phy.rf_write = bwn_phy_lp_rf_write; mac->mac_phy.rf_onoff = bwn_phy_lp_rf_onoff; mac->mac_phy.switch_analog = bwn_phy_lp_switch_analog; mac->mac_phy.switch_channel = bwn_phy_lp_switch_channel; mac->mac_phy.get_default_chan = bwn_phy_lp_get_default_chan; mac->mac_phy.set_antenna = bwn_phy_lp_set_antenna; mac->mac_phy.task_60s = bwn_phy_lp_task_60s; } else { device_printf(sc->sc_dev, "unsupported PHY type (%d)\n", mac->mac_phy.type); error = ENXIO; goto fail; } mac->mac_phy.gmode = have_bg; if (mac->mac_phy.attach != NULL) { error = mac->mac_phy.attach(mac); if (error) { device_printf(sc->sc_dev, "failed\n"); goto fail; } } bwn_reset_core(mac, have_bg ? BWN_TGSLOW_SUPPORT_G : 0); error = bwn_chiptest(mac); if (error) goto fail; error = bwn_setup_channels(mac, have_bg, have_a); if (error) { device_printf(sc->sc_dev, "failed to setup channels\n"); goto fail; } if (sc->sc_curmac == NULL) sc->sc_curmac = mac; error = bwn_dma_attach(mac); if (error != 0) { device_printf(sc->sc_dev, "failed to initialize DMA\n"); goto fail; } mac->mac_phy.switch_analog(mac, 0); siba_dev_down(sc->sc_dev, 0); fail: siba_powerdown(sc->sc_dev); return (error); } void bwn_reset_core(struct bwn_mac *mac, uint32_t flags) { struct bwn_softc *sc = mac->mac_sc; uint32_t low, ctl; flags |= (BWN_TGSLOW_PHYCLOCK_ENABLE | BWN_TGSLOW_PHYRESET); siba_dev_up(sc->sc_dev, flags); DELAY(2000); low = (siba_read_4(sc->sc_dev, SIBA_TGSLOW) | SIBA_TGSLOW_FGC) & ~BWN_TGSLOW_PHYRESET; siba_write_4(sc->sc_dev, SIBA_TGSLOW, low); siba_read_4(sc->sc_dev, SIBA_TGSLOW); DELAY(1000); siba_write_4(sc->sc_dev, SIBA_TGSLOW, low & ~SIBA_TGSLOW_FGC); siba_read_4(sc->sc_dev, SIBA_TGSLOW); DELAY(1000); if (mac->mac_phy.switch_analog != NULL) mac->mac_phy.switch_analog(mac, 1); ctl = BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_GMODE; if (flags & BWN_TGSLOW_SUPPORT_G) ctl |= BWN_MACCTL_GMODE; BWN_WRITE_4(mac, BWN_MACCTL, ctl | BWN_MACCTL_IHR_ON); } static int bwn_phy_getinfo(struct bwn_mac *mac, int tgshigh) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; uint32_t tmp; /* PHY */ tmp = BWN_READ_2(mac, BWN_PHYVER); phy->gmode = (tgshigh & BWN_TGSHIGH_HAVE_2GHZ) ? 1 : 0; phy->rf_on = 1; phy->analog = (tmp & BWN_PHYVER_ANALOG) >> 12; phy->type = (tmp & BWN_PHYVER_TYPE) >> 8; phy->rev = (tmp & BWN_PHYVER_VERSION); if ((phy->type == BWN_PHYTYPE_A && phy->rev >= 4) || (phy->type == BWN_PHYTYPE_B && phy->rev != 2 && phy->rev != 4 && phy->rev != 6 && phy->rev != 7) || (phy->type == BWN_PHYTYPE_G && phy->rev > 9) || (phy->type == BWN_PHYTYPE_N && phy->rev > 4) || (phy->type == BWN_PHYTYPE_LP && phy->rev > 2)) goto unsupphy; /* RADIO */ if (siba_get_chipid(sc->sc_dev) == 0x4317) { if (siba_get_chiprev(sc->sc_dev) == 0) tmp = 0x3205017f; else if (siba_get_chiprev(sc->sc_dev) == 1) tmp = 0x4205017f; else tmp = 0x5205017f; } else { BWN_WRITE_2(mac, BWN_RFCTL, BWN_RFCTL_ID); tmp = BWN_READ_2(mac, BWN_RFDATALO); BWN_WRITE_2(mac, BWN_RFCTL, BWN_RFCTL_ID); tmp |= (uint32_t)BWN_READ_2(mac, BWN_RFDATAHI) << 16; } phy->rf_rev = (tmp & 0xf0000000) >> 28; phy->rf_ver = (tmp & 0x0ffff000) >> 12; phy->rf_manuf = (tmp & 0x00000fff); if (phy->rf_manuf != 0x17f) /* 0x17f is broadcom */ goto unsupradio; if ((phy->type == BWN_PHYTYPE_A && (phy->rf_ver != 0x2060 || phy->rf_rev != 1 || phy->rf_manuf != 0x17f)) || (phy->type == BWN_PHYTYPE_B && (phy->rf_ver & 0xfff0) != 0x2050) || (phy->type == BWN_PHYTYPE_G && phy->rf_ver != 0x2050) || (phy->type == BWN_PHYTYPE_N && phy->rf_ver != 0x2055 && phy->rf_ver != 0x2056) || (phy->type == BWN_PHYTYPE_LP && phy->rf_ver != 0x2062 && phy->rf_ver != 0x2063)) goto unsupradio; return (0); unsupphy: device_printf(sc->sc_dev, "unsupported PHY (type %#x, rev %#x, " "analog %#x)\n", phy->type, phy->rev, phy->analog); return (ENXIO); unsupradio: device_printf(sc->sc_dev, "unsupported radio (manuf %#x, ver %#x, " "rev %#x)\n", phy->rf_manuf, phy->rf_ver, phy->rf_rev); return (ENXIO); } static int bwn_chiptest(struct bwn_mac *mac) { #define TESTVAL0 0x55aaaa55 #define TESTVAL1 0xaa5555aa struct bwn_softc *sc = mac->mac_sc; uint32_t v, backup; BWN_LOCK(sc); backup = bwn_shm_read_4(mac, BWN_SHARED, 0); bwn_shm_write_4(mac, BWN_SHARED, 0, TESTVAL0); if (bwn_shm_read_4(mac, BWN_SHARED, 0) != TESTVAL0) goto error; bwn_shm_write_4(mac, BWN_SHARED, 0, TESTVAL1); if (bwn_shm_read_4(mac, BWN_SHARED, 0) != TESTVAL1) goto error; bwn_shm_write_4(mac, BWN_SHARED, 0, backup); if ((siba_get_revid(sc->sc_dev) >= 3) && (siba_get_revid(sc->sc_dev) <= 10)) { BWN_WRITE_2(mac, BWN_TSF_CFP_START, 0xaaaa); BWN_WRITE_4(mac, BWN_TSF_CFP_START, 0xccccbbbb); if (BWN_READ_2(mac, BWN_TSF_CFP_START_LOW) != 0xbbbb) goto error; if (BWN_READ_2(mac, BWN_TSF_CFP_START_HIGH) != 0xcccc) goto error; } BWN_WRITE_4(mac, BWN_TSF_CFP_START, 0); v = BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_GMODE; if (v != (BWN_MACCTL_GMODE | BWN_MACCTL_IHR_ON)) goto error; BWN_UNLOCK(sc); return (0); error: BWN_UNLOCK(sc); device_printf(sc->sc_dev, "failed to validate the chipaccess\n"); return (ENODEV); } #define IEEE80211_CHAN_HTG (IEEE80211_CHAN_HT | IEEE80211_CHAN_G) #define IEEE80211_CHAN_HTA (IEEE80211_CHAN_HT | IEEE80211_CHAN_A) static int bwn_setup_channels(struct bwn_mac *mac, int have_bg, int have_a) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; memset(ic->ic_channels, 0, sizeof(ic->ic_channels)); ic->ic_nchans = 0; if (have_bg) bwn_addchannels(ic->ic_channels, IEEE80211_CHAN_MAX, &ic->ic_nchans, &bwn_chantable_bg, IEEE80211_CHAN_G); if (mac->mac_phy.type == BWN_PHYTYPE_N) { if (have_a) bwn_addchannels(ic->ic_channels, IEEE80211_CHAN_MAX, &ic->ic_nchans, &bwn_chantable_n, IEEE80211_CHAN_HTA); } else { if (have_a) bwn_addchannels(ic->ic_channels, IEEE80211_CHAN_MAX, &ic->ic_nchans, &bwn_chantable_a, IEEE80211_CHAN_A); } mac->mac_phy.supports_2ghz = have_bg; mac->mac_phy.supports_5ghz = have_a; return (ic->ic_nchans == 0 ? ENXIO : 0); } uint32_t bwn_shm_read_4(struct bwn_mac *mac, uint16_t way, uint16_t offset) { uint32_t ret; BWN_ASSERT_LOCKED(mac->mac_sc); if (way == BWN_SHARED) { KASSERT((offset & 0x0001) == 0, ("%s:%d warn", __func__, __LINE__)); if (offset & 0x0003) { bwn_shm_ctlword(mac, way, offset >> 2); ret = BWN_READ_2(mac, BWN_SHM_DATA_UNALIGNED); ret <<= 16; bwn_shm_ctlword(mac, way, (offset >> 2) + 1); ret |= BWN_READ_2(mac, BWN_SHM_DATA); goto out; } offset >>= 2; } bwn_shm_ctlword(mac, way, offset); ret = BWN_READ_4(mac, BWN_SHM_DATA); out: return (ret); } uint16_t bwn_shm_read_2(struct bwn_mac *mac, uint16_t way, uint16_t offset) { uint16_t ret; BWN_ASSERT_LOCKED(mac->mac_sc); if (way == BWN_SHARED) { KASSERT((offset & 0x0001) == 0, ("%s:%d warn", __func__, __LINE__)); if (offset & 0x0003) { bwn_shm_ctlword(mac, way, offset >> 2); ret = BWN_READ_2(mac, BWN_SHM_DATA_UNALIGNED); goto out; } offset >>= 2; } bwn_shm_ctlword(mac, way, offset); ret = BWN_READ_2(mac, BWN_SHM_DATA); out: return (ret); } static void bwn_shm_ctlword(struct bwn_mac *mac, uint16_t way, uint16_t offset) { uint32_t control; control = way; control <<= 16; control |= offset; BWN_WRITE_4(mac, BWN_SHM_CONTROL, control); } void bwn_shm_write_4(struct bwn_mac *mac, uint16_t way, uint16_t offset, uint32_t value) { BWN_ASSERT_LOCKED(mac->mac_sc); if (way == BWN_SHARED) { KASSERT((offset & 0x0001) == 0, ("%s:%d warn", __func__, __LINE__)); if (offset & 0x0003) { bwn_shm_ctlword(mac, way, offset >> 2); BWN_WRITE_2(mac, BWN_SHM_DATA_UNALIGNED, (value >> 16) & 0xffff); bwn_shm_ctlword(mac, way, (offset >> 2) + 1); BWN_WRITE_2(mac, BWN_SHM_DATA, value & 0xffff); return; } offset >>= 2; } bwn_shm_ctlword(mac, way, offset); BWN_WRITE_4(mac, BWN_SHM_DATA, value); } void bwn_shm_write_2(struct bwn_mac *mac, uint16_t way, uint16_t offset, uint16_t value) { BWN_ASSERT_LOCKED(mac->mac_sc); if (way == BWN_SHARED) { KASSERT((offset & 0x0001) == 0, ("%s:%d warn", __func__, __LINE__)); if (offset & 0x0003) { bwn_shm_ctlword(mac, way, offset >> 2); BWN_WRITE_2(mac, BWN_SHM_DATA_UNALIGNED, value); return; } offset >>= 2; } bwn_shm_ctlword(mac, way, offset); BWN_WRITE_2(mac, BWN_SHM_DATA, value); } static void bwn_addchan(struct ieee80211_channel *c, int freq, int flags, int ieee, int txpow) { c->ic_freq = freq; c->ic_flags = flags; c->ic_ieee = ieee; c->ic_minpower = 0; c->ic_maxpower = 2 * txpow; c->ic_maxregpower = txpow; } static void bwn_addchannels(struct ieee80211_channel chans[], int maxchans, int *nchans, const struct bwn_channelinfo *ci, int flags) { struct ieee80211_channel *c; int i; c = &chans[*nchans]; for (i = 0; i < ci->nchannels; i++) { const struct bwn_channel *hc; hc = &ci->channels[i]; if (*nchans >= maxchans) break; bwn_addchan(c, hc->freq, flags, hc->ieee, hc->maxTxPow); c++, (*nchans)++; if (flags == IEEE80211_CHAN_G || flags == IEEE80211_CHAN_HTG) { /* g channel have a separate b-only entry */ if (*nchans >= maxchans) break; c[0] = c[-1]; c[-1].ic_flags = IEEE80211_CHAN_B; c++, (*nchans)++; } if (flags == IEEE80211_CHAN_HTG) { /* HT g channel have a separate g-only entry */ if (*nchans >= maxchans) break; c[-1].ic_flags = IEEE80211_CHAN_G; c[0] = c[-1]; c[0].ic_flags &= ~IEEE80211_CHAN_HT; c[0].ic_flags |= IEEE80211_CHAN_HT20; /* HT20 */ c++, (*nchans)++; } if (flags == IEEE80211_CHAN_HTA) { /* HT a channel have a separate a-only entry */ if (*nchans >= maxchans) break; c[-1].ic_flags = IEEE80211_CHAN_A; c[0] = c[-1]; c[0].ic_flags &= ~IEEE80211_CHAN_HT; c[0].ic_flags |= IEEE80211_CHAN_HT20; /* HT20 */ c++, (*nchans)++; } } } static int bwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct bwn_softc *sc = ic->ic_softc; struct bwn_mac *mac = sc->sc_curmac; int error; if ((sc->sc_flags & BWN_FLAG_RUNNING) == 0 || mac->mac_status < BWN_MAC_STATUS_STARTED) { m_freem(m); return (ENETDOWN); } BWN_LOCK(sc); if (bwn_tx_isfull(sc, m)) { m_freem(m); BWN_UNLOCK(sc); return (ENOBUFS); } error = bwn_tx_start(sc, ni, m); if (error == 0) sc->sc_watchdog_timer = 5; BWN_UNLOCK(sc); return (error); } /* * Callback from the 802.11 layer to update the slot time * based on the current setting. We use it to notify the * firmware of ERP changes and the f/w takes care of things * like slot time and preamble. */ static void bwn_updateslot(struct ieee80211com *ic) { struct bwn_softc *sc = ic->ic_softc; struct bwn_mac *mac; BWN_LOCK(sc); if (sc->sc_flags & BWN_FLAG_RUNNING) { mac = (struct bwn_mac *)sc->sc_curmac; bwn_set_slot_time(mac, IEEE80211_GET_SLOTTIME(ic)); } BWN_UNLOCK(sc); } /* * Callback from the 802.11 layer after a promiscuous mode change. * Note this interface does not check the operating mode as this * is an internal callback and we are expected to honor the current * state (e.g. this is used for setting the interface in promiscuous * mode when operating in hostap mode to do ACS). */ static void bwn_update_promisc(struct ieee80211com *ic) { struct bwn_softc *sc = ic->ic_softc; struct bwn_mac *mac = sc->sc_curmac; BWN_LOCK(sc); mac = sc->sc_curmac; if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { if (ic->ic_promisc > 0) sc->sc_filters |= BWN_MACCTL_PROMISC; else sc->sc_filters &= ~BWN_MACCTL_PROMISC; bwn_set_opmode(mac); } BWN_UNLOCK(sc); } /* * Callback from the 802.11 layer to update WME parameters. */ static int bwn_wme_update(struct ieee80211com *ic) { struct bwn_softc *sc = ic->ic_softc; struct bwn_mac *mac = sc->sc_curmac; struct wmeParams *wmep; int i; BWN_LOCK(sc); mac = sc->sc_curmac; if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { bwn_mac_suspend(mac); for (i = 0; i < N(sc->sc_wmeParams); i++) { wmep = &ic->ic_wme.wme_chanParams.cap_wmeParams[i]; bwn_wme_loadparams(mac, wmep, bwn_wme_shm_offsets[i]); } bwn_mac_enable(mac); } BWN_UNLOCK(sc); return (0); } static void bwn_scan_start(struct ieee80211com *ic) { struct bwn_softc *sc = ic->ic_softc; struct bwn_mac *mac; BWN_LOCK(sc); mac = sc->sc_curmac; if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { sc->sc_filters |= BWN_MACCTL_BEACON_PROMISC; bwn_set_opmode(mac); /* disable CFP update during scan */ bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_SKIP_CFP_UPDATE); } BWN_UNLOCK(sc); } static void bwn_scan_end(struct ieee80211com *ic) { struct bwn_softc *sc = ic->ic_softc; struct bwn_mac *mac; BWN_LOCK(sc); mac = sc->sc_curmac; if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { sc->sc_filters &= ~BWN_MACCTL_BEACON_PROMISC; bwn_set_opmode(mac); bwn_hf_write(mac, bwn_hf_read(mac) & ~BWN_HF_SKIP_CFP_UPDATE); } BWN_UNLOCK(sc); } static void bwn_set_channel(struct ieee80211com *ic) { struct bwn_softc *sc = ic->ic_softc; struct bwn_mac *mac = sc->sc_curmac; struct bwn_phy *phy = &mac->mac_phy; int chan, error; BWN_LOCK(sc); error = bwn_switch_band(sc, ic->ic_curchan); if (error) goto fail; bwn_mac_suspend(mac); bwn_set_txretry(mac, BWN_RETRY_SHORT, BWN_RETRY_LONG); chan = ieee80211_chan2ieee(ic, ic->ic_curchan); if (chan != phy->chan) bwn_switch_channel(mac, chan); /* TX power level */ if (ic->ic_curchan->ic_maxpower != 0 && ic->ic_curchan->ic_maxpower != phy->txpower) { phy->txpower = ic->ic_curchan->ic_maxpower / 2; bwn_phy_txpower_check(mac, BWN_TXPWR_IGNORE_TIME | BWN_TXPWR_IGNORE_TSSI); } bwn_set_txantenna(mac, BWN_ANT_DEFAULT); if (phy->set_antenna) phy->set_antenna(mac, BWN_ANT_DEFAULT); if (sc->sc_rf_enabled != phy->rf_on) { if (sc->sc_rf_enabled) { bwn_rf_turnon(mac); if (!(mac->mac_flags & BWN_MAC_FLAG_RADIO_ON)) device_printf(sc->sc_dev, "please turn on the RF switch\n"); } else bwn_rf_turnoff(mac); } bwn_mac_enable(mac); fail: /* * Setup radio tap channel freq and flags */ sc->sc_tx_th.wt_chan_freq = sc->sc_rx_th.wr_chan_freq = htole16(ic->ic_curchan->ic_freq); sc->sc_tx_th.wt_chan_flags = sc->sc_rx_th.wr_chan_flags = htole16(ic->ic_curchan->ic_flags & 0xffff); BWN_UNLOCK(sc); } static struct ieee80211vap * bwn_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct ieee80211vap *vap; struct bwn_vap *bvp; switch (opmode) { case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: case IEEE80211_M_STA: case IEEE80211_M_WDS: case IEEE80211_M_MONITOR: case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: break; default: return (NULL); } bvp = malloc(sizeof(struct bwn_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &bvp->bv_vap; ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid); /* override with driver methods */ bvp->bv_newstate = vap->iv_newstate; vap->iv_newstate = bwn_newstate; /* override max aid so sta's cannot assoc when we're out of sta id's */ vap->iv_max_aid = BWN_STAID_MAX; ieee80211_ratectl_init(vap); /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); return (vap); } static void bwn_vap_delete(struct ieee80211vap *vap) { struct bwn_vap *bvp = BWN_VAP(vap); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(bvp, M_80211_VAP); } static int bwn_init(struct bwn_softc *sc) { struct bwn_mac *mac; int error; BWN_ASSERT_LOCKED(sc); bzero(sc->sc_bssid, IEEE80211_ADDR_LEN); sc->sc_flags |= BWN_FLAG_NEED_BEACON_TP; sc->sc_filters = 0; bwn_wme_clear(sc); sc->sc_beacons[0] = sc->sc_beacons[1] = 0; sc->sc_rf_enabled = 1; mac = sc->sc_curmac; if (mac->mac_status == BWN_MAC_STATUS_UNINIT) { error = bwn_core_init(mac); if (error != 0) return (error); } if (mac->mac_status == BWN_MAC_STATUS_INITED) bwn_core_start(mac); bwn_set_opmode(mac); bwn_set_pretbtt(mac); bwn_spu_setdelay(mac, 0); bwn_set_macaddr(mac); sc->sc_flags |= BWN_FLAG_RUNNING; callout_reset(&sc->sc_rfswitch_ch, hz, bwn_rfswitch, sc); callout_reset(&sc->sc_watchdog_ch, hz, bwn_watchdog, sc); return (0); } static void bwn_stop(struct bwn_softc *sc) { struct bwn_mac *mac = sc->sc_curmac; BWN_ASSERT_LOCKED(sc); if (mac->mac_status >= BWN_MAC_STATUS_INITED) { /* XXX FIXME opmode not based on VAP */ bwn_set_opmode(mac); bwn_set_macaddr(mac); } if (mac->mac_status >= BWN_MAC_STATUS_STARTED) bwn_core_stop(mac); callout_stop(&sc->sc_led_blink_ch); sc->sc_led_blinking = 0; bwn_core_exit(mac); sc->sc_rf_enabled = 0; sc->sc_flags &= ~BWN_FLAG_RUNNING; } static void bwn_wme_clear(struct bwn_softc *sc) { #define MS(_v, _f) (((_v) & _f) >> _f##_S) struct wmeParams *p; unsigned int i; KASSERT(N(bwn_wme_shm_offsets) == N(sc->sc_wmeParams), ("%s:%d: fail", __func__, __LINE__)); for (i = 0; i < N(sc->sc_wmeParams); i++) { p = &(sc->sc_wmeParams[i]); switch (bwn_wme_shm_offsets[i]) { case BWN_WME_VOICE: p->wmep_txopLimit = 0; p->wmep_aifsn = 2; /* XXX FIXME: log2(cwmin) */ p->wmep_logcwmin = MS(0x0001, WME_PARAM_LOGCWMIN); p->wmep_logcwmax = MS(0x0001, WME_PARAM_LOGCWMAX); break; case BWN_WME_VIDEO: p->wmep_txopLimit = 0; p->wmep_aifsn = 2; /* XXX FIXME: log2(cwmin) */ p->wmep_logcwmin = MS(0x0001, WME_PARAM_LOGCWMIN); p->wmep_logcwmax = MS(0x0001, WME_PARAM_LOGCWMAX); break; case BWN_WME_BESTEFFORT: p->wmep_txopLimit = 0; p->wmep_aifsn = 3; /* XXX FIXME: log2(cwmin) */ p->wmep_logcwmin = MS(0x0001, WME_PARAM_LOGCWMIN); p->wmep_logcwmax = MS(0x03ff, WME_PARAM_LOGCWMAX); break; case BWN_WME_BACKGROUND: p->wmep_txopLimit = 0; p->wmep_aifsn = 7; /* XXX FIXME: log2(cwmin) */ p->wmep_logcwmin = MS(0x0001, WME_PARAM_LOGCWMIN); p->wmep_logcwmax = MS(0x03ff, WME_PARAM_LOGCWMAX); break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } } } static int bwn_core_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint64_t hf; int error; KASSERT(mac->mac_status == BWN_MAC_STATUS_UNINIT, ("%s:%d: fail", __func__, __LINE__)); siba_powerup(sc->sc_dev, 0); if (!siba_dev_isup(sc->sc_dev)) bwn_reset_core(mac, mac->mac_phy.gmode ? BWN_TGSLOW_SUPPORT_G : 0); mac->mac_flags &= ~BWN_MAC_FLAG_DFQVALID; mac->mac_flags |= BWN_MAC_FLAG_RADIO_ON; mac->mac_phy.hwpctl = (bwn_hwpctl) ? 1 : 0; BWN_GETTIME(mac->mac_phy.nexttime); mac->mac_phy.txerrors = BWN_TXERROR_MAX; bzero(&mac->mac_stats, sizeof(mac->mac_stats)); mac->mac_stats.link_noise = -95; mac->mac_reason_intr = 0; bzero(mac->mac_reason, sizeof(mac->mac_reason)); mac->mac_intr_mask = BWN_INTR_MASKTEMPLATE; #ifdef BWN_DEBUG if (sc->sc_debug & BWN_DEBUG_XMIT) mac->mac_intr_mask &= ~BWN_INTR_PHY_TXERR; #endif mac->mac_suspended = 1; mac->mac_task_state = 0; memset(&mac->mac_noise, 0, sizeof(mac->mac_noise)); mac->mac_phy.init_pre(mac); siba_pcicore_intr(sc->sc_dev); siba_fix_imcfglobug(sc->sc_dev); bwn_bt_disable(mac); if (mac->mac_phy.prepare_hw) { error = mac->mac_phy.prepare_hw(mac); if (error) goto fail0; } error = bwn_chip_init(mac); if (error) goto fail0; bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_COREREV, siba_get_revid(sc->sc_dev)); hf = bwn_hf_read(mac); if (mac->mac_phy.type == BWN_PHYTYPE_G) { hf |= BWN_HF_GPHY_SYM_WORKAROUND; if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_PACTRL) hf |= BWN_HF_PAGAINBOOST_OFDM_ON; if (mac->mac_phy.rev == 1) hf |= BWN_HF_GPHY_DC_CANCELFILTER; } if (mac->mac_phy.rf_ver == 0x2050) { if (mac->mac_phy.rf_rev < 6) hf |= BWN_HF_FORCE_VCO_RECALC; if (mac->mac_phy.rf_rev == 6) hf |= BWN_HF_4318_TSSI; } if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_CRYSTAL_NOSLOW) hf |= BWN_HF_SLOWCLOCK_REQ_OFF; if ((siba_get_type(sc->sc_dev) == SIBA_TYPE_PCI) && (siba_get_pcicore_revid(sc->sc_dev) <= 10)) hf |= BWN_HF_PCI_SLOWCLOCK_WORKAROUND; hf &= ~BWN_HF_SKIP_CFP_UPDATE; bwn_hf_write(mac, hf); bwn_set_txretry(mac, BWN_RETRY_SHORT, BWN_RETRY_LONG); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_SHORT_RETRY_FALLBACK, 3); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_LONG_RETRY_FALLBACK, 2); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PROBE_RESP_MAXTIME, 1); bwn_rate_init(mac); bwn_set_phytxctl(mac); bwn_shm_write_2(mac, BWN_SCRATCH, BWN_SCRATCH_CONT_MIN, (mac->mac_phy.type == BWN_PHYTYPE_B) ? 0x1f : 0xf); bwn_shm_write_2(mac, BWN_SCRATCH, BWN_SCRATCH_CONT_MAX, 0x3ff); if (siba_get_type(sc->sc_dev) == SIBA_TYPE_PCMCIA || bwn_usedma == 0) bwn_pio_init(mac); else bwn_dma_init(mac); bwn_wme_init(mac); bwn_spu_setdelay(mac, 1); bwn_bt_enable(mac); siba_powerup(sc->sc_dev, !(siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_CRYSTAL_NOSLOW)); bwn_set_macaddr(mac); bwn_crypt_init(mac); /* XXX LED initializatin */ mac->mac_status = BWN_MAC_STATUS_INITED; return (error); fail0: siba_powerdown(sc->sc_dev); KASSERT(mac->mac_status == BWN_MAC_STATUS_UNINIT, ("%s:%d: fail", __func__, __LINE__)); return (error); } static void bwn_core_start(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint32_t tmp; KASSERT(mac->mac_status == BWN_MAC_STATUS_INITED, ("%s:%d: fail", __func__, __LINE__)); if (siba_get_revid(sc->sc_dev) < 5) return; while (1) { tmp = BWN_READ_4(mac, BWN_XMITSTAT_0); if (!(tmp & 0x00000001)) break; tmp = BWN_READ_4(mac, BWN_XMITSTAT_1); } bwn_mac_enable(mac); BWN_WRITE_4(mac, BWN_INTR_MASK, mac->mac_intr_mask); callout_reset(&sc->sc_task_ch, hz * 15, bwn_tasks, mac); mac->mac_status = BWN_MAC_STATUS_STARTED; } static void bwn_core_exit(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint32_t macctl; BWN_ASSERT_LOCKED(mac->mac_sc); KASSERT(mac->mac_status <= BWN_MAC_STATUS_INITED, ("%s:%d: fail", __func__, __LINE__)); if (mac->mac_status != BWN_MAC_STATUS_INITED) return; mac->mac_status = BWN_MAC_STATUS_UNINIT; macctl = BWN_READ_4(mac, BWN_MACCTL); macctl &= ~BWN_MACCTL_MCODE_RUN; macctl |= BWN_MACCTL_MCODE_JMP0; BWN_WRITE_4(mac, BWN_MACCTL, macctl); bwn_dma_stop(mac); bwn_pio_stop(mac); bwn_chip_exit(mac); mac->mac_phy.switch_analog(mac, 0); siba_dev_down(sc->sc_dev, 0); siba_powerdown(sc->sc_dev); } static void bwn_bt_disable(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; (void)sc; /* XXX do nothing yet */ } static int bwn_chip_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct bwn_phy *phy = &mac->mac_phy; uint32_t macctl; int error; macctl = BWN_MACCTL_IHR_ON | BWN_MACCTL_SHM_ON | BWN_MACCTL_STA; if (phy->gmode) macctl |= BWN_MACCTL_GMODE; BWN_WRITE_4(mac, BWN_MACCTL, macctl); error = bwn_fw_fillinfo(mac); if (error) return (error); error = bwn_fw_loaducode(mac); if (error) return (error); error = bwn_gpio_init(mac); if (error) return (error); error = bwn_fw_loadinitvals(mac); if (error) { siba_gpio_set(sc->sc_dev, 0); return (error); } phy->switch_analog(mac, 1); error = bwn_phy_init(mac); if (error) { siba_gpio_set(sc->sc_dev, 0); return (error); } if (phy->set_im) phy->set_im(mac, BWN_IMMODE_NONE); if (phy->set_antenna) phy->set_antenna(mac, BWN_ANT_DEFAULT); bwn_set_txantenna(mac, BWN_ANT_DEFAULT); if (phy->type == BWN_PHYTYPE_B) BWN_WRITE_2(mac, 0x005e, BWN_READ_2(mac, 0x005e) | 0x0004); BWN_WRITE_4(mac, 0x0100, 0x01000000); if (siba_get_revid(sc->sc_dev) < 5) BWN_WRITE_4(mac, 0x010c, 0x01000000); BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_STA); BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_STA); bwn_shm_write_2(mac, BWN_SHARED, 0x0074, 0x0000); bwn_set_opmode(mac); if (siba_get_revid(sc->sc_dev) < 3) { BWN_WRITE_2(mac, 0x060e, 0x0000); BWN_WRITE_2(mac, 0x0610, 0x8000); BWN_WRITE_2(mac, 0x0604, 0x0000); BWN_WRITE_2(mac, 0x0606, 0x0200); } else { BWN_WRITE_4(mac, 0x0188, 0x80000000); BWN_WRITE_4(mac, 0x018c, 0x02000000); } BWN_WRITE_4(mac, BWN_INTR_REASON, 0x00004000); BWN_WRITE_4(mac, BWN_DMA0_INTR_MASK, 0x0001dc00); BWN_WRITE_4(mac, BWN_DMA1_INTR_MASK, 0x0000dc00); BWN_WRITE_4(mac, BWN_DMA2_INTR_MASK, 0x0000dc00); BWN_WRITE_4(mac, BWN_DMA3_INTR_MASK, 0x0001dc00); BWN_WRITE_4(mac, BWN_DMA4_INTR_MASK, 0x0000dc00); BWN_WRITE_4(mac, BWN_DMA5_INTR_MASK, 0x0000dc00); siba_write_4(sc->sc_dev, SIBA_TGSLOW, siba_read_4(sc->sc_dev, SIBA_TGSLOW) | 0x00100000); BWN_WRITE_2(mac, BWN_POWERUP_DELAY, siba_get_cc_powerdelay(sc->sc_dev)); return (error); } /* read hostflags */ uint64_t bwn_hf_read(struct bwn_mac *mac) { uint64_t ret; ret = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_HFHI); ret <<= 16; ret |= bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_HFMI); ret <<= 16; ret |= bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_HFLO); return (ret); } void bwn_hf_write(struct bwn_mac *mac, uint64_t value) { bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_HFLO, (value & 0x00000000ffffull)); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_HFMI, (value & 0x0000ffff0000ull) >> 16); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_HFHI, (value & 0xffff00000000ULL) >> 32); } static void bwn_set_txretry(struct bwn_mac *mac, int s, int l) { bwn_shm_write_2(mac, BWN_SCRATCH, BWN_SCRATCH_SHORT_RETRY, MIN(s, 0xf)); bwn_shm_write_2(mac, BWN_SCRATCH, BWN_SCRATCH_LONG_RETRY, MIN(l, 0xf)); } static void bwn_rate_init(struct bwn_mac *mac) { switch (mac->mac_phy.type) { case BWN_PHYTYPE_A: case BWN_PHYTYPE_G: case BWN_PHYTYPE_LP: case BWN_PHYTYPE_N: bwn_rate_write(mac, BWN_OFDM_RATE_6MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_12MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_18MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_24MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_36MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_48MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_54MB, 1); if (mac->mac_phy.type == BWN_PHYTYPE_A) break; /* FALLTHROUGH */ case BWN_PHYTYPE_B: bwn_rate_write(mac, BWN_CCK_RATE_1MB, 0); bwn_rate_write(mac, BWN_CCK_RATE_2MB, 0); bwn_rate_write(mac, BWN_CCK_RATE_5MB, 0); bwn_rate_write(mac, BWN_CCK_RATE_11MB, 0); break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } } static void bwn_rate_write(struct bwn_mac *mac, uint16_t rate, int ofdm) { uint16_t offset; if (ofdm) { offset = 0x480; offset += (bwn_plcp_getofdm(rate) & 0x000f) * 2; } else { offset = 0x4c0; offset += (bwn_plcp_getcck(rate) & 0x000f) * 2; } bwn_shm_write_2(mac, BWN_SHARED, offset + 0x20, bwn_shm_read_2(mac, BWN_SHARED, offset)); } static uint8_t bwn_plcp_getcck(const uint8_t bitrate) { switch (bitrate) { case BWN_CCK_RATE_1MB: return (0x0a); case BWN_CCK_RATE_2MB: return (0x14); case BWN_CCK_RATE_5MB: return (0x37); case BWN_CCK_RATE_11MB: return (0x6e); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (0); } static uint8_t bwn_plcp_getofdm(const uint8_t bitrate) { switch (bitrate) { case BWN_OFDM_RATE_6MB: return (0xb); case BWN_OFDM_RATE_9MB: return (0xf); case BWN_OFDM_RATE_12MB: return (0xa); case BWN_OFDM_RATE_18MB: return (0xe); case BWN_OFDM_RATE_24MB: return (0x9); case BWN_OFDM_RATE_36MB: return (0xd); case BWN_OFDM_RATE_48MB: return (0x8); case BWN_OFDM_RATE_54MB: return (0xc); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (0); } static void bwn_set_phytxctl(struct bwn_mac *mac) { uint16_t ctl; ctl = (BWN_TX_PHY_ENC_CCK | BWN_TX_PHY_ANT01AUTO | BWN_TX_PHY_TXPWR); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_BEACON_PHYCTL, ctl); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_ACKCTS_PHYCTL, ctl); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PROBE_RESP_PHYCTL, ctl); } static void bwn_pio_init(struct bwn_mac *mac) { struct bwn_pio *pio = &mac->mac_method.pio; BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_BIGENDIAN); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_RX_PADOFFSET, 0); bwn_pio_set_txqueue(mac, &pio->wme[WME_AC_BK], 0); bwn_pio_set_txqueue(mac, &pio->wme[WME_AC_BE], 1); bwn_pio_set_txqueue(mac, &pio->wme[WME_AC_VI], 2); bwn_pio_set_txqueue(mac, &pio->wme[WME_AC_VO], 3); bwn_pio_set_txqueue(mac, &pio->mcast, 4); bwn_pio_setupqueue_rx(mac, &pio->rx, 0); } static void bwn_pio_set_txqueue(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, int index) { struct bwn_pio_txpkt *tp; struct bwn_softc *sc = mac->mac_sc; unsigned int i; tq->tq_base = bwn_pio_idx2base(mac, index) + BWN_PIO_TXQOFFSET(mac); tq->tq_index = index; tq->tq_free = BWN_PIO_MAX_TXPACKETS; if (siba_get_revid(sc->sc_dev) >= 8) tq->tq_size = 1920; else { tq->tq_size = bwn_pio_read_2(mac, tq, BWN_PIO_TXQBUFSIZE); tq->tq_size -= 80; } TAILQ_INIT(&tq->tq_pktlist); for (i = 0; i < N(tq->tq_pkts); i++) { tp = &(tq->tq_pkts[i]); tp->tp_index = i; tp->tp_queue = tq; TAILQ_INSERT_TAIL(&tq->tq_pktlist, tp, tp_list); } } static uint16_t bwn_pio_idx2base(struct bwn_mac *mac, int index) { struct bwn_softc *sc = mac->mac_sc; static const uint16_t bases[] = { BWN_PIO_BASE0, BWN_PIO_BASE1, BWN_PIO_BASE2, BWN_PIO_BASE3, BWN_PIO_BASE4, BWN_PIO_BASE5, BWN_PIO_BASE6, BWN_PIO_BASE7, }; static const uint16_t bases_rev11[] = { BWN_PIO11_BASE0, BWN_PIO11_BASE1, BWN_PIO11_BASE2, BWN_PIO11_BASE3, BWN_PIO11_BASE4, BWN_PIO11_BASE5, }; if (siba_get_revid(sc->sc_dev) >= 11) { if (index >= N(bases_rev11)) device_printf(sc->sc_dev, "%s: warning\n", __func__); return (bases_rev11[index]); } if (index >= N(bases)) device_printf(sc->sc_dev, "%s: warning\n", __func__); return (bases[index]); } static void bwn_pio_setupqueue_rx(struct bwn_mac *mac, struct bwn_pio_rxqueue *prq, int index) { struct bwn_softc *sc = mac->mac_sc; prq->prq_mac = mac; prq->prq_rev = siba_get_revid(sc->sc_dev); prq->prq_base = bwn_pio_idx2base(mac, index) + BWN_PIO_RXQOFFSET(mac); bwn_dma_rxdirectfifo(mac, index, 1); } static void bwn_destroy_pioqueue_tx(struct bwn_pio_txqueue *tq) { if (tq == NULL) return; bwn_pio_cancel_tx_packets(tq); } static void bwn_destroy_queue_tx(struct bwn_pio_txqueue *pio) { bwn_destroy_pioqueue_tx(pio); } static uint16_t bwn_pio_read_2(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint16_t offset) { return (BWN_READ_2(mac, tq->tq_base + offset)); } static void bwn_dma_rxdirectfifo(struct bwn_mac *mac, int idx, uint8_t enable) { uint32_t ctl; int type; uint16_t base; type = bwn_dma_mask2type(bwn_dma_mask(mac)); base = bwn_dma_base(type, idx); if (type == BWN_DMA_64BIT) { ctl = BWN_READ_4(mac, base + BWN_DMA64_RXCTL); ctl &= ~BWN_DMA64_RXDIRECTFIFO; if (enable) ctl |= BWN_DMA64_RXDIRECTFIFO; BWN_WRITE_4(mac, base + BWN_DMA64_RXCTL, ctl); } else { ctl = BWN_READ_4(mac, base + BWN_DMA32_RXCTL); ctl &= ~BWN_DMA32_RXDIRECTFIFO; if (enable) ctl |= BWN_DMA32_RXDIRECTFIFO; BWN_WRITE_4(mac, base + BWN_DMA32_RXCTL, ctl); } } static uint64_t bwn_dma_mask(struct bwn_mac *mac) { uint32_t tmp; uint16_t base; tmp = BWN_READ_4(mac, SIBA_TGSHIGH); if (tmp & SIBA_TGSHIGH_DMA64) return (BWN_DMA_BIT_MASK(64)); base = bwn_dma_base(0, 0); BWN_WRITE_4(mac, base + BWN_DMA32_TXCTL, BWN_DMA32_TXADDREXT_MASK); tmp = BWN_READ_4(mac, base + BWN_DMA32_TXCTL); if (tmp & BWN_DMA32_TXADDREXT_MASK) return (BWN_DMA_BIT_MASK(32)); return (BWN_DMA_BIT_MASK(30)); } static int bwn_dma_mask2type(uint64_t dmamask) { if (dmamask == BWN_DMA_BIT_MASK(30)) return (BWN_DMA_30BIT); if (dmamask == BWN_DMA_BIT_MASK(32)) return (BWN_DMA_32BIT); if (dmamask == BWN_DMA_BIT_MASK(64)) return (BWN_DMA_64BIT); KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (BWN_DMA_30BIT); } static void bwn_pio_cancel_tx_packets(struct bwn_pio_txqueue *tq) { struct bwn_pio_txpkt *tp; unsigned int i; for (i = 0; i < N(tq->tq_pkts); i++) { tp = &(tq->tq_pkts[i]); if (tp->tp_m) { m_freem(tp->tp_m); tp->tp_m = NULL; } } } static uint16_t bwn_dma_base(int type, int controller_idx) { static const uint16_t map64[] = { BWN_DMA64_BASE0, BWN_DMA64_BASE1, BWN_DMA64_BASE2, BWN_DMA64_BASE3, BWN_DMA64_BASE4, BWN_DMA64_BASE5, }; static const uint16_t map32[] = { BWN_DMA32_BASE0, BWN_DMA32_BASE1, BWN_DMA32_BASE2, BWN_DMA32_BASE3, BWN_DMA32_BASE4, BWN_DMA32_BASE5, }; if (type == BWN_DMA_64BIT) { KASSERT(controller_idx >= 0 && controller_idx < N(map64), ("%s:%d: fail", __func__, __LINE__)); return (map64[controller_idx]); } KASSERT(controller_idx >= 0 && controller_idx < N(map32), ("%s:%d: fail", __func__, __LINE__)); return (map32[controller_idx]); } static void bwn_dma_init(struct bwn_mac *mac) { struct bwn_dma *dma = &mac->mac_method.dma; /* setup TX DMA channels. */ bwn_dma_setup(dma->wme[WME_AC_BK]); bwn_dma_setup(dma->wme[WME_AC_BE]); bwn_dma_setup(dma->wme[WME_AC_VI]); bwn_dma_setup(dma->wme[WME_AC_VO]); bwn_dma_setup(dma->mcast); /* setup RX DMA channel. */ bwn_dma_setup(dma->rx); } static struct bwn_dma_ring * bwn_dma_ringsetup(struct bwn_mac *mac, int controller_index, int for_tx, int type) { struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dma_ring *dr; struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *mt; struct bwn_softc *sc = mac->mac_sc; int error, i; dr = malloc(sizeof(*dr), M_DEVBUF, M_NOWAIT | M_ZERO); if (dr == NULL) goto out; dr->dr_numslots = BWN_RXRING_SLOTS; if (for_tx) dr->dr_numslots = BWN_TXRING_SLOTS; dr->dr_meta = malloc(dr->dr_numslots * sizeof(struct bwn_dmadesc_meta), M_DEVBUF, M_NOWAIT | M_ZERO); if (dr->dr_meta == NULL) goto fail0; dr->dr_type = type; dr->dr_mac = mac; dr->dr_base = bwn_dma_base(type, controller_index); dr->dr_index = controller_index; if (type == BWN_DMA_64BIT) { dr->getdesc = bwn_dma_64_getdesc; dr->setdesc = bwn_dma_64_setdesc; dr->start_transfer = bwn_dma_64_start_transfer; dr->suspend = bwn_dma_64_suspend; dr->resume = bwn_dma_64_resume; dr->get_curslot = bwn_dma_64_get_curslot; dr->set_curslot = bwn_dma_64_set_curslot; } else { dr->getdesc = bwn_dma_32_getdesc; dr->setdesc = bwn_dma_32_setdesc; dr->start_transfer = bwn_dma_32_start_transfer; dr->suspend = bwn_dma_32_suspend; dr->resume = bwn_dma_32_resume; dr->get_curslot = bwn_dma_32_get_curslot; dr->set_curslot = bwn_dma_32_set_curslot; } if (for_tx) { dr->dr_tx = 1; dr->dr_curslot = -1; } else { if (dr->dr_index == 0) { dr->dr_rx_bufsize = BWN_DMA0_RX_BUFFERSIZE; dr->dr_frameoffset = BWN_DMA0_RX_FRAMEOFFSET; } else KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } error = bwn_dma_allocringmemory(dr); if (error) goto fail2; if (for_tx) { /* * Assumption: BWN_TXRING_SLOTS can be divided by * BWN_TX_SLOTS_PER_FRAME */ KASSERT(BWN_TXRING_SLOTS % BWN_TX_SLOTS_PER_FRAME == 0, ("%s:%d: fail", __func__, __LINE__)); dr->dr_txhdr_cache = malloc((dr->dr_numslots / BWN_TX_SLOTS_PER_FRAME) * BWN_HDRSIZE(mac), M_DEVBUF, M_NOWAIT | M_ZERO); KASSERT(dr->dr_txhdr_cache != NULL, ("%s:%d: fail", __func__, __LINE__)); /* * Create TX ring DMA stuffs */ error = bus_dma_tag_create(dma->parent_dtag, BWN_ALIGN, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BWN_HDRSIZE(mac), 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &dr->dr_txring_dtag); if (error) { device_printf(sc->sc_dev, "can't create TX ring DMA tag: TODO frees\n"); goto fail1; } for (i = 0; i < dr->dr_numslots; i += 2) { dr->getdesc(dr, i, &desc, &mt); mt->mt_txtype = BWN_DMADESC_METATYPE_HEADER; mt->mt_m = NULL; mt->mt_ni = NULL; mt->mt_islast = 0; error = bus_dmamap_create(dr->dr_txring_dtag, 0, &mt->mt_dmap); if (error) { device_printf(sc->sc_dev, "can't create RX buf DMA map\n"); goto fail1; } dr->getdesc(dr, i + 1, &desc, &mt); mt->mt_txtype = BWN_DMADESC_METATYPE_BODY; mt->mt_m = NULL; mt->mt_ni = NULL; mt->mt_islast = 1; error = bus_dmamap_create(dma->txbuf_dtag, 0, &mt->mt_dmap); if (error) { device_printf(sc->sc_dev, "can't create RX buf DMA map\n"); goto fail1; } } } else { error = bus_dmamap_create(dma->rxbuf_dtag, 0, &dr->dr_spare_dmap); if (error) { device_printf(sc->sc_dev, "can't create RX buf DMA map\n"); goto out; /* XXX wrong! */ } for (i = 0; i < dr->dr_numslots; i++) { dr->getdesc(dr, i, &desc, &mt); error = bus_dmamap_create(dma->rxbuf_dtag, 0, &mt->mt_dmap); if (error) { device_printf(sc->sc_dev, "can't create RX buf DMA map\n"); goto out; /* XXX wrong! */ } error = bwn_dma_newbuf(dr, desc, mt, 1); if (error) { device_printf(sc->sc_dev, "failed to allocate RX buf\n"); goto out; /* XXX wrong! */ } } bus_dmamap_sync(dr->dr_ring_dtag, dr->dr_ring_dmap, BUS_DMASYNC_PREWRITE); dr->dr_usedslot = dr->dr_numslots; } out: return (dr); fail2: free(dr->dr_txhdr_cache, M_DEVBUF); fail1: free(dr->dr_meta, M_DEVBUF); fail0: free(dr, M_DEVBUF); return (NULL); } static void bwn_dma_ringfree(struct bwn_dma_ring **dr) { if (dr == NULL) return; bwn_dma_free_descbufs(*dr); bwn_dma_free_ringmemory(*dr); free((*dr)->dr_txhdr_cache, M_DEVBUF); free((*dr)->dr_meta, M_DEVBUF); free(*dr, M_DEVBUF); *dr = NULL; } static void bwn_dma_32_getdesc(struct bwn_dma_ring *dr, int slot, struct bwn_dmadesc_generic **gdesc, struct bwn_dmadesc_meta **meta) { struct bwn_dmadesc32 *desc; *meta = &(dr->dr_meta[slot]); desc = dr->dr_ring_descbase; desc = &(desc[slot]); *gdesc = (struct bwn_dmadesc_generic *)desc; } static void bwn_dma_32_setdesc(struct bwn_dma_ring *dr, struct bwn_dmadesc_generic *desc, bus_addr_t dmaaddr, uint16_t bufsize, int start, int end, int irq) { struct bwn_dmadesc32 *descbase = dr->dr_ring_descbase; struct bwn_softc *sc = dr->dr_mac->mac_sc; uint32_t addr, addrext, ctl; int slot; slot = (int)(&(desc->dma.dma32) - descbase); KASSERT(slot >= 0 && slot < dr->dr_numslots, ("%s:%d: fail", __func__, __LINE__)); addr = (uint32_t) (dmaaddr & ~SIBA_DMA_TRANSLATION_MASK); addrext = (uint32_t) (dmaaddr & SIBA_DMA_TRANSLATION_MASK) >> 30; addr |= siba_dma_translation(sc->sc_dev); ctl = bufsize & BWN_DMA32_DCTL_BYTECNT; if (slot == dr->dr_numslots - 1) ctl |= BWN_DMA32_DCTL_DTABLEEND; if (start) ctl |= BWN_DMA32_DCTL_FRAMESTART; if (end) ctl |= BWN_DMA32_DCTL_FRAMEEND; if (irq) ctl |= BWN_DMA32_DCTL_IRQ; ctl |= (addrext << BWN_DMA32_DCTL_ADDREXT_SHIFT) & BWN_DMA32_DCTL_ADDREXT_MASK; desc->dma.dma32.control = htole32(ctl); desc->dma.dma32.address = htole32(addr); } static void bwn_dma_32_start_transfer(struct bwn_dma_ring *dr, int slot) { BWN_DMA_WRITE(dr, BWN_DMA32_TXINDEX, (uint32_t)(slot * sizeof(struct bwn_dmadesc32))); } static void bwn_dma_32_suspend(struct bwn_dma_ring *dr) { BWN_DMA_WRITE(dr, BWN_DMA32_TXCTL, BWN_DMA_READ(dr, BWN_DMA32_TXCTL) | BWN_DMA32_TXSUSPEND); } static void bwn_dma_32_resume(struct bwn_dma_ring *dr) { BWN_DMA_WRITE(dr, BWN_DMA32_TXCTL, BWN_DMA_READ(dr, BWN_DMA32_TXCTL) & ~BWN_DMA32_TXSUSPEND); } static int bwn_dma_32_get_curslot(struct bwn_dma_ring *dr) { uint32_t val; val = BWN_DMA_READ(dr, BWN_DMA32_RXSTATUS); val &= BWN_DMA32_RXDPTR; return (val / sizeof(struct bwn_dmadesc32)); } static void bwn_dma_32_set_curslot(struct bwn_dma_ring *dr, int slot) { BWN_DMA_WRITE(dr, BWN_DMA32_RXINDEX, (uint32_t) (slot * sizeof(struct bwn_dmadesc32))); } static void bwn_dma_64_getdesc(struct bwn_dma_ring *dr, int slot, struct bwn_dmadesc_generic **gdesc, struct bwn_dmadesc_meta **meta) { struct bwn_dmadesc64 *desc; *meta = &(dr->dr_meta[slot]); desc = dr->dr_ring_descbase; desc = &(desc[slot]); *gdesc = (struct bwn_dmadesc_generic *)desc; } static void bwn_dma_64_setdesc(struct bwn_dma_ring *dr, struct bwn_dmadesc_generic *desc, bus_addr_t dmaaddr, uint16_t bufsize, int start, int end, int irq) { struct bwn_dmadesc64 *descbase = dr->dr_ring_descbase; struct bwn_softc *sc = dr->dr_mac->mac_sc; int slot; uint32_t ctl0 = 0, ctl1 = 0; uint32_t addrlo, addrhi; uint32_t addrext; slot = (int)(&(desc->dma.dma64) - descbase); KASSERT(slot >= 0 && slot < dr->dr_numslots, ("%s:%d: fail", __func__, __LINE__)); addrlo = (uint32_t) (dmaaddr & 0xffffffff); addrhi = (((uint64_t) dmaaddr >> 32) & ~SIBA_DMA_TRANSLATION_MASK); addrext = (((uint64_t) dmaaddr >> 32) & SIBA_DMA_TRANSLATION_MASK) >> 30; addrhi |= (siba_dma_translation(sc->sc_dev) << 1); if (slot == dr->dr_numslots - 1) ctl0 |= BWN_DMA64_DCTL0_DTABLEEND; if (start) ctl0 |= BWN_DMA64_DCTL0_FRAMESTART; if (end) ctl0 |= BWN_DMA64_DCTL0_FRAMEEND; if (irq) ctl0 |= BWN_DMA64_DCTL0_IRQ; ctl1 |= bufsize & BWN_DMA64_DCTL1_BYTECNT; ctl1 |= (addrext << BWN_DMA64_DCTL1_ADDREXT_SHIFT) & BWN_DMA64_DCTL1_ADDREXT_MASK; desc->dma.dma64.control0 = htole32(ctl0); desc->dma.dma64.control1 = htole32(ctl1); desc->dma.dma64.address_low = htole32(addrlo); desc->dma.dma64.address_high = htole32(addrhi); } static void bwn_dma_64_start_transfer(struct bwn_dma_ring *dr, int slot) { BWN_DMA_WRITE(dr, BWN_DMA64_TXINDEX, (uint32_t)(slot * sizeof(struct bwn_dmadesc64))); } static void bwn_dma_64_suspend(struct bwn_dma_ring *dr) { BWN_DMA_WRITE(dr, BWN_DMA64_TXCTL, BWN_DMA_READ(dr, BWN_DMA64_TXCTL) | BWN_DMA64_TXSUSPEND); } static void bwn_dma_64_resume(struct bwn_dma_ring *dr) { BWN_DMA_WRITE(dr, BWN_DMA64_TXCTL, BWN_DMA_READ(dr, BWN_DMA64_TXCTL) & ~BWN_DMA64_TXSUSPEND); } static int bwn_dma_64_get_curslot(struct bwn_dma_ring *dr) { uint32_t val; val = BWN_DMA_READ(dr, BWN_DMA64_RXSTATUS); val &= BWN_DMA64_RXSTATDPTR; return (val / sizeof(struct bwn_dmadesc64)); } static void bwn_dma_64_set_curslot(struct bwn_dma_ring *dr, int slot) { BWN_DMA_WRITE(dr, BWN_DMA64_RXINDEX, (uint32_t)(slot * sizeof(struct bwn_dmadesc64))); } static int bwn_dma_allocringmemory(struct bwn_dma_ring *dr) { struct bwn_mac *mac = dr->dr_mac; struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_softc *sc = mac->mac_sc; int error; error = bus_dma_tag_create(dma->parent_dtag, BWN_ALIGN, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BWN_DMA_RINGMEMSIZE, 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &dr->dr_ring_dtag); if (error) { device_printf(sc->sc_dev, "can't create TX ring DMA tag: TODO frees\n"); return (-1); } error = bus_dmamem_alloc(dr->dr_ring_dtag, &dr->dr_ring_descbase, BUS_DMA_WAITOK | BUS_DMA_ZERO, &dr->dr_ring_dmap); if (error) { device_printf(sc->sc_dev, "can't allocate DMA mem: TODO frees\n"); return (-1); } error = bus_dmamap_load(dr->dr_ring_dtag, dr->dr_ring_dmap, dr->dr_ring_descbase, BWN_DMA_RINGMEMSIZE, bwn_dma_ring_addr, &dr->dr_ring_dmabase, BUS_DMA_NOWAIT); if (error) { device_printf(sc->sc_dev, "can't load DMA mem: TODO free\n"); return (-1); } return (0); } static void bwn_dma_setup(struct bwn_dma_ring *dr) { struct bwn_softc *sc = dr->dr_mac->mac_sc; uint64_t ring64; uint32_t addrext, ring32, value; uint32_t trans = siba_dma_translation(sc->sc_dev); if (dr->dr_tx) { dr->dr_curslot = -1; if (dr->dr_type == BWN_DMA_64BIT) { ring64 = (uint64_t)(dr->dr_ring_dmabase); addrext = ((ring64 >> 32) & SIBA_DMA_TRANSLATION_MASK) >> 30; value = BWN_DMA64_TXENABLE; value |= (addrext << BWN_DMA64_TXADDREXT_SHIFT) & BWN_DMA64_TXADDREXT_MASK; BWN_DMA_WRITE(dr, BWN_DMA64_TXCTL, value); BWN_DMA_WRITE(dr, BWN_DMA64_TXRINGLO, (ring64 & 0xffffffff)); BWN_DMA_WRITE(dr, BWN_DMA64_TXRINGHI, ((ring64 >> 32) & ~SIBA_DMA_TRANSLATION_MASK) | (trans << 1)); } else { ring32 = (uint32_t)(dr->dr_ring_dmabase); addrext = (ring32 & SIBA_DMA_TRANSLATION_MASK) >> 30; value = BWN_DMA32_TXENABLE; value |= (addrext << BWN_DMA32_TXADDREXT_SHIFT) & BWN_DMA32_TXADDREXT_MASK; BWN_DMA_WRITE(dr, BWN_DMA32_TXCTL, value); BWN_DMA_WRITE(dr, BWN_DMA32_TXRING, (ring32 & ~SIBA_DMA_TRANSLATION_MASK) | trans); } return; } /* * set for RX */ dr->dr_usedslot = dr->dr_numslots; if (dr->dr_type == BWN_DMA_64BIT) { ring64 = (uint64_t)(dr->dr_ring_dmabase); addrext = ((ring64 >> 32) & SIBA_DMA_TRANSLATION_MASK) >> 30; value = (dr->dr_frameoffset << BWN_DMA64_RXFROFF_SHIFT); value |= BWN_DMA64_RXENABLE; value |= (addrext << BWN_DMA64_RXADDREXT_SHIFT) & BWN_DMA64_RXADDREXT_MASK; BWN_DMA_WRITE(dr, BWN_DMA64_RXCTL, value); BWN_DMA_WRITE(dr, BWN_DMA64_RXRINGLO, (ring64 & 0xffffffff)); BWN_DMA_WRITE(dr, BWN_DMA64_RXRINGHI, ((ring64 >> 32) & ~SIBA_DMA_TRANSLATION_MASK) | (trans << 1)); BWN_DMA_WRITE(dr, BWN_DMA64_RXINDEX, dr->dr_numslots * sizeof(struct bwn_dmadesc64)); } else { ring32 = (uint32_t)(dr->dr_ring_dmabase); addrext = (ring32 & SIBA_DMA_TRANSLATION_MASK) >> 30; value = (dr->dr_frameoffset << BWN_DMA32_RXFROFF_SHIFT); value |= BWN_DMA32_RXENABLE; value |= (addrext << BWN_DMA32_RXADDREXT_SHIFT) & BWN_DMA32_RXADDREXT_MASK; BWN_DMA_WRITE(dr, BWN_DMA32_RXCTL, value); BWN_DMA_WRITE(dr, BWN_DMA32_RXRING, (ring32 & ~SIBA_DMA_TRANSLATION_MASK) | trans); BWN_DMA_WRITE(dr, BWN_DMA32_RXINDEX, dr->dr_numslots * sizeof(struct bwn_dmadesc32)); } } static void bwn_dma_free_ringmemory(struct bwn_dma_ring *dr) { bus_dmamap_unload(dr->dr_ring_dtag, dr->dr_ring_dmap); bus_dmamem_free(dr->dr_ring_dtag, dr->dr_ring_descbase, dr->dr_ring_dmap); } static void bwn_dma_cleanup(struct bwn_dma_ring *dr) { if (dr->dr_tx) { bwn_dma_tx_reset(dr->dr_mac, dr->dr_base, dr->dr_type); if (dr->dr_type == BWN_DMA_64BIT) { BWN_DMA_WRITE(dr, BWN_DMA64_TXRINGLO, 0); BWN_DMA_WRITE(dr, BWN_DMA64_TXRINGHI, 0); } else BWN_DMA_WRITE(dr, BWN_DMA32_TXRING, 0); } else { bwn_dma_rx_reset(dr->dr_mac, dr->dr_base, dr->dr_type); if (dr->dr_type == BWN_DMA_64BIT) { BWN_DMA_WRITE(dr, BWN_DMA64_RXRINGLO, 0); BWN_DMA_WRITE(dr, BWN_DMA64_RXRINGHI, 0); } else BWN_DMA_WRITE(dr, BWN_DMA32_RXRING, 0); } } static void bwn_dma_free_descbufs(struct bwn_dma_ring *dr) { struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *meta; struct bwn_mac *mac = dr->dr_mac; struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_softc *sc = mac->mac_sc; int i; if (!dr->dr_usedslot) return; for (i = 0; i < dr->dr_numslots; i++) { dr->getdesc(dr, i, &desc, &meta); if (meta->mt_m == NULL) { if (!dr->dr_tx) device_printf(sc->sc_dev, "%s: not TX?\n", __func__); continue; } if (dr->dr_tx) { if (meta->mt_txtype == BWN_DMADESC_METATYPE_HEADER) bus_dmamap_unload(dr->dr_txring_dtag, meta->mt_dmap); else if (meta->mt_txtype == BWN_DMADESC_METATYPE_BODY) bus_dmamap_unload(dma->txbuf_dtag, meta->mt_dmap); } else bus_dmamap_unload(dma->rxbuf_dtag, meta->mt_dmap); bwn_dma_free_descbuf(dr, meta); } } static int bwn_dma_tx_reset(struct bwn_mac *mac, uint16_t base, int type) { struct bwn_softc *sc = mac->mac_sc; uint32_t value; int i; uint16_t offset; for (i = 0; i < 10; i++) { offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_TXSTATUS : BWN_DMA32_TXSTATUS; value = BWN_READ_4(mac, base + offset); if (type == BWN_DMA_64BIT) { value &= BWN_DMA64_TXSTAT; if (value == BWN_DMA64_TXSTAT_DISABLED || value == BWN_DMA64_TXSTAT_IDLEWAIT || value == BWN_DMA64_TXSTAT_STOPPED) break; } else { value &= BWN_DMA32_TXSTATE; if (value == BWN_DMA32_TXSTAT_DISABLED || value == BWN_DMA32_TXSTAT_IDLEWAIT || value == BWN_DMA32_TXSTAT_STOPPED) break; } DELAY(1000); } offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_TXCTL : BWN_DMA32_TXCTL; BWN_WRITE_4(mac, base + offset, 0); for (i = 0; i < 10; i++) { offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_TXSTATUS : BWN_DMA32_TXSTATUS; value = BWN_READ_4(mac, base + offset); if (type == BWN_DMA_64BIT) { value &= BWN_DMA64_TXSTAT; if (value == BWN_DMA64_TXSTAT_DISABLED) { i = -1; break; } } else { value &= BWN_DMA32_TXSTATE; if (value == BWN_DMA32_TXSTAT_DISABLED) { i = -1; break; } } DELAY(1000); } if (i != -1) { device_printf(sc->sc_dev, "%s: timed out\n", __func__); return (ENODEV); } DELAY(1000); return (0); } static int bwn_dma_rx_reset(struct bwn_mac *mac, uint16_t base, int type) { struct bwn_softc *sc = mac->mac_sc; uint32_t value; int i; uint16_t offset; offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_RXCTL : BWN_DMA32_RXCTL; BWN_WRITE_4(mac, base + offset, 0); for (i = 0; i < 10; i++) { offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_RXSTATUS : BWN_DMA32_RXSTATUS; value = BWN_READ_4(mac, base + offset); if (type == BWN_DMA_64BIT) { value &= BWN_DMA64_RXSTAT; if (value == BWN_DMA64_RXSTAT_DISABLED) { i = -1; break; } } else { value &= BWN_DMA32_RXSTATE; if (value == BWN_DMA32_RXSTAT_DISABLED) { i = -1; break; } } DELAY(1000); } if (i != -1) { device_printf(sc->sc_dev, "%s: timed out\n", __func__); return (ENODEV); } return (0); } static void bwn_dma_free_descbuf(struct bwn_dma_ring *dr, struct bwn_dmadesc_meta *meta) { if (meta->mt_m != NULL) { m_freem(meta->mt_m); meta->mt_m = NULL; } if (meta->mt_ni != NULL) { ieee80211_free_node(meta->mt_ni); meta->mt_ni = NULL; } } static void bwn_dma_set_redzone(struct bwn_dma_ring *dr, struct mbuf *m) { struct bwn_rxhdr4 *rxhdr; unsigned char *frame; rxhdr = mtod(m, struct bwn_rxhdr4 *); rxhdr->frame_len = 0; KASSERT(dr->dr_rx_bufsize >= dr->dr_frameoffset + sizeof(struct bwn_plcp6) + 2, ("%s:%d: fail", __func__, __LINE__)); frame = mtod(m, char *) + dr->dr_frameoffset; memset(frame, 0xff, sizeof(struct bwn_plcp6) + 2 /* padding */); } static uint8_t bwn_dma_check_redzone(struct bwn_dma_ring *dr, struct mbuf *m) { unsigned char *f = mtod(m, char *) + dr->dr_frameoffset; return ((f[0] & f[1] & f[2] & f[3] & f[4] & f[5] & f[6] & f[7]) == 0xff); } static void bwn_wme_init(struct bwn_mac *mac) { bwn_wme_load(mac); /* enable WME support. */ bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_EDCF); BWN_WRITE_2(mac, BWN_IFSCTL, BWN_READ_2(mac, BWN_IFSCTL) | BWN_IFSCTL_USE_EDCF); } static void bwn_spu_setdelay(struct bwn_mac *mac, int idle) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; uint16_t delay; /* microsec */ delay = (mac->mac_phy.type == BWN_PHYTYPE_A) ? 3700 : 1050; if (ic->ic_opmode == IEEE80211_M_IBSS || idle) delay = 500; if ((mac->mac_phy.rf_ver == 0x2050) && (mac->mac_phy.rf_rev == 8)) delay = max(delay, (uint16_t)2400); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_SPU_WAKEUP, delay); } static void bwn_bt_enable(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint64_t hf; if (bwn_bluetooth == 0) return; if ((siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_BTCOEXIST) == 0) return; if (mac->mac_phy.type != BWN_PHYTYPE_B && !mac->mac_phy.gmode) return; hf = bwn_hf_read(mac); if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_BTCMOD) hf |= BWN_HF_BT_COEXISTALT; else hf |= BWN_HF_BT_COEXIST; bwn_hf_write(mac, hf); } static void bwn_set_macaddr(struct bwn_mac *mac) { bwn_mac_write_bssid(mac); bwn_mac_setfilter(mac, BWN_MACFILTER_SELF, mac->mac_sc->sc_ic.ic_macaddr); } static void bwn_clear_keys(struct bwn_mac *mac) { int i; for (i = 0; i < mac->mac_max_nr_keys; i++) { KASSERT(i >= 0 && i < mac->mac_max_nr_keys, ("%s:%d: fail", __func__, __LINE__)); bwn_key_dowrite(mac, i, BWN_SEC_ALGO_NONE, NULL, BWN_SEC_KEYSIZE, NULL); if ((i <= 3) && !BWN_SEC_NEWAPI(mac)) { bwn_key_dowrite(mac, i + 4, BWN_SEC_ALGO_NONE, NULL, BWN_SEC_KEYSIZE, NULL); } mac->mac_key[i].keyconf = NULL; } } static void bwn_crypt_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; mac->mac_max_nr_keys = (siba_get_revid(sc->sc_dev) >= 5) ? 58 : 20; KASSERT(mac->mac_max_nr_keys <= N(mac->mac_key), ("%s:%d: fail", __func__, __LINE__)); mac->mac_ktp = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_KEY_TABLEP); mac->mac_ktp *= 2; if (siba_get_revid(sc->sc_dev) >= 5) BWN_WRITE_2(mac, BWN_RCMTA_COUNT, mac->mac_max_nr_keys - 8); bwn_clear_keys(mac); } static void bwn_chip_exit(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; bwn_phy_exit(mac); siba_gpio_set(sc->sc_dev, 0); } static int bwn_fw_fillinfo(struct bwn_mac *mac) { int error; error = bwn_fw_gets(mac, BWN_FWTYPE_DEFAULT); if (error == 0) return (0); error = bwn_fw_gets(mac, BWN_FWTYPE_OPENSOURCE); if (error == 0) return (0); return (error); } static int bwn_gpio_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint32_t mask = 0x1f, set = 0xf, value; BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_GPOUT_MASK); BWN_WRITE_2(mac, BWN_GPIO_MASK, BWN_READ_2(mac, BWN_GPIO_MASK) | 0x000f); if (siba_get_chipid(sc->sc_dev) == 0x4301) { mask |= 0x0060; set |= 0x0060; } if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_PACTRL) { BWN_WRITE_2(mac, BWN_GPIO_MASK, BWN_READ_2(mac, BWN_GPIO_MASK) | 0x0200); mask |= 0x0200; set |= 0x0200; } if (siba_get_revid(sc->sc_dev) >= 2) mask |= 0x0010; value = siba_gpio_get(sc->sc_dev); if (value == -1) return (0); siba_gpio_set(sc->sc_dev, (value & mask) | set); return (0); } static int bwn_fw_loadinitvals(struct bwn_mac *mac) { #define GETFWOFFSET(fwp, offset) \ ((const struct bwn_fwinitvals *)((const char *)fwp.fw->data + offset)) const size_t hdr_len = sizeof(struct bwn_fwhdr); const struct bwn_fwhdr *hdr; struct bwn_fw *fw = &mac->mac_fw; int error; hdr = (const struct bwn_fwhdr *)(fw->initvals.fw->data); error = bwn_fwinitvals_write(mac, GETFWOFFSET(fw->initvals, hdr_len), be32toh(hdr->size), fw->initvals.fw->datasize - hdr_len); if (error) return (error); if (fw->initvals_band.fw) { hdr = (const struct bwn_fwhdr *)(fw->initvals_band.fw->data); error = bwn_fwinitvals_write(mac, GETFWOFFSET(fw->initvals_band, hdr_len), be32toh(hdr->size), fw->initvals_band.fw->datasize - hdr_len); } return (error); #undef GETFWOFFSET } static int bwn_phy_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int error; mac->mac_phy.chan = mac->mac_phy.get_default_chan(mac); mac->mac_phy.rf_onoff(mac, 1); error = mac->mac_phy.init(mac); if (error) { device_printf(sc->sc_dev, "PHY init failed\n"); goto fail0; } error = bwn_switch_channel(mac, mac->mac_phy.get_default_chan(mac)); if (error) { device_printf(sc->sc_dev, "failed to switch default channel\n"); goto fail1; } return (0); fail1: if (mac->mac_phy.exit) mac->mac_phy.exit(mac); fail0: mac->mac_phy.rf_onoff(mac, 0); return (error); } static void bwn_set_txantenna(struct bwn_mac *mac, int antenna) { uint16_t ant; uint16_t tmp; ant = bwn_ant2phy(antenna); /* For ACK/CTS */ tmp = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_ACKCTS_PHYCTL); tmp = (tmp & ~BWN_TX_PHY_ANT) | ant; bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_ACKCTS_PHYCTL, tmp); /* For Probe Resposes */ tmp = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_PROBE_RESP_PHYCTL); tmp = (tmp & ~BWN_TX_PHY_ANT) | ant; bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PROBE_RESP_PHYCTL, tmp); } static void bwn_set_opmode(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; uint32_t ctl; uint16_t cfp_pretbtt; ctl = BWN_READ_4(mac, BWN_MACCTL); ctl &= ~(BWN_MACCTL_HOSTAP | BWN_MACCTL_PASS_CTL | BWN_MACCTL_PASS_BADPLCP | BWN_MACCTL_PASS_BADFCS | BWN_MACCTL_PROMISC | BWN_MACCTL_BEACON_PROMISC); ctl |= BWN_MACCTL_STA; if (ic->ic_opmode == IEEE80211_M_HOSTAP || ic->ic_opmode == IEEE80211_M_MBSS) ctl |= BWN_MACCTL_HOSTAP; else if (ic->ic_opmode == IEEE80211_M_IBSS) ctl &= ~BWN_MACCTL_STA; ctl |= sc->sc_filters; if (siba_get_revid(sc->sc_dev) <= 4) ctl |= BWN_MACCTL_PROMISC; BWN_WRITE_4(mac, BWN_MACCTL, ctl); cfp_pretbtt = 2; if ((ctl & BWN_MACCTL_STA) && !(ctl & BWN_MACCTL_HOSTAP)) { if (siba_get_chipid(sc->sc_dev) == 0x4306 && siba_get_chiprev(sc->sc_dev) == 3) cfp_pretbtt = 100; else cfp_pretbtt = 50; } BWN_WRITE_2(mac, 0x612, cfp_pretbtt); } static int bwn_dma_gettype(struct bwn_mac *mac) { uint32_t tmp; uint16_t base; tmp = BWN_READ_4(mac, SIBA_TGSHIGH); if (tmp & SIBA_TGSHIGH_DMA64) return (BWN_DMA_64BIT); base = bwn_dma_base(0, 0); BWN_WRITE_4(mac, base + BWN_DMA32_TXCTL, BWN_DMA32_TXADDREXT_MASK); tmp = BWN_READ_4(mac, base + BWN_DMA32_TXCTL); if (tmp & BWN_DMA32_TXADDREXT_MASK) return (BWN_DMA_32BIT); return (BWN_DMA_30BIT); } static void bwn_dma_ring_addr(void *arg, bus_dma_segment_t *seg, int nseg, int error) { if (!error) { KASSERT(nseg == 1, ("too many segments(%d)\n", nseg)); *((bus_addr_t *)arg) = seg->ds_addr; } } void bwn_dummy_transmission(struct bwn_mac *mac, int ofdm, int paon) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; unsigned int i, max_loop; uint16_t value; uint32_t buffer[5] = { 0x00000000, 0x00d40000, 0x00000000, 0x01000000, 0x00000000 }; if (ofdm) { max_loop = 0x1e; buffer[0] = 0x000201cc; } else { max_loop = 0xfa; buffer[0] = 0x000b846e; } BWN_ASSERT_LOCKED(mac->mac_sc); for (i = 0; i < 5; i++) bwn_ram_write(mac, i * 4, buffer[i]); BWN_WRITE_2(mac, 0x0568, 0x0000); BWN_WRITE_2(mac, 0x07c0, (siba_get_revid(sc->sc_dev) < 11) ? 0x0000 : 0x0100); value = (ofdm ? 0x41 : 0x40); BWN_WRITE_2(mac, 0x050c, value); if (phy->type == BWN_PHYTYPE_N || phy->type == BWN_PHYTYPE_LP || phy->type == BWN_PHYTYPE_LCN) BWN_WRITE_2(mac, 0x0514, 0x1a02); BWN_WRITE_2(mac, 0x0508, 0x0000); BWN_WRITE_2(mac, 0x050a, 0x0000); BWN_WRITE_2(mac, 0x054c, 0x0000); BWN_WRITE_2(mac, 0x056a, 0x0014); BWN_WRITE_2(mac, 0x0568, 0x0826); BWN_WRITE_2(mac, 0x0500, 0x0000); /* XXX TODO: n phy pa override? */ switch (phy->type) { case BWN_PHYTYPE_N: case BWN_PHYTYPE_LCN: BWN_WRITE_2(mac, 0x0502, 0x00d0); break; case BWN_PHYTYPE_LP: BWN_WRITE_2(mac, 0x0502, 0x0050); break; default: BWN_WRITE_2(mac, 0x0502, 0x0030); break; } /* flush */ BWN_READ_2(mac, 0x0502); if (phy->rf_ver == 0x2050 && phy->rf_rev <= 0x5) BWN_RF_WRITE(mac, 0x0051, 0x0017); for (i = 0x00; i < max_loop; i++) { value = BWN_READ_2(mac, 0x050e); if (value & 0x0080) break; DELAY(10); } for (i = 0x00; i < 0x0a; i++) { value = BWN_READ_2(mac, 0x050e); if (value & 0x0400) break; DELAY(10); } for (i = 0x00; i < 0x19; i++) { value = BWN_READ_2(mac, 0x0690); if (!(value & 0x0100)) break; DELAY(10); } if (phy->rf_ver == 0x2050 && phy->rf_rev <= 0x5) BWN_RF_WRITE(mac, 0x0051, 0x0037); } void bwn_ram_write(struct bwn_mac *mac, uint16_t offset, uint32_t val) { uint32_t macctl; KASSERT(offset % 4 == 0, ("%s:%d: fail", __func__, __LINE__)); macctl = BWN_READ_4(mac, BWN_MACCTL); if (macctl & BWN_MACCTL_BIGENDIAN) printf("TODO: need swap\n"); BWN_WRITE_4(mac, BWN_RAM_CONTROL, offset); BWN_BARRIER(mac, BUS_SPACE_BARRIER_WRITE); BWN_WRITE_4(mac, BWN_RAM_DATA, val); } void bwn_mac_suspend(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int i; uint32_t tmp; KASSERT(mac->mac_suspended >= 0, ("%s:%d: fail", __func__, __LINE__)); if (mac->mac_suspended == 0) { bwn_psctl(mac, BWN_PS_AWAKE); BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_ON); BWN_READ_4(mac, BWN_MACCTL); for (i = 35; i; i--) { tmp = BWN_READ_4(mac, BWN_INTR_REASON); if (tmp & BWN_INTR_MAC_SUSPENDED) goto out; DELAY(10); } for (i = 40; i; i--) { tmp = BWN_READ_4(mac, BWN_INTR_REASON); if (tmp & BWN_INTR_MAC_SUSPENDED) goto out; DELAY(1000); } device_printf(sc->sc_dev, "MAC suspend failed\n"); } out: mac->mac_suspended++; } void bwn_mac_enable(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint16_t state; state = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODESTAT); if (state != BWN_SHARED_UCODESTAT_SUSPEND && state != BWN_SHARED_UCODESTAT_SLEEP) device_printf(sc->sc_dev, "warn: firmware state (%d)\n", state); mac->mac_suspended--; KASSERT(mac->mac_suspended >= 0, ("%s:%d: fail", __func__, __LINE__)); if (mac->mac_suspended == 0) { BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_ON); BWN_WRITE_4(mac, BWN_INTR_REASON, BWN_INTR_MAC_SUSPENDED); BWN_READ_4(mac, BWN_MACCTL); BWN_READ_4(mac, BWN_INTR_REASON); bwn_psctl(mac, 0); } } void bwn_psctl(struct bwn_mac *mac, uint32_t flags) { struct bwn_softc *sc = mac->mac_sc; int i; uint16_t ucstat; KASSERT(!((flags & BWN_PS_ON) && (flags & BWN_PS_OFF)), ("%s:%d: fail", __func__, __LINE__)); KASSERT(!((flags & BWN_PS_AWAKE) && (flags & BWN_PS_ASLEEP)), ("%s:%d: fail", __func__, __LINE__)); /* XXX forcibly awake and hwps-off */ BWN_WRITE_4(mac, BWN_MACCTL, (BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_AWAKE) & ~BWN_MACCTL_HWPS); BWN_READ_4(mac, BWN_MACCTL); if (siba_get_revid(sc->sc_dev) >= 5) { for (i = 0; i < 100; i++) { ucstat = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODESTAT); if (ucstat != BWN_SHARED_UCODESTAT_SLEEP) break; DELAY(10); } } } static int bwn_fw_gets(struct bwn_mac *mac, enum bwn_fwtype type) { struct bwn_softc *sc = mac->mac_sc; struct bwn_fw *fw = &mac->mac_fw; const uint8_t rev = siba_get_revid(sc->sc_dev); const char *filename; uint32_t high; int error; /* microcode */ if (rev >= 5 && rev <= 10) filename = "ucode5"; else if (rev >= 11 && rev <= 12) filename = "ucode11"; else if (rev == 13) filename = "ucode13"; else if (rev == 14) filename = "ucode14"; else if (rev >= 15) filename = "ucode15"; else { device_printf(sc->sc_dev, "no ucode for rev %d\n", rev); bwn_release_firmware(mac); return (EOPNOTSUPP); } error = bwn_fw_get(mac, type, filename, &fw->ucode); if (error) { bwn_release_firmware(mac); return (error); } /* PCM */ KASSERT(fw->no_pcmfile == 0, ("%s:%d fail", __func__, __LINE__)); if (rev >= 5 && rev <= 10) { error = bwn_fw_get(mac, type, "pcm5", &fw->pcm); if (error == ENOENT) fw->no_pcmfile = 1; else if (error) { bwn_release_firmware(mac); return (error); } } else if (rev < 11) { device_printf(sc->sc_dev, "no PCM for rev %d\n", rev); return (EOPNOTSUPP); } /* initvals */ high = siba_read_4(sc->sc_dev, SIBA_TGSHIGH); switch (mac->mac_phy.type) { case BWN_PHYTYPE_A: if (rev < 5 || rev > 10) goto fail1; if (high & BWN_TGSHIGH_HAVE_2GHZ) filename = "a0g1initvals5"; else filename = "a0g0initvals5"; break; case BWN_PHYTYPE_G: if (rev >= 5 && rev <= 10) filename = "b0g0initvals5"; else if (rev >= 13) filename = "b0g0initvals13"; else goto fail1; break; case BWN_PHYTYPE_LP: if (rev == 13) filename = "lp0initvals13"; else if (rev == 14) filename = "lp0initvals14"; else if (rev >= 15) filename = "lp0initvals15"; else goto fail1; break; case BWN_PHYTYPE_N: if (rev >= 11 && rev <= 12) filename = "n0initvals11"; else goto fail1; break; default: goto fail1; } error = bwn_fw_get(mac, type, filename, &fw->initvals); if (error) { bwn_release_firmware(mac); return (error); } /* bandswitch initvals */ switch (mac->mac_phy.type) { case BWN_PHYTYPE_A: if (rev >= 5 && rev <= 10) { if (high & BWN_TGSHIGH_HAVE_2GHZ) filename = "a0g1bsinitvals5"; else filename = "a0g0bsinitvals5"; } else if (rev >= 11) filename = NULL; else goto fail1; break; case BWN_PHYTYPE_G: if (rev >= 5 && rev <= 10) filename = "b0g0bsinitvals5"; else if (rev >= 11) filename = NULL; else goto fail1; break; case BWN_PHYTYPE_LP: if (rev == 13) filename = "lp0bsinitvals13"; else if (rev == 14) filename = "lp0bsinitvals14"; else if (rev >= 15) filename = "lp0bsinitvals15"; else goto fail1; break; case BWN_PHYTYPE_N: if (rev >= 11 && rev <= 12) filename = "n0bsinitvals11"; else goto fail1; break; default: goto fail1; } error = bwn_fw_get(mac, type, filename, &fw->initvals_band); if (error) { bwn_release_firmware(mac); return (error); } return (0); fail1: device_printf(sc->sc_dev, "no INITVALS for rev %d\n", rev); bwn_release_firmware(mac); return (EOPNOTSUPP); } static int bwn_fw_get(struct bwn_mac *mac, enum bwn_fwtype type, const char *name, struct bwn_fwfile *bfw) { const struct bwn_fwhdr *hdr; struct bwn_softc *sc = mac->mac_sc; const struct firmware *fw; char namebuf[64]; if (name == NULL) { bwn_do_release_fw(bfw); return (0); } if (bfw->filename != NULL) { if (bfw->type == type && (strcmp(bfw->filename, name) == 0)) return (0); bwn_do_release_fw(bfw); } snprintf(namebuf, sizeof(namebuf), "bwn%s_v4_%s%s", (type == BWN_FWTYPE_OPENSOURCE) ? "-open" : "", (mac->mac_phy.type == BWN_PHYTYPE_LP) ? "lp_" : "", name); /* XXX Sleeping on "fwload" with the non-sleepable locks held */ fw = firmware_get(namebuf); if (fw == NULL) { device_printf(sc->sc_dev, "the fw file(%s) not found\n", namebuf); return (ENOENT); } if (fw->datasize < sizeof(struct bwn_fwhdr)) goto fail; hdr = (const struct bwn_fwhdr *)(fw->data); switch (hdr->type) { case BWN_FWTYPE_UCODE: case BWN_FWTYPE_PCM: if (be32toh(hdr->size) != (fw->datasize - sizeof(struct bwn_fwhdr))) goto fail; /* FALLTHROUGH */ case BWN_FWTYPE_IV: if (hdr->ver != 1) goto fail; break; default: goto fail; } bfw->filename = name; bfw->fw = fw; bfw->type = type; return (0); fail: device_printf(sc->sc_dev, "the fw file(%s) format error\n", namebuf); if (fw != NULL) firmware_put(fw, FIRMWARE_UNLOAD); return (EPROTO); } static void bwn_release_firmware(struct bwn_mac *mac) { bwn_do_release_fw(&mac->mac_fw.ucode); bwn_do_release_fw(&mac->mac_fw.pcm); bwn_do_release_fw(&mac->mac_fw.initvals); bwn_do_release_fw(&mac->mac_fw.initvals_band); } static void bwn_do_release_fw(struct bwn_fwfile *bfw) { if (bfw->fw != NULL) firmware_put(bfw->fw, FIRMWARE_UNLOAD); bfw->fw = NULL; bfw->filename = NULL; } static int bwn_fw_loaducode(struct bwn_mac *mac) { #define GETFWOFFSET(fwp, offset) \ ((const uint32_t *)((const char *)fwp.fw->data + offset)) #define GETFWSIZE(fwp, offset) \ ((fwp.fw->datasize - offset) / sizeof(uint32_t)) struct bwn_softc *sc = mac->mac_sc; const uint32_t *data; unsigned int i; uint32_t ctl; uint16_t date, fwcaps, time; int error = 0; ctl = BWN_READ_4(mac, BWN_MACCTL); ctl |= BWN_MACCTL_MCODE_JMP0; KASSERT(!(ctl & BWN_MACCTL_MCODE_RUN), ("%s:%d: fail", __func__, __LINE__)); BWN_WRITE_4(mac, BWN_MACCTL, ctl); for (i = 0; i < 64; i++) bwn_shm_write_2(mac, BWN_SCRATCH, i, 0); for (i = 0; i < 4096; i += 2) bwn_shm_write_2(mac, BWN_SHARED, i, 0); data = GETFWOFFSET(mac->mac_fw.ucode, sizeof(struct bwn_fwhdr)); bwn_shm_ctlword(mac, BWN_UCODE | BWN_SHARED_AUTOINC, 0x0000); for (i = 0; i < GETFWSIZE(mac->mac_fw.ucode, sizeof(struct bwn_fwhdr)); i++) { BWN_WRITE_4(mac, BWN_SHM_DATA, be32toh(data[i])); DELAY(10); } if (mac->mac_fw.pcm.fw) { data = GETFWOFFSET(mac->mac_fw.pcm, sizeof(struct bwn_fwhdr)); bwn_shm_ctlword(mac, BWN_HW, 0x01ea); BWN_WRITE_4(mac, BWN_SHM_DATA, 0x00004000); bwn_shm_ctlword(mac, BWN_HW, 0x01eb); for (i = 0; i < GETFWSIZE(mac->mac_fw.pcm, sizeof(struct bwn_fwhdr)); i++) { BWN_WRITE_4(mac, BWN_SHM_DATA, be32toh(data[i])); DELAY(10); } } BWN_WRITE_4(mac, BWN_INTR_REASON, BWN_INTR_ALL); BWN_WRITE_4(mac, BWN_MACCTL, (BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_MCODE_JMP0) | BWN_MACCTL_MCODE_RUN); for (i = 0; i < 21; i++) { if (BWN_READ_4(mac, BWN_INTR_REASON) == BWN_INTR_MAC_SUSPENDED) break; if (i >= 20) { device_printf(sc->sc_dev, "ucode timeout\n"); error = ENXIO; goto error; } DELAY(50000); } BWN_READ_4(mac, BWN_INTR_REASON); mac->mac_fw.rev = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODE_REV); if (mac->mac_fw.rev <= 0x128) { device_printf(sc->sc_dev, "the firmware is too old\n"); error = EOPNOTSUPP; goto error; } + + /* + * Determine firmware header version; needed for TX/RX packet + * handling. + */ + if (mac->mac_fw.rev >= 598) + mac->mac_fw.fw_hdr_format = BWN_FW_HDR_598; + else if (mac->mac_fw.rev >= 410) + mac->mac_fw.fw_hdr_format = BWN_FW_HDR_410; + else + mac->mac_fw.fw_hdr_format = BWN_FW_HDR_351; + + /* + * We don't support rev 598 or later; that requires + * another round of changes to the TX/RX descriptor + * and status layout. + * + * So, complain this is the case and exit out, rather + * than attaching and then failing. + */ + if (mac->mac_fw.fw_hdr_format == BWN_FW_HDR_598) { + device_printf(sc->sc_dev, + "firmware is too new (>=598); not supported\n"); + error = EOPNOTSUPP; + goto error; + } + mac->mac_fw.patch = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODE_PATCH); date = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODE_DATE); mac->mac_fw.opensource = (date == 0xffff); if (bwn_wme != 0) mac->mac_flags |= BWN_MAC_FLAG_WME; mac->mac_flags |= BWN_MAC_FLAG_HWCRYPTO; time = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODE_TIME); if (mac->mac_fw.opensource == 0) { device_printf(sc->sc_dev, "firmware version (rev %u patch %u date %#x time %#x)\n", mac->mac_fw.rev, mac->mac_fw.patch, date, time); if (mac->mac_fw.no_pcmfile) device_printf(sc->sc_dev, "no HW crypto acceleration due to pcm5\n"); } else { mac->mac_fw.patch = time; fwcaps = bwn_fwcaps_read(mac); if (!(fwcaps & BWN_FWCAPS_HWCRYPTO) || mac->mac_fw.no_pcmfile) { device_printf(sc->sc_dev, "disabling HW crypto acceleration\n"); mac->mac_flags &= ~BWN_MAC_FLAG_HWCRYPTO; } if (!(fwcaps & BWN_FWCAPS_WME)) { device_printf(sc->sc_dev, "disabling WME support\n"); mac->mac_flags &= ~BWN_MAC_FLAG_WME; } } if (BWN_ISOLDFMT(mac)) device_printf(sc->sc_dev, "using old firmware image\n"); return (0); error: BWN_WRITE_4(mac, BWN_MACCTL, (BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_MCODE_RUN) | BWN_MACCTL_MCODE_JMP0); return (error); #undef GETFWSIZE #undef GETFWOFFSET } /* OpenFirmware only */ static uint16_t bwn_fwcaps_read(struct bwn_mac *mac) { KASSERT(mac->mac_fw.opensource == 1, ("%s:%d: fail", __func__, __LINE__)); return (bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_FWCAPS)); } static int bwn_fwinitvals_write(struct bwn_mac *mac, const struct bwn_fwinitvals *ivals, size_t count, size_t array_size) { #define GET_NEXTIV16(iv) \ ((const struct bwn_fwinitvals *)((const uint8_t *)(iv) + \ sizeof(uint16_t) + sizeof(uint16_t))) #define GET_NEXTIV32(iv) \ ((const struct bwn_fwinitvals *)((const uint8_t *)(iv) + \ sizeof(uint16_t) + sizeof(uint32_t))) struct bwn_softc *sc = mac->mac_sc; const struct bwn_fwinitvals *iv; uint16_t offset; size_t i; uint8_t bit32; KASSERT(sizeof(struct bwn_fwinitvals) == 6, ("%s:%d: fail", __func__, __LINE__)); iv = ivals; for (i = 0; i < count; i++) { if (array_size < sizeof(iv->offset_size)) goto fail; array_size -= sizeof(iv->offset_size); offset = be16toh(iv->offset_size); bit32 = (offset & BWN_FWINITVALS_32BIT) ? 1 : 0; offset &= BWN_FWINITVALS_OFFSET_MASK; if (offset >= 0x1000) goto fail; if (bit32) { if (array_size < sizeof(iv->data.d32)) goto fail; array_size -= sizeof(iv->data.d32); BWN_WRITE_4(mac, offset, be32toh(iv->data.d32)); iv = GET_NEXTIV32(iv); } else { if (array_size < sizeof(iv->data.d16)) goto fail; array_size -= sizeof(iv->data.d16); BWN_WRITE_2(mac, offset, be16toh(iv->data.d16)); iv = GET_NEXTIV16(iv); } } if (array_size != 0) goto fail; return (0); fail: device_printf(sc->sc_dev, "initvals: invalid format\n"); return (EPROTO); #undef GET_NEXTIV16 #undef GET_NEXTIV32 } int bwn_switch_channel(struct bwn_mac *mac, int chan) { struct bwn_phy *phy = &(mac->mac_phy); struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; uint16_t channelcookie, savedcookie; int error; if (chan == 0xffff) chan = phy->get_default_chan(mac); channelcookie = chan; if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) channelcookie |= 0x100; savedcookie = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_CHAN); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_CHAN, channelcookie); error = phy->switch_channel(mac, chan); if (error) goto fail; mac->mac_phy.chan = chan; DELAY(8000); return (0); fail: device_printf(sc->sc_dev, "failed to switch channel\n"); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_CHAN, savedcookie); return (error); } static uint16_t bwn_ant2phy(int antenna) { switch (antenna) { case BWN_ANT0: return (BWN_TX_PHY_ANT0); case BWN_ANT1: return (BWN_TX_PHY_ANT1); case BWN_ANT2: return (BWN_TX_PHY_ANT2); case BWN_ANT3: return (BWN_TX_PHY_ANT3); case BWN_ANTAUTO: return (BWN_TX_PHY_ANT01AUTO); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (0); } static void bwn_wme_load(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int i; KASSERT(N(bwn_wme_shm_offsets) == N(sc->sc_wmeParams), ("%s:%d: fail", __func__, __LINE__)); bwn_mac_suspend(mac); for (i = 0; i < N(sc->sc_wmeParams); i++) bwn_wme_loadparams(mac, &(sc->sc_wmeParams[i]), bwn_wme_shm_offsets[i]); bwn_mac_enable(mac); } static void bwn_wme_loadparams(struct bwn_mac *mac, const struct wmeParams *p, uint16_t shm_offset) { #define SM(_v, _f) (((_v) << _f##_S) & _f) struct bwn_softc *sc = mac->mac_sc; uint16_t params[BWN_NR_WMEPARAMS]; int slot, tmp; unsigned int i; slot = BWN_READ_2(mac, BWN_RNG) & SM(p->wmep_logcwmin, WME_PARAM_LOGCWMIN); memset(¶ms, 0, sizeof(params)); DPRINTF(sc, BWN_DEBUG_WME, "wmep_txopLimit %d wmep_logcwmin %d " "wmep_logcwmax %d wmep_aifsn %d\n", p->wmep_txopLimit, p->wmep_logcwmin, p->wmep_logcwmax, p->wmep_aifsn); params[BWN_WMEPARAM_TXOP] = p->wmep_txopLimit * 32; params[BWN_WMEPARAM_CWMIN] = SM(p->wmep_logcwmin, WME_PARAM_LOGCWMIN); params[BWN_WMEPARAM_CWMAX] = SM(p->wmep_logcwmax, WME_PARAM_LOGCWMAX); params[BWN_WMEPARAM_CWCUR] = SM(p->wmep_logcwmin, WME_PARAM_LOGCWMIN); params[BWN_WMEPARAM_AIFS] = p->wmep_aifsn; params[BWN_WMEPARAM_BSLOTS] = slot; params[BWN_WMEPARAM_REGGAP] = slot + p->wmep_aifsn; for (i = 0; i < N(params); i++) { if (i == BWN_WMEPARAM_STATUS) { tmp = bwn_shm_read_2(mac, BWN_SHARED, shm_offset + (i * 2)); tmp |= 0x100; bwn_shm_write_2(mac, BWN_SHARED, shm_offset + (i * 2), tmp); } else { bwn_shm_write_2(mac, BWN_SHARED, shm_offset + (i * 2), params[i]); } } } static void bwn_mac_write_bssid(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint32_t tmp; int i; uint8_t mac_bssid[IEEE80211_ADDR_LEN * 2]; bwn_mac_setfilter(mac, BWN_MACFILTER_BSSID, sc->sc_bssid); memcpy(mac_bssid, sc->sc_ic.ic_macaddr, IEEE80211_ADDR_LEN); memcpy(mac_bssid + IEEE80211_ADDR_LEN, sc->sc_bssid, IEEE80211_ADDR_LEN); for (i = 0; i < N(mac_bssid); i += sizeof(uint32_t)) { tmp = (uint32_t) (mac_bssid[i + 0]); tmp |= (uint32_t) (mac_bssid[i + 1]) << 8; tmp |= (uint32_t) (mac_bssid[i + 2]) << 16; tmp |= (uint32_t) (mac_bssid[i + 3]) << 24; bwn_ram_write(mac, 0x20 + i, tmp); } } static void bwn_mac_setfilter(struct bwn_mac *mac, uint16_t offset, const uint8_t *macaddr) { static const uint8_t zero[IEEE80211_ADDR_LEN] = { 0 }; uint16_t data; if (!mac) macaddr = zero; offset |= 0x0020; BWN_WRITE_2(mac, BWN_MACFILTER_CONTROL, offset); data = macaddr[0]; data |= macaddr[1] << 8; BWN_WRITE_2(mac, BWN_MACFILTER_DATA, data); data = macaddr[2]; data |= macaddr[3] << 8; BWN_WRITE_2(mac, BWN_MACFILTER_DATA, data); data = macaddr[4]; data |= macaddr[5] << 8; BWN_WRITE_2(mac, BWN_MACFILTER_DATA, data); } static void bwn_key_dowrite(struct bwn_mac *mac, uint8_t index, uint8_t algorithm, const uint8_t *key, size_t key_len, const uint8_t *mac_addr) { uint8_t buf[BWN_SEC_KEYSIZE] = { 0, }; uint8_t per_sta_keys_start = 8; if (BWN_SEC_NEWAPI(mac)) per_sta_keys_start = 4; KASSERT(index < mac->mac_max_nr_keys, ("%s:%d: fail", __func__, __LINE__)); KASSERT(key_len <= BWN_SEC_KEYSIZE, ("%s:%d: fail", __func__, __LINE__)); if (index >= per_sta_keys_start) bwn_key_macwrite(mac, index, NULL); if (key) memcpy(buf, key, key_len); bwn_key_write(mac, index, algorithm, buf); if (index >= per_sta_keys_start) bwn_key_macwrite(mac, index, mac_addr); mac->mac_key[index].algorithm = algorithm; } static void bwn_key_macwrite(struct bwn_mac *mac, uint8_t index, const uint8_t *addr) { struct bwn_softc *sc = mac->mac_sc; uint32_t addrtmp[2] = { 0, 0 }; uint8_t start = 8; if (BWN_SEC_NEWAPI(mac)) start = 4; KASSERT(index >= start, ("%s:%d: fail", __func__, __LINE__)); index -= start; if (addr) { addrtmp[0] = addr[0]; addrtmp[0] |= ((uint32_t) (addr[1]) << 8); addrtmp[0] |= ((uint32_t) (addr[2]) << 16); addrtmp[0] |= ((uint32_t) (addr[3]) << 24); addrtmp[1] = addr[4]; addrtmp[1] |= ((uint32_t) (addr[5]) << 8); } if (siba_get_revid(sc->sc_dev) >= 5) { bwn_shm_write_4(mac, BWN_RCMTA, (index * 2) + 0, addrtmp[0]); bwn_shm_write_2(mac, BWN_RCMTA, (index * 2) + 1, addrtmp[1]); } else { if (index >= 8) { bwn_shm_write_4(mac, BWN_SHARED, BWN_SHARED_PSM + (index * 6) + 0, addrtmp[0]); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PSM + (index * 6) + 4, addrtmp[1]); } } } static void bwn_key_write(struct bwn_mac *mac, uint8_t index, uint8_t algorithm, const uint8_t *key) { unsigned int i; uint32_t offset; uint16_t kidx, value; kidx = BWN_SEC_KEY2FW(mac, index); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_KEYIDX_BLOCK + (kidx * 2), (kidx << 4) | algorithm); offset = mac->mac_ktp + (index * BWN_SEC_KEYSIZE); for (i = 0; i < BWN_SEC_KEYSIZE; i += 2) { value = key[i]; value |= (uint16_t)(key[i + 1]) << 8; bwn_shm_write_2(mac, BWN_SHARED, offset + i, value); } } static void bwn_phy_exit(struct bwn_mac *mac) { mac->mac_phy.rf_onoff(mac, 0); if (mac->mac_phy.exit != NULL) mac->mac_phy.exit(mac); } static void bwn_dma_free(struct bwn_mac *mac) { struct bwn_dma *dma; if ((mac->mac_flags & BWN_MAC_FLAG_DMA) == 0) return; dma = &mac->mac_method.dma; bwn_dma_ringfree(&dma->rx); bwn_dma_ringfree(&dma->wme[WME_AC_BK]); bwn_dma_ringfree(&dma->wme[WME_AC_BE]); bwn_dma_ringfree(&dma->wme[WME_AC_VI]); bwn_dma_ringfree(&dma->wme[WME_AC_VO]); bwn_dma_ringfree(&dma->mcast); } static void bwn_core_stop(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; BWN_ASSERT_LOCKED(sc); if (mac->mac_status < BWN_MAC_STATUS_STARTED) return; callout_stop(&sc->sc_rfswitch_ch); callout_stop(&sc->sc_task_ch); callout_stop(&sc->sc_watchdog_ch); sc->sc_watchdog_timer = 0; BWN_WRITE_4(mac, BWN_INTR_MASK, 0); BWN_READ_4(mac, BWN_INTR_MASK); bwn_mac_suspend(mac); mac->mac_status = BWN_MAC_STATUS_INITED; } static int bwn_switch_band(struct bwn_softc *sc, struct ieee80211_channel *chan) { struct bwn_mac *up_dev = NULL; struct bwn_mac *down_dev; struct bwn_mac *mac; int err, status; uint8_t gmode; BWN_ASSERT_LOCKED(sc); TAILQ_FOREACH(mac, &sc->sc_maclist, mac_list) { if (IEEE80211_IS_CHAN_2GHZ(chan) && mac->mac_phy.supports_2ghz) { up_dev = mac; gmode = 1; } else if (IEEE80211_IS_CHAN_5GHZ(chan) && mac->mac_phy.supports_5ghz) { up_dev = mac; gmode = 0; } else { KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (EINVAL); } if (up_dev != NULL) break; } if (up_dev == NULL) { device_printf(sc->sc_dev, "Could not find a device\n"); return (ENODEV); } if (up_dev == sc->sc_curmac && sc->sc_curmac->mac_phy.gmode == gmode) return (0); device_printf(sc->sc_dev, "switching to %s-GHz band\n", IEEE80211_IS_CHAN_2GHZ(chan) ? "2" : "5"); down_dev = sc->sc_curmac; status = down_dev->mac_status; if (status >= BWN_MAC_STATUS_STARTED) bwn_core_stop(down_dev); if (status >= BWN_MAC_STATUS_INITED) bwn_core_exit(down_dev); if (down_dev != up_dev) bwn_phy_reset(down_dev); up_dev->mac_phy.gmode = gmode; if (status >= BWN_MAC_STATUS_INITED) { err = bwn_core_init(up_dev); if (err) { device_printf(sc->sc_dev, "fatal: failed to initialize for %s-GHz\n", IEEE80211_IS_CHAN_2GHZ(chan) ? "2" : "5"); goto fail; } } if (status >= BWN_MAC_STATUS_STARTED) bwn_core_start(up_dev); KASSERT(up_dev->mac_status == status, ("%s: fail", __func__)); sc->sc_curmac = up_dev; return (0); fail: sc->sc_curmac = NULL; return (err); } static void bwn_rf_turnon(struct bwn_mac *mac) { bwn_mac_suspend(mac); mac->mac_phy.rf_onoff(mac, 1); mac->mac_phy.rf_on = 1; bwn_mac_enable(mac); } static void bwn_rf_turnoff(struct bwn_mac *mac) { bwn_mac_suspend(mac); mac->mac_phy.rf_onoff(mac, 0); mac->mac_phy.rf_on = 0; bwn_mac_enable(mac); } static void bwn_phy_reset(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; siba_write_4(sc->sc_dev, SIBA_TGSLOW, ((siba_read_4(sc->sc_dev, SIBA_TGSLOW) & ~BWN_TGSLOW_SUPPORT_G) | BWN_TGSLOW_PHYRESET) | SIBA_TGSLOW_FGC); DELAY(1000); siba_write_4(sc->sc_dev, SIBA_TGSLOW, (siba_read_4(sc->sc_dev, SIBA_TGSLOW) & ~SIBA_TGSLOW_FGC) | BWN_TGSLOW_PHYRESET); DELAY(1000); } static int bwn_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct bwn_vap *bvp = BWN_VAP(vap); struct ieee80211com *ic= vap->iv_ic; enum ieee80211_state ostate = vap->iv_state; struct bwn_softc *sc = ic->ic_softc; struct bwn_mac *mac = sc->sc_curmac; int error; DPRINTF(sc, BWN_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); error = bvp->bv_newstate(vap, nstate, arg); if (error != 0) return (error); BWN_LOCK(sc); bwn_led_newstate(mac, nstate); /* * Clear the BSSID when we stop a STA */ if (vap->iv_opmode == IEEE80211_M_STA) { if (ostate == IEEE80211_S_RUN && nstate != IEEE80211_S_RUN) { /* * Clear out the BSSID. If we reassociate to * the same AP, this will reinialize things * correctly... */ if (ic->ic_opmode == IEEE80211_M_STA && (sc->sc_flags & BWN_FLAG_INVALID) == 0) { memset(sc->sc_bssid, 0, IEEE80211_ADDR_LEN); bwn_set_macaddr(mac); } } } if (vap->iv_opmode == IEEE80211_M_MONITOR || vap->iv_opmode == IEEE80211_M_AHDEMO) { /* XXX nothing to do? */ } else if (nstate == IEEE80211_S_RUN) { memcpy(sc->sc_bssid, vap->iv_bss->ni_bssid, IEEE80211_ADDR_LEN); bwn_set_opmode(mac); bwn_set_pretbtt(mac); bwn_spu_setdelay(mac, 0); bwn_set_macaddr(mac); } BWN_UNLOCK(sc); return (error); } static void bwn_set_pretbtt(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; uint16_t pretbtt; if (ic->ic_opmode == IEEE80211_M_IBSS) pretbtt = 2; else pretbtt = (mac->mac_phy.type == BWN_PHYTYPE_A) ? 120 : 250; bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PRETBTT, pretbtt); BWN_WRITE_2(mac, BWN_TSF_CFP_PRETBTT, pretbtt); } static int bwn_intr(void *arg) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; uint32_t reason; if (mac->mac_status < BWN_MAC_STATUS_STARTED || (sc->sc_flags & BWN_FLAG_INVALID)) return (FILTER_STRAY); reason = BWN_READ_4(mac, BWN_INTR_REASON); if (reason == 0xffffffff) /* shared IRQ */ return (FILTER_STRAY); reason &= mac->mac_intr_mask; if (reason == 0) return (FILTER_HANDLED); mac->mac_reason[0] = BWN_READ_4(mac, BWN_DMA0_REASON) & 0x0001dc00; mac->mac_reason[1] = BWN_READ_4(mac, BWN_DMA1_REASON) & 0x0000dc00; mac->mac_reason[2] = BWN_READ_4(mac, BWN_DMA2_REASON) & 0x0000dc00; mac->mac_reason[3] = BWN_READ_4(mac, BWN_DMA3_REASON) & 0x0001dc00; mac->mac_reason[4] = BWN_READ_4(mac, BWN_DMA4_REASON) & 0x0000dc00; BWN_WRITE_4(mac, BWN_INTR_REASON, reason); BWN_WRITE_4(mac, BWN_DMA0_REASON, mac->mac_reason[0]); BWN_WRITE_4(mac, BWN_DMA1_REASON, mac->mac_reason[1]); BWN_WRITE_4(mac, BWN_DMA2_REASON, mac->mac_reason[2]); BWN_WRITE_4(mac, BWN_DMA3_REASON, mac->mac_reason[3]); BWN_WRITE_4(mac, BWN_DMA4_REASON, mac->mac_reason[4]); /* Disable interrupts. */ BWN_WRITE_4(mac, BWN_INTR_MASK, 0); mac->mac_reason_intr = reason; BWN_BARRIER(mac, BUS_SPACE_BARRIER_READ); BWN_BARRIER(mac, BUS_SPACE_BARRIER_WRITE); taskqueue_enqueue(sc->sc_tq, &mac->mac_intrtask); return (FILTER_HANDLED); } static void bwn_intrtask(void *arg, int npending) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; uint32_t merged = 0; int i, tx = 0, rx = 0; BWN_LOCK(sc); if (mac->mac_status < BWN_MAC_STATUS_STARTED || (sc->sc_flags & BWN_FLAG_INVALID)) { BWN_UNLOCK(sc); return; } for (i = 0; i < N(mac->mac_reason); i++) merged |= mac->mac_reason[i]; if (mac->mac_reason_intr & BWN_INTR_MAC_TXERR) device_printf(sc->sc_dev, "MAC trans error\n"); if (mac->mac_reason_intr & BWN_INTR_PHY_TXERR) { DPRINTF(sc, BWN_DEBUG_INTR, "%s: PHY trans error\n", __func__); mac->mac_phy.txerrors--; if (mac->mac_phy.txerrors == 0) { mac->mac_phy.txerrors = BWN_TXERROR_MAX; bwn_restart(mac, "PHY TX errors"); } } if (merged & (BWN_DMAINTR_FATALMASK | BWN_DMAINTR_NONFATALMASK)) { if (merged & BWN_DMAINTR_FATALMASK) { device_printf(sc->sc_dev, "Fatal DMA error: %#x %#x %#x %#x %#x %#x\n", mac->mac_reason[0], mac->mac_reason[1], mac->mac_reason[2], mac->mac_reason[3], mac->mac_reason[4], mac->mac_reason[5]); bwn_restart(mac, "DMA error"); BWN_UNLOCK(sc); return; } if (merged & BWN_DMAINTR_NONFATALMASK) { device_printf(sc->sc_dev, "DMA error: %#x %#x %#x %#x %#x %#x\n", mac->mac_reason[0], mac->mac_reason[1], mac->mac_reason[2], mac->mac_reason[3], mac->mac_reason[4], mac->mac_reason[5]); } } if (mac->mac_reason_intr & BWN_INTR_UCODE_DEBUG) bwn_intr_ucode_debug(mac); if (mac->mac_reason_intr & BWN_INTR_TBTT_INDI) bwn_intr_tbtt_indication(mac); if (mac->mac_reason_intr & BWN_INTR_ATIM_END) bwn_intr_atim_end(mac); if (mac->mac_reason_intr & BWN_INTR_BEACON) bwn_intr_beacon(mac); if (mac->mac_reason_intr & BWN_INTR_PMQ) bwn_intr_pmq(mac); if (mac->mac_reason_intr & BWN_INTR_NOISESAMPLE_OK) bwn_intr_noise(mac); if (mac->mac_flags & BWN_MAC_FLAG_DMA) { if (mac->mac_reason[0] & BWN_DMAINTR_RX_DONE) { bwn_dma_rx(mac->mac_method.dma.rx); rx = 1; } } else rx = bwn_pio_rx(&mac->mac_method.pio.rx); KASSERT(!(mac->mac_reason[1] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); KASSERT(!(mac->mac_reason[2] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); KASSERT(!(mac->mac_reason[3] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); KASSERT(!(mac->mac_reason[4] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); KASSERT(!(mac->mac_reason[5] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); if (mac->mac_reason_intr & BWN_INTR_TX_OK) { bwn_intr_txeof(mac); tx = 1; } BWN_WRITE_4(mac, BWN_INTR_MASK, mac->mac_intr_mask); if (sc->sc_blink_led != NULL && sc->sc_led_blink) { int evt = BWN_LED_EVENT_NONE; if (tx && rx) { if (sc->sc_rx_rate > sc->sc_tx_rate) evt = BWN_LED_EVENT_RX; else evt = BWN_LED_EVENT_TX; } else if (tx) { evt = BWN_LED_EVENT_TX; } else if (rx) { evt = BWN_LED_EVENT_RX; } else if (rx == 0) { evt = BWN_LED_EVENT_POLL; } if (evt != BWN_LED_EVENT_NONE) bwn_led_event(mac, evt); } if (mbufq_first(&sc->sc_snd) != NULL) bwn_start(sc); BWN_BARRIER(mac, BUS_SPACE_BARRIER_READ); BWN_BARRIER(mac, BUS_SPACE_BARRIER_WRITE); BWN_UNLOCK(sc); } static void bwn_restart(struct bwn_mac *mac, const char *msg) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; if (mac->mac_status < BWN_MAC_STATUS_INITED) return; device_printf(sc->sc_dev, "HW reset: %s\n", msg); ieee80211_runtask(ic, &mac->mac_hwreset); } static void bwn_intr_ucode_debug(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint16_t reason; if (mac->mac_fw.opensource == 0) return; reason = bwn_shm_read_2(mac, BWN_SCRATCH, BWN_DEBUGINTR_REASON_REG); switch (reason) { case BWN_DEBUGINTR_PANIC: bwn_handle_fwpanic(mac); break; case BWN_DEBUGINTR_DUMP_SHM: device_printf(sc->sc_dev, "BWN_DEBUGINTR_DUMP_SHM\n"); break; case BWN_DEBUGINTR_DUMP_REGS: device_printf(sc->sc_dev, "BWN_DEBUGINTR_DUMP_REGS\n"); break; case BWN_DEBUGINTR_MARKER: device_printf(sc->sc_dev, "BWN_DEBUGINTR_MARKER\n"); break; default: device_printf(sc->sc_dev, "ucode debug unknown reason: %#x\n", reason); } bwn_shm_write_2(mac, BWN_SCRATCH, BWN_DEBUGINTR_REASON_REG, BWN_DEBUGINTR_ACK); } static void bwn_intr_tbtt_indication(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; if (ic->ic_opmode != IEEE80211_M_HOSTAP) bwn_psctl(mac, 0); if (ic->ic_opmode == IEEE80211_M_IBSS) mac->mac_flags |= BWN_MAC_FLAG_DFQVALID; } static void bwn_intr_atim_end(struct bwn_mac *mac) { if (mac->mac_flags & BWN_MAC_FLAG_DFQVALID) { BWN_WRITE_4(mac, BWN_MACCMD, BWN_READ_4(mac, BWN_MACCMD) | BWN_MACCMD_DFQ_VALID); mac->mac_flags &= ~BWN_MAC_FLAG_DFQVALID; } } static void bwn_intr_beacon(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; uint32_t cmd, beacon0, beacon1; if (ic->ic_opmode == IEEE80211_M_HOSTAP || ic->ic_opmode == IEEE80211_M_MBSS) return; mac->mac_intr_mask &= ~BWN_INTR_BEACON; cmd = BWN_READ_4(mac, BWN_MACCMD); beacon0 = (cmd & BWN_MACCMD_BEACON0_VALID); beacon1 = (cmd & BWN_MACCMD_BEACON1_VALID); if (beacon0 && beacon1) { BWN_WRITE_4(mac, BWN_INTR_REASON, BWN_INTR_BEACON); mac->mac_intr_mask |= BWN_INTR_BEACON; return; } if (sc->sc_flags & BWN_FLAG_NEED_BEACON_TP) { sc->sc_flags &= ~BWN_FLAG_NEED_BEACON_TP; bwn_load_beacon0(mac); bwn_load_beacon1(mac); cmd = BWN_READ_4(mac, BWN_MACCMD); cmd |= BWN_MACCMD_BEACON0_VALID; BWN_WRITE_4(mac, BWN_MACCMD, cmd); } else { if (!beacon0) { bwn_load_beacon0(mac); cmd = BWN_READ_4(mac, BWN_MACCMD); cmd |= BWN_MACCMD_BEACON0_VALID; BWN_WRITE_4(mac, BWN_MACCMD, cmd); } else if (!beacon1) { bwn_load_beacon1(mac); cmd = BWN_READ_4(mac, BWN_MACCMD); cmd |= BWN_MACCMD_BEACON1_VALID; BWN_WRITE_4(mac, BWN_MACCMD, cmd); } } } static void bwn_intr_pmq(struct bwn_mac *mac) { uint32_t tmp; while (1) { tmp = BWN_READ_4(mac, BWN_PS_STATUS); if (!(tmp & 0x00000008)) break; } BWN_WRITE_2(mac, BWN_PS_STATUS, 0x0002); } static void bwn_intr_noise(struct bwn_mac *mac) { struct bwn_phy_g *pg = &mac->mac_phy.phy_g; uint16_t tmp; uint8_t noise[4]; uint8_t i, j; int32_t average; if (mac->mac_phy.type != BWN_PHYTYPE_G) return; KASSERT(mac->mac_noise.noi_running, ("%s: fail", __func__)); *((uint32_t *)noise) = htole32(bwn_jssi_read(mac)); if (noise[0] == 0x7f || noise[1] == 0x7f || noise[2] == 0x7f || noise[3] == 0x7f) goto new; KASSERT(mac->mac_noise.noi_nsamples < 8, ("%s:%d: fail", __func__, __LINE__)); i = mac->mac_noise.noi_nsamples; noise[0] = MIN(MAX(noise[0], 0), N(pg->pg_nrssi_lt) - 1); noise[1] = MIN(MAX(noise[1], 0), N(pg->pg_nrssi_lt) - 1); noise[2] = MIN(MAX(noise[2], 0), N(pg->pg_nrssi_lt) - 1); noise[3] = MIN(MAX(noise[3], 0), N(pg->pg_nrssi_lt) - 1); mac->mac_noise.noi_samples[i][0] = pg->pg_nrssi_lt[noise[0]]; mac->mac_noise.noi_samples[i][1] = pg->pg_nrssi_lt[noise[1]]; mac->mac_noise.noi_samples[i][2] = pg->pg_nrssi_lt[noise[2]]; mac->mac_noise.noi_samples[i][3] = pg->pg_nrssi_lt[noise[3]]; mac->mac_noise.noi_nsamples++; if (mac->mac_noise.noi_nsamples == 8) { average = 0; for (i = 0; i < 8; i++) { for (j = 0; j < 4; j++) average += mac->mac_noise.noi_samples[i][j]; } average = (((average / 32) * 125) + 64) / 128; tmp = (bwn_shm_read_2(mac, BWN_SHARED, 0x40c) / 128) & 0x1f; if (tmp >= 8) average += 2; else average -= 25; average -= (tmp == 8) ? 72 : 48; mac->mac_stats.link_noise = average; mac->mac_noise.noi_running = 0; return; } new: bwn_noise_gensample(mac); } static int bwn_pio_rx(struct bwn_pio_rxqueue *prq) { struct bwn_mac *mac = prq->prq_mac; struct bwn_softc *sc = mac->mac_sc; unsigned int i; BWN_ASSERT_LOCKED(sc); if (mac->mac_status < BWN_MAC_STATUS_STARTED) return (0); for (i = 0; i < 5000; i++) { if (bwn_pio_rxeof(prq) == 0) break; } if (i >= 5000) device_printf(sc->sc_dev, "too many RX frames in PIO mode\n"); return ((i > 0) ? 1 : 0); } static void bwn_dma_rx(struct bwn_dma_ring *dr) { int slot, curslot; KASSERT(!dr->dr_tx, ("%s:%d: fail", __func__, __LINE__)); curslot = dr->get_curslot(dr); KASSERT(curslot >= 0 && curslot < dr->dr_numslots, ("%s:%d: fail", __func__, __LINE__)); slot = dr->dr_curslot; for (; slot != curslot; slot = bwn_dma_nextslot(dr, slot)) bwn_dma_rxeof(dr, &slot); bus_dmamap_sync(dr->dr_ring_dtag, dr->dr_ring_dmap, BUS_DMASYNC_PREWRITE); dr->set_curslot(dr, slot); dr->dr_curslot = slot; } static void bwn_intr_txeof(struct bwn_mac *mac) { struct bwn_txstatus stat; uint32_t stat0, stat1; uint16_t tmp; BWN_ASSERT_LOCKED(mac->mac_sc); while (1) { stat0 = BWN_READ_4(mac, BWN_XMITSTAT_0); if (!(stat0 & 0x00000001)) break; stat1 = BWN_READ_4(mac, BWN_XMITSTAT_1); DPRINTF(mac->mac_sc, BWN_DEBUG_XMIT, "%s: stat0=0x%08x, stat1=0x%08x\n", __func__, stat0, stat1); stat.cookie = (stat0 >> 16); stat.seq = (stat1 & 0x0000ffff); stat.phy_stat = ((stat1 & 0x00ff0000) >> 16); tmp = (stat0 & 0x0000ffff); stat.framecnt = ((tmp & 0xf000) >> 12); stat.rtscnt = ((tmp & 0x0f00) >> 8); stat.sreason = ((tmp & 0x001c) >> 2); stat.pm = (tmp & 0x0080) ? 1 : 0; stat.im = (tmp & 0x0040) ? 1 : 0; stat.ampdu = (tmp & 0x0020) ? 1 : 0; stat.ack = (tmp & 0x0002) ? 1 : 0; bwn_handle_txeof(mac, &stat); } } static void bwn_hwreset(void *arg, int npending) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; int error = 0; int prev_status; BWN_LOCK(sc); prev_status = mac->mac_status; if (prev_status >= BWN_MAC_STATUS_STARTED) bwn_core_stop(mac); if (prev_status >= BWN_MAC_STATUS_INITED) bwn_core_exit(mac); if (prev_status >= BWN_MAC_STATUS_INITED) { error = bwn_core_init(mac); if (error) goto out; } if (prev_status >= BWN_MAC_STATUS_STARTED) bwn_core_start(mac); out: if (error) { device_printf(sc->sc_dev, "%s: failed (%d)\n", __func__, error); sc->sc_curmac = NULL; } BWN_UNLOCK(sc); } static void bwn_handle_fwpanic(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint16_t reason; reason = bwn_shm_read_2(mac, BWN_SCRATCH, BWN_FWPANIC_REASON_REG); device_printf(sc->sc_dev,"fw panic (%u)\n", reason); if (reason == BWN_FWPANIC_RESTART) bwn_restart(mac, "ucode panic"); } static void bwn_load_beacon0(struct bwn_mac *mac) { KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } static void bwn_load_beacon1(struct bwn_mac *mac) { KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } static uint32_t bwn_jssi_read(struct bwn_mac *mac) { uint32_t val = 0; val = bwn_shm_read_2(mac, BWN_SHARED, 0x08a); val <<= 16; val |= bwn_shm_read_2(mac, BWN_SHARED, 0x088); return (val); } static void bwn_noise_gensample(struct bwn_mac *mac) { uint32_t jssi = 0x7f7f7f7f; bwn_shm_write_2(mac, BWN_SHARED, 0x088, (jssi & 0x0000ffff)); bwn_shm_write_2(mac, BWN_SHARED, 0x08a, (jssi & 0xffff0000) >> 16); BWN_WRITE_4(mac, BWN_MACCMD, BWN_READ_4(mac, BWN_MACCMD) | BWN_MACCMD_BGNOISE); } static int bwn_dma_freeslot(struct bwn_dma_ring *dr) { BWN_ASSERT_LOCKED(dr->dr_mac->mac_sc); return (dr->dr_numslots - dr->dr_usedslot); } static int bwn_dma_nextslot(struct bwn_dma_ring *dr, int slot) { BWN_ASSERT_LOCKED(dr->dr_mac->mac_sc); KASSERT(slot >= -1 && slot <= dr->dr_numslots - 1, ("%s:%d: fail", __func__, __LINE__)); if (slot == dr->dr_numslots - 1) return (0); return (slot + 1); } static void bwn_dma_rxeof(struct bwn_dma_ring *dr, int *slot) { struct bwn_mac *mac = dr->dr_mac; struct bwn_softc *sc = mac->mac_sc; struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *meta; struct bwn_rxhdr4 *rxhdr; struct mbuf *m; uint32_t macstat; int32_t tmp; int cnt = 0; uint16_t len; dr->getdesc(dr, *slot, &desc, &meta); bus_dmamap_sync(dma->rxbuf_dtag, meta->mt_dmap, BUS_DMASYNC_POSTREAD); m = meta->mt_m; if (bwn_dma_newbuf(dr, desc, meta, 0)) { counter_u64_add(sc->sc_ic.ic_ierrors, 1); return; } rxhdr = mtod(m, struct bwn_rxhdr4 *); len = le16toh(rxhdr->frame_len); if (len <= 0) { counter_u64_add(sc->sc_ic.ic_ierrors, 1); return; } if (bwn_dma_check_redzone(dr, m)) { device_printf(sc->sc_dev, "redzone error.\n"); bwn_dma_set_redzone(dr, m); bus_dmamap_sync(dma->rxbuf_dtag, meta->mt_dmap, BUS_DMASYNC_PREWRITE); return; } if (len > dr->dr_rx_bufsize) { tmp = len; while (1) { dr->getdesc(dr, *slot, &desc, &meta); bwn_dma_set_redzone(dr, meta->mt_m); bus_dmamap_sync(dma->rxbuf_dtag, meta->mt_dmap, BUS_DMASYNC_PREWRITE); *slot = bwn_dma_nextslot(dr, *slot); cnt++; tmp -= dr->dr_rx_bufsize; if (tmp <= 0) break; } device_printf(sc->sc_dev, "too small buffer " "(len %u buffer %u dropped %d)\n", len, dr->dr_rx_bufsize, cnt); return; } macstat = le32toh(rxhdr->mac_status); if (macstat & BWN_RX_MAC_FCSERR) { if (!(mac->mac_sc->sc_filters & BWN_MACCTL_PASS_BADFCS)) { device_printf(sc->sc_dev, "RX drop\n"); return; } } m->m_len = m->m_pkthdr.len = len + dr->dr_frameoffset; m_adj(m, dr->dr_frameoffset); bwn_rxeof(dr->dr_mac, m, rxhdr); } static void bwn_handle_txeof(struct bwn_mac *mac, const struct bwn_txstatus *status) { struct bwn_softc *sc = mac->mac_sc; struct bwn_stats *stats = &mac->mac_stats; BWN_ASSERT_LOCKED(mac->mac_sc); if (status->im) device_printf(sc->sc_dev, "TODO: STATUS IM\n"); if (status->ampdu) device_printf(sc->sc_dev, "TODO: STATUS AMPDU\n"); if (status->rtscnt) { if (status->rtscnt == 0xf) stats->rtsfail++; else stats->rts++; } if (mac->mac_flags & BWN_MAC_FLAG_DMA) { bwn_dma_handle_txeof(mac, status); } else { bwn_pio_handle_txeof(mac, status); } bwn_phy_txpower_check(mac, 0); } static uint8_t bwn_pio_rxeof(struct bwn_pio_rxqueue *prq) { struct bwn_mac *mac = prq->prq_mac; struct bwn_softc *sc = mac->mac_sc; struct bwn_rxhdr4 rxhdr; struct mbuf *m; uint32_t ctl32, macstat, v32; unsigned int i, padding; uint16_t ctl16, len, totlen, v16; unsigned char *mp; char *data; memset(&rxhdr, 0, sizeof(rxhdr)); if (prq->prq_rev >= 8) { ctl32 = bwn_pio_rx_read_4(prq, BWN_PIO8_RXCTL); if (!(ctl32 & BWN_PIO8_RXCTL_FRAMEREADY)) return (0); bwn_pio_rx_write_4(prq, BWN_PIO8_RXCTL, BWN_PIO8_RXCTL_FRAMEREADY); for (i = 0; i < 10; i++) { ctl32 = bwn_pio_rx_read_4(prq, BWN_PIO8_RXCTL); if (ctl32 & BWN_PIO8_RXCTL_DATAREADY) goto ready; DELAY(10); } } else { ctl16 = bwn_pio_rx_read_2(prq, BWN_PIO_RXCTL); if (!(ctl16 & BWN_PIO_RXCTL_FRAMEREADY)) return (0); bwn_pio_rx_write_2(prq, BWN_PIO_RXCTL, BWN_PIO_RXCTL_FRAMEREADY); for (i = 0; i < 10; i++) { ctl16 = bwn_pio_rx_read_2(prq, BWN_PIO_RXCTL); if (ctl16 & BWN_PIO_RXCTL_DATAREADY) goto ready; DELAY(10); } } device_printf(sc->sc_dev, "%s: timed out\n", __func__); return (1); ready: if (prq->prq_rev >= 8) siba_read_multi_4(sc->sc_dev, &rxhdr, sizeof(rxhdr), prq->prq_base + BWN_PIO8_RXDATA); else siba_read_multi_2(sc->sc_dev, &rxhdr, sizeof(rxhdr), prq->prq_base + BWN_PIO_RXDATA); len = le16toh(rxhdr.frame_len); if (len > 0x700) { device_printf(sc->sc_dev, "%s: len is too big\n", __func__); goto error; } if (len == 0) { device_printf(sc->sc_dev, "%s: len is 0\n", __func__); goto error; } macstat = le32toh(rxhdr.mac_status); if (macstat & BWN_RX_MAC_FCSERR) { if (!(mac->mac_sc->sc_filters & BWN_MACCTL_PASS_BADFCS)) { device_printf(sc->sc_dev, "%s: FCS error", __func__); goto error; } } padding = (macstat & BWN_RX_MAC_PADDING) ? 2 : 0; totlen = len + padding; KASSERT(totlen <= MCLBYTES, ("too big..\n")); m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { device_printf(sc->sc_dev, "%s: out of memory", __func__); goto error; } mp = mtod(m, unsigned char *); if (prq->prq_rev >= 8) { siba_read_multi_4(sc->sc_dev, mp, (totlen & ~3), prq->prq_base + BWN_PIO8_RXDATA); if (totlen & 3) { v32 = bwn_pio_rx_read_4(prq, BWN_PIO8_RXDATA); data = &(mp[totlen - 1]); switch (totlen & 3) { case 3: *data = (v32 >> 16); data--; case 2: *data = (v32 >> 8); data--; case 1: *data = v32; } } } else { siba_read_multi_2(sc->sc_dev, mp, (totlen & ~1), prq->prq_base + BWN_PIO_RXDATA); if (totlen & 1) { v16 = bwn_pio_rx_read_2(prq, BWN_PIO_RXDATA); mp[totlen - 1] = v16; } } m->m_len = m->m_pkthdr.len = totlen; bwn_rxeof(prq->prq_mac, m, &rxhdr); return (1); error: if (prq->prq_rev >= 8) bwn_pio_rx_write_4(prq, BWN_PIO8_RXCTL, BWN_PIO8_RXCTL_DATAREADY); else bwn_pio_rx_write_2(prq, BWN_PIO_RXCTL, BWN_PIO_RXCTL_DATAREADY); return (1); } static int bwn_dma_newbuf(struct bwn_dma_ring *dr, struct bwn_dmadesc_generic *desc, struct bwn_dmadesc_meta *meta, int init) { struct bwn_mac *mac = dr->dr_mac; struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_rxhdr4 *hdr; bus_dmamap_t map; bus_addr_t paddr; struct mbuf *m; int error; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { error = ENOBUFS; /* * If the NIC is up and running, we need to: * - Clear RX buffer's header. * - Restore RX descriptor settings. */ if (init) return (error); else goto back; } m->m_len = m->m_pkthdr.len = MCLBYTES; bwn_dma_set_redzone(dr, m); /* * Try to load RX buf into temporary DMA map */ error = bus_dmamap_load_mbuf(dma->rxbuf_dtag, dr->dr_spare_dmap, m, bwn_dma_buf_addr, &paddr, BUS_DMA_NOWAIT); if (error) { m_freem(m); /* * See the comment above */ if (init) return (error); else goto back; } if (!init) bus_dmamap_unload(dma->rxbuf_dtag, meta->mt_dmap); meta->mt_m = m; meta->mt_paddr = paddr; /* * Swap RX buf's DMA map with the loaded temporary one */ map = meta->mt_dmap; meta->mt_dmap = dr->dr_spare_dmap; dr->dr_spare_dmap = map; back: /* * Clear RX buf header */ hdr = mtod(meta->mt_m, struct bwn_rxhdr4 *); bzero(hdr, sizeof(*hdr)); bus_dmamap_sync(dma->rxbuf_dtag, meta->mt_dmap, BUS_DMASYNC_PREWRITE); /* * Setup RX buf descriptor */ dr->setdesc(dr, desc, meta->mt_paddr, meta->mt_m->m_len - sizeof(*hdr), 0, 0, 0); return (error); } static void bwn_dma_buf_addr(void *arg, bus_dma_segment_t *seg, int nseg, bus_size_t mapsz __unused, int error) { if (!error) { KASSERT(nseg == 1, ("too many segments(%d)\n", nseg)); *((bus_addr_t *)arg) = seg->ds_addr; } } static int bwn_hwrate2ieeerate(int rate) { switch (rate) { case BWN_CCK_RATE_1MB: return (2); case BWN_CCK_RATE_2MB: return (4); case BWN_CCK_RATE_5MB: return (11); case BWN_CCK_RATE_11MB: return (22); case BWN_OFDM_RATE_6MB: return (12); case BWN_OFDM_RATE_9MB: return (18); case BWN_OFDM_RATE_12MB: return (24); case BWN_OFDM_RATE_18MB: return (36); case BWN_OFDM_RATE_24MB: return (48); case BWN_OFDM_RATE_36MB: return (72); case BWN_OFDM_RATE_48MB: return (96); case BWN_OFDM_RATE_54MB: return (108); default: printf("Ooops\n"); return (0); } } +/* + * Post process the RX provided RSSI. + * + * Valid for A, B, G, LP PHYs. + */ +static int8_t +bwn_rx_rssi_calc(struct bwn_mac *mac, int8_t in_rssi, + int ofdm, int adjust_2053, int adjust_2050) +{ + struct bwn_phy *phy = &mac->mac_phy; + struct bwn_phy_g *gphy = &phy->phy_g; + int tmp; + + switch (phy->rf_ver) { + case 0x2050: + if (ofdm) { + tmp = in_rssi; + if (tmp > 127) + tmp -= 256; + tmp = tmp * 73 / 64; + if (adjust_2050) + tmp += 25; + else + tmp -= 3; + } else { + if (siba_sprom_get_bf_lo(mac->mac_sc->sc_dev) + & BWN_BFL_RSSI) { + if (in_rssi > 63) + in_rssi = 63; + tmp = gphy->pg_nrssi_lt[in_rssi]; + tmp = (31 - tmp) * -131 / 128 - 57; + } else { + tmp = in_rssi; + tmp = (31 - tmp) * -149 / 128 - 68; + } + if (phy->type == BWN_PHYTYPE_G && adjust_2050) + tmp += 25; + } + break; + case 0x2060: + if (in_rssi > 127) + tmp = in_rssi - 256; + else + tmp = in_rssi; + break; + default: + tmp = in_rssi; + tmp = (tmp - 11) * 103 / 64; + if (adjust_2053) + tmp -= 109; + else + tmp -= 83; + } + + return (tmp); +} + static void bwn_rxeof(struct bwn_mac *mac, struct mbuf *m, const void *_rxhdr) { const struct bwn_rxhdr4 *rxhdr = _rxhdr; struct bwn_plcp6 *plcp; struct bwn_softc *sc = mac->mac_sc; struct ieee80211_frame_min *wh; struct ieee80211_node *ni; struct ieee80211com *ic = &sc->sc_ic; uint32_t macstat; int padding, rate, rssi = 0, noise = 0, type; uint16_t phytype, phystat0, phystat3, chanstat; unsigned char *mp = mtod(m, unsigned char *); static int rx_mac_dec_rpt = 0; BWN_ASSERT_LOCKED(sc); phystat0 = le16toh(rxhdr->phy_status0); phystat3 = le16toh(rxhdr->phy_status3); + + /* XXX Note: mactime, macstat, chanstat need fixing for fw 598 */ macstat = le32toh(rxhdr->mac_status); chanstat = le16toh(rxhdr->channel); + phytype = chanstat & BWN_RX_CHAN_PHYTYPE; if (macstat & BWN_RX_MAC_FCSERR) device_printf(sc->sc_dev, "TODO RX: RX_FLAG_FAILED_FCS_CRC\n"); if (phystat0 & (BWN_RX_PHYST0_PLCPHCF | BWN_RX_PHYST0_PLCPFV)) device_printf(sc->sc_dev, "TODO RX: RX_FLAG_FAILED_PLCP_CRC\n"); if (macstat & BWN_RX_MAC_DECERR) goto drop; padding = (macstat & BWN_RX_MAC_PADDING) ? 2 : 0; if (m->m_pkthdr.len < (sizeof(struct bwn_plcp6) + padding)) { device_printf(sc->sc_dev, "frame too short (length=%d)\n", m->m_pkthdr.len); goto drop; } plcp = (struct bwn_plcp6 *)(mp + padding); m_adj(m, sizeof(struct bwn_plcp6) + padding); if (m->m_pkthdr.len < IEEE80211_MIN_LEN) { device_printf(sc->sc_dev, "frame too short (length=%d)\n", m->m_pkthdr.len); goto drop; } wh = mtod(m, struct ieee80211_frame_min *); if (macstat & BWN_RX_MAC_DEC && rx_mac_dec_rpt++ < 50) device_printf(sc->sc_dev, "RX decryption attempted (old %d keyidx %#x)\n", BWN_ISOLDFMT(mac), (macstat & BWN_RX_MAC_KEYIDX) >> BWN_RX_MAC_KEYIDX_SHIFT); - /* XXX calculating RSSI & noise & antenna */ - if (phystat0 & BWN_RX_PHYST0_OFDM) rate = bwn_plcp_get_ofdmrate(mac, plcp, phytype == BWN_PHYTYPE_A); else rate = bwn_plcp_get_cckrate(mac, plcp); if (rate == -1) { if (!(mac->mac_sc->sc_filters & BWN_MACCTL_PASS_BADPLCP)) goto drop; } sc->sc_rx_rate = bwn_hwrate2ieeerate(rate); + /* rssi/noise */ + switch (phytype) { + case BWN_PHYTYPE_A: + case BWN_PHYTYPE_B: + case BWN_PHYTYPE_G: + case BWN_PHYTYPE_LP: + rssi = bwn_rx_rssi_calc(mac, rxhdr->phy.abg.rssi, + !! (phystat0 & BWN_RX_PHYST0_OFDM), + !! (phystat0 & BWN_RX_PHYST0_GAINCTL), + !! (phystat3 & BWN_RX_PHYST3_TRSTATE)); + break; + default: + /* XXX TODO: implement rssi for other PHYs */ + break; + } + + noise = mac->mac_stats.link_noise; + /* RX radio tap */ if (ieee80211_radiotap_active(ic)) bwn_rx_radiotap(mac, m, rxhdr, plcp, rate, rssi, noise); m_adj(m, -IEEE80211_CRC_LEN); - - rssi = rxhdr->phy.abg.rssi; /* XXX incorrect RSSI calculation? */ - noise = mac->mac_stats.link_noise; BWN_UNLOCK(sc); ni = ieee80211_find_rxnode(ic, wh); if (ni != NULL) { type = ieee80211_input(ni, m, rssi, noise); ieee80211_free_node(ni); } else type = ieee80211_input_all(ic, m, rssi, noise); BWN_LOCK(sc); return; drop: device_printf(sc->sc_dev, "%s: dropped\n", __func__); } static void bwn_dma_handle_txeof(struct bwn_mac *mac, const struct bwn_txstatus *status) { struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dma_ring *dr; struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *meta; struct bwn_softc *sc = mac->mac_sc; int slot; int retrycnt = 0; BWN_ASSERT_LOCKED(sc); dr = bwn_dma_parse_cookie(mac, status, status->cookie, &slot); if (dr == NULL) { device_printf(sc->sc_dev, "failed to parse cookie\n"); return; } KASSERT(dr->dr_tx, ("%s:%d: fail", __func__, __LINE__)); while (1) { KASSERT(slot >= 0 && slot < dr->dr_numslots, ("%s:%d: fail", __func__, __LINE__)); dr->getdesc(dr, slot, &desc, &meta); if (meta->mt_txtype == BWN_DMADESC_METATYPE_HEADER) bus_dmamap_unload(dr->dr_txring_dtag, meta->mt_dmap); else if (meta->mt_txtype == BWN_DMADESC_METATYPE_BODY) bus_dmamap_unload(dma->txbuf_dtag, meta->mt_dmap); if (meta->mt_islast) { KASSERT(meta->mt_m != NULL, ("%s:%d: fail", __func__, __LINE__)); /* Just count full frame retries for now */ retrycnt = status->framecnt - 1; ieee80211_ratectl_tx_complete(meta->mt_ni->ni_vap, meta->mt_ni, status->ack ? IEEE80211_RATECTL_TX_SUCCESS : IEEE80211_RATECTL_TX_FAILURE, &retrycnt, 0); ieee80211_tx_complete(meta->mt_ni, meta->mt_m, 0); meta->mt_ni = NULL; meta->mt_m = NULL; } else KASSERT(meta->mt_m == NULL, ("%s:%d: fail", __func__, __LINE__)); dr->dr_usedslot--; if (meta->mt_islast) break; slot = bwn_dma_nextslot(dr, slot); } sc->sc_watchdog_timer = 0; if (dr->dr_stop) { KASSERT(bwn_dma_freeslot(dr) >= BWN_TX_SLOTS_PER_FRAME, ("%s:%d: fail", __func__, __LINE__)); dr->dr_stop = 0; } } static void bwn_pio_handle_txeof(struct bwn_mac *mac, const struct bwn_txstatus *status) { struct bwn_pio_txqueue *tq; struct bwn_pio_txpkt *tp = NULL; struct bwn_softc *sc = mac->mac_sc; int retrycnt = 0; BWN_ASSERT_LOCKED(sc); tq = bwn_pio_parse_cookie(mac, status->cookie, &tp); if (tq == NULL) return; tq->tq_used -= roundup(tp->tp_m->m_pkthdr.len + BWN_HDRSIZE(mac), 4); tq->tq_free++; if (tp->tp_ni != NULL) { /* * Do any tx complete callback. Note this must * be done before releasing the node reference. */ /* Just count full frame retries for now */ retrycnt = status->framecnt - 1; ieee80211_ratectl_tx_complete(tp->tp_ni->ni_vap, tp->tp_ni, status->ack ? IEEE80211_RATECTL_TX_SUCCESS : IEEE80211_RATECTL_TX_FAILURE, &retrycnt, 0); if (tp->tp_m->m_flags & M_TXCB) ieee80211_process_callback(tp->tp_ni, tp->tp_m, 0); ieee80211_free_node(tp->tp_ni); tp->tp_ni = NULL; } m_freem(tp->tp_m); tp->tp_m = NULL; TAILQ_INSERT_TAIL(&tq->tq_pktlist, tp, tp_list); sc->sc_watchdog_timer = 0; } static void bwn_phy_txpower_check(struct bwn_mac *mac, uint32_t flags) { struct bwn_softc *sc = mac->mac_sc; struct bwn_phy *phy = &mac->mac_phy; struct ieee80211com *ic = &sc->sc_ic; unsigned long now; int result; BWN_GETTIME(now); if (!(flags & BWN_TXPWR_IGNORE_TIME) && ieee80211_time_before(now, phy->nexttime)) return; phy->nexttime = now + 2 * 1000; if (siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM && siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BU4306) return; if (phy->recalc_txpwr != NULL) { result = phy->recalc_txpwr(mac, (flags & BWN_TXPWR_IGNORE_TSSI) ? 1 : 0); if (result == BWN_TXPWR_RES_DONE) return; KASSERT(result == BWN_TXPWR_RES_NEED_ADJUST, ("%s: fail", __func__)); KASSERT(phy->set_txpwr != NULL, ("%s: fail", __func__)); ieee80211_runtask(ic, &mac->mac_txpower); } } static uint16_t bwn_pio_rx_read_2(struct bwn_pio_rxqueue *prq, uint16_t offset) { return (BWN_READ_2(prq->prq_mac, prq->prq_base + offset)); } static uint32_t bwn_pio_rx_read_4(struct bwn_pio_rxqueue *prq, uint16_t offset) { return (BWN_READ_4(prq->prq_mac, prq->prq_base + offset)); } static void bwn_pio_rx_write_2(struct bwn_pio_rxqueue *prq, uint16_t offset, uint16_t value) { BWN_WRITE_2(prq->prq_mac, prq->prq_base + offset, value); } static void bwn_pio_rx_write_4(struct bwn_pio_rxqueue *prq, uint16_t offset, uint32_t value) { BWN_WRITE_4(prq->prq_mac, prq->prq_base + offset, value); } static int bwn_ieeerate2hwrate(struct bwn_softc *sc, int rate) { switch (rate) { /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ case 12: return (BWN_OFDM_RATE_6MB); case 18: return (BWN_OFDM_RATE_9MB); case 24: return (BWN_OFDM_RATE_12MB); case 36: return (BWN_OFDM_RATE_18MB); case 48: return (BWN_OFDM_RATE_24MB); case 72: return (BWN_OFDM_RATE_36MB); case 96: return (BWN_OFDM_RATE_48MB); case 108: return (BWN_OFDM_RATE_54MB); /* CCK rates (NB: not IEEE std, device-specific) */ case 2: return (BWN_CCK_RATE_1MB); case 4: return (BWN_CCK_RATE_2MB); case 11: return (BWN_CCK_RATE_5MB); case 22: return (BWN_CCK_RATE_11MB); } device_printf(sc->sc_dev, "unsupported rate %d\n", rate); return (BWN_CCK_RATE_1MB); } static int bwn_set_txhdr(struct bwn_mac *mac, struct ieee80211_node *ni, struct mbuf *m, struct bwn_txhdr *txhdr, uint16_t cookie) { const struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; struct ieee80211_frame *wh; struct ieee80211_frame *protwh; struct ieee80211_frame_cts *cts; struct ieee80211_frame_rts *rts; const struct ieee80211_txparam *tp; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = &sc->sc_ic; struct mbuf *mprot; unsigned int len; uint32_t macctl = 0; int protdur, rts_rate, rts_rate_fb, ismcast, isshort, rix, type; uint16_t phyctl = 0; uint8_t rate, rate_fb; wh = mtod(m, struct ieee80211_frame *); memset(txhdr, 0, sizeof(*txhdr)); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; /* * Find TX rate */ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; if (type != IEEE80211_FC0_TYPE_DATA || (m->m_flags & M_EAPOL)) rate = rate_fb = tp->mgmtrate; else if (ismcast) rate = rate_fb = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = rate_fb = tp->ucastrate; else { /* XXX TODO: don't fall back to CCK rates for OFDM */ rix = ieee80211_ratectl_rate(ni, NULL, 0); rate = ni->ni_txrate; if (rix > 0) rate_fb = ni->ni_rates.rs_rates[rix - 1] & IEEE80211_RATE_VAL; else rate_fb = rate; } sc->sc_tx_rate = rate; /* Note: this maps the select ieee80211 rate to hardware rate */ rate = bwn_ieeerate2hwrate(sc, rate); rate_fb = bwn_ieeerate2hwrate(sc, rate_fb); txhdr->phyrate = (BWN_ISOFDMRATE(rate)) ? bwn_plcp_getofdm(rate) : bwn_plcp_getcck(rate); bcopy(wh->i_fc, txhdr->macfc, sizeof(txhdr->macfc)); bcopy(wh->i_addr1, txhdr->addr1, IEEE80211_ADDR_LEN); /* XXX rate/rate_fb is the hardware rate */ if ((rate_fb == rate) || (*(u_int16_t *)wh->i_dur & htole16(0x8000)) || (*(u_int16_t *)wh->i_dur == htole16(0))) txhdr->dur_fb = *(u_int16_t *)wh->i_dur; else txhdr->dur_fb = ieee80211_compute_duration(ic->ic_rt, m->m_pkthdr.len, rate, isshort); /* XXX TX encryption */ bwn_plcp_genhdr(BWN_ISOLDFMT(mac) ? (struct bwn_plcp4 *)(&txhdr->body.old.plcp) : (struct bwn_plcp4 *)(&txhdr->body.new.plcp), m->m_pkthdr.len + IEEE80211_CRC_LEN, rate); bwn_plcp_genhdr((struct bwn_plcp4 *)(&txhdr->plcp_fb), m->m_pkthdr.len + IEEE80211_CRC_LEN, rate_fb); txhdr->eftypes |= (BWN_ISOFDMRATE(rate_fb)) ? BWN_TX_EFT_FB_OFDM : BWN_TX_EFT_FB_CCK; txhdr->chan = phy->chan; phyctl |= (BWN_ISOFDMRATE(rate)) ? BWN_TX_PHY_ENC_OFDM : BWN_TX_PHY_ENC_CCK; /* XXX preamble? obey net80211 */ if (isshort && (rate == BWN_CCK_RATE_2MB || rate == BWN_CCK_RATE_5MB || rate == BWN_CCK_RATE_11MB)) phyctl |= BWN_TX_PHY_SHORTPRMBL; /* XXX TX antenna selection */ switch (bwn_antenna_sanitize(mac, 0)) { case 0: phyctl |= BWN_TX_PHY_ANT01AUTO; break; case 1: phyctl |= BWN_TX_PHY_ANT0; break; case 2: phyctl |= BWN_TX_PHY_ANT1; break; case 3: phyctl |= BWN_TX_PHY_ANT2; break; case 4: phyctl |= BWN_TX_PHY_ANT3; break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } if (!ismcast) macctl |= BWN_TX_MAC_ACK; macctl |= (BWN_TX_MAC_HWSEQ | BWN_TX_MAC_START_MSDU); if (!IEEE80211_IS_MULTICAST(wh->i_addr1) && m->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) macctl |= BWN_TX_MAC_LONGFRAME; if (ic->ic_flags & IEEE80211_F_USEPROT) { /* XXX RTS rate is always 1MB??? */ /* XXX TODO: don't fall back to CCK rates for OFDM */ rts_rate = BWN_CCK_RATE_1MB; rts_rate_fb = bwn_get_fbrate(rts_rate); /* XXX 'rate' here is hardware rate now, not the net80211 rate */ protdur = ieee80211_compute_duration(ic->ic_rt, m->m_pkthdr.len, rate, isshort) + + ieee80211_ack_duration(ic->ic_rt, rate, isshort); if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) { cts = (struct ieee80211_frame_cts *)(BWN_ISOLDFMT(mac) ? (txhdr->body.old.rts_frame) : (txhdr->body.new.rts_frame)); mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, protdur); KASSERT(mprot != NULL, ("failed to alloc mbuf\n")); bcopy(mtod(mprot, uint8_t *), (uint8_t *)cts, mprot->m_pkthdr.len); m_freem(mprot); macctl |= BWN_TX_MAC_SEND_CTSTOSELF; len = sizeof(struct ieee80211_frame_cts); } else { rts = (struct ieee80211_frame_rts *)(BWN_ISOLDFMT(mac) ? (txhdr->body.old.rts_frame) : (txhdr->body.new.rts_frame)); /* XXX rate/rate_fb is the hardware rate */ protdur += ieee80211_ack_duration(ic->ic_rt, rate, isshort); mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, protdur); KASSERT(mprot != NULL, ("failed to alloc mbuf\n")); bcopy(mtod(mprot, uint8_t *), (uint8_t *)rts, mprot->m_pkthdr.len); m_freem(mprot); macctl |= BWN_TX_MAC_SEND_RTSCTS; len = sizeof(struct ieee80211_frame_rts); } len += IEEE80211_CRC_LEN; bwn_plcp_genhdr((struct bwn_plcp4 *)((BWN_ISOLDFMT(mac)) ? &txhdr->body.old.rts_plcp : &txhdr->body.new.rts_plcp), len, rts_rate); bwn_plcp_genhdr((struct bwn_plcp4 *)&txhdr->rts_plcp_fb, len, rts_rate_fb); protwh = (struct ieee80211_frame *)(BWN_ISOLDFMT(mac) ? (&txhdr->body.old.rts_frame) : (&txhdr->body.new.rts_frame)); txhdr->rts_dur_fb = *(u_int16_t *)protwh->i_dur; if (BWN_ISOFDMRATE(rts_rate)) { txhdr->eftypes |= BWN_TX_EFT_RTS_OFDM; txhdr->phyrate_rts = bwn_plcp_getofdm(rts_rate); } else { txhdr->eftypes |= BWN_TX_EFT_RTS_CCK; txhdr->phyrate_rts = bwn_plcp_getcck(rts_rate); } txhdr->eftypes |= (BWN_ISOFDMRATE(rts_rate_fb)) ? BWN_TX_EFT_RTS_FBOFDM : BWN_TX_EFT_RTS_FBCCK; } if (BWN_ISOLDFMT(mac)) txhdr->body.old.cookie = htole16(cookie); else txhdr->body.new.cookie = htole16(cookie); txhdr->macctl = htole32(macctl); txhdr->phyctl = htole16(phyctl); /* * TX radio tap */ if (ieee80211_radiotap_active_vap(vap)) { sc->sc_tx_th.wt_flags = 0; if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_WEP; if (isshort && (rate == BWN_CCK_RATE_2MB || rate == BWN_CCK_RATE_5MB || rate == BWN_CCK_RATE_11MB)) sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; sc->sc_tx_th.wt_rate = rate; ieee80211_radiotap_tx(vap, m); } return (0); } static void bwn_plcp_genhdr(struct bwn_plcp4 *plcp, const uint16_t octets, const uint8_t rate) { uint32_t d, plen; uint8_t *raw = plcp->o.raw; if (BWN_ISOFDMRATE(rate)) { d = bwn_plcp_getofdm(rate); KASSERT(!(octets & 0xf000), ("%s:%d: fail", __func__, __LINE__)); d |= (octets << 5); plcp->o.data = htole32(d); } else { plen = octets * 16 / rate; if ((octets * 16 % rate) > 0) { plen++; if ((rate == BWN_CCK_RATE_11MB) && ((octets * 8 % 11) < 4)) { raw[1] = 0x84; } else raw[1] = 0x04; } else raw[1] = 0x04; plcp->o.data |= htole32(plen << 16); raw[0] = bwn_plcp_getcck(rate); } } static uint8_t bwn_antenna_sanitize(struct bwn_mac *mac, uint8_t n) { struct bwn_softc *sc = mac->mac_sc; uint8_t mask; if (n == 0) return (0); if (mac->mac_phy.gmode) mask = siba_sprom_get_ant_bg(sc->sc_dev); else mask = siba_sprom_get_ant_a(sc->sc_dev); if (!(mask & (1 << (n - 1)))) return (0); return (n); } /* * Return a fallback rate for the given rate. * * Note: Don't fall back from OFDM to CCK. */ static uint8_t bwn_get_fbrate(uint8_t bitrate) { switch (bitrate) { /* CCK */ case BWN_CCK_RATE_1MB: return (BWN_CCK_RATE_1MB); case BWN_CCK_RATE_2MB: return (BWN_CCK_RATE_1MB); case BWN_CCK_RATE_5MB: return (BWN_CCK_RATE_2MB); case BWN_CCK_RATE_11MB: return (BWN_CCK_RATE_5MB); /* OFDM */ case BWN_OFDM_RATE_6MB: return (BWN_OFDM_RATE_6MB); case BWN_OFDM_RATE_9MB: return (BWN_OFDM_RATE_6MB); case BWN_OFDM_RATE_12MB: return (BWN_OFDM_RATE_9MB); case BWN_OFDM_RATE_18MB: return (BWN_OFDM_RATE_12MB); case BWN_OFDM_RATE_24MB: return (BWN_OFDM_RATE_18MB); case BWN_OFDM_RATE_36MB: return (BWN_OFDM_RATE_24MB); case BWN_OFDM_RATE_48MB: return (BWN_OFDM_RATE_36MB); case BWN_OFDM_RATE_54MB: return (BWN_OFDM_RATE_48MB); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (0); } static uint32_t bwn_pio_write_multi_4(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint32_t ctl, const void *_data, int len) { struct bwn_softc *sc = mac->mac_sc; uint32_t value = 0; const uint8_t *data = _data; ctl |= BWN_PIO8_TXCTL_0_7 | BWN_PIO8_TXCTL_8_15 | BWN_PIO8_TXCTL_16_23 | BWN_PIO8_TXCTL_24_31; bwn_pio_write_4(mac, tq, BWN_PIO8_TXCTL, ctl); siba_write_multi_4(sc->sc_dev, data, (len & ~3), tq->tq_base + BWN_PIO8_TXDATA); if (len & 3) { ctl &= ~(BWN_PIO8_TXCTL_8_15 | BWN_PIO8_TXCTL_16_23 | BWN_PIO8_TXCTL_24_31); data = &(data[len - 1]); switch (len & 3) { case 3: ctl |= BWN_PIO8_TXCTL_16_23; value |= (uint32_t)(*data) << 16; data--; case 2: ctl |= BWN_PIO8_TXCTL_8_15; value |= (uint32_t)(*data) << 8; data--; case 1: value |= (uint32_t)(*data); } bwn_pio_write_4(mac, tq, BWN_PIO8_TXCTL, ctl); bwn_pio_write_4(mac, tq, BWN_PIO8_TXDATA, value); } return (ctl); } static void bwn_pio_write_4(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint16_t offset, uint32_t value) { BWN_WRITE_4(mac, tq->tq_base + offset, value); } static uint16_t bwn_pio_write_multi_2(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint16_t ctl, const void *_data, int len) { struct bwn_softc *sc = mac->mac_sc; const uint8_t *data = _data; ctl |= BWN_PIO_TXCTL_WRITELO | BWN_PIO_TXCTL_WRITEHI; BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl); siba_write_multi_2(sc->sc_dev, data, (len & ~1), tq->tq_base + BWN_PIO_TXDATA); if (len & 1) { ctl &= ~BWN_PIO_TXCTL_WRITEHI; BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl); BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXDATA, data[len - 1]); } return (ctl); } static uint16_t bwn_pio_write_mbuf_2(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint16_t ctl, struct mbuf *m0) { int i, j = 0; uint16_t data = 0; const uint8_t *buf; struct mbuf *m = m0; ctl |= BWN_PIO_TXCTL_WRITELO | BWN_PIO_TXCTL_WRITEHI; BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl); for (; m != NULL; m = m->m_next) { buf = mtod(m, const uint8_t *); for (i = 0; i < m->m_len; i++) { if (!((j++) % 2)) data |= buf[i]; else { data |= (buf[i] << 8); BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXDATA, data); data = 0; } } } if (m0->m_pkthdr.len % 2) { ctl &= ~BWN_PIO_TXCTL_WRITEHI; BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl); BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXDATA, data); } return (ctl); } static void bwn_set_slot_time(struct bwn_mac *mac, uint16_t time) { if (mac->mac_phy.type != BWN_PHYTYPE_G) return; BWN_WRITE_2(mac, 0x684, 510 + time); bwn_shm_write_2(mac, BWN_SHARED, 0x0010, time); } static struct bwn_dma_ring * bwn_dma_select(struct bwn_mac *mac, uint8_t prio) { if ((mac->mac_flags & BWN_MAC_FLAG_WME) == 0) return (mac->mac_method.dma.wme[WME_AC_BE]); switch (prio) { case 3: return (mac->mac_method.dma.wme[WME_AC_VO]); case 2: return (mac->mac_method.dma.wme[WME_AC_VI]); case 0: return (mac->mac_method.dma.wme[WME_AC_BE]); case 1: return (mac->mac_method.dma.wme[WME_AC_BK]); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (NULL); } static int bwn_dma_getslot(struct bwn_dma_ring *dr) { int slot; BWN_ASSERT_LOCKED(dr->dr_mac->mac_sc); KASSERT(dr->dr_tx, ("%s:%d: fail", __func__, __LINE__)); KASSERT(!(dr->dr_stop), ("%s:%d: fail", __func__, __LINE__)); KASSERT(bwn_dma_freeslot(dr) != 0, ("%s:%d: fail", __func__, __LINE__)); slot = bwn_dma_nextslot(dr, dr->dr_curslot); KASSERT(!(slot & ~0x0fff), ("%s:%d: fail", __func__, __LINE__)); dr->dr_curslot = slot; dr->dr_usedslot++; return (slot); } static struct bwn_pio_txqueue * bwn_pio_parse_cookie(struct bwn_mac *mac, uint16_t cookie, struct bwn_pio_txpkt **pack) { struct bwn_pio *pio = &mac->mac_method.pio; struct bwn_pio_txqueue *tq = NULL; unsigned int index; switch (cookie & 0xf000) { case 0x1000: tq = &pio->wme[WME_AC_BK]; break; case 0x2000: tq = &pio->wme[WME_AC_BE]; break; case 0x3000: tq = &pio->wme[WME_AC_VI]; break; case 0x4000: tq = &pio->wme[WME_AC_VO]; break; case 0x5000: tq = &pio->mcast; break; } KASSERT(tq != NULL, ("%s:%d: fail", __func__, __LINE__)); if (tq == NULL) return (NULL); index = (cookie & 0x0fff); KASSERT(index < N(tq->tq_pkts), ("%s:%d: fail", __func__, __LINE__)); if (index >= N(tq->tq_pkts)) return (NULL); *pack = &tq->tq_pkts[index]; KASSERT(*pack != NULL, ("%s:%d: fail", __func__, __LINE__)); return (tq); } static void bwn_txpwr(void *arg, int npending) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; BWN_LOCK(sc); if (mac && mac->mac_status >= BWN_MAC_STATUS_STARTED && mac->mac_phy.set_txpwr != NULL) mac->mac_phy.set_txpwr(mac); BWN_UNLOCK(sc); } static void bwn_task_15s(struct bwn_mac *mac) { uint16_t reg; if (mac->mac_fw.opensource) { reg = bwn_shm_read_2(mac, BWN_SCRATCH, BWN_WATCHDOG_REG); if (reg) { bwn_restart(mac, "fw watchdog"); return; } bwn_shm_write_2(mac, BWN_SCRATCH, BWN_WATCHDOG_REG, 1); } if (mac->mac_phy.task_15s) mac->mac_phy.task_15s(mac); mac->mac_phy.txerrors = BWN_TXERROR_MAX; } static void bwn_task_30s(struct bwn_mac *mac) { if (mac->mac_phy.type != BWN_PHYTYPE_G || mac->mac_noise.noi_running) return; mac->mac_noise.noi_running = 1; mac->mac_noise.noi_nsamples = 0; bwn_noise_gensample(mac); } static void bwn_task_60s(struct bwn_mac *mac) { if (mac->mac_phy.task_60s) mac->mac_phy.task_60s(mac); bwn_phy_txpower_check(mac, BWN_TXPWR_IGNORE_TIME); } static void bwn_tasks(void *arg) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; BWN_ASSERT_LOCKED(sc); if (mac->mac_status != BWN_MAC_STATUS_STARTED) return; if (mac->mac_task_state % 4 == 0) bwn_task_60s(mac); if (mac->mac_task_state % 2 == 0) bwn_task_30s(mac); bwn_task_15s(mac); mac->mac_task_state++; callout_reset(&sc->sc_task_ch, hz * 15, bwn_tasks, mac); } static int bwn_plcp_get_ofdmrate(struct bwn_mac *mac, struct bwn_plcp6 *plcp, uint8_t a) { struct bwn_softc *sc = mac->mac_sc; KASSERT(a == 0, ("not support APHY\n")); switch (plcp->o.raw[0] & 0xf) { case 0xb: return (BWN_OFDM_RATE_6MB); case 0xf: return (BWN_OFDM_RATE_9MB); case 0xa: return (BWN_OFDM_RATE_12MB); case 0xe: return (BWN_OFDM_RATE_18MB); case 0x9: return (BWN_OFDM_RATE_24MB); case 0xd: return (BWN_OFDM_RATE_36MB); case 0x8: return (BWN_OFDM_RATE_48MB); case 0xc: return (BWN_OFDM_RATE_54MB); } device_printf(sc->sc_dev, "incorrect OFDM rate %d\n", plcp->o.raw[0] & 0xf); return (-1); } static int bwn_plcp_get_cckrate(struct bwn_mac *mac, struct bwn_plcp6 *plcp) { struct bwn_softc *sc = mac->mac_sc; switch (plcp->o.raw[0]) { case 0x0a: return (BWN_CCK_RATE_1MB); case 0x14: return (BWN_CCK_RATE_2MB); case 0x37: return (BWN_CCK_RATE_5MB); case 0x6e: return (BWN_CCK_RATE_11MB); } device_printf(sc->sc_dev, "incorrect CCK rate %d\n", plcp->o.raw[0]); return (-1); } static void bwn_rx_radiotap(struct bwn_mac *mac, struct mbuf *m, const struct bwn_rxhdr4 *rxhdr, struct bwn_plcp6 *plcp, int rate, int rssi, int noise) { struct bwn_softc *sc = mac->mac_sc; const struct ieee80211_frame_min *wh; uint64_t tsf; uint16_t low_mactime_now; if (htole16(rxhdr->phy_status0) & BWN_RX_PHYST0_SHORTPRMBL) sc->sc_rx_th.wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; wh = mtod(m, const struct ieee80211_frame_min *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) sc->sc_rx_th.wr_flags |= IEEE80211_RADIOTAP_F_WEP; bwn_tsf_read(mac, &tsf); low_mactime_now = tsf; tsf = tsf & ~0xffffULL; tsf += le16toh(rxhdr->mac_time); if (low_mactime_now < le16toh(rxhdr->mac_time)) tsf -= 0x10000; sc->sc_rx_th.wr_tsf = tsf; sc->sc_rx_th.wr_rate = rate; sc->sc_rx_th.wr_antsignal = rssi; sc->sc_rx_th.wr_antnoise = noise; } static void bwn_tsf_read(struct bwn_mac *mac, uint64_t *tsf) { uint32_t low, high; KASSERT(siba_get_revid(mac->mac_sc->sc_dev) >= 3, ("%s:%d: fail", __func__, __LINE__)); low = BWN_READ_4(mac, BWN_REV3PLUS_TSF_LOW); high = BWN_READ_4(mac, BWN_REV3PLUS_TSF_HIGH); *tsf = high; *tsf <<= 32; *tsf |= low; } static int bwn_dma_attach(struct bwn_mac *mac) { struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_softc *sc = mac->mac_sc; bus_addr_t lowaddr = 0; int error; if (siba_get_type(sc->sc_dev) == SIBA_TYPE_PCMCIA || bwn_usedma == 0) return (0); KASSERT(siba_get_revid(sc->sc_dev) >= 5, ("%s: fail", __func__)); mac->mac_flags |= BWN_MAC_FLAG_DMA; dma->dmatype = bwn_dma_gettype(mac); if (dma->dmatype == BWN_DMA_30BIT) lowaddr = BWN_BUS_SPACE_MAXADDR_30BIT; else if (dma->dmatype == BWN_DMA_32BIT) lowaddr = BUS_SPACE_MAXADDR_32BIT; else lowaddr = BUS_SPACE_MAXADDR; /* * Create top level DMA tag */ error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), /* parent */ BWN_ALIGN, 0, /* alignment, bounds */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE, /* maxsize */ BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &dma->parent_dtag); if (error) { device_printf(sc->sc_dev, "can't create parent DMA tag\n"); return (error); } /* * Create TX/RX mbuf DMA tag */ error = bus_dma_tag_create(dma->parent_dtag, 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &dma->rxbuf_dtag); if (error) { device_printf(sc->sc_dev, "can't create mbuf DMA tag\n"); goto fail0; } error = bus_dma_tag_create(dma->parent_dtag, 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &dma->txbuf_dtag); if (error) { device_printf(sc->sc_dev, "can't create mbuf DMA tag\n"); goto fail1; } dma->wme[WME_AC_BK] = bwn_dma_ringsetup(mac, 0, 1, dma->dmatype); if (!dma->wme[WME_AC_BK]) goto fail2; dma->wme[WME_AC_BE] = bwn_dma_ringsetup(mac, 1, 1, dma->dmatype); if (!dma->wme[WME_AC_BE]) goto fail3; dma->wme[WME_AC_VI] = bwn_dma_ringsetup(mac, 2, 1, dma->dmatype); if (!dma->wme[WME_AC_VI]) goto fail4; dma->wme[WME_AC_VO] = bwn_dma_ringsetup(mac, 3, 1, dma->dmatype); if (!dma->wme[WME_AC_VO]) goto fail5; dma->mcast = bwn_dma_ringsetup(mac, 4, 1, dma->dmatype); if (!dma->mcast) goto fail6; dma->rx = bwn_dma_ringsetup(mac, 0, 0, dma->dmatype); if (!dma->rx) goto fail7; return (error); fail7: bwn_dma_ringfree(&dma->mcast); fail6: bwn_dma_ringfree(&dma->wme[WME_AC_VO]); fail5: bwn_dma_ringfree(&dma->wme[WME_AC_VI]); fail4: bwn_dma_ringfree(&dma->wme[WME_AC_BE]); fail3: bwn_dma_ringfree(&dma->wme[WME_AC_BK]); fail2: bus_dma_tag_destroy(dma->txbuf_dtag); fail1: bus_dma_tag_destroy(dma->rxbuf_dtag); fail0: bus_dma_tag_destroy(dma->parent_dtag); return (error); } static struct bwn_dma_ring * bwn_dma_parse_cookie(struct bwn_mac *mac, const struct bwn_txstatus *status, uint16_t cookie, int *slot) { struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dma_ring *dr; struct bwn_softc *sc = mac->mac_sc; BWN_ASSERT_LOCKED(mac->mac_sc); switch (cookie & 0xf000) { case 0x1000: dr = dma->wme[WME_AC_BK]; break; case 0x2000: dr = dma->wme[WME_AC_BE]; break; case 0x3000: dr = dma->wme[WME_AC_VI]; break; case 0x4000: dr = dma->wme[WME_AC_VO]; break; case 0x5000: dr = dma->mcast; break; default: dr = NULL; KASSERT(0 == 1, ("invalid cookie value %d", cookie & 0xf000)); } *slot = (cookie & 0x0fff); if (*slot < 0 || *slot >= dr->dr_numslots) { /* * XXX FIXME: sometimes H/W returns TX DONE events duplicately * that it occurs events which have same H/W sequence numbers. * When it's occurred just prints a WARNING msgs and ignores. */ KASSERT(status->seq == dma->lastseq, ("%s:%d: fail", __func__, __LINE__)); device_printf(sc->sc_dev, "out of slot ranges (0 < %d < %d)\n", *slot, dr->dr_numslots); return (NULL); } dma->lastseq = status->seq; return (dr); } static void bwn_dma_stop(struct bwn_mac *mac) { struct bwn_dma *dma; if ((mac->mac_flags & BWN_MAC_FLAG_DMA) == 0) return; dma = &mac->mac_method.dma; bwn_dma_ringstop(&dma->rx); bwn_dma_ringstop(&dma->wme[WME_AC_BK]); bwn_dma_ringstop(&dma->wme[WME_AC_BE]); bwn_dma_ringstop(&dma->wme[WME_AC_VI]); bwn_dma_ringstop(&dma->wme[WME_AC_VO]); bwn_dma_ringstop(&dma->mcast); } static void bwn_dma_ringstop(struct bwn_dma_ring **dr) { if (dr == NULL) return; bwn_dma_cleanup(*dr); } static void bwn_pio_stop(struct bwn_mac *mac) { struct bwn_pio *pio; if (mac->mac_flags & BWN_MAC_FLAG_DMA) return; pio = &mac->mac_method.pio; bwn_destroy_queue_tx(&pio->mcast); bwn_destroy_queue_tx(&pio->wme[WME_AC_VO]); bwn_destroy_queue_tx(&pio->wme[WME_AC_VI]); bwn_destroy_queue_tx(&pio->wme[WME_AC_BE]); bwn_destroy_queue_tx(&pio->wme[WME_AC_BK]); } static void bwn_led_attach(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; const uint8_t *led_act = NULL; uint16_t val[BWN_LED_MAX]; int i; sc->sc_led_idle = (2350 * hz) / 1000; sc->sc_led_blink = 1; for (i = 0; i < N(bwn_vendor_led_act); ++i) { if (siba_get_pci_subvendor(sc->sc_dev) == bwn_vendor_led_act[i].vid) { led_act = bwn_vendor_led_act[i].led_act; break; } } if (led_act == NULL) led_act = bwn_default_led_act; val[0] = siba_sprom_get_gpio0(sc->sc_dev); val[1] = siba_sprom_get_gpio1(sc->sc_dev); val[2] = siba_sprom_get_gpio2(sc->sc_dev); val[3] = siba_sprom_get_gpio3(sc->sc_dev); for (i = 0; i < BWN_LED_MAX; ++i) { struct bwn_led *led = &sc->sc_leds[i]; if (val[i] == 0xff) { led->led_act = led_act[i]; } else { if (val[i] & BWN_LED_ACT_LOW) led->led_flags |= BWN_LED_F_ACTLOW; led->led_act = val[i] & BWN_LED_ACT_MASK; } led->led_mask = (1 << i); if (led->led_act == BWN_LED_ACT_BLINK_SLOW || led->led_act == BWN_LED_ACT_BLINK_POLL || led->led_act == BWN_LED_ACT_BLINK) { led->led_flags |= BWN_LED_F_BLINK; if (led->led_act == BWN_LED_ACT_BLINK_POLL) led->led_flags |= BWN_LED_F_POLLABLE; else if (led->led_act == BWN_LED_ACT_BLINK_SLOW) led->led_flags |= BWN_LED_F_SLOW; if (sc->sc_blink_led == NULL) { sc->sc_blink_led = led; if (led->led_flags & BWN_LED_F_SLOW) BWN_LED_SLOWDOWN(sc->sc_led_idle); } } DPRINTF(sc, BWN_DEBUG_LED, "%dth led, act %d, lowact %d\n", i, led->led_act, led->led_flags & BWN_LED_F_ACTLOW); } callout_init_mtx(&sc->sc_led_blink_ch, &sc->sc_mtx, 0); } static __inline uint16_t bwn_led_onoff(const struct bwn_led *led, uint16_t val, int on) { if (led->led_flags & BWN_LED_F_ACTLOW) on = !on; if (on) val |= led->led_mask; else val &= ~led->led_mask; return val; } static void bwn_led_newstate(struct bwn_mac *mac, enum ieee80211_state nstate) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = &sc->sc_ic; uint16_t val; int i; if (nstate == IEEE80211_S_INIT) { callout_stop(&sc->sc_led_blink_ch); sc->sc_led_blinking = 0; } if ((sc->sc_flags & BWN_FLAG_RUNNING) == 0) return; val = BWN_READ_2(mac, BWN_GPIO_CONTROL); for (i = 0; i < BWN_LED_MAX; ++i) { struct bwn_led *led = &sc->sc_leds[i]; int on; if (led->led_act == BWN_LED_ACT_UNKN || led->led_act == BWN_LED_ACT_NULL) continue; if ((led->led_flags & BWN_LED_F_BLINK) && nstate != IEEE80211_S_INIT) continue; switch (led->led_act) { case BWN_LED_ACT_ON: /* Always on */ on = 1; break; case BWN_LED_ACT_OFF: /* Always off */ case BWN_LED_ACT_5GHZ: /* TODO: 11A */ on = 0; break; default: on = 1; switch (nstate) { case IEEE80211_S_INIT: on = 0; break; case IEEE80211_S_RUN: if (led->led_act == BWN_LED_ACT_11G && ic->ic_curmode != IEEE80211_MODE_11G) on = 0; break; default: if (led->led_act == BWN_LED_ACT_ASSOC) on = 0; break; } break; } val = bwn_led_onoff(led, val, on); } BWN_WRITE_2(mac, BWN_GPIO_CONTROL, val); } static void bwn_led_event(struct bwn_mac *mac, int event) { struct bwn_softc *sc = mac->mac_sc; struct bwn_led *led = sc->sc_blink_led; int rate; if (event == BWN_LED_EVENT_POLL) { if ((led->led_flags & BWN_LED_F_POLLABLE) == 0) return; if (ticks - sc->sc_led_ticks < sc->sc_led_idle) return; } sc->sc_led_ticks = ticks; if (sc->sc_led_blinking) return; switch (event) { case BWN_LED_EVENT_RX: rate = sc->sc_rx_rate; break; case BWN_LED_EVENT_TX: rate = sc->sc_tx_rate; break; case BWN_LED_EVENT_POLL: rate = 0; break; default: panic("unknown LED event %d\n", event); break; } bwn_led_blink_start(mac, bwn_led_duration[rate].on_dur, bwn_led_duration[rate].off_dur); } static void bwn_led_blink_start(struct bwn_mac *mac, int on_dur, int off_dur) { struct bwn_softc *sc = mac->mac_sc; struct bwn_led *led = sc->sc_blink_led; uint16_t val; val = BWN_READ_2(mac, BWN_GPIO_CONTROL); val = bwn_led_onoff(led, val, 1); BWN_WRITE_2(mac, BWN_GPIO_CONTROL, val); if (led->led_flags & BWN_LED_F_SLOW) { BWN_LED_SLOWDOWN(on_dur); BWN_LED_SLOWDOWN(off_dur); } sc->sc_led_blinking = 1; sc->sc_led_blink_offdur = off_dur; callout_reset(&sc->sc_led_blink_ch, on_dur, bwn_led_blink_next, mac); } static void bwn_led_blink_next(void *arg) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; uint16_t val; val = BWN_READ_2(mac, BWN_GPIO_CONTROL); val = bwn_led_onoff(sc->sc_blink_led, val, 0); BWN_WRITE_2(mac, BWN_GPIO_CONTROL, val); callout_reset(&sc->sc_led_blink_ch, sc->sc_led_blink_offdur, bwn_led_blink_end, mac); } static void bwn_led_blink_end(void *arg) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; sc->sc_led_blinking = 0; } static int bwn_suspend(device_t dev) { struct bwn_softc *sc = device_get_softc(dev); BWN_LOCK(sc); bwn_stop(sc); BWN_UNLOCK(sc); return (0); } static int bwn_resume(device_t dev) { struct bwn_softc *sc = device_get_softc(dev); int error = EDOOFUS; BWN_LOCK(sc); if (sc->sc_ic.ic_nrunning > 0) error = bwn_init(sc); BWN_UNLOCK(sc); if (error == 0) ieee80211_start_all(&sc->sc_ic); return (0); } static void bwn_rfswitch(void *arg) { struct bwn_softc *sc = arg; struct bwn_mac *mac = sc->sc_curmac; int cur = 0, prev = 0; KASSERT(mac->mac_status >= BWN_MAC_STATUS_STARTED, ("%s: invalid MAC status %d", __func__, mac->mac_status)); if (mac->mac_phy.rev >= 3 || mac->mac_phy.type == BWN_PHYTYPE_LP) { if (!(BWN_READ_4(mac, BWN_RF_HWENABLED_HI) & BWN_RF_HWENABLED_HI_MASK)) cur = 1; } else { if (BWN_READ_2(mac, BWN_RF_HWENABLED_LO) & BWN_RF_HWENABLED_LO_MASK) cur = 1; } if (mac->mac_flags & BWN_MAC_FLAG_RADIO_ON) prev = 1; if (cur != prev) { if (cur) mac->mac_flags |= BWN_MAC_FLAG_RADIO_ON; else mac->mac_flags &= ~BWN_MAC_FLAG_RADIO_ON; device_printf(sc->sc_dev, "status of RF switch is changed to %s\n", cur ? "ON" : "OFF"); if (cur != mac->mac_phy.rf_on) { if (cur) bwn_rf_turnon(mac); else bwn_rf_turnoff(mac); } } callout_schedule(&sc->sc_rfswitch_ch, hz); } static void bwn_sysctl_node(struct bwn_softc *sc) { device_t dev = sc->sc_dev; struct bwn_mac *mac; struct bwn_stats *stats; /* XXX assume that count of MAC is only 1. */ if ((mac = sc->sc_curmac) == NULL) return; stats = &mac->mac_stats; SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "linknoise", CTLFLAG_RW, &stats->rts, 0, "Noise level"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rts", CTLFLAG_RW, &stats->rts, 0, "RTS"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rtsfail", CTLFLAG_RW, &stats->rtsfail, 0, "RTS failed to send"); #ifdef BWN_DEBUG SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, 0, "Debug flags"); #endif } static device_method_t bwn_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bwn_probe), DEVMETHOD(device_attach, bwn_attach), DEVMETHOD(device_detach, bwn_detach), DEVMETHOD(device_suspend, bwn_suspend), DEVMETHOD(device_resume, bwn_resume), DEVMETHOD_END }; static driver_t bwn_driver = { "bwn", bwn_methods, sizeof(struct bwn_softc) }; static devclass_t bwn_devclass; DRIVER_MODULE(bwn, siba_bwn, bwn_driver, bwn_devclass, 0, 0); MODULE_DEPEND(bwn, siba_bwn, 1, 1, 1); MODULE_DEPEND(bwn, wlan, 1, 1, 1); /* 802.11 media layer */ MODULE_DEPEND(bwn, firmware, 1, 1, 1); /* firmware support */ MODULE_DEPEND(bwn, wlan_amrr, 1, 1, 1); MODULE_VERSION(bwn, 1); Index: user/ngie/detangle-rc/sys/dev/bwn/if_bwnvar.h =================================================================== --- user/ngie/detangle-rc/sys/dev/bwn/if_bwnvar.h (revision 299126) +++ user/ngie/detangle-rc/sys/dev/bwn/if_bwnvar.h (revision 299127) @@ -1,952 +1,959 @@ /*- * Copyright (c) 2009-2010 Weongyo Jeong * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. * * $FreeBSD$ */ #ifndef _IF_BWNVAR_H #define _IF_BWNVAR_H struct siba_dev_softc; struct bwn_softc; struct bwn_mac; #define N(a) (sizeof(a) / sizeof(a[0])) #define BWN_ALIGN 0x1000 #define BWN_BUS_SPACE_MAXADDR_30BIT 0x3fffffff #define BWN_RETRY_SHORT 7 #define BWN_RETRY_LONG 4 #define BWN_STAID_MAX 64 #define BWN_TXPWR_IGNORE_TIME (1 << 0) #define BWN_TXPWR_IGNORE_TSSI (1 << 1) #define BWN_HAS_TXMAG(phy) \ (((phy)->rev >= 2) && ((phy)->rf_ver == 0x2050) && \ ((phy)->rf_rev == 8)) #define BWN_HAS_LOOPBACK(phy) \ (((phy)->rev > 1) || ((phy)->gmode)) #define BWN_TXERROR_MAX 1000 #define BWN_GETTIME(v) do { \ struct timespec ts; \ nanouptime(&ts); \ (v) = ts.tv_nsec / 1000000 + ts.tv_sec * 1000; \ } while (0) #define BWN_ISOLDFMT(mac) ((mac)->mac_fw.rev <= 351) #define BWN_TSSI2DBM(num, den) \ ((int32_t)((num < 0) ? num / den : (num + den / 2) / den)) #define BWN_HDRSIZE(mac) \ ((BWN_ISOLDFMT(mac)) ? (100 + sizeof(struct bwn_plcp6)) : \ (104 + sizeof(struct bwn_plcp6))) #define BWN_PIO_COOKIE(tq, tp) \ ((uint16_t)((((uint16_t)tq->tq_index + 1) << 12) | tp->tp_index)) #define BWN_DMA_COOKIE(dr, slot) \ ((uint16_t)(((uint16_t)dr->dr_index + 1) << 12) | (uint16_t)slot) #define BWN_READ_2(mac, o) (siba_read_2(mac->mac_sc->sc_dev, o)) #define BWN_READ_4(mac, o) (siba_read_4(mac->mac_sc->sc_dev, o)) #define BWN_WRITE_2(mac, o, v) \ (siba_write_2(mac->mac_sc->sc_dev, o, v)) #define BWN_WRITE_4(mac, o, v) \ (siba_write_4(mac->mac_sc->sc_dev, o, v)) #define BWN_PIO_TXQOFFSET(mac) \ ((siba_get_revid(mac->mac_sc->sc_dev) >= 11) ? 0x18 : 0) #define BWN_PIO_RXQOFFSET(mac) \ ((siba_get_revid(mac->mac_sc->sc_dev) >= 11) ? 0x38 : 8) #define BWN_SEC_NEWAPI(mac) (mac->mac_fw.rev >= 351) #define BWN_SEC_KEY2FW(mac, idx) \ (BWN_SEC_NEWAPI(mac) ? idx : ((idx >= 4) ? idx - 4 : idx)) #define BWN_RF_READ(mac, r) (mac->mac_phy.rf_read(mac, r)) #define BWN_RF_WRITE(mac, r, v) (mac->mac_phy.rf_write(mac, r, v)) #define BWN_RF_MASK(mac, o, m) \ BWN_RF_WRITE(mac, o, BWN_RF_READ(mac, o) & m) #define BWN_RF_SETMASK(mac, offset, mask, set) \ BWN_RF_WRITE(mac, offset, (BWN_RF_READ(mac, offset) & mask) | set) #define BWN_RF_SET(mac, offset, set) \ BWN_RF_WRITE(mac, offset, BWN_RF_READ(mac, offset) | set) #define BWN_PHY_READ(mac, r) (mac->mac_phy.phy_read(mac, r)) #define BWN_PHY_WRITE(mac, r, v) \ (mac->mac_phy.phy_write(mac, r, v)) #define BWN_PHY_SET(mac, offset, set) do { \ if (mac->mac_phy.phy_maskset != NULL) { \ KASSERT(mac->mac_status < BWN_MAC_STATUS_INITED || \ mac->mac_suspended > 0, \ ("dont access PHY or RF registers after turning on MAC")); \ mac->mac_phy.phy_maskset(mac, offset, 0xffff, set); \ } else \ BWN_PHY_WRITE(mac, offset, \ BWN_PHY_READ(mac, offset) | (set)); \ } while (0) #define BWN_PHY_SETMASK(mac, offset, mask, set) do { \ if (mac->mac_phy.phy_maskset != NULL) { \ KASSERT(mac->mac_status < BWN_MAC_STATUS_INITED || \ mac->mac_suspended > 0, \ ("dont access PHY or RF registers after turning on MAC")); \ mac->mac_phy.phy_maskset(mac, offset, mask, set); \ } else \ BWN_PHY_WRITE(mac, offset, \ (BWN_PHY_READ(mac, offset) & (mask)) | (set)); \ } while (0) #define BWN_PHY_MASK(mac, offset, mask) do { \ if (mac->mac_phy.phy_maskset != NULL) { \ KASSERT(mac->mac_status < BWN_MAC_STATUS_INITED || \ mac->mac_suspended > 0, \ ("dont access PHY or RF registers after turning on MAC")); \ mac->mac_phy.phy_maskset(mac, offset, mask, 0); \ } else \ BWN_PHY_WRITE(mac, offset, \ BWN_PHY_READ(mac, offset) & mask); \ } while (0) #define BWN_PHY_COPY(mac, dst, src) do { \ KASSERT(mac->mac_status < BWN_MAC_STATUS_INITED || \ mac->mac_suspended > 0, \ ("dont access PHY or RF registers after turning on MAC")); \ BWN_PHY_WRITE(mac, dst, BWN_PHY_READ(mac, src)); \ } while (0) #define BWN_LO_CALIB_EXPIRE (1000 * (30 - 2)) #define BWN_LO_PWRVEC_EXPIRE (1000 * (30 - 2)) #define BWN_LO_TXCTL_EXPIRE (1000 * (180 - 4)) #define BWN_DMA_BIT_MASK(n) (((n) == 64) ? ~0ULL : ((1ULL<<(n))-1)) #define BWN_LPD(L, P, D) (((L) << 2) | ((P) << 1) | ((D) << 0)) #define BWN_BITREV4(tmp) (BWN_BITREV8(tmp) >> 4) #define BWN_BITREV8(byte) (bwn_bitrev_table[byte]) #define BWN_BBATTCMP(a, b) ((a)->att == (b)->att) #define BWN_RFATTCMP(a, b) \ (((a)->att == (b)->att) && ((a)->padmix == (b)->padmix)) #define BWN_PIO_WRITE_2(mac, tq, offset, value) \ BWN_WRITE_2(mac, (tq)->tq_base + offset, value) #define BWN_PIO_READ_4(mac, tq, offset) \ BWN_READ_4(mac, tq->tq_base + offset) #define BWN_ISCCKRATE(rate) \ (rate == BWN_CCK_RATE_1MB || rate == BWN_CCK_RATE_2MB || \ rate == BWN_CCK_RATE_5MB || rate == BWN_CCK_RATE_11MB) #define BWN_ISOFDMRATE(rate) (!BWN_ISCCKRATE(rate)) #define BWN_BARRIER(mac, flags) siba_barrier(mac->mac_sc->sc_dev, flags) #define BWN_DMA_READ(dr, offset) \ (BWN_READ_4(dr->dr_mac, dr->dr_base + offset)) #define BWN_DMA_WRITE(dr, offset, value) \ (BWN_WRITE_4(dr->dr_mac, dr->dr_base + offset, value)) struct bwn_rate { uint16_t rateid; uint32_t flags; }; #define BWN_ANT0 0 #define BWN_ANT1 1 #define BWN_ANTAUTO0 2 #define BWN_ANTAUTO1 3 #define BWN_ANT2 4 #define BWN_ANT3 8 #define BWN_ANTAUTO BWN_ANTAUTO0 #define BWN_ANT_DEFAULT BWN_ANTAUTO #define BWN_TX_SLOTS_PER_FRAME 2 struct bwn_channel { unsigned freq; unsigned ieee; unsigned maxTxPow; }; struct bwn_channelinfo { struct bwn_channel channels[IEEE80211_CHAN_MAX]; unsigned nchannels; }; struct bwn_bbatt { uint8_t att; }; struct bwn_bbatt_list { const struct bwn_bbatt *array; uint8_t len; uint8_t min; uint8_t max; }; struct bwn_rfatt { uint8_t att; int padmix; }; struct bwn_rfatt_list { const struct bwn_rfatt *array; uint8_t len; uint8_t min; uint8_t max; }; #define BWN_DC_LT_SIZE 32 struct bwn_loctl { int8_t i; int8_t q; }; struct bwn_lo_calib { struct bwn_bbatt bbatt; struct bwn_rfatt rfatt; struct bwn_loctl ctl; unsigned long calib_time; TAILQ_ENTRY(bwn_lo_calib) list; }; struct bwn_rxhdr4 { uint16_t frame_len; uint8_t pad1[2]; uint16_t phy_status0; union { struct { uint8_t rssi; uint8_t sig_qual; } __packed abg; struct { int8_t power0; int8_t power1; } __packed n; } __packed phy; uint16_t phy_status2; uint16_t phy_status3; uint32_t mac_status; uint16_t mac_time; uint16_t channel; } __packed; struct bwn_txstatus { uint16_t cookie; uint16_t seq; uint8_t phy_stat; uint8_t framecnt; uint8_t rtscnt; uint8_t sreason; uint8_t pm; uint8_t im; uint8_t ampdu; uint8_t ack; }; #define BWN_TXCTL_PA3DB 0x40 #define BWN_TXCTL_PA2DB 0x20 #define BWN_TXCTL_TXMIX 0x10 struct bwn_txpwr_loctl { struct bwn_rfatt_list rfatt; struct bwn_bbatt_list bbatt; uint16_t dc_lt[BWN_DC_LT_SIZE]; TAILQ_HEAD(, bwn_lo_calib) calib_list; unsigned long pwr_vec_read_time; unsigned long txctl_measured_time; uint8_t tx_bias; uint8_t tx_magn; uint64_t power_vector; }; #define BWN_OFDMTAB_DIR_UNKNOWN 0 #define BWN_OFDMTAB_DIR_READ 1 #define BWN_OFDMTAB_DIR_WRITE 2 struct bwn_phy_g { unsigned pg_flags; #define BWN_PHY_G_FLAG_TSSITABLE_ALLOC (1 << 0) #define BWN_PHY_G_FLAG_RADIOCTX_VALID (1 << 1) int pg_aci_enable; int pg_aci_wlan_automatic; int pg_aci_hw_rssi; int pg_rf_on; uint16_t pg_radioctx_over; uint16_t pg_radioctx_overval; uint16_t pg_minlowsig[2]; uint16_t pg_minlowsigpos[2]; int8_t *pg_tssi2dbm; int pg_idletssi; int pg_curtssi; uint8_t pg_avgtssi; struct bwn_bbatt pg_bbatt; struct bwn_rfatt pg_rfatt; uint8_t pg_txctl; int pg_bbatt_delta; int pg_rfatt_delta; struct bwn_txpwr_loctl pg_loctl; int16_t pg_max_lb_gain; int16_t pg_trsw_rx_gain; int16_t pg_lna_lod_gain; int16_t pg_lna_gain; int16_t pg_pga_gain; int pg_immode; #define BWN_INTERFSTACK_SIZE 26 uint32_t pg_interfstack[BWN_INTERFSTACK_SIZE]; int16_t pg_nrssi[2]; int32_t pg_nrssi_slope; int8_t pg_nrssi_lt[64]; uint16_t pg_lofcal; uint16_t pg_initval; uint16_t pg_ofdmtab_addr; unsigned pg_ofdmtab_dir; }; #define BWN_IMMODE_NONE 0 #define BWN_IMMODE_NONWLAN 1 #define BWN_IMMODE_MANUAL 2 #define BWN_IMMODE_AUTO 3 #define BWN_TXPWR_RES_NEED_ADJUST 0 #define BWN_TXPWR_RES_DONE 1 #define BWN_PHYLP_TXPCTL_UNKNOWN 0 #define BWN_PHYLP_TXPCTL_OFF 1 #define BWN_PHYLP_TXPCTL_ON_SW 2 #define BWN_PHYLP_TXPCTL_ON_HW 3 struct bwn_phy_lp { uint8_t plp_chan; uint8_t plp_chanfullcal; int32_t plp_antenna; uint8_t plp_txpctlmode; uint8_t plp_txisoband_h; uint8_t plp_txisoband_m; uint8_t plp_txisoband_l; uint8_t plp_rxpwroffset; int8_t plp_txpwridx; uint16_t plp_tssiidx; uint16_t plp_tssinpt; uint8_t plp_rssivf; uint8_t plp_rssivc; uint8_t plp_rssigs; uint8_t plp_rccap; uint8_t plp_bxarch; uint8_t plp_crsusr_off; uint8_t plp_crssys_off; uint32_t plp_div; int32_t plp_tonefreq; uint16_t plp_digfilt[9]; }; /* for LP */ struct bwn_txgain { uint16_t tg_gm; uint16_t tg_pga; uint16_t tg_pad; uint16_t tg_dac; }; struct bwn_rxcompco { uint8_t rc_chan; int8_t rc_c1; int8_t rc_c0; }; struct bwn_phy_lp_iq_est { uint32_t ie_iqprod; uint32_t ie_ipwr; uint32_t ie_qpwr; }; struct bwn_txgain_entry { uint8_t te_gm; uint8_t te_pga; uint8_t te_pad; uint8_t te_dac; uint8_t te_bbmult; }; /* only for LP PHY */ struct bwn_stxtable { uint16_t st_phyoffset; uint16_t st_physhift; uint16_t st_rfaddr; uint16_t st_rfshift; uint16_t st_mask; }; struct bwn_b206x_chan { uint8_t bc_chan; uint16_t bc_freq; const uint8_t *bc_data; }; struct bwn_b206x_rfinit_entry { uint16_t br_offset; uint16_t br_valuea; uint16_t br_valueg; uint8_t br_flags; }; struct bwn_phy { uint8_t type; uint8_t rev; uint8_t analog; int supports_2ghz; int supports_5ghz; int gmode; struct bwn_phy_g phy_g; struct bwn_phy_lp phy_lp; uint16_t rf_manuf; uint16_t rf_ver; uint8_t rf_rev; int rf_on; int txpower; int hwpctl; unsigned long nexttime; unsigned int chan; int txerrors; int (*attach)(struct bwn_mac *); void (*detach)(struct bwn_mac *); int (*prepare_hw)(struct bwn_mac *); void (*init_pre)(struct bwn_mac *); int (*init)(struct bwn_mac *); void (*exit)(struct bwn_mac *); uint16_t (*phy_read)(struct bwn_mac *, uint16_t); void (*phy_write)(struct bwn_mac *, uint16_t, uint16_t); void (*phy_maskset)(struct bwn_mac *, uint16_t, uint16_t, uint16_t); uint16_t (*rf_read)(struct bwn_mac *, uint16_t); void (*rf_write)(struct bwn_mac *, uint16_t, uint16_t); int (*use_hwpctl)(struct bwn_mac *); void (*rf_onoff)(struct bwn_mac *, int); void (*switch_analog)(struct bwn_mac *, int); int (*switch_channel)(struct bwn_mac *, unsigned int); uint32_t (*get_default_chan)(struct bwn_mac *); void (*set_antenna)(struct bwn_mac *, int); int (*set_im)(struct bwn_mac *, int); int (*recalc_txpwr)(struct bwn_mac *, int); void (*set_txpwr)(struct bwn_mac *); void (*task_15s)(struct bwn_mac *); void (*task_60s)(struct bwn_mac *); }; struct bwn_chan_band { uint32_t flags; uint8_t nchan; #define BWN_MAX_CHAN_PER_BAND 14 uint8_t chan[BWN_MAX_CHAN_PER_BAND]; }; #define BWN_NR_WMEPARAMS 16 enum { BWN_WMEPARAM_TXOP = 0, BWN_WMEPARAM_CWMIN, BWN_WMEPARAM_CWMAX, BWN_WMEPARAM_CWCUR, BWN_WMEPARAM_AIFS, BWN_WMEPARAM_BSLOTS, BWN_WMEPARAM_REGGAP, BWN_WMEPARAM_STATUS, }; #define BWN_WME_PARAMS(queue) \ (BWN_SHARED_EDCFQ + (BWN_NR_WMEPARAMS * sizeof(uint16_t) * (queue))) #define BWN_WME_BACKGROUND BWN_WME_PARAMS(0) #define BWN_WME_BESTEFFORT BWN_WME_PARAMS(1) #define BWN_WME_VIDEO BWN_WME_PARAMS(2) #define BWN_WME_VOICE BWN_WME_PARAMS(3) /* * Radio capture format. */ #define BWN_RX_RADIOTAP_PRESENT ( \ (1 << IEEE80211_RADIOTAP_TSFT) | \ (1 << IEEE80211_RADIOTAP_FLAGS) | \ (1 << IEEE80211_RADIOTAP_RATE) | \ (1 << IEEE80211_RADIOTAP_CHANNEL) | \ (1 << IEEE80211_RADIOTAP_ANTENNA) | \ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ 0) struct bwn_rx_radiotap_header { struct ieee80211_radiotap_header wr_ihdr; uint64_t wr_tsf; u_int8_t wr_flags; u_int8_t wr_rate; u_int16_t wr_chan_freq; u_int16_t wr_chan_flags; int8_t wr_antsignal; int8_t wr_antnoise; u_int8_t wr_antenna; }; #define BWN_TX_RADIOTAP_PRESENT ( \ (1 << IEEE80211_RADIOTAP_FLAGS) | \ (1 << IEEE80211_RADIOTAP_RATE) | \ (1 << IEEE80211_RADIOTAP_CHANNEL) | \ (1 << IEEE80211_RADIOTAP_DBM_TX_POWER) | \ (1 << IEEE80211_RADIOTAP_ANTENNA) | \ 0) struct bwn_tx_radiotap_header { struct ieee80211_radiotap_header wt_ihdr; u_int8_t wt_flags; u_int8_t wt_rate; u_int16_t wt_chan_freq; u_int16_t wt_chan_flags; u_int8_t wt_txpower; u_int8_t wt_antenna; }; struct bwn_stats { int32_t rtsfail; int32_t rts; int32_t link_noise; }; /* Noise Calculation (Link Quality) */ struct bwn_noise { uint8_t noi_running; uint8_t noi_nsamples; int8_t noi_samples[8][4]; }; #define BWN_DMA_30BIT 30 #define BWN_DMA_32BIT 32 #define BWN_DMA_64BIT 64 struct bwn_dmadesc_meta { bus_dmamap_t mt_dmap; bus_addr_t mt_paddr; struct mbuf *mt_m; struct ieee80211_node *mt_ni; uint8_t mt_txtype; #define BWN_DMADESC_METATYPE_HEADER 0 #define BWN_DMADESC_METATYPE_BODY 1 uint8_t mt_islast; }; #define BWN_DMAINTR_FATALMASK \ ((1 << 10) | (1 << 11) | (1 << 12) | (1 << 14) | (1 << 15)) #define BWN_DMAINTR_NONFATALMASK (1 << 13) #define BWN_DMAINTR_RX_DONE (1 << 16) #define BWN_DMA32_DCTL_BYTECNT 0x00001fff #define BWN_DMA32_DCTL_ADDREXT_MASK 0x00030000 #define BWN_DMA32_DCTL_ADDREXT_SHIFT 16 #define BWN_DMA32_DCTL_DTABLEEND 0x10000000 #define BWN_DMA32_DCTL_IRQ 0x20000000 #define BWN_DMA32_DCTL_FRAMEEND 0x40000000 #define BWN_DMA32_DCTL_FRAMESTART 0x80000000 struct bwn_dmadesc32 { uint32_t control; uint32_t address; } __packed; #define BWN_DMA64_DCTL0_DTABLEEND 0x10000000 #define BWN_DMA64_DCTL0_IRQ 0x20000000 #define BWN_DMA64_DCTL0_FRAMEEND 0x40000000 #define BWN_DMA64_DCTL0_FRAMESTART 0x80000000 #define BWN_DMA64_DCTL1_BYTECNT 0x00001fff #define BWN_DMA64_DCTL1_ADDREXT_MASK 0x00030000 #define BWN_DMA64_DCTL1_ADDREXT_SHIFT 16 struct bwn_dmadesc64 { uint32_t control0; uint32_t control1; uint32_t address_low; uint32_t address_high; } __packed; struct bwn_dmadesc_generic { union { struct bwn_dmadesc32 dma32; struct bwn_dmadesc64 dma64; } __packed dma; } __packed; struct bwn_dma_ring; struct bwn_dma_ring { struct bwn_mac *dr_mac; const struct bwn_dma_ops *dr_ops; struct bwn_dmadesc_meta *dr_meta; void *dr_txhdr_cache; bus_dma_tag_t dr_ring_dtag; bus_dma_tag_t dr_txring_dtag; bus_dmamap_t dr_spare_dmap; /* only for RX */ bus_dmamap_t dr_ring_dmap; bus_addr_t dr_txring_paddr; void *dr_ring_descbase; bus_addr_t dr_ring_dmabase; int dr_numslots; int dr_usedslot; int dr_curslot; uint32_t dr_frameoffset; uint16_t dr_rx_bufsize; uint16_t dr_base; int dr_index; uint8_t dr_tx; uint8_t dr_stop; int dr_type; void (*getdesc)(struct bwn_dma_ring *, int, struct bwn_dmadesc_generic **, struct bwn_dmadesc_meta **); void (*setdesc)(struct bwn_dma_ring *, struct bwn_dmadesc_generic *, bus_addr_t, uint16_t, int, int, int); void (*start_transfer)(struct bwn_dma_ring *, int); void (*suspend)(struct bwn_dma_ring *); void (*resume)(struct bwn_dma_ring *); int (*get_curslot)(struct bwn_dma_ring *); void (*set_curslot)(struct bwn_dma_ring *, int); }; struct bwn_dma { int dmatype; bus_dma_tag_t parent_dtag; bus_dma_tag_t rxbuf_dtag; bus_dma_tag_t txbuf_dtag; struct bwn_dma_ring *wme[5]; struct bwn_dma_ring *mcast; struct bwn_dma_ring *rx; uint64_t lastseq; /* XXX FIXME */ }; struct bwn_pio_rxqueue { struct bwn_mac *prq_mac; uint16_t prq_base; uint8_t prq_rev; }; struct bwn_pio_txqueue; struct bwn_pio_txpkt { struct bwn_pio_txqueue *tp_queue; struct ieee80211_node *tp_ni; struct mbuf *tp_m; uint8_t tp_index; TAILQ_ENTRY(bwn_pio_txpkt) tp_list; }; #define BWN_PIO_MAX_TXPACKETS 32 struct bwn_pio_txqueue { uint16_t tq_base; uint16_t tq_size; uint16_t tq_used; uint16_t tq_free; uint8_t tq_index; struct bwn_pio_txpkt tq_pkts[BWN_PIO_MAX_TXPACKETS]; TAILQ_HEAD(, bwn_pio_txpkt) tq_pktlist; }; struct bwn_pio { struct bwn_pio_txqueue wme[5]; struct bwn_pio_txqueue mcast; struct bwn_pio_rxqueue rx; }; struct bwn_plcp4 { union { uint32_t data; uint8_t raw[4]; } __packed o; } __packed; struct bwn_plcp6 { union { uint32_t data; uint8_t raw[6]; } __packed o; } __packed; struct bwn_txhdr { uint32_t macctl; uint8_t macfc[2]; uint16_t tx_festime; uint16_t phyctl; uint16_t phyctl_1; uint16_t phyctl_1fb; uint16_t phyctl_1rts; uint16_t phyctl_1rtsfb; uint8_t phyrate; uint8_t phyrate_rts; uint8_t eftypes; /* extra frame types */ uint8_t chan; uint8_t iv[16]; uint8_t addr1[IEEE80211_ADDR_LEN]; uint16_t tx_festime_fb; struct bwn_plcp6 rts_plcp_fb; uint16_t rts_dur_fb; struct bwn_plcp6 plcp_fb; uint16_t dur_fb; uint16_t mimo_modelen; uint16_t mimo_ratelen_fb; uint32_t timeout; union { /* format <= r351 */ struct { uint8_t pad0[2]; uint16_t cookie; uint16_t tx_status; struct bwn_plcp6 rts_plcp; uint8_t rts_frame[16]; uint8_t pad1[2]; struct bwn_plcp6 plcp; } __packed old; /* format > r410 */ struct { uint16_t mimo_antenna; uint16_t preload_size; uint8_t pad0[2]; uint16_t cookie; uint16_t tx_status; struct bwn_plcp6 rts_plcp; uint8_t rts_frame[16]; uint8_t pad1[2]; struct bwn_plcp6 plcp; } __packed new; } __packed body; } __packed; #define BWN_FWTYPE_UCODE 'u' #define BWN_FWTYPE_PCM 'p' #define BWN_FWTYPE_IV 'i' struct bwn_fwhdr { uint8_t type; uint8_t ver; uint8_t pad[2]; uint32_t size; } __packed; #define BWN_FWINITVALS_OFFSET_MASK 0x7fff #define BWN_FWINITVALS_32BIT 0x8000 struct bwn_fwinitvals { uint16_t offset_size; union { uint16_t d16; uint32_t d32; } __packed data; } __packed; +enum bwn_fw_hdr_format { + BWN_FW_HDR_598, + BWN_FW_HDR_410, + BWN_FW_HDR_351, +}; + enum bwn_fwtype { BWN_FWTYPE_DEFAULT, BWN_FWTYPE_OPENSOURCE, BWN_NR_FWTYPES, }; struct bwn_fwfile { const char *filename; const struct firmware *fw; enum bwn_fwtype type; }; struct bwn_key { void *keyconf; uint8_t algorithm; }; struct bwn_fw { struct bwn_fwfile ucode; struct bwn_fwfile pcm; struct bwn_fwfile initvals; struct bwn_fwfile initvals_band; + enum bwn_fw_hdr_format fw_hdr_format; uint16_t rev; uint16_t patch; uint8_t opensource; uint8_t no_pcmfile; }; struct bwn_lo_g_sm { int curstate; int nmeasure; int multipler; uint16_t feedth; struct bwn_loctl loctl; }; struct bwn_lo_g_value { uint8_t old_channel; uint16_t phy_lomask; uint16_t phy_extg; uint16_t phy_dacctl_hwpctl; uint16_t phy_dacctl; uint16_t phy_hpwr_tssictl; uint16_t phy_analogover; uint16_t phy_analogoverval; uint16_t phy_rfover; uint16_t phy_rfoverval; uint16_t phy_classctl; uint16_t phy_crs0; uint16_t phy_pgactl; uint16_t phy_syncctl; uint16_t phy_cck0; uint16_t phy_cck1; uint16_t phy_cck2; uint16_t phy_cck3; uint16_t phy_cck4; uint16_t reg0; uint16_t reg1; uint16_t rf0; uint16_t rf1; uint16_t rf2; }; #define BWN_LED_MAX 4 #define BWN_LED_EVENT_NONE -1 #define BWN_LED_EVENT_POLL 0 #define BWN_LED_EVENT_TX 1 #define BWN_LED_EVENT_RX 2 #define BWN_LED_SLOWDOWN(dur) (dur) = (((dur) * 3) / 2) struct bwn_led { uint8_t led_flags; /* BWN_LED_F_ */ uint8_t led_act; /* BWN_LED_ACT_ */ uint8_t led_mask; }; #define BWN_LED_F_ACTLOW 0x1 #define BWN_LED_F_BLINK 0x2 #define BWN_LED_F_POLLABLE 0x4 #define BWN_LED_F_SLOW 0x8 struct bwn_mac { struct bwn_softc *mac_sc; unsigned mac_status; #define BWN_MAC_STATUS_UNINIT 0 #define BWN_MAC_STATUS_INITED 1 #define BWN_MAC_STATUS_STARTED 2 unsigned mac_flags; /* use "Bad Frames Preemption" */ #define BWN_MAC_FLAG_BADFRAME_PREEMP (1 << 0) #define BWN_MAC_FLAG_DFQVALID (1 << 1) #define BWN_MAC_FLAG_RADIO_ON (1 << 2) #define BWN_MAC_FLAG_DMA (1 << 3) #define BWN_MAC_FLAG_WME (1 << 4) #define BWN_MAC_FLAG_HWCRYPTO (1 << 5) struct resource_spec *mac_intr_spec; #define BWN_MSI_MESSAGES 1 struct resource *mac_res_irq[BWN_MSI_MESSAGES]; void *mac_intrhand[BWN_MSI_MESSAGES]; int mac_msi; struct bwn_noise mac_noise; struct bwn_phy mac_phy; struct bwn_stats mac_stats; uint32_t mac_reason_intr; uint32_t mac_reason[6]; uint32_t mac_intr_mask; int mac_suspended; struct bwn_fw mac_fw; union { struct bwn_dma dma; struct bwn_pio pio; } mac_method; uint16_t mac_ktp; /* Key table pointer */ uint8_t mac_max_nr_keys; struct bwn_key mac_key[58]; unsigned int mac_task_state; struct task mac_intrtask; struct task mac_hwreset; struct task mac_txpower; TAILQ_ENTRY(bwn_mac) mac_list; }; /* * Driver-specific vap state. */ struct bwn_vap { struct ieee80211vap bv_vap; /* base class */ int (*bv_newstate)(struct ieee80211vap *, enum ieee80211_state, int); }; #define BWN_VAP(vap) ((struct bwn_vap *)(vap)) #define BWN_VAP_CONST(vap) ((const struct mwl_vap *)(vap)) struct bwn_softc { device_t sc_dev; struct mtx sc_mtx; struct ieee80211com sc_ic; struct mbufq sc_snd; unsigned sc_flags; #define BWN_FLAG_ATTACHED (1 << 0) #define BWN_FLAG_INVALID (1 << 1) #define BWN_FLAG_NEED_BEACON_TP (1 << 2) #define BWN_FLAG_RUNNING (1 << 3) unsigned sc_debug; struct bwn_mac *sc_curmac; TAILQ_HEAD(, bwn_mac) sc_maclist; uint8_t sc_bssid[IEEE80211_ADDR_LEN]; unsigned int sc_filters; uint8_t sc_beacons[2]; uint8_t sc_rf_enabled; struct wmeParams sc_wmeParams[4]; struct callout sc_rfswitch_ch; /* for laptop */ struct callout sc_task_ch; struct callout sc_watchdog_ch; int sc_watchdog_timer; struct taskqueue *sc_tq; /* private task queue */ int (*sc_newstate)(struct ieee80211com *, enum ieee80211_state, int); void (*sc_node_cleanup)( struct ieee80211_node *); int sc_rx_rate; int sc_tx_rate; int sc_led_blinking; int sc_led_ticks; struct bwn_led *sc_blink_led; struct callout sc_led_blink_ch; int sc_led_blink_offdur; struct bwn_led sc_leds[BWN_LED_MAX]; int sc_led_idle; int sc_led_blink; struct bwn_tx_radiotap_header sc_tx_th; struct bwn_rx_radiotap_header sc_rx_th; }; #define BWN_LOCK_INIT(sc) \ mtx_init(&(sc)->sc_mtx, device_get_nameunit((sc)->sc_dev), \ MTX_NETWORK_LOCK, MTX_DEF) #define BWN_LOCK_DESTROY(sc) mtx_destroy(&(sc)->sc_mtx) #define BWN_LOCK(sc) mtx_lock(&(sc)->sc_mtx) #define BWN_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) #define BWN_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) #endif /* !_IF_BWNVAR_H */ Index: user/ngie/detangle-rc/sys/dev/mpr/mpr_sas.c =================================================================== --- user/ngie/detangle-rc/sys/dev/mpr/mpr_sas.c (revision 299126) +++ user/ngie/detangle-rc/sys/dev/mpr/mpr_sas.c (revision 299127) @@ -1,3537 +1,3546 @@ /*- * Copyright (c) 2009 Yahoo! Inc. * Copyright (c) 2011-2015 LSI Corp. * Copyright (c) 2013-2015 Avago Technologies * 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. * * Avago Technologies (LSI) MPT-Fusion Host Adapter FreeBSD * */ #include __FBSDID("$FreeBSD$"); /* Communications core for Avago Technologies (LSI) MPT3 */ /* TODO Move headers to mprvar */ #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 #if __FreeBSD_version >= 900026 #include #endif #include #include #include #include #include #include #include #include #include #include #include #define MPRSAS_DISCOVERY_TIMEOUT 20 #define MPRSAS_MAX_DISCOVERY_TIMEOUTS 10 /* 200 seconds */ /* * static array to check SCSI OpCode for EEDP protection bits */ #define PRO_R MPI2_SCSIIO_EEDPFLAGS_CHECK_REMOVE_OP #define PRO_W MPI2_SCSIIO_EEDPFLAGS_INSERT_OP #define PRO_V MPI2_SCSIIO_EEDPFLAGS_INSERT_OP static uint8_t op_code_prot[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PRO_W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V, 0, 0, 0, PRO_W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; MALLOC_DEFINE(M_MPRSAS, "MPRSAS", "MPR SAS memory"); static void mprsas_remove_device(struct mpr_softc *, struct mpr_command *); static void mprsas_remove_complete(struct mpr_softc *, struct mpr_command *); static void mprsas_action(struct cam_sim *sim, union ccb *ccb); static void mprsas_poll(struct cam_sim *sim); static void mprsas_scsiio_timeout(void *data); static void mprsas_abort_complete(struct mpr_softc *sc, struct mpr_command *cm); static void mprsas_action_scsiio(struct mprsas_softc *, union ccb *); static void mprsas_scsiio_complete(struct mpr_softc *, struct mpr_command *); static void mprsas_action_resetdev(struct mprsas_softc *, union ccb *); static void mprsas_resetdev_complete(struct mpr_softc *, struct mpr_command *); static int mprsas_send_abort(struct mpr_softc *sc, struct mpr_command *tm, struct mpr_command *cm); static void mprsas_async(void *callback_arg, uint32_t code, struct cam_path *path, void *arg); #if (__FreeBSD_version < 901503) || \ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006)) static void mprsas_check_eedp(struct mpr_softc *sc, struct cam_path *path, struct ccb_getdev *cgd); static void mprsas_read_cap_done(struct cam_periph *periph, union ccb *done_ccb); #endif static int mprsas_send_portenable(struct mpr_softc *sc); static void mprsas_portenable_complete(struct mpr_softc *sc, struct mpr_command *cm); #if __FreeBSD_version >= 900026 static void mprsas_smpio_complete(struct mpr_softc *sc, struct mpr_command *cm); static void mprsas_send_smpcmd(struct mprsas_softc *sassc, union ccb *ccb, uint64_t sasaddr); static void mprsas_action_smpio(struct mprsas_softc *sassc, union ccb *ccb); #endif //FreeBSD_version >= 900026 struct mprsas_target * mprsas_find_target_by_handle(struct mprsas_softc *sassc, int start, uint16_t handle) { struct mprsas_target *target; int i; for (i = start; i < sassc->maxtargets; i++) { target = &sassc->targets[i]; if (target->handle == handle) return (target); } return (NULL); } /* we need to freeze the simq during attach and diag reset, to avoid failing * commands before device handles have been found by discovery. Since * discovery involves reading config pages and possibly sending commands, * discovery actions may continue even after we receive the end of discovery * event, so refcount discovery actions instead of assuming we can unfreeze * the simq when we get the event. */ void mprsas_startup_increment(struct mprsas_softc *sassc) { MPR_FUNCTRACE(sassc->sc); if ((sassc->flags & MPRSAS_IN_STARTUP) != 0) { if (sassc->startup_refcount++ == 0) { /* just starting, freeze the simq */ mpr_dprint(sassc->sc, MPR_INIT, "%s freezing simq\n", __func__); #if (__FreeBSD_version >= 1000039) || \ ((__FreeBSD_version < 1000000) && (__FreeBSD_version >= 902502)) xpt_hold_boot(); #endif xpt_freeze_simq(sassc->sim, 1); } mpr_dprint(sassc->sc, MPR_INIT, "%s refcount %u\n", __func__, sassc->startup_refcount); } } void mprsas_release_simq_reinit(struct mprsas_softc *sassc) { if (sassc->flags & MPRSAS_QUEUE_FROZEN) { sassc->flags &= ~MPRSAS_QUEUE_FROZEN; xpt_release_simq(sassc->sim, 1); mpr_dprint(sassc->sc, MPR_INFO, "Unfreezing SIM queue\n"); } } void mprsas_startup_decrement(struct mprsas_softc *sassc) { MPR_FUNCTRACE(sassc->sc); if ((sassc->flags & MPRSAS_IN_STARTUP) != 0) { if (--sassc->startup_refcount == 0) { /* finished all discovery-related actions, release * the simq and rescan for the latest topology. */ mpr_dprint(sassc->sc, MPR_INIT, "%s releasing simq\n", __func__); sassc->flags &= ~MPRSAS_IN_STARTUP; xpt_release_simq(sassc->sim, 1); #if (__FreeBSD_version >= 1000039) || \ ((__FreeBSD_version < 1000000) && (__FreeBSD_version >= 902502)) xpt_release_boot(); #else mprsas_rescan_target(sassc->sc, NULL); #endif } mpr_dprint(sassc->sc, MPR_INIT, "%s refcount %u\n", __func__, sassc->startup_refcount); } } /* The firmware requires us to stop sending commands when we're doing task * management, so refcount the TMs and keep the simq frozen when any are in * use. */ struct mpr_command * mprsas_alloc_tm(struct mpr_softc *sc) { struct mpr_command *tm; MPR_FUNCTRACE(sc); tm = mpr_alloc_high_priority_command(sc); return tm; } void mprsas_free_tm(struct mpr_softc *sc, struct mpr_command *tm) { MPR_FUNCTRACE(sc); if (tm == NULL) return; /* * For TM's the devq is frozen for the device. Unfreeze it here and * free the resources used for freezing the devq. Must clear the * INRESET flag as well or scsi I/O will not work. */ if (tm->cm_targ != NULL) { tm->cm_targ->flags &= ~MPRSAS_TARGET_INRESET; } if (tm->cm_ccb) { mpr_dprint(sc, MPR_INFO, "Unfreezing devq for target ID %d\n", tm->cm_targ->tid); xpt_release_devq(tm->cm_ccb->ccb_h.path, 1, TRUE); xpt_free_path(tm->cm_ccb->ccb_h.path); xpt_free_ccb(tm->cm_ccb); } mpr_free_high_priority_command(sc, tm); } void mprsas_rescan_target(struct mpr_softc *sc, struct mprsas_target *targ) { struct mprsas_softc *sassc = sc->sassc; path_id_t pathid; target_id_t targetid; union ccb *ccb; MPR_FUNCTRACE(sc); pathid = cam_sim_path(sassc->sim); if (targ == NULL) targetid = CAM_TARGET_WILDCARD; else targetid = targ - sassc->targets; /* * Allocate a CCB and schedule a rescan. */ ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { mpr_dprint(sc, MPR_ERROR, "unable to alloc CCB for rescan\n"); return; } if (xpt_create_path(&ccb->ccb_h.path, NULL, pathid, targetid, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { mpr_dprint(sc, MPR_ERROR, "unable to create path for rescan\n"); xpt_free_ccb(ccb); return; } if (targetid == CAM_TARGET_WILDCARD) ccb->ccb_h.func_code = XPT_SCAN_BUS; else ccb->ccb_h.func_code = XPT_SCAN_TGT; mpr_dprint(sc, MPR_TRACE, "%s targetid %u\n", __func__, targetid); xpt_rescan(ccb); } static void mprsas_log_command(struct mpr_command *cm, u_int level, const char *fmt, ...) { struct sbuf sb; va_list ap; char str[192]; char path_str[64]; if (cm == NULL) return; /* No need to be in here if debugging isn't enabled */ if ((cm->cm_sc->mpr_debug & level) == 0) return; sbuf_new(&sb, str, sizeof(str), 0); va_start(ap, fmt); if (cm->cm_ccb != NULL) { xpt_path_string(cm->cm_ccb->csio.ccb_h.path, path_str, sizeof(path_str)); sbuf_cat(&sb, path_str); if (cm->cm_ccb->ccb_h.func_code == XPT_SCSI_IO) { scsi_command_string(&cm->cm_ccb->csio, &sb); sbuf_printf(&sb, "length %d ", cm->cm_ccb->csio.dxfer_len); } } else { sbuf_printf(&sb, "(noperiph:%s%d:%u:%u:%u): ", cam_sim_name(cm->cm_sc->sassc->sim), cam_sim_unit(cm->cm_sc->sassc->sim), cam_sim_bus(cm->cm_sc->sassc->sim), cm->cm_targ ? cm->cm_targ->tid : 0xFFFFFFFF, cm->cm_lun); } sbuf_printf(&sb, "SMID %u ", cm->cm_desc.Default.SMID); sbuf_vprintf(&sb, fmt, ap); sbuf_finish(&sb); mpr_dprint_field(cm->cm_sc, level, "%s", sbuf_data(&sb)); va_end(ap); } static void mprsas_remove_volume(struct mpr_softc *sc, struct mpr_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *reply; struct mprsas_target *targ; uint16_t handle; MPR_FUNCTRACE(sc); reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; handle = (uint16_t)(uintptr_t)tm->cm_complete_data; targ = tm->cm_targ; if (reply == NULL) { /* XXX retry the remove after the diag reset completes? */ mpr_dprint(sc, MPR_FAULT, "%s NULL reply resetting device " "0x%04x\n", __func__, handle); mprsas_free_tm(sc, tm); return; } if (reply->IOCStatus != MPI2_IOCSTATUS_SUCCESS) { mpr_dprint(sc, MPR_FAULT, "IOCStatus = 0x%x while resetting " "device 0x%x\n", reply->IOCStatus, handle); mprsas_free_tm(sc, tm); return; } mpr_dprint(sc, MPR_XINFO, "Reset aborted %u commands\n", reply->TerminationCount); mpr_free_reply(sc, tm->cm_reply_data); tm->cm_reply = NULL; /* Ensures the reply won't get re-freed */ mpr_dprint(sc, MPR_XINFO, "clearing target %u handle 0x%04x\n", targ->tid, handle); /* * Don't clear target if remove fails because things will get confusing. * Leave the devname and sasaddr intact so that we know to avoid reusing * this target id if possible, and so we can assign the same target id * to this device if it comes back in the future. */ if (reply->IOCStatus == MPI2_IOCSTATUS_SUCCESS) { targ = tm->cm_targ; targ->handle = 0x0; targ->encl_handle = 0x0; targ->encl_level_valid = 0x0; targ->encl_level = 0x0; targ->connector_name[0] = ' '; targ->connector_name[1] = ' '; targ->connector_name[2] = ' '; targ->connector_name[3] = ' '; targ->encl_slot = 0x0; targ->exp_dev_handle = 0x0; targ->phy_num = 0x0; targ->linkrate = 0x0; targ->devinfo = 0x0; targ->flags = 0x0; targ->scsi_req_desc_type = 0; } mprsas_free_tm(sc, tm); } /* * No Need to call "MPI2_SAS_OP_REMOVE_DEVICE" For Volume removal. * Otherwise Volume Delete is same as Bare Drive Removal. */ void mprsas_prepare_volume_remove(struct mprsas_softc *sassc, uint16_t handle) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mpr_softc *sc; struct mpr_command *cm; struct mprsas_target *targ = NULL; MPR_FUNCTRACE(sassc->sc); sc = sassc->sc; targ = mprsas_find_target_by_handle(sassc, 0, handle); if (targ == NULL) { /* FIXME: what is the action? */ /* We don't know about this device? */ mpr_dprint(sc, MPR_ERROR, "%s %d : invalid handle 0x%x \n", __func__,__LINE__, handle); return; } targ->flags |= MPRSAS_TARGET_INREMOVAL; cm = mprsas_alloc_tm(sc); if (cm == NULL) { mpr_dprint(sc, MPR_ERROR, "%s: command alloc failure\n", __func__); return; } mprsas_rescan_target(sc, targ); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)cm->cm_req; req->DevHandle = targ->handle; req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET; /* SAS Hard Link Reset / SATA Link Reset */ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET; cm->cm_targ = targ; cm->cm_data = NULL; cm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; cm->cm_complete = mprsas_remove_volume; cm->cm_complete_data = (void *)(uintptr_t)handle; mpr_dprint(sc, MPR_INFO, "%s: Sending reset for target ID %d\n", __func__, targ->tid); mprsas_prepare_for_tm(sc, cm, targ, CAM_LUN_WILDCARD); mpr_map_command(sc, cm); } /* * The MPT3 firmware performs debounce on the link to avoid transient link * errors and false removals. When it does decide that link has been lost * and a device needs to go away, it expects that the host will perform a * target reset and then an op remove. The reset has the side-effect of * aborting any outstanding requests for the device, which is required for * the op-remove to succeed. It's not clear if the host should check for * the device coming back alive after the reset. */ void mprsas_prepare_remove(struct mprsas_softc *sassc, uint16_t handle) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mpr_softc *sc; struct mpr_command *cm; struct mprsas_target *targ = NULL; MPR_FUNCTRACE(sassc->sc); sc = sassc->sc; targ = mprsas_find_target_by_handle(sassc, 0, handle); if (targ == NULL) { /* FIXME: what is the action? */ /* We don't know about this device? */ mpr_dprint(sc, MPR_ERROR, "%s : invalid handle 0x%x \n", __func__, handle); return; } targ->flags |= MPRSAS_TARGET_INREMOVAL; cm = mprsas_alloc_tm(sc); if (cm == NULL) { mpr_dprint(sc, MPR_ERROR, "%s: command alloc failure\n", __func__); return; } mprsas_rescan_target(sc, targ); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)cm->cm_req; memset(req, 0, sizeof(*req)); req->DevHandle = htole16(targ->handle); req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET; /* SAS Hard Link Reset / SATA Link Reset */ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET; cm->cm_targ = targ; cm->cm_data = NULL; cm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; cm->cm_complete = mprsas_remove_device; cm->cm_complete_data = (void *)(uintptr_t)handle; mpr_dprint(sc, MPR_INFO, "%s: Sending reset for target ID %d\n", __func__, targ->tid); mprsas_prepare_for_tm(sc, cm, targ, CAM_LUN_WILDCARD); mpr_map_command(sc, cm); } static void mprsas_remove_device(struct mpr_softc *sc, struct mpr_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *reply; MPI2_SAS_IOUNIT_CONTROL_REQUEST *req; struct mprsas_target *targ; struct mpr_command *next_cm; uint16_t handle; MPR_FUNCTRACE(sc); reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; handle = (uint16_t)(uintptr_t)tm->cm_complete_data; targ = tm->cm_targ; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for remove of " "handle %#04x! This should not happen!\n", __func__, tm->cm_flags, handle); mprsas_free_tm(sc, tm); return; } if (reply == NULL) { /* XXX retry the remove after the diag reset completes? */ mpr_dprint(sc, MPR_FAULT, "%s NULL reply resetting device " "0x%04x\n", __func__, handle); mprsas_free_tm(sc, tm); return; } if (le16toh(reply->IOCStatus) != MPI2_IOCSTATUS_SUCCESS) { mpr_dprint(sc, MPR_FAULT, "IOCStatus = 0x%x while resetting " "device 0x%x\n", le16toh(reply->IOCStatus), handle); mprsas_free_tm(sc, tm); return; } mpr_dprint(sc, MPR_XINFO, "Reset aborted %u commands\n", le32toh(reply->TerminationCount)); mpr_free_reply(sc, tm->cm_reply_data); tm->cm_reply = NULL; /* Ensures the reply won't get re-freed */ /* Reuse the existing command */ req = (MPI2_SAS_IOUNIT_CONTROL_REQUEST *)tm->cm_req; memset(req, 0, sizeof(*req)); req->Function = MPI2_FUNCTION_SAS_IO_UNIT_CONTROL; req->Operation = MPI2_SAS_OP_REMOVE_DEVICE; req->DevHandle = htole16(handle); tm->cm_data = NULL; tm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE; tm->cm_complete = mprsas_remove_complete; tm->cm_complete_data = (void *)(uintptr_t)handle; mpr_map_command(sc, tm); mpr_dprint(sc, MPR_INFO, "clearing target %u handle 0x%04x\n", targ->tid, handle); if (targ->encl_level_valid) { mpr_dprint(sc, MPR_INFO, "At enclosure level %d, slot %d, " "connector name (%4s)\n", targ->encl_level, targ->encl_slot, targ->connector_name); } TAILQ_FOREACH_SAFE(tm, &targ->commands, cm_link, next_cm) { union ccb *ccb; mpr_dprint(sc, MPR_XINFO, "Completing missed command %p\n", tm); ccb = tm->cm_complete_data; mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); mprsas_scsiio_complete(sc, tm); } } static void mprsas_remove_complete(struct mpr_softc *sc, struct mpr_command *tm) { MPI2_SAS_IOUNIT_CONTROL_REPLY *reply; uint16_t handle; struct mprsas_target *targ; struct mprsas_lun *lun; MPR_FUNCTRACE(sc); reply = (MPI2_SAS_IOUNIT_CONTROL_REPLY *)tm->cm_reply; handle = (uint16_t)(uintptr_t)tm->cm_complete_data; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { mpr_dprint(sc, MPR_XINFO, "%s: cm_flags = %#x for remove of " "handle %#04x! This should not happen!\n", __func__, tm->cm_flags, handle); mprsas_free_tm(sc, tm); return; } if (reply == NULL) { /* most likely a chip reset */ mpr_dprint(sc, MPR_FAULT, "%s NULL reply removing device " "0x%04x\n", __func__, handle); mprsas_free_tm(sc, tm); return; } mpr_dprint(sc, MPR_XINFO, "%s on handle 0x%04x, IOCStatus= 0x%x\n", __func__, handle, le16toh(reply->IOCStatus)); /* * Don't clear target if remove fails because things will get confusing. * Leave the devname and sasaddr intact so that we know to avoid reusing * this target id if possible, and so we can assign the same target id * to this device if it comes back in the future. */ if (le16toh(reply->IOCStatus) == MPI2_IOCSTATUS_SUCCESS) { targ = tm->cm_targ; targ->handle = 0x0; targ->encl_handle = 0x0; targ->encl_level_valid = 0x0; targ->encl_level = 0x0; targ->connector_name[0] = ' '; targ->connector_name[1] = ' '; targ->connector_name[2] = ' '; targ->connector_name[3] = ' '; targ->encl_slot = 0x0; targ->exp_dev_handle = 0x0; targ->phy_num = 0x0; targ->linkrate = 0x0; targ->devinfo = 0x0; targ->flags = 0x0; targ->scsi_req_desc_type = 0; while (!SLIST_EMPTY(&targ->luns)) { lun = SLIST_FIRST(&targ->luns); SLIST_REMOVE_HEAD(&targ->luns, lun_link); free(lun, M_MPR); } } mprsas_free_tm(sc, tm); } static int mprsas_register_events(struct mpr_softc *sc) { uint8_t events[16]; bzero(events, 16); setbit(events, MPI2_EVENT_SAS_DEVICE_STATUS_CHANGE); setbit(events, MPI2_EVENT_SAS_DISCOVERY); setbit(events, MPI2_EVENT_SAS_BROADCAST_PRIMITIVE); setbit(events, MPI2_EVENT_SAS_INIT_DEVICE_STATUS_CHANGE); setbit(events, MPI2_EVENT_SAS_INIT_TABLE_OVERFLOW); setbit(events, MPI2_EVENT_SAS_TOPOLOGY_CHANGE_LIST); setbit(events, MPI2_EVENT_SAS_ENCL_DEVICE_STATUS_CHANGE); setbit(events, MPI2_EVENT_IR_CONFIGURATION_CHANGE_LIST); setbit(events, MPI2_EVENT_IR_VOLUME); setbit(events, MPI2_EVENT_IR_PHYSICAL_DISK); setbit(events, MPI2_EVENT_IR_OPERATION_STATUS); setbit(events, MPI2_EVENT_TEMP_THRESHOLD); mpr_register_events(sc, events, mprsas_evt_handler, NULL, &sc->sassc->mprsas_eh); return (0); } int mpr_attach_sas(struct mpr_softc *sc) { struct mprsas_softc *sassc; cam_status status; int unit, error = 0; MPR_FUNCTRACE(sc); sassc = malloc(sizeof(struct mprsas_softc), M_MPR, M_WAITOK|M_ZERO); if (!sassc) { device_printf(sc->mpr_dev, "Cannot allocate memory %s %d\n", __func__, __LINE__); return (ENOMEM); } /* * XXX MaxTargets could change during a reinit. Since we don't * resize the targets[] array during such an event, cache the value * of MaxTargets here so that we don't get into trouble later. This * should move into the reinit logic. */ sassc->maxtargets = sc->facts->MaxTargets; sassc->targets = malloc(sizeof(struct mprsas_target) * sassc->maxtargets, M_MPR, M_WAITOK|M_ZERO); if (!sassc->targets) { device_printf(sc->mpr_dev, "Cannot allocate memory %s %d\n", __func__, __LINE__); free(sassc, M_MPR); return (ENOMEM); } sc->sassc = sassc; sassc->sc = sc; if ((sassc->devq = cam_simq_alloc(sc->num_reqs)) == NULL) { mpr_dprint(sc, MPR_ERROR, "Cannot allocate SIMQ\n"); error = ENOMEM; goto out; } unit = device_get_unit(sc->mpr_dev); sassc->sim = cam_sim_alloc(mprsas_action, mprsas_poll, "mpr", sassc, unit, &sc->mpr_mtx, sc->num_reqs, sc->num_reqs, sassc->devq); if (sassc->sim == NULL) { mpr_dprint(sc, MPR_ERROR, "Cannot allocate SIM\n"); error = EINVAL; goto out; } TAILQ_INIT(&sassc->ev_queue); /* Initialize taskqueue for Event Handling */ TASK_INIT(&sassc->ev_task, 0, mprsas_firmware_event_work, sc); sassc->ev_tq = taskqueue_create("mpr_taskq", M_NOWAIT | M_ZERO, taskqueue_thread_enqueue, &sassc->ev_tq); taskqueue_start_threads(&sassc->ev_tq, 1, PRIBIO, "%s taskq", device_get_nameunit(sc->mpr_dev)); mpr_lock(sc); /* * XXX There should be a bus for every port on the adapter, but since * we're just going to fake the topology for now, we'll pretend that * everything is just a target on a single bus. */ if ((error = xpt_bus_register(sassc->sim, sc->mpr_dev, 0)) != 0) { mpr_dprint(sc, MPR_ERROR, "Error %d registering SCSI bus\n", error); mpr_unlock(sc); goto out; } /* * Assume that discovery events will start right away. * * Hold off boot until discovery is complete. */ sassc->flags |= MPRSAS_IN_STARTUP | MPRSAS_IN_DISCOVERY; sc->sassc->startup_refcount = 0; mprsas_startup_increment(sassc); callout_init(&sassc->discovery_callout, 1 /*mpsafe*/); /* * Register for async events so we can determine the EEDP * capabilities of devices. */ status = xpt_create_path(&sassc->path, /*periph*/NULL, cam_sim_path(sc->sassc->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); if (status != CAM_REQ_CMP) { mpr_printf(sc, "Error %#x creating sim path\n", status); sassc->path = NULL; } else { int event; #if (__FreeBSD_version >= 1000006) || \ ((__FreeBSD_version >= 901503) && (__FreeBSD_version < 1000000)) event = AC_ADVINFO_CHANGED | AC_FOUND_DEVICE; #else event = AC_FOUND_DEVICE; #endif /* * Prior to the CAM locking improvements, we can't call * xpt_register_async() with a particular path specified. * * If a path isn't specified, xpt_register_async() will * generate a wildcard path and acquire the XPT lock while * it calls xpt_action() to execute the XPT_SASYNC_CB CCB. * It will then drop the XPT lock once that is done. * * If a path is specified for xpt_register_async(), it will * not acquire and drop the XPT lock around the call to * xpt_action(). xpt_action() asserts that the caller * holds the SIM lock, so the SIM lock has to be held when * calling xpt_register_async() when the path is specified. * * But xpt_register_async calls xpt_for_all_devices(), * which calls xptbustraverse(), which will acquire each * SIM lock. When it traverses our particular bus, it will * necessarily acquire the SIM lock, which will lead to a * recursive lock acquisition. * * The CAM locking changes fix this problem by acquiring * the XPT topology lock around bus traversal in * xptbustraverse(), so the caller can hold the SIM lock * and it does not cause a recursive lock acquisition. * * These __FreeBSD_version values are approximate, especially * for stable/10, which is two months later than the actual * change. */ #if (__FreeBSD_version < 1000703) || \ ((__FreeBSD_version >= 1100000) && (__FreeBSD_version < 1100002)) mpr_unlock(sc); status = xpt_register_async(event, mprsas_async, sc, NULL); mpr_lock(sc); #else status = xpt_register_async(event, mprsas_async, sc, sassc->path); #endif if (status != CAM_REQ_CMP) { mpr_dprint(sc, MPR_ERROR, "Error %#x registering async handler for " "AC_ADVINFO_CHANGED events\n", status); xpt_free_path(sassc->path); sassc->path = NULL; } } if (status != CAM_REQ_CMP) { /* * EEDP use is the exception, not the rule. * Warn the user, but do not fail to attach. */ mpr_printf(sc, "EEDP capabilities disabled.\n"); } mpr_unlock(sc); mprsas_register_events(sc); out: if (error) mpr_detach_sas(sc); return (error); } int mpr_detach_sas(struct mpr_softc *sc) { struct mprsas_softc *sassc; struct mprsas_lun *lun, *lun_tmp; struct mprsas_target *targ; int i; MPR_FUNCTRACE(sc); if (sc->sassc == NULL) return (0); sassc = sc->sassc; mpr_deregister_events(sc, sassc->mprsas_eh); /* * Drain and free the event handling taskqueue with the lock * unheld so that any parallel processing tasks drain properly * without deadlocking. */ if (sassc->ev_tq != NULL) taskqueue_free(sassc->ev_tq); /* Make sure CAM doesn't wedge if we had to bail out early. */ mpr_lock(sc); /* Deregister our async handler */ if (sassc->path != NULL) { xpt_register_async(0, mprsas_async, sc, sassc->path); xpt_free_path(sassc->path); sassc->path = NULL; } if (sassc->flags & MPRSAS_IN_STARTUP) xpt_release_simq(sassc->sim, 1); if (sassc->sim != NULL) { xpt_bus_deregister(cam_sim_path(sassc->sim)); cam_sim_free(sassc->sim, FALSE); } sassc->flags |= MPRSAS_SHUTDOWN; mpr_unlock(sc); if (sassc->devq != NULL) cam_simq_free(sassc->devq); for (i = 0; i < sassc->maxtargets; i++) { targ = &sassc->targets[i]; SLIST_FOREACH_SAFE(lun, &targ->luns, lun_link, lun_tmp) { free(lun, M_MPR); } } free(sassc->targets, M_MPR); free(sassc, M_MPR); sc->sassc = NULL; return (0); } void mprsas_discovery_end(struct mprsas_softc *sassc) { struct mpr_softc *sc = sassc->sc; MPR_FUNCTRACE(sc); if (sassc->flags & MPRSAS_DISCOVERY_TIMEOUT_PENDING) callout_stop(&sassc->discovery_callout); } static void mprsas_action(struct cam_sim *sim, union ccb *ccb) { struct mprsas_softc *sassc; sassc = cam_sim_softc(sim); MPR_FUNCTRACE(sassc->sc); mpr_dprint(sassc->sc, MPR_TRACE, "ccb func_code 0x%x\n", ccb->ccb_h.func_code); mtx_assert(&sassc->sc->mpr_mtx, MA_OWNED); switch (ccb->ccb_h.func_code) { case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16; cpi->target_sprt = 0; #if (__FreeBSD_version >= 1000039) || \ ((__FreeBSD_version < 1000000) && (__FreeBSD_version >= 902502)) cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED | PIM_NOSCAN; #else cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED; #endif cpi->hba_eng_cnt = 0; cpi->max_target = sassc->maxtargets - 1; cpi->max_lun = 255; cpi->initiator_id = sassc->maxtargets - 1; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "Avago Tech (LSI)", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); /* * XXXSLM-I think this needs to change based on config page or * something instead of hardcoded to 150000. */ cpi->base_transfer_speed = 150000; cpi->transport = XPORT_SAS; cpi->transport_version = 0; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_SPC; #if __FreeBSD_version >= 800001 /* * XXXSLM-probably need to base this number on max SGL's and * page size. */ cpi->maxio = 256 * 1024; #endif mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts; struct ccb_trans_settings_sas *sas; struct ccb_trans_settings_scsi *scsi; struct mprsas_target *targ; cts = &ccb->cts; sas = &cts->xport_specific.sas; scsi = &cts->proto_specific.scsi; KASSERT(cts->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in XPT_GET_TRAN_SETTINGS\n", cts->ccb_h.target_id)); targ = &sassc->targets[cts->ccb_h.target_id]; if (targ->handle == 0x0) { mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); break; } cts->protocol_version = SCSI_REV_SPC2; cts->transport = XPORT_SAS; cts->transport_version = 0; sas->valid = CTS_SAS_VALID_SPEED; switch (targ->linkrate) { case 0x08: sas->bitrate = 150000; break; case 0x09: sas->bitrate = 300000; break; case 0x0a: sas->bitrate = 600000; break; case 0x0b: sas->bitrate = 1200000; break; default: sas->valid = 0; } cts->protocol = PROTO_SCSI; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); break; } case XPT_CALC_GEOMETRY: cam_calc_geometry(&ccb->ccg, /*extended*/1); mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); break; case XPT_RESET_DEV: mpr_dprint(sassc->sc, MPR_XINFO, "mprsas_action XPT_RESET_DEV\n"); mprsas_action_resetdev(sassc, ccb); return; case XPT_RESET_BUS: case XPT_ABORT: case XPT_TERM_IO: mpr_dprint(sassc->sc, MPR_XINFO, "mprsas_action faking success for abort or reset\n"); mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); break; case XPT_SCSI_IO: mprsas_action_scsiio(sassc, ccb); return; #if __FreeBSD_version >= 900026 case XPT_SMP_IO: mprsas_action_smpio(sassc, ccb); return; #endif default: mprsas_set_ccbstatus(ccb, CAM_FUNC_NOTAVAIL); break; } xpt_done(ccb); } static void mprsas_announce_reset(struct mpr_softc *sc, uint32_t ac_code, target_id_t target_id, lun_id_t lun_id) { path_id_t path_id = cam_sim_path(sc->sassc->sim); struct cam_path *path; mpr_dprint(sc, MPR_XINFO, "%s code %x target %d lun %jx\n", __func__, ac_code, target_id, (uintmax_t)lun_id); if (xpt_create_path(&path, NULL, path_id, target_id, lun_id) != CAM_REQ_CMP) { mpr_dprint(sc, MPR_ERROR, "unable to create path for reset " "notification\n"); return; } xpt_async(ac_code, path, NULL); xpt_free_path(path); } static void mprsas_complete_all_commands(struct mpr_softc *sc) { struct mpr_command *cm; int i; int completed; MPR_FUNCTRACE(sc); mtx_assert(&sc->mpr_mtx, MA_OWNED); /* complete all commands with a NULL reply */ for (i = 1; i < sc->num_reqs; i++) { cm = &sc->commands[i]; cm->cm_reply = NULL; completed = 0; if (cm->cm_flags & MPR_CM_FLAGS_POLLED) cm->cm_flags |= MPR_CM_FLAGS_COMPLETE; if (cm->cm_complete != NULL) { mprsas_log_command(cm, MPR_RECOVERY, "completing cm %p state %x ccb %p for diag " "reset\n", cm, cm->cm_state, cm->cm_ccb); cm->cm_complete(sc, cm); completed = 1; } if (cm->cm_flags & MPR_CM_FLAGS_WAKEUP) { mprsas_log_command(cm, MPR_RECOVERY, "waking up cm %p state %x ccb %p for diag reset\n", cm, cm->cm_state, cm->cm_ccb); wakeup(cm); completed = 1; } if (cm->cm_sc->io_cmds_active != 0) { cm->cm_sc->io_cmds_active--; } else { mpr_dprint(cm->cm_sc, MPR_INFO, "Warning: " "io_cmds_active is out of sync - resynching to " "0\n"); } if ((completed == 0) && (cm->cm_state != MPR_CM_STATE_FREE)) { /* this should never happen, but if it does, log */ mprsas_log_command(cm, MPR_RECOVERY, "cm %p state %x flags 0x%x ccb %p during diag " "reset\n", cm, cm->cm_state, cm->cm_flags, cm->cm_ccb); } } } void mprsas_handle_reinit(struct mpr_softc *sc) { int i; /* Go back into startup mode and freeze the simq, so that CAM * doesn't send any commands until after we've rediscovered all * targets and found the proper device handles for them. * * After the reset, portenable will trigger discovery, and after all * discovery-related activities have finished, the simq will be * released. */ mpr_dprint(sc, MPR_INIT, "%s startup\n", __func__); sc->sassc->flags |= MPRSAS_IN_STARTUP; sc->sassc->flags |= MPRSAS_IN_DISCOVERY; mprsas_startup_increment(sc->sassc); /* notify CAM of a bus reset */ mprsas_announce_reset(sc, AC_BUS_RESET, CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); /* complete and cleanup after all outstanding commands */ mprsas_complete_all_commands(sc); mpr_dprint(sc, MPR_INIT, "%s startup %u after command completion\n", __func__, sc->sassc->startup_refcount); /* zero all the target handles, since they may change after the * reset, and we have to rediscover all the targets and use the new * handles. */ for (i = 0; i < sc->sassc->maxtargets; i++) { if (sc->sassc->targets[i].outstanding != 0) mpr_dprint(sc, MPR_INIT, "target %u outstanding %u\n", i, sc->sassc->targets[i].outstanding); sc->sassc->targets[i].handle = 0x0; sc->sassc->targets[i].exp_dev_handle = 0x0; sc->sassc->targets[i].outstanding = 0; sc->sassc->targets[i].flags = MPRSAS_TARGET_INDIAGRESET; } } static void mprsas_tm_timeout(void *data) { struct mpr_command *tm = data; struct mpr_softc *sc = tm->cm_sc; mtx_assert(&sc->mpr_mtx, MA_OWNED); mprsas_log_command(tm, MPR_INFO|MPR_RECOVERY, "task mgmt %p timed out\n", tm); mpr_reinit(sc); } static void mprsas_logical_unit_reset_complete(struct mpr_softc *sc, struct mpr_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *reply; MPI2_SCSI_TASK_MANAGE_REQUEST *req; unsigned int cm_count = 0; struct mpr_command *cm; struct mprsas_target *targ; callout_stop(&tm->cm_callout); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; targ = tm->cm_targ; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for LUN reset! " "This should not happen!\n", __func__, tm->cm_flags); mprsas_free_tm(sc, tm); return; } if (reply == NULL) { mprsas_log_command(tm, MPR_RECOVERY, "NULL reset reply for tm %p\n", tm); if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) { /* this completion was due to a reset, just cleanup */ targ->tm = NULL; mprsas_free_tm(sc, tm); } else { /* we should have gotten a reply. */ mpr_reinit(sc); } return; } mprsas_log_command(tm, MPR_RECOVERY, "logical unit reset status 0x%x code 0x%x count %u\n", le16toh(reply->IOCStatus), le32toh(reply->ResponseCode), le32toh(reply->TerminationCount)); /* See if there are any outstanding commands for this LUN. * This could be made more efficient by using a per-LU data * structure of some sort. */ TAILQ_FOREACH(cm, &targ->commands, cm_link) { if (cm->cm_lun == tm->cm_lun) cm_count++; } if (cm_count == 0) { mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO, "logical unit %u finished recovery after reset\n", tm->cm_lun, tm); mprsas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid, tm->cm_lun); /* we've finished recovery for this logical unit. check and * see if some other logical unit has a timedout command * that needs to be processed. */ cm = TAILQ_FIRST(&targ->timedout_commands); if (cm) { mprsas_send_abort(sc, tm, cm); } else { targ->tm = NULL; mprsas_free_tm(sc, tm); } } else { /* if we still have commands for this LUN, the reset * effectively failed, regardless of the status reported. * Escalate to a target reset. */ mprsas_log_command(tm, MPR_RECOVERY, "logical unit reset complete for tm %p, but still have %u " "command(s)\n", tm, cm_count); mprsas_send_reset(sc, tm, MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET); } } static void mprsas_target_reset_complete(struct mpr_softc *sc, struct mpr_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *reply; MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mprsas_target *targ; callout_stop(&tm->cm_callout); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; targ = tm->cm_targ; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for target " "reset! This should not happen!\n", __func__, tm->cm_flags); mprsas_free_tm(sc, tm); return; } if (reply == NULL) { mprsas_log_command(tm, MPR_RECOVERY, "NULL reset reply for tm %p\n", tm); if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) { /* this completion was due to a reset, just cleanup */ targ->tm = NULL; mprsas_free_tm(sc, tm); } else { /* we should have gotten a reply. */ mpr_reinit(sc); } return; } mprsas_log_command(tm, MPR_RECOVERY, "target reset status 0x%x code 0x%x count %u\n", le16toh(reply->IOCStatus), le32toh(reply->ResponseCode), le32toh(reply->TerminationCount)); if (targ->outstanding == 0) { /* we've finished recovery for this target and all * of its logical units. */ mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO, "recovery finished after target reset\n"); mprsas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid, CAM_LUN_WILDCARD); targ->tm = NULL; mprsas_free_tm(sc, tm); } else { /* after a target reset, if this target still has * outstanding commands, the reset effectively failed, * regardless of the status reported. escalate. */ mprsas_log_command(tm, MPR_RECOVERY, "target reset complete for tm %p, but still have %u " "command(s)\n", tm, targ->outstanding); mpr_reinit(sc); } } #define MPR_RESET_TIMEOUT 30 int mprsas_send_reset(struct mpr_softc *sc, struct mpr_command *tm, uint8_t type) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mprsas_target *target; int err; target = tm->cm_targ; if (target->handle == 0) { mpr_dprint(sc, MPR_ERROR, "%s null devhandle for target_id " "%d\n", __func__, target->tid); return -1; } req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; req->DevHandle = htole16(target->handle); req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = type; if (type == MPI2_SCSITASKMGMT_TASKTYPE_LOGICAL_UNIT_RESET) { /* XXX Need to handle invalid LUNs */ MPR_SET_LUN(req->LUN, tm->cm_lun); tm->cm_targ->logical_unit_resets++; mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO, "sending logical unit reset\n"); tm->cm_complete = mprsas_logical_unit_reset_complete; mprsas_prepare_for_tm(sc, tm, target, tm->cm_lun); } else if (type == MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET) { /* * Target reset method = * SAS Hard Link Reset / SATA Link Reset */ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET; tm->cm_targ->target_resets++; mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO, "sending target reset\n"); tm->cm_complete = mprsas_target_reset_complete; mprsas_prepare_for_tm(sc, tm, target, CAM_LUN_WILDCARD); } else { mpr_dprint(sc, MPR_ERROR, "unexpected reset type 0x%x\n", type); return -1; } mpr_dprint(sc, MPR_INFO, "to target %u handle 0x%04x\n", target->tid, target->handle); if (target->encl_level_valid) { mpr_dprint(sc, MPR_INFO, "At enclosure level %d, slot %d, " "connector name (%4s)\n", target->encl_level, target->encl_slot, target->connector_name); } tm->cm_data = NULL; tm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; tm->cm_complete_data = (void *)tm; callout_reset(&tm->cm_callout, MPR_RESET_TIMEOUT * hz, mprsas_tm_timeout, tm); err = mpr_map_command(sc, tm); if (err) mprsas_log_command(tm, MPR_RECOVERY, "error %d sending reset type %u\n", err, type); return err; } static void mprsas_abort_complete(struct mpr_softc *sc, struct mpr_command *tm) { struct mpr_command *cm; MPI2_SCSI_TASK_MANAGE_REPLY *reply; MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mprsas_target *targ; callout_stop(&tm->cm_callout); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; targ = tm->cm_targ; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { mprsas_log_command(tm, MPR_RECOVERY, "cm_flags = %#x for abort %p TaskMID %u!\n", tm->cm_flags, tm, le16toh(req->TaskMID)); mprsas_free_tm(sc, tm); return; } if (reply == NULL) { mprsas_log_command(tm, MPR_RECOVERY, "NULL abort reply for tm %p TaskMID %u\n", tm, le16toh(req->TaskMID)); if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) { /* this completion was due to a reset, just cleanup */ targ->tm = NULL; mprsas_free_tm(sc, tm); } else { /* we should have gotten a reply. */ mpr_reinit(sc); } return; } mprsas_log_command(tm, MPR_RECOVERY, "abort TaskMID %u status 0x%x code 0x%x count %u\n", le16toh(req->TaskMID), le16toh(reply->IOCStatus), le32toh(reply->ResponseCode), le32toh(reply->TerminationCount)); cm = TAILQ_FIRST(&tm->cm_targ->timedout_commands); if (cm == NULL) { /* if there are no more timedout commands, we're done with * error recovery for this target. */ mprsas_log_command(tm, MPR_RECOVERY, "finished recovery after aborting TaskMID %u\n", le16toh(req->TaskMID)); targ->tm = NULL; mprsas_free_tm(sc, tm); } else if (le16toh(req->TaskMID) != cm->cm_desc.Default.SMID) { /* abort success, but we have more timedout commands to abort */ mprsas_log_command(tm, MPR_RECOVERY, "continuing recovery after aborting TaskMID %u\n", le16toh(req->TaskMID)); mprsas_send_abort(sc, tm, cm); } else { /* we didn't get a command completion, so the abort * failed as far as we're concerned. escalate. */ mprsas_log_command(tm, MPR_RECOVERY, "abort failed for TaskMID %u tm %p\n", le16toh(req->TaskMID), tm); mprsas_send_reset(sc, tm, MPI2_SCSITASKMGMT_TASKTYPE_LOGICAL_UNIT_RESET); } } #define MPR_ABORT_TIMEOUT 5 static int mprsas_send_abort(struct mpr_softc *sc, struct mpr_command *tm, struct mpr_command *cm) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mprsas_target *targ; int err; targ = cm->cm_targ; if (targ->handle == 0) { mpr_dprint(sc, MPR_ERROR,"%s null devhandle for target_id %d\n", __func__, cm->cm_ccb->ccb_h.target_id); return -1; } mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO, "Aborting command %p\n", cm); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; req->DevHandle = htole16(targ->handle); req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_ABORT_TASK; /* XXX Need to handle invalid LUNs */ MPR_SET_LUN(req->LUN, cm->cm_ccb->ccb_h.target_lun); req->TaskMID = htole16(cm->cm_desc.Default.SMID); tm->cm_data = NULL; tm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; tm->cm_complete = mprsas_abort_complete; tm->cm_complete_data = (void *)tm; tm->cm_targ = cm->cm_targ; tm->cm_lun = cm->cm_lun; callout_reset(&tm->cm_callout, MPR_ABORT_TIMEOUT * hz, mprsas_tm_timeout, tm); targ->aborts++; mpr_dprint(sc, MPR_INFO, "Sending reset from %s for target ID %d\n", __func__, targ->tid); mprsas_prepare_for_tm(sc, tm, targ, tm->cm_lun); err = mpr_map_command(sc, tm); if (err) mprsas_log_command(tm, MPR_RECOVERY, "error %d sending abort for cm %p SMID %u\n", err, cm, req->TaskMID); return err; } static void mprsas_scsiio_timeout(void *data) { struct mpr_softc *sc; struct mpr_command *cm; struct mprsas_target *targ; cm = (struct mpr_command *)data; sc = cm->cm_sc; MPR_FUNCTRACE(sc); mtx_assert(&sc->mpr_mtx, MA_OWNED); mpr_dprint(sc, MPR_XINFO, "Timeout checking cm %p\n", cm); /* * Run the interrupt handler to make sure it's not pending. This * isn't perfect because the command could have already completed * and been re-used, though this is unlikely. */ mpr_intr_locked(sc); if (cm->cm_state == MPR_CM_STATE_FREE) { mprsas_log_command(cm, MPR_XINFO, "SCSI command %p almost timed out\n", cm); return; } if (cm->cm_ccb == NULL) { mpr_dprint(sc, MPR_ERROR, "command timeout with NULL ccb\n"); return; } targ = cm->cm_targ; targ->timeouts++; mprsas_log_command(cm, MPR_ERROR, "command timeout cm %p ccb %p " "target %u, handle(0x%04x)\n", cm, cm->cm_ccb, targ->tid, targ->handle); if (targ->encl_level_valid) { mpr_dprint(sc, MPR_ERROR, "At enclosure level %d, slot %d, " "connector name (%4s)\n", targ->encl_level, targ->encl_slot, targ->connector_name); } /* XXX first, check the firmware state, to see if it's still * operational. if not, do a diag reset. */ mprsas_set_ccbstatus(cm->cm_ccb, CAM_CMD_TIMEOUT); cm->cm_state = MPR_CM_STATE_TIMEDOUT; TAILQ_INSERT_TAIL(&targ->timedout_commands, cm, cm_recovery); if (targ->tm != NULL) { /* target already in recovery, just queue up another * timedout command to be processed later. */ mpr_dprint(sc, MPR_RECOVERY, "queued timedout cm %p for " "processing by tm %p\n", cm, targ->tm); } else if ((targ->tm = mprsas_alloc_tm(sc)) != NULL) { mpr_dprint(sc, MPR_RECOVERY, "timedout cm %p allocated tm %p\n", cm, targ->tm); /* start recovery by aborting the first timedout command */ mprsas_send_abort(sc, targ->tm, cm); } else { /* XXX queue this target up for recovery once a TM becomes * available. The firmware only has a limited number of * HighPriority credits for the high priority requests used * for task management, and we ran out. * * Isilon: don't worry about this for now, since we have * more credits than disks in an enclosure, and limit * ourselves to one TM per target for recovery. */ mpr_dprint(sc, MPR_RECOVERY, "timedout cm %p failed to allocate a tm\n", cm); } } static void mprsas_action_scsiio(struct mprsas_softc *sassc, union ccb *ccb) { MPI2_SCSI_IO_REQUEST *req; struct ccb_scsiio *csio; struct mpr_softc *sc; struct mprsas_target *targ; struct mprsas_lun *lun; struct mpr_command *cm; uint8_t i, lba_byte, *ref_tag_addr; uint16_t eedp_flags; uint32_t mpi_control; sc = sassc->sc; MPR_FUNCTRACE(sc); mtx_assert(&sc->mpr_mtx, MA_OWNED); csio = &ccb->csio; KASSERT(csio->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in XPT_SCSI_IO\n", csio->ccb_h.target_id)); targ = &sassc->targets[csio->ccb_h.target_id]; mpr_dprint(sc, MPR_TRACE, "ccb %p target flag %x\n", ccb, targ->flags); if (targ->handle == 0x0) { mpr_dprint(sc, MPR_ERROR, "%s NULL handle for target %u\n", __func__, csio->ccb_h.target_id); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); return; } if (targ->flags & MPR_TARGET_FLAGS_RAID_COMPONENT) { mpr_dprint(sc, MPR_ERROR, "%s Raid component no SCSI IO " "supported %u\n", __func__, csio->ccb_h.target_id); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); return; } /* * Sometimes, it is possible to get a command that is not "In * Progress" and was actually aborted by the upper layer. Check for * this here and complete the command without error. */ if (mprsas_get_ccbstatus(ccb) != CAM_REQ_INPROG) { mpr_dprint(sc, MPR_TRACE, "%s Command is not in progress for " "target %u\n", __func__, csio->ccb_h.target_id); xpt_done(ccb); return; } /* * If devinfo is 0 this will be a volume. In that case don't tell CAM * that the volume has timed out. We want volumes to be enumerated * until they are deleted/removed, not just failed. */ if (targ->flags & MPRSAS_TARGET_INREMOVAL) { if (targ->devinfo == 0) mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); else mprsas_set_ccbstatus(ccb, CAM_SEL_TIMEOUT); xpt_done(ccb); return; } if ((sc->mpr_flags & MPR_FLAGS_SHUTDOWN) != 0) { mpr_dprint(sc, MPR_INFO, "%s shutting down\n", __func__); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); return; } /* * If target has a reset in progress, freeze the devq and return. The * devq will be released when the TM reset is finished. */ if (targ->flags & MPRSAS_TARGET_INRESET) { ccb->ccb_h.status = CAM_BUSY | CAM_DEV_QFRZN; mpr_dprint(sc, MPR_INFO, "%s: Freezing devq for target ID %d\n", __func__, targ->tid); xpt_freeze_devq(ccb->ccb_h.path, 1); xpt_done(ccb); return; } cm = mpr_alloc_command(sc); if (cm == NULL || (sc->mpr_flags & MPR_FLAGS_DIAGRESET)) { if (cm != NULL) { mpr_free_command(sc, cm); } if ((sassc->flags & MPRSAS_QUEUE_FROZEN) == 0) { xpt_freeze_simq(sassc->sim, 1); sassc->flags |= MPRSAS_QUEUE_FROZEN; } ccb->ccb_h.status &= ~CAM_SIM_QUEUED; ccb->ccb_h.status |= CAM_REQUEUE_REQ; xpt_done(ccb); return; } req = (MPI2_SCSI_IO_REQUEST *)cm->cm_req; bzero(req, sizeof(*req)); req->DevHandle = htole16(targ->handle); req->Function = MPI2_FUNCTION_SCSI_IO_REQUEST; req->MsgFlags = 0; req->SenseBufferLowAddress = htole32(cm->cm_sense_busaddr); req->SenseBufferLength = MPR_SENSE_LEN; req->SGLFlags = 0; req->ChainOffset = 0; req->SGLOffset0 = 24; /* 32bit word offset to the SGL */ req->SGLOffset1= 0; req->SGLOffset2= 0; req->SGLOffset3= 0; req->SkipCount = 0; req->DataLength = htole32(csio->dxfer_len); req->BidirectionalDataLength = 0; req->IoFlags = htole16(csio->cdb_len); req->EEDPFlags = 0; /* Note: BiDirectional transfers are not supported */ switch (csio->ccb_h.flags & CAM_DIR_MASK) { case CAM_DIR_IN: mpi_control = MPI2_SCSIIO_CONTROL_READ; cm->cm_flags |= MPR_CM_FLAGS_DATAIN; break; case CAM_DIR_OUT: mpi_control = MPI2_SCSIIO_CONTROL_WRITE; cm->cm_flags |= MPR_CM_FLAGS_DATAOUT; break; case CAM_DIR_NONE: default: mpi_control = MPI2_SCSIIO_CONTROL_NODATATRANSFER; break; } if (csio->cdb_len == 32) mpi_control |= 4 << MPI2_SCSIIO_CONTROL_ADDCDBLEN_SHIFT; /* * It looks like the hardware doesn't require an explicit tag * number for each transaction. SAM Task Management not supported * at the moment. */ switch (csio->tag_action) { case MSG_HEAD_OF_Q_TAG: mpi_control |= MPI2_SCSIIO_CONTROL_HEADOFQ; break; case MSG_ORDERED_Q_TAG: mpi_control |= MPI2_SCSIIO_CONTROL_ORDEREDQ; break; case MSG_ACA_TASK: mpi_control |= MPI2_SCSIIO_CONTROL_ACAQ; break; case CAM_TAG_ACTION_NONE: case MSG_SIMPLE_Q_TAG: default: mpi_control |= MPI2_SCSIIO_CONTROL_SIMPLEQ; break; } mpi_control |= sc->mapping_table[csio->ccb_h.target_id].TLR_bits; req->Control = htole32(mpi_control); if (MPR_SET_LUN(req->LUN, csio->ccb_h.target_lun) != 0) { mpr_free_command(sc, cm); mprsas_set_ccbstatus(ccb, CAM_LUN_INVALID); xpt_done(ccb); return; } if (csio->ccb_h.flags & CAM_CDB_POINTER) bcopy(csio->cdb_io.cdb_ptr, &req->CDB.CDB32[0], csio->cdb_len); else bcopy(csio->cdb_io.cdb_bytes, &req->CDB.CDB32[0],csio->cdb_len); req->IoFlags = htole16(csio->cdb_len); /* * Check if EEDP is supported and enabled. If it is then check if the * SCSI opcode could be using EEDP. If so, make sure the LUN exists and * is formatted for EEDP support. If all of this is true, set CDB up * for EEDP transfer. */ eedp_flags = op_code_prot[req->CDB.CDB32[0]]; if (sc->eedp_enabled && eedp_flags) { SLIST_FOREACH(lun, &targ->luns, lun_link) { if (lun->lun_id == csio->ccb_h.target_lun) { break; } } if ((lun != NULL) && (lun->eedp_formatted)) { req->EEDPBlockSize = htole16(lun->eedp_block_size); eedp_flags |= (MPI2_SCSIIO_EEDPFLAGS_INC_PRI_REFTAG | MPI2_SCSIIO_EEDPFLAGS_CHECK_REFTAG | MPI2_SCSIIO_EEDPFLAGS_CHECK_GUARD); req->EEDPFlags = htole16(eedp_flags); /* * If CDB less than 32, fill in Primary Ref Tag with * low 4 bytes of LBA. If CDB is 32, tag stuff is * already there. Also, set protection bit. FreeBSD * currently does not support CDBs bigger than 16, but * the code doesn't hurt, and will be here for the * future. */ if (csio->cdb_len != 32) { lba_byte = (csio->cdb_len == 16) ? 6 : 2; ref_tag_addr = (uint8_t *)&req->CDB.EEDP32. PrimaryReferenceTag; for (i = 0; i < 4; i++) { *ref_tag_addr = req->CDB.CDB32[lba_byte + i]; ref_tag_addr++; } req->CDB.EEDP32.PrimaryReferenceTag = htole32(req-> CDB.EEDP32.PrimaryReferenceTag); req->CDB.EEDP32.PrimaryApplicationTagMask = 0xFFFF; req->CDB.CDB32[1] = (req->CDB.CDB32[1] & 0x1F) | 0x20; } else { eedp_flags |= MPI2_SCSIIO_EEDPFLAGS_INC_PRI_APPTAG; req->EEDPFlags = htole16(eedp_flags); req->CDB.CDB32[10] = (req->CDB.CDB32[10] & 0x1F) | 0x20; } } } cm->cm_length = csio->dxfer_len; if (cm->cm_length != 0) { cm->cm_data = ccb; cm->cm_flags |= MPR_CM_FLAGS_USE_CCB; } else { cm->cm_data = NULL; } cm->cm_sge = &req->SGL; cm->cm_sglsize = (32 - 24) * 4; cm->cm_complete = mprsas_scsiio_complete; cm->cm_complete_data = ccb; cm->cm_targ = targ; cm->cm_lun = csio->ccb_h.target_lun; cm->cm_ccb = ccb; /* * If using FP desc type, need to set a bit in IoFlags (SCSI IO is 0) * and set descriptor type. */ if (targ->scsi_req_desc_type == MPI25_REQ_DESCRIPT_FLAGS_FAST_PATH_SCSI_IO) { req->IoFlags |= MPI25_SCSIIO_IOFLAGS_FAST_PATH; cm->cm_desc.FastPathSCSIIO.RequestFlags = MPI25_REQ_DESCRIPT_FLAGS_FAST_PATH_SCSI_IO; cm->cm_desc.FastPathSCSIIO.DevHandle = htole16(targ->handle); } else { cm->cm_desc.SCSIIO.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_SCSI_IO; cm->cm_desc.SCSIIO.DevHandle = htole16(targ->handle); } callout_reset_sbt(&cm->cm_callout, SBT_1MS * ccb->ccb_h.timeout, 0, mprsas_scsiio_timeout, cm, 0); targ->issued++; targ->outstanding++; TAILQ_INSERT_TAIL(&targ->commands, cm, cm_link); ccb->ccb_h.status |= CAM_SIM_QUEUED; mprsas_log_command(cm, MPR_XINFO, "%s cm %p ccb %p outstanding %u\n", __func__, cm, ccb, targ->outstanding); mpr_map_command(sc, cm); return; } static void mpr_response_code(struct mpr_softc *sc, u8 response_code) { char *desc; switch (response_code) { case MPI2_SCSITASKMGMT_RSP_TM_COMPLETE: desc = "task management request completed"; break; case MPI2_SCSITASKMGMT_RSP_INVALID_FRAME: desc = "invalid frame"; break; case MPI2_SCSITASKMGMT_RSP_TM_NOT_SUPPORTED: desc = "task management request not supported"; break; case MPI2_SCSITASKMGMT_RSP_TM_FAILED: desc = "task management request failed"; break; case MPI2_SCSITASKMGMT_RSP_TM_SUCCEEDED: desc = "task management request succeeded"; break; case MPI2_SCSITASKMGMT_RSP_TM_INVALID_LUN: desc = "invalid lun"; break; case 0xA: desc = "overlapped tag attempted"; break; case MPI2_SCSITASKMGMT_RSP_IO_QUEUED_ON_IOC: desc = "task queued, however not sent to target"; break; default: desc = "unknown"; break; } mpr_dprint(sc, MPR_XINFO, "response_code(0x%01x): %s\n", response_code, desc); } /** * mpr_sc_failed_io_info - translated non-succesfull SCSI_IO request */ static void mpr_sc_failed_io_info(struct mpr_softc *sc, struct ccb_scsiio *csio, Mpi2SCSIIOReply_t *mpi_reply, struct mprsas_target *targ) { u32 response_info; u8 *response_bytes; u16 ioc_status = le16toh(mpi_reply->IOCStatus) & MPI2_IOCSTATUS_MASK; u8 scsi_state = mpi_reply->SCSIState; u8 scsi_status = mpi_reply->SCSIStatus; char *desc_ioc_state = NULL; char *desc_scsi_status = NULL; char *desc_scsi_state = sc->tmp_string; u32 log_info = le32toh(mpi_reply->IOCLogInfo); if (log_info == 0x31170000) return; switch (ioc_status) { case MPI2_IOCSTATUS_SUCCESS: desc_ioc_state = "success"; break; case MPI2_IOCSTATUS_INVALID_FUNCTION: desc_ioc_state = "invalid function"; break; case MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR: desc_ioc_state = "scsi recovered error"; break; case MPI2_IOCSTATUS_SCSI_INVALID_DEVHANDLE: desc_ioc_state = "scsi invalid dev handle"; break; case MPI2_IOCSTATUS_SCSI_DEVICE_NOT_THERE: desc_ioc_state = "scsi device not there"; break; case MPI2_IOCSTATUS_SCSI_DATA_OVERRUN: desc_ioc_state = "scsi data overrun"; break; case MPI2_IOCSTATUS_SCSI_DATA_UNDERRUN: desc_ioc_state = "scsi data underrun"; break; case MPI2_IOCSTATUS_SCSI_IO_DATA_ERROR: desc_ioc_state = "scsi io data error"; break; case MPI2_IOCSTATUS_SCSI_PROTOCOL_ERROR: desc_ioc_state = "scsi protocol error"; break; case MPI2_IOCSTATUS_SCSI_TASK_TERMINATED: desc_ioc_state = "scsi task terminated"; break; case MPI2_IOCSTATUS_SCSI_RESIDUAL_MISMATCH: desc_ioc_state = "scsi residual mismatch"; break; case MPI2_IOCSTATUS_SCSI_TASK_MGMT_FAILED: desc_ioc_state = "scsi task mgmt failed"; break; case MPI2_IOCSTATUS_SCSI_IOC_TERMINATED: desc_ioc_state = "scsi ioc terminated"; break; case MPI2_IOCSTATUS_SCSI_EXT_TERMINATED: desc_ioc_state = "scsi ext terminated"; break; case MPI2_IOCSTATUS_EEDP_GUARD_ERROR: desc_ioc_state = "eedp guard error"; break; case MPI2_IOCSTATUS_EEDP_REF_TAG_ERROR: desc_ioc_state = "eedp ref tag error"; break; case MPI2_IOCSTATUS_EEDP_APP_TAG_ERROR: desc_ioc_state = "eedp app tag error"; break; default: desc_ioc_state = "unknown"; break; } switch (scsi_status) { case MPI2_SCSI_STATUS_GOOD: desc_scsi_status = "good"; break; case MPI2_SCSI_STATUS_CHECK_CONDITION: desc_scsi_status = "check condition"; break; case MPI2_SCSI_STATUS_CONDITION_MET: desc_scsi_status = "condition met"; break; case MPI2_SCSI_STATUS_BUSY: desc_scsi_status = "busy"; break; case MPI2_SCSI_STATUS_INTERMEDIATE: desc_scsi_status = "intermediate"; break; case MPI2_SCSI_STATUS_INTERMEDIATE_CONDMET: desc_scsi_status = "intermediate condmet"; break; case MPI2_SCSI_STATUS_RESERVATION_CONFLICT: desc_scsi_status = "reservation conflict"; break; case MPI2_SCSI_STATUS_COMMAND_TERMINATED: desc_scsi_status = "command terminated"; break; case MPI2_SCSI_STATUS_TASK_SET_FULL: desc_scsi_status = "task set full"; break; case MPI2_SCSI_STATUS_ACA_ACTIVE: desc_scsi_status = "aca active"; break; case MPI2_SCSI_STATUS_TASK_ABORTED: desc_scsi_status = "task aborted"; break; default: desc_scsi_status = "unknown"; break; } desc_scsi_state[0] = '\0'; if (!scsi_state) desc_scsi_state = " "; if (scsi_state & MPI2_SCSI_STATE_RESPONSE_INFO_VALID) strcat(desc_scsi_state, "response info "); if (scsi_state & MPI2_SCSI_STATE_TERMINATED) strcat(desc_scsi_state, "state terminated "); if (scsi_state & MPI2_SCSI_STATE_NO_SCSI_STATUS) strcat(desc_scsi_state, "no status "); if (scsi_state & MPI2_SCSI_STATE_AUTOSENSE_FAILED) strcat(desc_scsi_state, "autosense failed "); if (scsi_state & MPI2_SCSI_STATE_AUTOSENSE_VALID) strcat(desc_scsi_state, "autosense valid "); mpr_dprint(sc, MPR_XINFO, "\thandle(0x%04x), ioc_status(%s)(0x%04x)\n", le16toh(mpi_reply->DevHandle), desc_ioc_state, ioc_status); if (targ->encl_level_valid) { mpr_dprint(sc, MPR_XINFO, "At enclosure level %d, slot %d, " "connector name (%4s)\n", targ->encl_level, targ->encl_slot, targ->connector_name); } /* We can add more detail about underflow data here * TO-DO * */ mpr_dprint(sc, MPR_XINFO, "\tscsi_status(%s)(0x%02x), " "scsi_state(%s)(0x%02x)\n", desc_scsi_status, scsi_status, desc_scsi_state, scsi_state); if (sc->mpr_debug & MPR_XINFO && scsi_state & MPI2_SCSI_STATE_AUTOSENSE_VALID) { mpr_dprint(sc, MPR_XINFO, "-> Sense Buffer Data : Start :\n"); scsi_sense_print(csio); mpr_dprint(sc, MPR_XINFO, "-> Sense Buffer Data : End :\n"); } if (scsi_state & MPI2_SCSI_STATE_RESPONSE_INFO_VALID) { response_info = le32toh(mpi_reply->ResponseInfo); response_bytes = (u8 *)&response_info; mpr_response_code(sc,response_bytes[0]); } } static void mprsas_scsiio_complete(struct mpr_softc *sc, struct mpr_command *cm) { MPI2_SCSI_IO_REPLY *rep; union ccb *ccb; struct ccb_scsiio *csio; struct mprsas_softc *sassc; struct scsi_vpd_supported_page_list *vpd_list = NULL; u8 *TLR_bits, TLR_on; int dir = 0, i; u16 alloc_len; struct mprsas_target *target; target_id_t target_id; MPR_FUNCTRACE(sc); mpr_dprint(sc, MPR_TRACE, "cm %p SMID %u ccb %p reply %p outstanding %u\n", cm, cm->cm_desc.Default.SMID, cm->cm_ccb, cm->cm_reply, cm->cm_targ->outstanding); callout_stop(&cm->cm_callout); mtx_assert(&sc->mpr_mtx, MA_OWNED); sassc = sc->sassc; ccb = cm->cm_complete_data; csio = &ccb->csio; target_id = csio->ccb_h.target_id; rep = (MPI2_SCSI_IO_REPLY *)cm->cm_reply; /* * XXX KDM if the chain allocation fails, does it matter if we do * the sync and unload here? It is simpler to do it in every case, * assuming it doesn't cause problems. */ if (cm->cm_data != NULL) { if (cm->cm_flags & MPR_CM_FLAGS_DATAIN) dir = BUS_DMASYNC_POSTREAD; else if (cm->cm_flags & MPR_CM_FLAGS_DATAOUT) dir = BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(sc->buffer_dmat, cm->cm_dmamap, dir); bus_dmamap_unload(sc->buffer_dmat, cm->cm_dmamap); } cm->cm_targ->completed++; cm->cm_targ->outstanding--; TAILQ_REMOVE(&cm->cm_targ->commands, cm, cm_link); ccb->ccb_h.status &= ~(CAM_STATUS_MASK | CAM_SIM_QUEUED); if (cm->cm_state == MPR_CM_STATE_TIMEDOUT) { TAILQ_REMOVE(&cm->cm_targ->timedout_commands, cm, cm_recovery); if (cm->cm_reply != NULL) mprsas_log_command(cm, MPR_RECOVERY, "completed timedout cm %p ccb %p during recovery " "ioc %x scsi %x state %x xfer %u\n", cm, cm->cm_ccb, le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); else mprsas_log_command(cm, MPR_RECOVERY, "completed timedout cm %p ccb %p during recovery\n", cm, cm->cm_ccb); } else if (cm->cm_targ->tm != NULL) { if (cm->cm_reply != NULL) mprsas_log_command(cm, MPR_RECOVERY, "completed cm %p ccb %p during recovery " "ioc %x scsi %x state %x xfer %u\n", cm, cm->cm_ccb, le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); else mprsas_log_command(cm, MPR_RECOVERY, "completed cm %p ccb %p during recovery\n", cm, cm->cm_ccb); } else if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) { mprsas_log_command(cm, MPR_RECOVERY, "reset completed cm %p ccb %p\n", cm, cm->cm_ccb); } if ((cm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { /* * We ran into an error after we tried to map the command, * so we're getting a callback without queueing the command * to the hardware. So we set the status here, and it will * be retained below. We'll go through the "fast path", * because there can be no reply when we haven't actually * gone out to the hardware. */ mprsas_set_ccbstatus(ccb, CAM_REQUEUE_REQ); /* * Currently the only error included in the mask is * MPR_CM_FLAGS_CHAIN_FAILED, which means we're out of * chain frames. We need to freeze the queue until we get * a command that completed without this error, which will * hopefully have some chain frames attached that we can * use. If we wanted to get smarter about it, we would * only unfreeze the queue in this condition when we're * sure that we're getting some chain frames back. That's * probably unnecessary. */ if ((sassc->flags & MPRSAS_QUEUE_FROZEN) == 0) { xpt_freeze_simq(sassc->sim, 1); sassc->flags |= MPRSAS_QUEUE_FROZEN; mpr_dprint(sc, MPR_INFO, "Error sending command, " "freezing SIM queue\n"); } } /* * If this is a Start Stop Unit command and it was issued by the driver * during shutdown, decrement the refcount to account for all of the * commands that were sent. All SSU commands should be completed before * shutdown completes, meaning SSU_refcount will be 0 after SSU_started * is TRUE. */ if (sc->SSU_started && (csio->cdb_io.cdb_bytes[0] == START_STOP_UNIT)) { mpr_dprint(sc, MPR_INFO, "Decrementing SSU count.\n"); sc->SSU_refcount--; } /* Take the fast path to completion */ if (cm->cm_reply == NULL) { if (mprsas_get_ccbstatus(ccb) == CAM_REQ_INPROG) { if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) mprsas_set_ccbstatus(ccb, CAM_SCSI_BUS_RESET); else { mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); csio->scsi_status = SCSI_STATUS_OK; } if (sassc->flags & MPRSAS_QUEUE_FROZEN) { ccb->ccb_h.status |= CAM_RELEASE_SIMQ; sassc->flags &= ~MPRSAS_QUEUE_FROZEN; mpr_dprint(sc, MPR_XINFO, "Unfreezing SIM queue\n"); } } /* * There are two scenarios where the status won't be * CAM_REQ_CMP. The first is if MPR_CM_FLAGS_ERROR_MASK is * set, the second is in the MPR_FLAGS_DIAGRESET above. */ if (mprsas_get_ccbstatus(ccb) != CAM_REQ_CMP) { /* * Freeze the dev queue so that commands are * executed in the correct order after error * recovery. */ ccb->ccb_h.status |= CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, /*count*/ 1); } mpr_free_command(sc, cm); xpt_done(ccb); return; } mprsas_log_command(cm, MPR_XINFO, "ioc %x scsi %x state %x xfer %u\n", le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); switch (le16toh(rep->IOCStatus) & MPI2_IOCSTATUS_MASK) { case MPI2_IOCSTATUS_SCSI_DATA_UNDERRUN: csio->resid = cm->cm_length - le32toh(rep->TransferCount); /* FALLTHROUGH */ case MPI2_IOCSTATUS_SUCCESS: case MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR: if ((le16toh(rep->IOCStatus) & MPI2_IOCSTATUS_MASK) == MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR) mprsas_log_command(cm, MPR_XINFO, "recovered error\n"); /* Completion failed at the transport level. */ if (rep->SCSIState & (MPI2_SCSI_STATE_NO_SCSI_STATUS | MPI2_SCSI_STATE_TERMINATED)) { mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); break; } /* In a modern packetized environment, an autosense failure * implies that there's not much else that can be done to * recover the command. */ if (rep->SCSIState & MPI2_SCSI_STATE_AUTOSENSE_FAILED) { mprsas_set_ccbstatus(ccb, CAM_AUTOSENSE_FAIL); break; } /* * CAM doesn't care about SAS Response Info data, but if this is * the state check if TLR should be done. If not, clear the * TLR_bits for the target. */ if ((rep->SCSIState & MPI2_SCSI_STATE_RESPONSE_INFO_VALID) && ((le32toh(rep->ResponseInfo) & MPI2_SCSI_RI_MASK_REASONCODE) == MPR_SCSI_RI_INVALID_FRAME)) { sc->mapping_table[target_id].TLR_bits = (u8)MPI2_SCSIIO_CONTROL_NO_TLR; } /* * Intentionally override the normal SCSI status reporting * for these two cases. These are likely to happen in a * multi-initiator environment, and we want to make sure that * CAM retries these commands rather than fail them. */ if ((rep->SCSIStatus == MPI2_SCSI_STATUS_COMMAND_TERMINATED) || (rep->SCSIStatus == MPI2_SCSI_STATUS_TASK_ABORTED)) { mprsas_set_ccbstatus(ccb, CAM_REQ_ABORTED); break; } /* Handle normal status and sense */ csio->scsi_status = rep->SCSIStatus; if (rep->SCSIStatus == MPI2_SCSI_STATUS_GOOD) mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); else mprsas_set_ccbstatus(ccb, CAM_SCSI_STATUS_ERROR); if (rep->SCSIState & MPI2_SCSI_STATE_AUTOSENSE_VALID) { int sense_len, returned_sense_len; returned_sense_len = min(le32toh(rep->SenseCount), sizeof(struct scsi_sense_data)); if (returned_sense_len < csio->sense_len) csio->sense_resid = csio->sense_len - returned_sense_len; else csio->sense_resid = 0; sense_len = min(returned_sense_len, csio->sense_len - csio->sense_resid); bzero(&csio->sense_data, sizeof(csio->sense_data)); bcopy(cm->cm_sense, &csio->sense_data, sense_len); ccb->ccb_h.status |= CAM_AUTOSNS_VALID; } /* * Check if this is an INQUIRY command. If it's a VPD inquiry, * and it's page code 0 (Supported Page List), and there is * inquiry data, and this is for a sequential access device, and * the device is an SSP target, and TLR is supported by the * controller, turn the TLR_bits value ON if page 0x90 is * supported. */ if ((csio->cdb_io.cdb_bytes[0] == INQUIRY) && (csio->cdb_io.cdb_bytes[1] & SI_EVPD) && (csio->cdb_io.cdb_bytes[2] == SVPD_SUPPORTED_PAGE_LIST) && ((csio->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR) && (csio->data_ptr != NULL) && ((csio->data_ptr[0] & 0x1f) == T_SEQUENTIAL) && (sc->control_TLR) && (sc->mapping_table[target_id].device_info & MPI2_SAS_DEVICE_INFO_SSP_TARGET)) { vpd_list = (struct scsi_vpd_supported_page_list *) csio->data_ptr; TLR_bits = &sc->mapping_table[target_id].TLR_bits; *TLR_bits = (u8)MPI2_SCSIIO_CONTROL_NO_TLR; TLR_on = (u8)MPI2_SCSIIO_CONTROL_TLR_ON; alloc_len = ((u16)csio->cdb_io.cdb_bytes[3] << 8) + csio->cdb_io.cdb_bytes[4]; alloc_len -= csio->resid; for (i = 0; i < MIN(vpd_list->length, alloc_len); i++) { if (vpd_list->list[i] == 0x90) { *TLR_bits = TLR_on; break; } } } /* * If this is a SATA direct-access end device, mark it so that * a SCSI StartStopUnit command will be sent to it when the * driver is being shutdown. */ if ((csio->cdb_io.cdb_bytes[0] == INQUIRY) && ((csio->data_ptr[0] & 0x1f) == T_DIRECT) && (sc->mapping_table[target_id].device_info & MPI2_SAS_DEVICE_INFO_SATA_DEVICE) && ((sc->mapping_table[target_id].device_info & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_END_DEVICE)) { target = &sassc->targets[target_id]; target->supports_SSU = TRUE; mpr_dprint(sc, MPR_XINFO, "Target %d supports SSU\n", target_id); } break; case MPI2_IOCSTATUS_SCSI_INVALID_DEVHANDLE: case MPI2_IOCSTATUS_SCSI_DEVICE_NOT_THERE: /* * If devinfo is 0 this will be a volume. In that case don't * tell CAM that the volume is not there. We want volumes to * be enumerated until they are deleted/removed, not just * failed. */ if (cm->cm_targ->devinfo == 0) mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); else mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); break; case MPI2_IOCSTATUS_INVALID_SGL: mpr_print_scsiio_cmd(sc, cm); mprsas_set_ccbstatus(ccb, CAM_UNREC_HBA_ERROR); break; case MPI2_IOCSTATUS_SCSI_TASK_TERMINATED: /* * This is one of the responses that comes back when an I/O * has been aborted. If it is because of a timeout that we * initiated, just set the status to CAM_CMD_TIMEOUT. * Otherwise set it to CAM_REQ_ABORTED. The effect on the * command is the same (it gets retried, subject to the * retry counter), the only difference is what gets printed * on the console. */ if (cm->cm_state == MPR_CM_STATE_TIMEDOUT) mprsas_set_ccbstatus(ccb, CAM_CMD_TIMEOUT); else mprsas_set_ccbstatus(ccb, CAM_REQ_ABORTED); break; case MPI2_IOCSTATUS_SCSI_DATA_OVERRUN: /* resid is ignored for this condition */ csio->resid = 0; mprsas_set_ccbstatus(ccb, CAM_DATA_RUN_ERR); break; case MPI2_IOCSTATUS_SCSI_IOC_TERMINATED: case MPI2_IOCSTATUS_SCSI_EXT_TERMINATED: /* - * Since these are generally external (i.e. hopefully - * transient transport-related) errors, retry these without - * decrementing the retry count. + * These can sometimes be transient transport-related + * errors, and sometimes persistent drive-related errors. + * We used to retry these without decrementing the retry + * count by returning CAM_REQUEUE_REQ. Unfortunately, if + * we hit a persistent drive problem that returns one of + * these error codes, we would retry indefinitely. So, + * return CAM_REQ_CMP_ERROR so that we decrement the retry + * count and avoid infinite retries. We're taking the + * potential risk of flagging false failures in the event + * of a topology-related error (e.g. a SAS expander problem + * causes a command addressed to a drive to fail), but + * avoiding getting into an infinite retry loop. */ - mprsas_set_ccbstatus(ccb, CAM_REQUEUE_REQ); + mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); mprsas_log_command(cm, MPR_INFO, "terminated ioc %x scsi %x state %x xfer %u\n", le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); break; case MPI2_IOCSTATUS_INVALID_FUNCTION: case MPI2_IOCSTATUS_INTERNAL_ERROR: case MPI2_IOCSTATUS_INVALID_VPID: case MPI2_IOCSTATUS_INVALID_FIELD: case MPI2_IOCSTATUS_INVALID_STATE: case MPI2_IOCSTATUS_OP_STATE_NOT_SUPPORTED: case MPI2_IOCSTATUS_SCSI_IO_DATA_ERROR: case MPI2_IOCSTATUS_SCSI_PROTOCOL_ERROR: case MPI2_IOCSTATUS_SCSI_RESIDUAL_MISMATCH: case MPI2_IOCSTATUS_SCSI_TASK_MGMT_FAILED: default: mprsas_log_command(cm, MPR_XINFO, "completed ioc %x scsi %x state %x xfer %u\n", le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); csio->resid = cm->cm_length; mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); break; } mpr_sc_failed_io_info(sc, csio, rep, cm->cm_targ); if (sassc->flags & MPRSAS_QUEUE_FROZEN) { ccb->ccb_h.status |= CAM_RELEASE_SIMQ; sassc->flags &= ~MPRSAS_QUEUE_FROZEN; mpr_dprint(sc, MPR_XINFO, "Command completed, unfreezing SIM " "queue\n"); } if (mprsas_get_ccbstatus(ccb) != CAM_REQ_CMP) { ccb->ccb_h.status |= CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, /*count*/ 1); } mpr_free_command(sc, cm); xpt_done(ccb); } #if __FreeBSD_version >= 900026 static void mprsas_smpio_complete(struct mpr_softc *sc, struct mpr_command *cm) { MPI2_SMP_PASSTHROUGH_REPLY *rpl; MPI2_SMP_PASSTHROUGH_REQUEST *req; uint64_t sasaddr; union ccb *ccb; ccb = cm->cm_complete_data; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and SMP * commands require two S/G elements only. That should be handled * in the standard request size. */ if ((cm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x on SMP " "request!\n", __func__, cm->cm_flags); mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); goto bailout; } rpl = (MPI2_SMP_PASSTHROUGH_REPLY *)cm->cm_reply; if (rpl == NULL) { mpr_dprint(sc, MPR_ERROR, "%s: NULL cm_reply!\n", __func__); mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); goto bailout; } req = (MPI2_SMP_PASSTHROUGH_REQUEST *)cm->cm_req; sasaddr = le32toh(req->SASAddress.Low); sasaddr |= ((uint64_t)(le32toh(req->SASAddress.High))) << 32; if ((le16toh(rpl->IOCStatus) & MPI2_IOCSTATUS_MASK) != MPI2_IOCSTATUS_SUCCESS || rpl->SASStatus != MPI2_SASSTATUS_SUCCESS) { mpr_dprint(sc, MPR_XINFO, "%s: IOCStatus %04x SASStatus %02x\n", __func__, le16toh(rpl->IOCStatus), rpl->SASStatus); mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); goto bailout; } mpr_dprint(sc, MPR_XINFO, "%s: SMP request to SAS address %#jx " "completed successfully\n", __func__, (uintmax_t)sasaddr); if (ccb->smpio.smp_response[2] == SMP_FR_ACCEPTED) mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); else mprsas_set_ccbstatus(ccb, CAM_SMP_STATUS_ERROR); bailout: /* * We sync in both directions because we had DMAs in the S/G list * in both directions. */ bus_dmamap_sync(sc->buffer_dmat, cm->cm_dmamap, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->buffer_dmat, cm->cm_dmamap); mpr_free_command(sc, cm); xpt_done(ccb); } static void mprsas_send_smpcmd(struct mprsas_softc *sassc, union ccb *ccb, uint64_t sasaddr) { struct mpr_command *cm; uint8_t *request, *response; MPI2_SMP_PASSTHROUGH_REQUEST *req; struct mpr_softc *sc; struct sglist *sg; int error; sc = sassc->sc; sg = NULL; error = 0; #if (__FreeBSD_version >= 1000028) || \ ((__FreeBSD_version >= 902001) && (__FreeBSD_version < 1000000)) switch (ccb->ccb_h.flags & CAM_DATA_MASK) { case CAM_DATA_PADDR: case CAM_DATA_SG_PADDR: /* * XXX We don't yet support physical addresses here. */ mpr_dprint(sc, MPR_ERROR, "%s: physical addresses not " "supported\n", __func__); mprsas_set_ccbstatus(ccb, CAM_REQ_INVALID); xpt_done(ccb); return; case CAM_DATA_SG: /* * The chip does not support more than one buffer for the * request or response. */ if ((ccb->smpio.smp_request_sglist_cnt > 1) || (ccb->smpio.smp_response_sglist_cnt > 1)) { mpr_dprint(sc, MPR_ERROR, "%s: multiple request or response buffer segments " "not supported for SMP\n", __func__); mprsas_set_ccbstatus(ccb, CAM_REQ_INVALID); xpt_done(ccb); return; } /* * The CAM_SCATTER_VALID flag was originally implemented * for the XPT_SCSI_IO CCB, which only has one data pointer. * We have two. So, just take that flag to mean that we * might have S/G lists, and look at the S/G segment count * to figure out whether that is the case for each individual * buffer. */ if (ccb->smpio.smp_request_sglist_cnt != 0) { bus_dma_segment_t *req_sg; req_sg = (bus_dma_segment_t *)ccb->smpio.smp_request; request = (uint8_t *)(uintptr_t)req_sg[0].ds_addr; } else request = ccb->smpio.smp_request; if (ccb->smpio.smp_response_sglist_cnt != 0) { bus_dma_segment_t *rsp_sg; rsp_sg = (bus_dma_segment_t *)ccb->smpio.smp_response; response = (uint8_t *)(uintptr_t)rsp_sg[0].ds_addr; } else response = ccb->smpio.smp_response; break; case CAM_DATA_VADDR: request = ccb->smpio.smp_request; response = ccb->smpio.smp_response; break; default: mprsas_set_ccbstatus(ccb, CAM_REQ_INVALID); xpt_done(ccb); return; } #else /* __FreeBSD_version < 1000028 */ /* * XXX We don't yet support physical addresses here. */ if (ccb->ccb_h.flags & (CAM_DATA_PHYS|CAM_SG_LIST_PHYS)) { mpr_dprint(sc, MPR_ERROR, "%s: physical addresses not " "supported\n", __func__); mprsas_set_ccbstatus(ccb, CAM_REQ_INVALID); xpt_done(ccb); return; } /* * If the user wants to send an S/G list, check to make sure they * have single buffers. */ if (ccb->ccb_h.flags & CAM_SCATTER_VALID) { /* * The chip does not support more than one buffer for the * request or response. */ if ((ccb->smpio.smp_request_sglist_cnt > 1) || (ccb->smpio.smp_response_sglist_cnt > 1)) { mpr_dprint(sc, MPR_ERROR, "%s: multiple request or " "response buffer segments not supported for SMP\n", __func__); mprsas_set_ccbstatus(ccb, CAM_REQ_INVALID); xpt_done(ccb); return; } /* * The CAM_SCATTER_VALID flag was originally implemented * for the XPT_SCSI_IO CCB, which only has one data pointer. * We have two. So, just take that flag to mean that we * might have S/G lists, and look at the S/G segment count * to figure out whether that is the case for each individual * buffer. */ if (ccb->smpio.smp_request_sglist_cnt != 0) { bus_dma_segment_t *req_sg; req_sg = (bus_dma_segment_t *)ccb->smpio.smp_request; request = (uint8_t *)(uintptr_t)req_sg[0].ds_addr; } else request = ccb->smpio.smp_request; if (ccb->smpio.smp_response_sglist_cnt != 0) { bus_dma_segment_t *rsp_sg; rsp_sg = (bus_dma_segment_t *)ccb->smpio.smp_response; response = (uint8_t *)(uintptr_t)rsp_sg[0].ds_addr; } else response = ccb->smpio.smp_response; } else { request = ccb->smpio.smp_request; response = ccb->smpio.smp_response; } #endif /* __FreeBSD_version < 1000028 */ cm = mpr_alloc_command(sc); if (cm == NULL) { mpr_dprint(sc, MPR_ERROR, "%s: cannot allocate command\n", __func__); mprsas_set_ccbstatus(ccb, CAM_RESRC_UNAVAIL); xpt_done(ccb); return; } req = (MPI2_SMP_PASSTHROUGH_REQUEST *)cm->cm_req; bzero(req, sizeof(*req)); req->Function = MPI2_FUNCTION_SMP_PASSTHROUGH; /* Allow the chip to use any route to this SAS address. */ req->PhysicalPort = 0xff; req->RequestDataLength = htole16(ccb->smpio.smp_request_len); req->SGLFlags = MPI2_SGLFLAGS_SYSTEM_ADDRESS_SPACE | MPI2_SGLFLAGS_SGL_TYPE_MPI; mpr_dprint(sc, MPR_XINFO, "%s: sending SMP request to SAS address " "%#jx\n", __func__, (uintmax_t)sasaddr); mpr_init_sge(cm, req, &req->SGL); /* * Set up a uio to pass into mpr_map_command(). This allows us to * do one map command, and one busdma call in there. */ cm->cm_uio.uio_iov = cm->cm_iovec; cm->cm_uio.uio_iovcnt = 2; cm->cm_uio.uio_segflg = UIO_SYSSPACE; /* * The read/write flag isn't used by busdma, but set it just in * case. This isn't exactly accurate, either, since we're going in * both directions. */ cm->cm_uio.uio_rw = UIO_WRITE; cm->cm_iovec[0].iov_base = request; cm->cm_iovec[0].iov_len = le16toh(req->RequestDataLength); cm->cm_iovec[1].iov_base = response; cm->cm_iovec[1].iov_len = ccb->smpio.smp_response_len; cm->cm_uio.uio_resid = cm->cm_iovec[0].iov_len + cm->cm_iovec[1].iov_len; /* * Trigger a warning message in mpr_data_cb() for the user if we * wind up exceeding two S/G segments. The chip expects one * segment for the request and another for the response. */ cm->cm_max_segs = 2; cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE; cm->cm_complete = mprsas_smpio_complete; cm->cm_complete_data = ccb; /* * Tell the mapping code that we're using a uio, and that this is * an SMP passthrough request. There is a little special-case * logic there (in mpr_data_cb()) to handle the bidirectional * transfer. */ cm->cm_flags |= MPR_CM_FLAGS_USE_UIO | MPR_CM_FLAGS_SMP_PASS | MPR_CM_FLAGS_DATAIN | MPR_CM_FLAGS_DATAOUT; /* The chip data format is little endian. */ req->SASAddress.High = htole32(sasaddr >> 32); req->SASAddress.Low = htole32(sasaddr); /* * XXX Note that we don't have a timeout/abort mechanism here. * From the manual, it looks like task management requests only * work for SCSI IO and SATA passthrough requests. We may need to * have a mechanism to retry requests in the event of a chip reset * at least. Hopefully the chip will insure that any errors short * of that are relayed back to the driver. */ error = mpr_map_command(sc, cm); if ((error != 0) && (error != EINPROGRESS)) { mpr_dprint(sc, MPR_ERROR, "%s: error %d returned from " "mpr_map_command()\n", __func__, error); goto bailout_error; } return; bailout_error: mpr_free_command(sc, cm); mprsas_set_ccbstatus(ccb, CAM_RESRC_UNAVAIL); xpt_done(ccb); return; } static void mprsas_action_smpio(struct mprsas_softc *sassc, union ccb *ccb) { struct mpr_softc *sc; struct mprsas_target *targ; uint64_t sasaddr = 0; sc = sassc->sc; /* * Make sure the target exists. */ KASSERT(ccb->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in XPT_SMP_IO\n", ccb->ccb_h.target_id)); targ = &sassc->targets[ccb->ccb_h.target_id]; if (targ->handle == 0x0) { mpr_dprint(sc, MPR_ERROR, "%s: target %d does not exist!\n", __func__, ccb->ccb_h.target_id); mprsas_set_ccbstatus(ccb, CAM_SEL_TIMEOUT); xpt_done(ccb); return; } /* * If this device has an embedded SMP target, we'll talk to it * directly. * figure out what the expander's address is. */ if ((targ->devinfo & MPI2_SAS_DEVICE_INFO_SMP_TARGET) != 0) sasaddr = targ->sasaddr; /* * If we don't have a SAS address for the expander yet, try * grabbing it from the page 0x83 information cached in the * transport layer for this target. LSI expanders report the * expander SAS address as the port-associated SAS address in * Inquiry VPD page 0x83. Maxim expanders don't report it in page * 0x83. * * XXX KDM disable this for now, but leave it commented out so that * it is obvious that this is another possible way to get the SAS * address. * * The parent handle method below is a little more reliable, and * the other benefit is that it works for devices other than SES * devices. So you can send a SMP request to a da(4) device and it * will get routed to the expander that device is attached to. * (Assuming the da(4) device doesn't contain an SMP target...) */ #if 0 if (sasaddr == 0) sasaddr = xpt_path_sas_addr(ccb->ccb_h.path); #endif /* * If we still don't have a SAS address for the expander, look for * the parent device of this device, which is probably the expander. */ if (sasaddr == 0) { #ifdef OLD_MPR_PROBE struct mprsas_target *parent_target; #endif if (targ->parent_handle == 0x0) { mpr_dprint(sc, MPR_ERROR, "%s: handle %d does not have " "a valid parent handle!\n", __func__, targ->handle); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } #ifdef OLD_MPR_PROBE parent_target = mprsas_find_target_by_handle(sassc, 0, targ->parent_handle); if (parent_target == NULL) { mpr_dprint(sc, MPR_ERROR, "%s: handle %d does not have " "a valid parent target!\n", __func__, targ->handle); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } if ((parent_target->devinfo & MPI2_SAS_DEVICE_INFO_SMP_TARGET) == 0) { mpr_dprint(sc, MPR_ERROR, "%s: handle %d parent %d " "does not have an SMP target!\n", __func__, targ->handle, parent_target->handle); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } sasaddr = parent_target->sasaddr; #else /* OLD_MPR_PROBE */ if ((targ->parent_devinfo & MPI2_SAS_DEVICE_INFO_SMP_TARGET) == 0) { mpr_dprint(sc, MPR_ERROR, "%s: handle %d parent %d " "does not have an SMP target!\n", __func__, targ->handle, targ->parent_handle); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } if (targ->parent_sasaddr == 0x0) { mpr_dprint(sc, MPR_ERROR, "%s: handle %d parent handle " "%d does not have a valid SAS address!\n", __func__, targ->handle, targ->parent_handle); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } sasaddr = targ->parent_sasaddr; #endif /* OLD_MPR_PROBE */ } if (sasaddr == 0) { mpr_dprint(sc, MPR_INFO, "%s: unable to find SAS address for " "handle %d\n", __func__, targ->handle); mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } mprsas_send_smpcmd(sassc, ccb, sasaddr); return; bailout: xpt_done(ccb); } #endif //__FreeBSD_version >= 900026 static void mprsas_action_resetdev(struct mprsas_softc *sassc, union ccb *ccb) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mpr_softc *sc; struct mpr_command *tm; struct mprsas_target *targ; MPR_FUNCTRACE(sassc->sc); mtx_assert(&sassc->sc->mpr_mtx, MA_OWNED); KASSERT(ccb->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in XPT_RESET_DEV\n", ccb->ccb_h.target_id)); sc = sassc->sc; tm = mpr_alloc_command(sc); if (tm == NULL) { mpr_dprint(sc, MPR_ERROR, "command alloc failure in mprsas_action_resetdev\n"); mprsas_set_ccbstatus(ccb, CAM_RESRC_UNAVAIL); xpt_done(ccb); return; } targ = &sassc->targets[ccb->ccb_h.target_id]; req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; req->DevHandle = htole16(targ->handle); req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET; /* SAS Hard Link Reset / SATA Link Reset */ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET; tm->cm_data = NULL; tm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; tm->cm_complete = mprsas_resetdev_complete; tm->cm_complete_data = ccb; mpr_dprint(sc, MPR_INFO, "%s: Sending reset for target ID %d\n", __func__, targ->tid); tm->cm_targ = targ; targ->flags |= MPRSAS_TARGET_INRESET; mpr_map_command(sc, tm); } static void mprsas_resetdev_complete(struct mpr_softc *sc, struct mpr_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *resp; union ccb *ccb; MPR_FUNCTRACE(sc); mtx_assert(&sc->mpr_mtx, MA_OWNED); resp = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; ccb = tm->cm_complete_data; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for reset of " "handle %#04x! This should not happen!\n", __func__, tm->cm_flags, req->DevHandle); mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); goto bailout; } mpr_dprint(sc, MPR_XINFO, "%s: IOCStatus = 0x%x ResponseCode = 0x%x\n", __func__, le16toh(resp->IOCStatus), le32toh(resp->ResponseCode)); if (le32toh(resp->ResponseCode) == MPI2_SCSITASKMGMT_RSP_TM_COMPLETE) { mprsas_set_ccbstatus(ccb, CAM_REQ_CMP); mprsas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid, CAM_LUN_WILDCARD); } else mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); bailout: mprsas_free_tm(sc, tm); xpt_done(ccb); } static void mprsas_poll(struct cam_sim *sim) { struct mprsas_softc *sassc; sassc = cam_sim_softc(sim); if (sassc->sc->mpr_debug & MPR_TRACE) { /* frequent debug messages during a panic just slow * everything down too much. */ mpr_dprint(sassc->sc, MPR_XINFO, "%s clearing MPR_TRACE\n", __func__); sassc->sc->mpr_debug &= ~MPR_TRACE; } mpr_intr_locked(sassc->sc); } static void mprsas_async(void *callback_arg, uint32_t code, struct cam_path *path, void *arg) { struct mpr_softc *sc; sc = (struct mpr_softc *)callback_arg; switch (code) { #if (__FreeBSD_version >= 1000006) || \ ((__FreeBSD_version >= 901503) && (__FreeBSD_version < 1000000)) case AC_ADVINFO_CHANGED: { struct mprsas_target *target; struct mprsas_softc *sassc; struct scsi_read_capacity_data_long rcap_buf; struct ccb_dev_advinfo cdai; struct mprsas_lun *lun; lun_id_t lunid; int found_lun; uintptr_t buftype; buftype = (uintptr_t)arg; found_lun = 0; sassc = sc->sassc; /* * We're only interested in read capacity data changes. */ if (buftype != CDAI_TYPE_RCAPLONG) break; /* * See the comment in mpr_attach_sas() for a detailed * explanation. In these versions of FreeBSD we register * for all events and filter out the events that don't * apply to us. */ #if (__FreeBSD_version < 1000703) || \ ((__FreeBSD_version >= 1100000) && (__FreeBSD_version < 1100002)) if (xpt_path_path_id(path) != sassc->sim->path_id) break; #endif /* * We should have a handle for this, but check to make sure. */ KASSERT(xpt_path_target_id(path) < sassc->maxtargets, ("Target %d out of bounds in mprsas_async\n", xpt_path_target_id(path))); target = &sassc->targets[xpt_path_target_id(path)]; if (target->handle == 0) break; lunid = xpt_path_lun_id(path); SLIST_FOREACH(lun, &target->luns, lun_link) { if (lun->lun_id == lunid) { found_lun = 1; break; } } if (found_lun == 0) { lun = malloc(sizeof(struct mprsas_lun), M_MPR, M_NOWAIT | M_ZERO); if (lun == NULL) { mpr_dprint(sc, MPR_ERROR, "Unable to alloc " "LUN for EEDP support.\n"); break; } lun->lun_id = lunid; SLIST_INSERT_HEAD(&target->luns, lun, lun_link); } bzero(&rcap_buf, sizeof(rcap_buf)); xpt_setup_ccb(&cdai.ccb_h, path, CAM_PRIORITY_NORMAL); cdai.ccb_h.func_code = XPT_DEV_ADVINFO; cdai.ccb_h.flags = CAM_DIR_IN; cdai.buftype = CDAI_TYPE_RCAPLONG; #if (__FreeBSD_version >= 1100061) || \ ((__FreeBSD_version >= 1001510) && (__FreeBSD_version < 1100000)) cdai.flags = CDAI_FLAG_NONE; #else cdai.flags = 0; #endif cdai.bufsiz = sizeof(rcap_buf); cdai.buf = (uint8_t *)&rcap_buf; xpt_action((union ccb *)&cdai); if ((cdai.ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(cdai.ccb_h.path, 0, 0, 0, FALSE); if ((mprsas_get_ccbstatus((union ccb *)&cdai) == CAM_REQ_CMP) && (rcap_buf.prot & SRC16_PROT_EN)) { lun->eedp_formatted = TRUE; lun->eedp_block_size = scsi_4btoul(rcap_buf.length); } else { lun->eedp_formatted = FALSE; lun->eedp_block_size = 0; } break; } #endif case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; /* * See the comment in mpr_attach_sas() for a detailed * explanation. In these versions of FreeBSD we register * for all events and filter out the events that don't * apply to us. */ #if (__FreeBSD_version < 1000703) || \ ((__FreeBSD_version >= 1100000) && (__FreeBSD_version < 1100002)) if (xpt_path_path_id(path) != sc->sassc->sim->path_id) break; #endif cgd = arg; #if (__FreeBSD_version < 901503) || \ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006)) mprsas_check_eedp(sc, path, cgd); #endif break; } default: break; } } #if (__FreeBSD_version < 901503) || \ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006)) static void mprsas_check_eedp(struct mpr_softc *sc, struct cam_path *path, struct ccb_getdev *cgd) { struct mprsas_softc *sassc = sc->sassc; struct ccb_scsiio *csio; struct scsi_read_capacity_16 *scsi_cmd; struct scsi_read_capacity_eedp *rcap_buf; path_id_t pathid; target_id_t targetid; lun_id_t lunid; union ccb *ccb; struct cam_path *local_path; struct mprsas_target *target; struct mprsas_lun *lun; uint8_t found_lun; char path_str[64]; pathid = cam_sim_path(sassc->sim); targetid = xpt_path_target_id(path); lunid = xpt_path_lun_id(path); KASSERT(targetid < sassc->maxtargets, ("Target %d out of bounds in mprsas_check_eedp\n", targetid)); target = &sassc->targets[targetid]; if (target->handle == 0x0) return; /* * Determine if the device is EEDP capable. * * If this flag is set in the inquiry data, the device supports * protection information, and must support the 16 byte read capacity * command, otherwise continue without sending read cap 16 */ if ((cgd->inq_data.spc3_flags & SPC3_SID_PROTECT) == 0) return; /* * Issue a READ CAPACITY 16 command. This info is used to determine if * the LUN is formatted for EEDP support. */ ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { mpr_dprint(sc, MPR_ERROR, "Unable to alloc CCB for EEDP " "support.\n"); return; } if (xpt_create_path(&local_path, xpt_periph, pathid, targetid, lunid) != CAM_REQ_CMP) { mpr_dprint(sc, MPR_ERROR, "Unable to create path for EEDP " "support\n"); xpt_free_ccb(ccb); return; } /* * If LUN is already in list, don't create a new one. */ found_lun = FALSE; SLIST_FOREACH(lun, &target->luns, lun_link) { if (lun->lun_id == lunid) { found_lun = TRUE; break; } } if (!found_lun) { lun = malloc(sizeof(struct mprsas_lun), M_MPR, M_NOWAIT | M_ZERO); if (lun == NULL) { mpr_dprint(sc, MPR_ERROR, "Unable to alloc LUN for " "EEDP support.\n"); xpt_free_path(local_path); xpt_free_ccb(ccb); return; } lun->lun_id = lunid; SLIST_INSERT_HEAD(&target->luns, lun, lun_link); } xpt_path_string(local_path, path_str, sizeof(path_str)); mpr_dprint(sc, MPR_INFO, "Sending read cap: path %s handle %d\n", path_str, target->handle); /* * Issue a READ CAPACITY 16 command for the LUN. The * mprsas_read_cap_done function will load the read cap info into the * LUN struct. */ rcap_buf = malloc(sizeof(struct scsi_read_capacity_eedp), M_MPR, M_NOWAIT | M_ZERO); if (rcap_buf == NULL) { mpr_dprint(sc, MPR_ERROR, "Unable to alloc read capacity " "buffer for EEDP support.\n"); xpt_free_path(ccb->ccb_h.path); xpt_free_ccb(ccb); return; } xpt_setup_ccb(&ccb->ccb_h, local_path, CAM_PRIORITY_XPT); csio = &ccb->csio; csio->ccb_h.func_code = XPT_SCSI_IO; csio->ccb_h.flags = CAM_DIR_IN; csio->ccb_h.retry_count = 4; csio->ccb_h.cbfcnp = mprsas_read_cap_done; csio->ccb_h.timeout = 60000; csio->data_ptr = (uint8_t *)rcap_buf; csio->dxfer_len = sizeof(struct scsi_read_capacity_eedp); csio->sense_len = MPR_SENSE_LEN; csio->cdb_len = sizeof(*scsi_cmd); csio->tag_action = MSG_SIMPLE_Q_TAG; scsi_cmd = (struct scsi_read_capacity_16 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = 0x9E; scsi_cmd->service_action = SRC16_SERVICE_ACTION; ((uint8_t *)scsi_cmd)[13] = sizeof(struct scsi_read_capacity_eedp); ccb->ccb_h.ppriv_ptr1 = sassc; xpt_action(ccb); } static void mprsas_read_cap_done(struct cam_periph *periph, union ccb *done_ccb) { struct mprsas_softc *sassc; struct mprsas_target *target; struct mprsas_lun *lun; struct scsi_read_capacity_eedp *rcap_buf; if (done_ccb == NULL) return; /* Driver need to release devq, it Scsi command is * generated by driver internally. * Currently there is a single place where driver * calls scsi command internally. In future if driver * calls more scsi command internally, it needs to release * devq internally, since those command will not go back to * cam_periph. */ if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) ) { done_ccb->ccb_h.status &= ~CAM_DEV_QFRZN; xpt_release_devq(done_ccb->ccb_h.path, /*count*/ 1, /*run_queue*/TRUE); } rcap_buf = (struct scsi_read_capacity_eedp *)done_ccb->csio.data_ptr; /* * Get the LUN ID for the path and look it up in the LUN list for the * target. */ sassc = (struct mprsas_softc *)done_ccb->ccb_h.ppriv_ptr1; KASSERT(done_ccb->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in mprsas_read_cap_done\n", done_ccb->ccb_h.target_id)); target = &sassc->targets[done_ccb->ccb_h.target_id]; SLIST_FOREACH(lun, &target->luns, lun_link) { if (lun->lun_id != done_ccb->ccb_h.target_lun) continue; /* * Got the LUN in the target's LUN list. Fill it in with EEDP * info. If the READ CAP 16 command had some SCSI error (common * if command is not supported), mark the lun as not supporting * EEDP and set the block size to 0. */ if ((mprsas_get_ccbstatus(done_ccb) != CAM_REQ_CMP) || (done_ccb->csio.scsi_status != SCSI_STATUS_OK)) { lun->eedp_formatted = FALSE; lun->eedp_block_size = 0; break; } if (rcap_buf->protect & 0x01) { mpr_dprint(sassc->sc, MPR_INFO, "LUN %d for target ID " "%d is formatted for EEDP support.\n", done_ccb->ccb_h.target_lun, done_ccb->ccb_h.target_id); lun->eedp_formatted = TRUE; lun->eedp_block_size = scsi_4btoul(rcap_buf->length); } break; } // Finished with this CCB and path. free(rcap_buf, M_MPR); xpt_free_path(done_ccb->ccb_h.path); xpt_free_ccb(done_ccb); } #endif /* (__FreeBSD_version < 901503) || \ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006)) */ void mprsas_prepare_for_tm(struct mpr_softc *sc, struct mpr_command *tm, struct mprsas_target *target, lun_id_t lun_id) { union ccb *ccb; path_id_t path_id; /* * Set the INRESET flag for this target so that no I/O will be sent to * the target until the reset has completed. If an I/O request does * happen, the devq will be frozen. The CCB holds the path which is * used to release the devq. The devq is released and the CCB is freed * when the TM completes. */ ccb = xpt_alloc_ccb_nowait(); if (ccb) { path_id = cam_sim_path(sc->sassc->sim); if (xpt_create_path(&ccb->ccb_h.path, xpt_periph, path_id, target->tid, lun_id) != CAM_REQ_CMP) { xpt_free_ccb(ccb); } else { tm->cm_ccb = ccb; tm->cm_targ = target; target->flags |= MPRSAS_TARGET_INRESET; } } } int mprsas_startup(struct mpr_softc *sc) { /* * Send the port enable message and set the wait_for_port_enable flag. * This flag helps to keep the simq frozen until all discovery events * are processed. */ sc->wait_for_port_enable = 1; mprsas_send_portenable(sc); return (0); } static int mprsas_send_portenable(struct mpr_softc *sc) { MPI2_PORT_ENABLE_REQUEST *request; struct mpr_command *cm; MPR_FUNCTRACE(sc); if ((cm = mpr_alloc_command(sc)) == NULL) return (EBUSY); request = (MPI2_PORT_ENABLE_REQUEST *)cm->cm_req; request->Function = MPI2_FUNCTION_PORT_ENABLE; request->MsgFlags = 0; request->VP_ID = 0; cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE; cm->cm_complete = mprsas_portenable_complete; cm->cm_data = NULL; cm->cm_sge = NULL; mpr_map_command(sc, cm); mpr_dprint(sc, MPR_XINFO, "mpr_send_portenable finished cm %p req %p complete %p\n", cm, cm->cm_req, cm->cm_complete); return (0); } static void mprsas_portenable_complete(struct mpr_softc *sc, struct mpr_command *cm) { MPI2_PORT_ENABLE_REPLY *reply; struct mprsas_softc *sassc; MPR_FUNCTRACE(sc); sassc = sc->sassc; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * port enable commands don't have S/G lists. */ if ((cm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) { mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for port enable! " "This should not happen!\n", __func__, cm->cm_flags); } reply = (MPI2_PORT_ENABLE_REPLY *)cm->cm_reply; if (reply == NULL) mpr_dprint(sc, MPR_FAULT, "Portenable NULL reply\n"); else if (le16toh(reply->IOCStatus & MPI2_IOCSTATUS_MASK) != MPI2_IOCSTATUS_SUCCESS) mpr_dprint(sc, MPR_FAULT, "Portenable failed\n"); mpr_free_command(sc, cm); if (sc->mpr_ich.ich_arg != NULL) { mpr_dprint(sc, MPR_XINFO, "disestablish config intrhook\n"); config_intrhook_disestablish(&sc->mpr_ich); sc->mpr_ich.ich_arg = NULL; } /* * Done waiting for port enable to complete. Decrement the refcount. * If refcount is 0, discovery is complete and a rescan of the bus can * take place. */ sc->wait_for_port_enable = 0; sc->port_enable_complete = 1; wakeup(&sc->port_enable_complete); mprsas_startup_decrement(sassc); } int mprsas_check_id(struct mprsas_softc *sassc, int id) { struct mpr_softc *sc = sassc->sc; char *ids; char *name; ids = &sc->exclude_ids[0]; while((name = strsep(&ids, ",")) != NULL) { if (name[0] == '\0') continue; if (strtol(name, NULL, 0) == (long)id) return (1); } return (0); } void mprsas_realloc_targets(struct mpr_softc *sc, int maxtargets) { struct mprsas_softc *sassc; struct mprsas_lun *lun, *lun_tmp; struct mprsas_target *targ; int i; sassc = sc->sassc; /* * The number of targets is based on IOC Facts, so free all of * the allocated LUNs for each target and then the target buffer * itself. */ for (i=0; i< maxtargets; i++) { targ = &sassc->targets[i]; SLIST_FOREACH_SAFE(lun, &targ->luns, lun_link, lun_tmp) { free(lun, M_MPR); } } free(sassc->targets, M_MPR); sassc->targets = malloc(sizeof(struct mprsas_target) * maxtargets, M_MPR, M_WAITOK|M_ZERO); if (!sassc->targets) { panic("%s failed to alloc targets with error %d\n", __func__, ENOMEM); } } Index: user/ngie/detangle-rc/sys/dev/mps/mps_sas.c =================================================================== --- user/ngie/detangle-rc/sys/dev/mps/mps_sas.c (revision 299126) +++ user/ngie/detangle-rc/sys/dev/mps/mps_sas.c (revision 299127) @@ -1,3689 +1,3698 @@ /*- * Copyright (c) 2009 Yahoo! Inc. * Copyright (c) 2011-2015 LSI Corp. * Copyright (c) 2013-2015 Avago Technologies * 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. * * Avago Technologies (LSI) MPT-Fusion Host Adapter FreeBSD * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); /* Communications core for Avago Technologies (LSI) MPT2 */ /* TODO Move headers to mpsvar */ #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 #if __FreeBSD_version >= 900026 #include #endif #include #include #include #include #include #include #include #include #include #include #include #define MPSSAS_DISCOVERY_TIMEOUT 20 #define MPSSAS_MAX_DISCOVERY_TIMEOUTS 10 /* 200 seconds */ /* * static array to check SCSI OpCode for EEDP protection bits */ #define PRO_R MPI2_SCSIIO_EEDPFLAGS_CHECK_REMOVE_OP #define PRO_W MPI2_SCSIIO_EEDPFLAGS_INSERT_OP #define PRO_V MPI2_SCSIIO_EEDPFLAGS_INSERT_OP static uint8_t op_code_prot[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PRO_W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V, 0, 0, 0, PRO_W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; MALLOC_DEFINE(M_MPSSAS, "MPSSAS", "MPS SAS memory"); static void mpssas_remove_device(struct mps_softc *, struct mps_command *); static void mpssas_remove_complete(struct mps_softc *, struct mps_command *); static void mpssas_action(struct cam_sim *sim, union ccb *ccb); static void mpssas_poll(struct cam_sim *sim); static int mpssas_send_abort(struct mps_softc *sc, struct mps_command *tm, struct mps_command *cm); static void mpssas_scsiio_timeout(void *data); static void mpssas_abort_complete(struct mps_softc *sc, struct mps_command *cm); static void mpssas_direct_drive_io(struct mpssas_softc *sassc, struct mps_command *cm, union ccb *ccb); static void mpssas_action_scsiio(struct mpssas_softc *, union ccb *); static void mpssas_scsiio_complete(struct mps_softc *, struct mps_command *); static void mpssas_action_resetdev(struct mpssas_softc *, union ccb *); #if __FreeBSD_version >= 900026 static void mpssas_smpio_complete(struct mps_softc *sc, struct mps_command *cm); static void mpssas_send_smpcmd(struct mpssas_softc *sassc, union ccb *ccb, uint64_t sasaddr); static void mpssas_action_smpio(struct mpssas_softc *sassc, union ccb *ccb); #endif //FreeBSD_version >= 900026 static void mpssas_resetdev_complete(struct mps_softc *, struct mps_command *); static void mpssas_async(void *callback_arg, uint32_t code, struct cam_path *path, void *arg); #if (__FreeBSD_version < 901503) || \ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006)) static void mpssas_check_eedp(struct mps_softc *sc, struct cam_path *path, struct ccb_getdev *cgd); static void mpssas_read_cap_done(struct cam_periph *periph, union ccb *done_ccb); #endif static int mpssas_send_portenable(struct mps_softc *sc); static void mpssas_portenable_complete(struct mps_softc *sc, struct mps_command *cm); struct mpssas_target * mpssas_find_target_by_handle(struct mpssas_softc *sassc, int start, uint16_t handle) { struct mpssas_target *target; int i; for (i = start; i < sassc->maxtargets; i++) { target = &sassc->targets[i]; if (target->handle == handle) return (target); } return (NULL); } /* we need to freeze the simq during attach and diag reset, to avoid failing * commands before device handles have been found by discovery. Since * discovery involves reading config pages and possibly sending commands, * discovery actions may continue even after we receive the end of discovery * event, so refcount discovery actions instead of assuming we can unfreeze * the simq when we get the event. */ void mpssas_startup_increment(struct mpssas_softc *sassc) { MPS_FUNCTRACE(sassc->sc); if ((sassc->flags & MPSSAS_IN_STARTUP) != 0) { if (sassc->startup_refcount++ == 0) { /* just starting, freeze the simq */ mps_dprint(sassc->sc, MPS_INIT, "%s freezing simq\n", __func__); #if __FreeBSD_version >= 1000039 xpt_hold_boot(); #endif xpt_freeze_simq(sassc->sim, 1); } mps_dprint(sassc->sc, MPS_INIT, "%s refcount %u\n", __func__, sassc->startup_refcount); } } void mpssas_release_simq_reinit(struct mpssas_softc *sassc) { if (sassc->flags & MPSSAS_QUEUE_FROZEN) { sassc->flags &= ~MPSSAS_QUEUE_FROZEN; xpt_release_simq(sassc->sim, 1); mps_dprint(sassc->sc, MPS_INFO, "Unfreezing SIM queue\n"); } } void mpssas_startup_decrement(struct mpssas_softc *sassc) { MPS_FUNCTRACE(sassc->sc); if ((sassc->flags & MPSSAS_IN_STARTUP) != 0) { if (--sassc->startup_refcount == 0) { /* finished all discovery-related actions, release * the simq and rescan for the latest topology. */ mps_dprint(sassc->sc, MPS_INIT, "%s releasing simq\n", __func__); sassc->flags &= ~MPSSAS_IN_STARTUP; xpt_release_simq(sassc->sim, 1); #if __FreeBSD_version >= 1000039 xpt_release_boot(); #else mpssas_rescan_target(sassc->sc, NULL); #endif } mps_dprint(sassc->sc, MPS_INIT, "%s refcount %u\n", __func__, sassc->startup_refcount); } } /* The firmware requires us to stop sending commands when we're doing task * management, so refcount the TMs and keep the simq frozen when any are in * use. */ struct mps_command * mpssas_alloc_tm(struct mps_softc *sc) { struct mps_command *tm; tm = mps_alloc_high_priority_command(sc); return tm; } void mpssas_free_tm(struct mps_softc *sc, struct mps_command *tm) { if (tm == NULL) return; /* * For TM's the devq is frozen for the device. Unfreeze it here and * free the resources used for freezing the devq. Must clear the * INRESET flag as well or scsi I/O will not work. */ if (tm->cm_targ != NULL) { tm->cm_targ->flags &= ~MPSSAS_TARGET_INRESET; } if (tm->cm_ccb) { mps_dprint(sc, MPS_INFO, "Unfreezing devq for target ID %d\n", tm->cm_targ->tid); xpt_release_devq(tm->cm_ccb->ccb_h.path, 1, TRUE); xpt_free_path(tm->cm_ccb->ccb_h.path); xpt_free_ccb(tm->cm_ccb); } mps_free_high_priority_command(sc, tm); } void mpssas_rescan_target(struct mps_softc *sc, struct mpssas_target *targ) { struct mpssas_softc *sassc = sc->sassc; path_id_t pathid; target_id_t targetid; union ccb *ccb; MPS_FUNCTRACE(sc); pathid = cam_sim_path(sassc->sim); if (targ == NULL) targetid = CAM_TARGET_WILDCARD; else targetid = targ - sassc->targets; /* * Allocate a CCB and schedule a rescan. */ ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { mps_dprint(sc, MPS_ERROR, "unable to alloc CCB for rescan\n"); return; } if (xpt_create_path(&ccb->ccb_h.path, NULL, pathid, targetid, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { mps_dprint(sc, MPS_ERROR, "unable to create path for rescan\n"); xpt_free_ccb(ccb); return; } if (targetid == CAM_TARGET_WILDCARD) ccb->ccb_h.func_code = XPT_SCAN_BUS; else ccb->ccb_h.func_code = XPT_SCAN_TGT; mps_dprint(sc, MPS_TRACE, "%s targetid %u\n", __func__, targetid); xpt_rescan(ccb); } static void mpssas_log_command(struct mps_command *cm, u_int level, const char *fmt, ...) { struct sbuf sb; va_list ap; char str[192]; char path_str[64]; if (cm == NULL) return; /* No need to be in here if debugging isn't enabled */ if ((cm->cm_sc->mps_debug & level) == 0) return; sbuf_new(&sb, str, sizeof(str), 0); va_start(ap, fmt); if (cm->cm_ccb != NULL) { xpt_path_string(cm->cm_ccb->csio.ccb_h.path, path_str, sizeof(path_str)); sbuf_cat(&sb, path_str); if (cm->cm_ccb->ccb_h.func_code == XPT_SCSI_IO) { scsi_command_string(&cm->cm_ccb->csio, &sb); sbuf_printf(&sb, "length %d ", cm->cm_ccb->csio.dxfer_len); } } else { sbuf_printf(&sb, "(noperiph:%s%d:%u:%u:%u): ", cam_sim_name(cm->cm_sc->sassc->sim), cam_sim_unit(cm->cm_sc->sassc->sim), cam_sim_bus(cm->cm_sc->sassc->sim), cm->cm_targ ? cm->cm_targ->tid : 0xFFFFFFFF, cm->cm_lun); } sbuf_printf(&sb, "SMID %u ", cm->cm_desc.Default.SMID); sbuf_vprintf(&sb, fmt, ap); sbuf_finish(&sb); mps_dprint_field(cm->cm_sc, level, "%s", sbuf_data(&sb)); va_end(ap); } static void mpssas_remove_volume(struct mps_softc *sc, struct mps_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *reply; struct mpssas_target *targ; uint16_t handle; MPS_FUNCTRACE(sc); reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; handle = (uint16_t)(uintptr_t)tm->cm_complete_data; targ = tm->cm_targ; if (reply == NULL) { /* XXX retry the remove after the diag reset completes? */ mps_dprint(sc, MPS_FAULT, "%s NULL reply resetting device 0x%04x\n", __func__, handle); mpssas_free_tm(sc, tm); return; } if (reply->IOCStatus != MPI2_IOCSTATUS_SUCCESS) { mps_dprint(sc, MPS_FAULT, "IOCStatus = 0x%x while resetting device 0x%x\n", reply->IOCStatus, handle); mpssas_free_tm(sc, tm); return; } mps_dprint(sc, MPS_XINFO, "Reset aborted %u commands\n", reply->TerminationCount); mps_free_reply(sc, tm->cm_reply_data); tm->cm_reply = NULL; /* Ensures the reply won't get re-freed */ mps_dprint(sc, MPS_XINFO, "clearing target %u handle 0x%04x\n", targ->tid, handle); /* * Don't clear target if remove fails because things will get confusing. * Leave the devname and sasaddr intact so that we know to avoid reusing * this target id if possible, and so we can assign the same target id * to this device if it comes back in the future. */ if (reply->IOCStatus == MPI2_IOCSTATUS_SUCCESS) { targ = tm->cm_targ; targ->handle = 0x0; targ->encl_handle = 0x0; targ->encl_slot = 0x0; targ->exp_dev_handle = 0x0; targ->phy_num = 0x0; targ->linkrate = 0x0; targ->devinfo = 0x0; targ->flags = 0x0; } mpssas_free_tm(sc, tm); } /* * No Need to call "MPI2_SAS_OP_REMOVE_DEVICE" For Volume removal. * Otherwise Volume Delete is same as Bare Drive Removal. */ void mpssas_prepare_volume_remove(struct mpssas_softc *sassc, uint16_t handle) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mps_softc *sc; struct mps_command *cm; struct mpssas_target *targ = NULL; MPS_FUNCTRACE(sassc->sc); sc = sassc->sc; #ifdef WD_SUPPORT /* * If this is a WD controller, determine if the disk should be exposed * to the OS or not. If disk should be exposed, return from this * function without doing anything. */ if (sc->WD_available && (sc->WD_hide_expose == MPS_WD_EXPOSE_ALWAYS)) { return; } #endif //WD_SUPPORT targ = mpssas_find_target_by_handle(sassc, 0, handle); if (targ == NULL) { /* FIXME: what is the action? */ /* We don't know about this device? */ mps_dprint(sc, MPS_ERROR, "%s %d : invalid handle 0x%x \n", __func__,__LINE__, handle); return; } targ->flags |= MPSSAS_TARGET_INREMOVAL; cm = mpssas_alloc_tm(sc); if (cm == NULL) { mps_dprint(sc, MPS_ERROR, "%s: command alloc failure\n", __func__); return; } mpssas_rescan_target(sc, targ); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)cm->cm_req; req->DevHandle = targ->handle; req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET; /* SAS Hard Link Reset / SATA Link Reset */ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET; cm->cm_targ = targ; cm->cm_data = NULL; cm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; cm->cm_complete = mpssas_remove_volume; cm->cm_complete_data = (void *)(uintptr_t)handle; mps_dprint(sc, MPS_INFO, "%s: Sending reset for target ID %d\n", __func__, targ->tid); mpssas_prepare_for_tm(sc, cm, targ, CAM_LUN_WILDCARD); mps_map_command(sc, cm); } /* * The MPT2 firmware performs debounce on the link to avoid transient link * errors and false removals. When it does decide that link has been lost * and a device need to go away, it expects that the host will perform a * target reset and then an op remove. The reset has the side-effect of * aborting any outstanding requests for the device, which is required for * the op-remove to succeed. It's not clear if the host should check for * the device coming back alive after the reset. */ void mpssas_prepare_remove(struct mpssas_softc *sassc, uint16_t handle) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mps_softc *sc; struct mps_command *cm; struct mpssas_target *targ = NULL; MPS_FUNCTRACE(sassc->sc); sc = sassc->sc; targ = mpssas_find_target_by_handle(sassc, 0, handle); if (targ == NULL) { /* FIXME: what is the action? */ /* We don't know about this device? */ mps_dprint(sc, MPS_ERROR, "%s : invalid handle 0x%x \n", __func__, handle); return; } targ->flags |= MPSSAS_TARGET_INREMOVAL; cm = mpssas_alloc_tm(sc); if (cm == NULL) { mps_dprint(sc, MPS_ERROR, "%s: command alloc failure\n", __func__); return; } mpssas_rescan_target(sc, targ); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)cm->cm_req; memset(req, 0, sizeof(*req)); req->DevHandle = htole16(targ->handle); req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET; /* SAS Hard Link Reset / SATA Link Reset */ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET; cm->cm_targ = targ; cm->cm_data = NULL; cm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; cm->cm_complete = mpssas_remove_device; cm->cm_complete_data = (void *)(uintptr_t)handle; mps_dprint(sc, MPS_INFO, "%s: Sending reset for target ID %d\n", __func__, targ->tid); mpssas_prepare_for_tm(sc, cm, targ, CAM_LUN_WILDCARD); mps_map_command(sc, cm); } static void mpssas_remove_device(struct mps_softc *sc, struct mps_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *reply; MPI2_SAS_IOUNIT_CONTROL_REQUEST *req; struct mpssas_target *targ; struct mps_command *next_cm; uint16_t handle; MPS_FUNCTRACE(sc); reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; handle = (uint16_t)(uintptr_t)tm->cm_complete_data; targ = tm->cm_targ; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { mps_dprint(sc, MPS_ERROR, "%s: cm_flags = %#x for remove of handle %#04x! " "This should not happen!\n", __func__, tm->cm_flags, handle); mpssas_free_tm(sc, tm); return; } if (reply == NULL) { /* XXX retry the remove after the diag reset completes? */ mps_dprint(sc, MPS_FAULT, "%s NULL reply reseting device 0x%04x\n", __func__, handle); mpssas_free_tm(sc, tm); return; } if (le16toh(reply->IOCStatus) != MPI2_IOCSTATUS_SUCCESS) { mps_dprint(sc, MPS_FAULT, "IOCStatus = 0x%x while resetting device 0x%x\n", le16toh(reply->IOCStatus), handle); mpssas_free_tm(sc, tm); return; } mps_dprint(sc, MPS_XINFO, "Reset aborted %u commands\n", le32toh(reply->TerminationCount)); mps_free_reply(sc, tm->cm_reply_data); tm->cm_reply = NULL; /* Ensures the reply won't get re-freed */ /* Reuse the existing command */ req = (MPI2_SAS_IOUNIT_CONTROL_REQUEST *)tm->cm_req; memset(req, 0, sizeof(*req)); req->Function = MPI2_FUNCTION_SAS_IO_UNIT_CONTROL; req->Operation = MPI2_SAS_OP_REMOVE_DEVICE; req->DevHandle = htole16(handle); tm->cm_data = NULL; tm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE; tm->cm_complete = mpssas_remove_complete; tm->cm_complete_data = (void *)(uintptr_t)handle; mps_map_command(sc, tm); mps_dprint(sc, MPS_XINFO, "clearing target %u handle 0x%04x\n", targ->tid, handle); TAILQ_FOREACH_SAFE(tm, &targ->commands, cm_link, next_cm) { union ccb *ccb; mps_dprint(sc, MPS_XINFO, "Completing missed command %p\n", tm); ccb = tm->cm_complete_data; mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); mpssas_scsiio_complete(sc, tm); } } static void mpssas_remove_complete(struct mps_softc *sc, struct mps_command *tm) { MPI2_SAS_IOUNIT_CONTROL_REPLY *reply; uint16_t handle; struct mpssas_target *targ; struct mpssas_lun *lun; MPS_FUNCTRACE(sc); reply = (MPI2_SAS_IOUNIT_CONTROL_REPLY *)tm->cm_reply; handle = (uint16_t)(uintptr_t)tm->cm_complete_data; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { mps_dprint(sc, MPS_XINFO, "%s: cm_flags = %#x for remove of handle %#04x! " "This should not happen!\n", __func__, tm->cm_flags, handle); mpssas_free_tm(sc, tm); return; } if (reply == NULL) { /* most likely a chip reset */ mps_dprint(sc, MPS_FAULT, "%s NULL reply removing device 0x%04x\n", __func__, handle); mpssas_free_tm(sc, tm); return; } mps_dprint(sc, MPS_XINFO, "%s on handle 0x%04x, IOCStatus= 0x%x\n", __func__, handle, le16toh(reply->IOCStatus)); /* * Don't clear target if remove fails because things will get confusing. * Leave the devname and sasaddr intact so that we know to avoid reusing * this target id if possible, and so we can assign the same target id * to this device if it comes back in the future. */ if (le16toh(reply->IOCStatus) == MPI2_IOCSTATUS_SUCCESS) { targ = tm->cm_targ; targ->handle = 0x0; targ->encl_handle = 0x0; targ->encl_slot = 0x0; targ->exp_dev_handle = 0x0; targ->phy_num = 0x0; targ->linkrate = 0x0; targ->devinfo = 0x0; targ->flags = 0x0; while(!SLIST_EMPTY(&targ->luns)) { lun = SLIST_FIRST(&targ->luns); SLIST_REMOVE_HEAD(&targ->luns, lun_link); free(lun, M_MPT2); } } mpssas_free_tm(sc, tm); } static int mpssas_register_events(struct mps_softc *sc) { u32 events[MPI2_EVENT_NOTIFY_EVENTMASK_WORDS]; bzero(events, 16); setbit(events, MPI2_EVENT_SAS_DEVICE_STATUS_CHANGE); setbit(events, MPI2_EVENT_SAS_DISCOVERY); setbit(events, MPI2_EVENT_SAS_BROADCAST_PRIMITIVE); setbit(events, MPI2_EVENT_SAS_INIT_DEVICE_STATUS_CHANGE); setbit(events, MPI2_EVENT_SAS_INIT_TABLE_OVERFLOW); setbit(events, MPI2_EVENT_SAS_TOPOLOGY_CHANGE_LIST); setbit(events, MPI2_EVENT_SAS_ENCL_DEVICE_STATUS_CHANGE); setbit(events, MPI2_EVENT_IR_CONFIGURATION_CHANGE_LIST); setbit(events, MPI2_EVENT_IR_VOLUME); setbit(events, MPI2_EVENT_IR_PHYSICAL_DISK); setbit(events, MPI2_EVENT_IR_OPERATION_STATUS); setbit(events, MPI2_EVENT_LOG_ENTRY_ADDED); mps_register_events(sc, events, mpssas_evt_handler, NULL, &sc->sassc->mpssas_eh); return (0); } int mps_attach_sas(struct mps_softc *sc) { struct mpssas_softc *sassc; cam_status status; int unit, error = 0; MPS_FUNCTRACE(sc); sassc = malloc(sizeof(struct mpssas_softc), M_MPT2, M_WAITOK|M_ZERO); if(!sassc) { device_printf(sc->mps_dev, "Cannot allocate memory %s %d\n", __func__, __LINE__); return (ENOMEM); } /* * XXX MaxTargets could change during a reinit. Since we don't * resize the targets[] array during such an event, cache the value * of MaxTargets here so that we don't get into trouble later. This * should move into the reinit logic. */ sassc->maxtargets = sc->facts->MaxTargets; sassc->targets = malloc(sizeof(struct mpssas_target) * sassc->maxtargets, M_MPT2, M_WAITOK|M_ZERO); if(!sassc->targets) { device_printf(sc->mps_dev, "Cannot allocate memory %s %d\n", __func__, __LINE__); free(sassc, M_MPT2); return (ENOMEM); } sc->sassc = sassc; sassc->sc = sc; if ((sassc->devq = cam_simq_alloc(sc->num_reqs)) == NULL) { mps_dprint(sc, MPS_ERROR, "Cannot allocate SIMQ\n"); error = ENOMEM; goto out; } unit = device_get_unit(sc->mps_dev); sassc->sim = cam_sim_alloc(mpssas_action, mpssas_poll, "mps", sassc, unit, &sc->mps_mtx, sc->num_reqs, sc->num_reqs, sassc->devq); if (sassc->sim == NULL) { mps_dprint(sc, MPS_ERROR, "Cannot allocate SIM\n"); error = EINVAL; goto out; } TAILQ_INIT(&sassc->ev_queue); /* Initialize taskqueue for Event Handling */ TASK_INIT(&sassc->ev_task, 0, mpssas_firmware_event_work, sc); sassc->ev_tq = taskqueue_create("mps_taskq", M_NOWAIT | M_ZERO, taskqueue_thread_enqueue, &sassc->ev_tq); taskqueue_start_threads(&sassc->ev_tq, 1, PRIBIO, "%s taskq", device_get_nameunit(sc->mps_dev)); mps_lock(sc); /* * XXX There should be a bus for every port on the adapter, but since * we're just going to fake the topology for now, we'll pretend that * everything is just a target on a single bus. */ if ((error = xpt_bus_register(sassc->sim, sc->mps_dev, 0)) != 0) { mps_dprint(sc, MPS_ERROR, "Error %d registering SCSI bus\n", error); mps_unlock(sc); goto out; } /* * Assume that discovery events will start right away. * * Hold off boot until discovery is complete. */ sassc->flags |= MPSSAS_IN_STARTUP | MPSSAS_IN_DISCOVERY; sc->sassc->startup_refcount = 0; mpssas_startup_increment(sassc); callout_init(&sassc->discovery_callout, 1 /*mpsafe*/); /* * Register for async events so we can determine the EEDP * capabilities of devices. */ status = xpt_create_path(&sassc->path, /*periph*/NULL, cam_sim_path(sc->sassc->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); if (status != CAM_REQ_CMP) { mps_printf(sc, "Error %#x creating sim path\n", status); sassc->path = NULL; } else { int event; #if (__FreeBSD_version >= 1000006) || \ ((__FreeBSD_version >= 901503) && (__FreeBSD_version < 1000000)) event = AC_ADVINFO_CHANGED; #else event = AC_FOUND_DEVICE; #endif status = xpt_register_async(event, mpssas_async, sc, sassc->path); if (status != CAM_REQ_CMP) { mps_dprint(sc, MPS_ERROR, "Error %#x registering async handler for " "AC_ADVINFO_CHANGED events\n", status); xpt_free_path(sassc->path); sassc->path = NULL; } } if (status != CAM_REQ_CMP) { /* * EEDP use is the exception, not the rule. * Warn the user, but do not fail to attach. */ mps_printf(sc, "EEDP capabilities disabled.\n"); } mps_unlock(sc); mpssas_register_events(sc); out: if (error) mps_detach_sas(sc); return (error); } int mps_detach_sas(struct mps_softc *sc) { struct mpssas_softc *sassc; struct mpssas_lun *lun, *lun_tmp; struct mpssas_target *targ; int i; MPS_FUNCTRACE(sc); if (sc->sassc == NULL) return (0); sassc = sc->sassc; mps_deregister_events(sc, sassc->mpssas_eh); /* * Drain and free the event handling taskqueue with the lock * unheld so that any parallel processing tasks drain properly * without deadlocking. */ if (sassc->ev_tq != NULL) taskqueue_free(sassc->ev_tq); /* Make sure CAM doesn't wedge if we had to bail out early. */ mps_lock(sc); /* Deregister our async handler */ if (sassc->path != NULL) { xpt_register_async(0, mpssas_async, sc, sassc->path); xpt_free_path(sassc->path); sassc->path = NULL; } if (sassc->flags & MPSSAS_IN_STARTUP) xpt_release_simq(sassc->sim, 1); if (sassc->sim != NULL) { xpt_bus_deregister(cam_sim_path(sassc->sim)); cam_sim_free(sassc->sim, FALSE); } sassc->flags |= MPSSAS_SHUTDOWN; mps_unlock(sc); if (sassc->devq != NULL) cam_simq_free(sassc->devq); for(i=0; i< sassc->maxtargets ;i++) { targ = &sassc->targets[i]; SLIST_FOREACH_SAFE(lun, &targ->luns, lun_link, lun_tmp) { free(lun, M_MPT2); } } free(sassc->targets, M_MPT2); free(sassc, M_MPT2); sc->sassc = NULL; return (0); } void mpssas_discovery_end(struct mpssas_softc *sassc) { struct mps_softc *sc = sassc->sc; MPS_FUNCTRACE(sc); if (sassc->flags & MPSSAS_DISCOVERY_TIMEOUT_PENDING) callout_stop(&sassc->discovery_callout); } static void mpssas_action(struct cam_sim *sim, union ccb *ccb) { struct mpssas_softc *sassc; sassc = cam_sim_softc(sim); MPS_FUNCTRACE(sassc->sc); mps_dprint(sassc->sc, MPS_TRACE, "ccb func_code 0x%x\n", ccb->ccb_h.func_code); mtx_assert(&sassc->sc->mps_mtx, MA_OWNED); switch (ccb->ccb_h.func_code) { case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16; cpi->target_sprt = 0; #if __FreeBSD_version >= 1000039 cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED | PIM_NOSCAN; #else cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED; #endif cpi->hba_eng_cnt = 0; cpi->max_target = sassc->maxtargets - 1; cpi->max_lun = 255; cpi->initiator_id = sassc->maxtargets - 1; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "Avago Tech (LSI)", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 150000; cpi->transport = XPORT_SAS; cpi->transport_version = 0; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_SPC; #if __FreeBSD_version >= 800001 /* * XXX KDM where does this number come from? */ cpi->maxio = 256 * 1024; #endif mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts; struct ccb_trans_settings_sas *sas; struct ccb_trans_settings_scsi *scsi; struct mpssas_target *targ; cts = &ccb->cts; sas = &cts->xport_specific.sas; scsi = &cts->proto_specific.scsi; KASSERT(cts->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in XPT_GET_TRANS_SETTINGS\n", cts->ccb_h.target_id)); targ = &sassc->targets[cts->ccb_h.target_id]; if (targ->handle == 0x0) { mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); break; } cts->protocol_version = SCSI_REV_SPC2; cts->transport = XPORT_SAS; cts->transport_version = 0; sas->valid = CTS_SAS_VALID_SPEED; switch (targ->linkrate) { case 0x08: sas->bitrate = 150000; break; case 0x09: sas->bitrate = 300000; break; case 0x0a: sas->bitrate = 600000; break; default: sas->valid = 0; } cts->protocol = PROTO_SCSI; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); break; } case XPT_CALC_GEOMETRY: cam_calc_geometry(&ccb->ccg, /*extended*/1); mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); break; case XPT_RESET_DEV: mps_dprint(sassc->sc, MPS_XINFO, "mpssas_action XPT_RESET_DEV\n"); mpssas_action_resetdev(sassc, ccb); return; case XPT_RESET_BUS: case XPT_ABORT: case XPT_TERM_IO: mps_dprint(sassc->sc, MPS_XINFO, "mpssas_action faking success for abort or reset\n"); mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); break; case XPT_SCSI_IO: mpssas_action_scsiio(sassc, ccb); return; #if __FreeBSD_version >= 900026 case XPT_SMP_IO: mpssas_action_smpio(sassc, ccb); return; #endif default: mpssas_set_ccbstatus(ccb, CAM_FUNC_NOTAVAIL); break; } xpt_done(ccb); } static void mpssas_announce_reset(struct mps_softc *sc, uint32_t ac_code, target_id_t target_id, lun_id_t lun_id) { path_id_t path_id = cam_sim_path(sc->sassc->sim); struct cam_path *path; mps_dprint(sc, MPS_XINFO, "%s code %x target %d lun %jx\n", __func__, ac_code, target_id, (uintmax_t)lun_id); if (xpt_create_path(&path, NULL, path_id, target_id, lun_id) != CAM_REQ_CMP) { mps_dprint(sc, MPS_ERROR, "unable to create path for reset " "notification\n"); return; } xpt_async(ac_code, path, NULL); xpt_free_path(path); } static void mpssas_complete_all_commands(struct mps_softc *sc) { struct mps_command *cm; int i; int completed; MPS_FUNCTRACE(sc); mtx_assert(&sc->mps_mtx, MA_OWNED); /* complete all commands with a NULL reply */ for (i = 1; i < sc->num_reqs; i++) { cm = &sc->commands[i]; cm->cm_reply = NULL; completed = 0; if (cm->cm_flags & MPS_CM_FLAGS_POLLED) cm->cm_flags |= MPS_CM_FLAGS_COMPLETE; if (cm->cm_complete != NULL) { mpssas_log_command(cm, MPS_RECOVERY, "completing cm %p state %x ccb %p for diag reset\n", cm, cm->cm_state, cm->cm_ccb); cm->cm_complete(sc, cm); completed = 1; } if (cm->cm_flags & MPS_CM_FLAGS_WAKEUP) { mpssas_log_command(cm, MPS_RECOVERY, "waking up cm %p state %x ccb %p for diag reset\n", cm, cm->cm_state, cm->cm_ccb); wakeup(cm); completed = 1; } if (cm->cm_sc->io_cmds_active != 0) { cm->cm_sc->io_cmds_active--; } else { mps_dprint(cm->cm_sc, MPS_INFO, "Warning: " "io_cmds_active is out of sync - resynching to " "0\n"); } if ((completed == 0) && (cm->cm_state != MPS_CM_STATE_FREE)) { /* this should never happen, but if it does, log */ mpssas_log_command(cm, MPS_RECOVERY, "cm %p state %x flags 0x%x ccb %p during diag " "reset\n", cm, cm->cm_state, cm->cm_flags, cm->cm_ccb); } } } void mpssas_handle_reinit(struct mps_softc *sc) { int i; /* Go back into startup mode and freeze the simq, so that CAM * doesn't send any commands until after we've rediscovered all * targets and found the proper device handles for them. * * After the reset, portenable will trigger discovery, and after all * discovery-related activities have finished, the simq will be * released. */ mps_dprint(sc, MPS_INIT, "%s startup\n", __func__); sc->sassc->flags |= MPSSAS_IN_STARTUP; sc->sassc->flags |= MPSSAS_IN_DISCOVERY; mpssas_startup_increment(sc->sassc); /* notify CAM of a bus reset */ mpssas_announce_reset(sc, AC_BUS_RESET, CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); /* complete and cleanup after all outstanding commands */ mpssas_complete_all_commands(sc); mps_dprint(sc, MPS_INIT, "%s startup %u after command completion\n", __func__, sc->sassc->startup_refcount); /* zero all the target handles, since they may change after the * reset, and we have to rediscover all the targets and use the new * handles. */ for (i = 0; i < sc->sassc->maxtargets; i++) { if (sc->sassc->targets[i].outstanding != 0) mps_dprint(sc, MPS_INIT, "target %u outstanding %u\n", i, sc->sassc->targets[i].outstanding); sc->sassc->targets[i].handle = 0x0; sc->sassc->targets[i].exp_dev_handle = 0x0; sc->sassc->targets[i].outstanding = 0; sc->sassc->targets[i].flags = MPSSAS_TARGET_INDIAGRESET; } } static void mpssas_tm_timeout(void *data) { struct mps_command *tm = data; struct mps_softc *sc = tm->cm_sc; mtx_assert(&sc->mps_mtx, MA_OWNED); mpssas_log_command(tm, MPS_INFO|MPS_RECOVERY, "task mgmt %p timed out\n", tm); mps_reinit(sc); } static void mpssas_logical_unit_reset_complete(struct mps_softc *sc, struct mps_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *reply; MPI2_SCSI_TASK_MANAGE_REQUEST *req; unsigned int cm_count = 0; struct mps_command *cm; struct mpssas_target *targ; callout_stop(&tm->cm_callout); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; targ = tm->cm_targ; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. * XXXSL So should it be an assertion? */ if ((tm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { mps_dprint(sc, MPS_ERROR, "%s: cm_flags = %#x for LUN reset! " "This should not happen!\n", __func__, tm->cm_flags); mpssas_free_tm(sc, tm); return; } if (reply == NULL) { mpssas_log_command(tm, MPS_RECOVERY, "NULL reset reply for tm %p\n", tm); if ((sc->mps_flags & MPS_FLAGS_DIAGRESET) != 0) { /* this completion was due to a reset, just cleanup */ targ->tm = NULL; mpssas_free_tm(sc, tm); } else { /* we should have gotten a reply. */ mps_reinit(sc); } return; } mpssas_log_command(tm, MPS_RECOVERY, "logical unit reset status 0x%x code 0x%x count %u\n", le16toh(reply->IOCStatus), le32toh(reply->ResponseCode), le32toh(reply->TerminationCount)); /* See if there are any outstanding commands for this LUN. * This could be made more efficient by using a per-LU data * structure of some sort. */ TAILQ_FOREACH(cm, &targ->commands, cm_link) { if (cm->cm_lun == tm->cm_lun) cm_count++; } if (cm_count == 0) { mpssas_log_command(tm, MPS_RECOVERY|MPS_INFO, "logical unit %u finished recovery after reset\n", tm->cm_lun, tm); mpssas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid, tm->cm_lun); /* we've finished recovery for this logical unit. check and * see if some other logical unit has a timedout command * that needs to be processed. */ cm = TAILQ_FIRST(&targ->timedout_commands); if (cm) { mpssas_send_abort(sc, tm, cm); } else { targ->tm = NULL; mpssas_free_tm(sc, tm); } } else { /* if we still have commands for this LUN, the reset * effectively failed, regardless of the status reported. * Escalate to a target reset. */ mpssas_log_command(tm, MPS_RECOVERY, "logical unit reset complete for tm %p, but still have %u command(s)\n", tm, cm_count); mpssas_send_reset(sc, tm, MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET); } } static void mpssas_target_reset_complete(struct mps_softc *sc, struct mps_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *reply; MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mpssas_target *targ; callout_stop(&tm->cm_callout); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; targ = tm->cm_targ; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { mps_dprint(sc, MPS_ERROR,"%s: cm_flags = %#x for target reset! " "This should not happen!\n", __func__, tm->cm_flags); mpssas_free_tm(sc, tm); return; } if (reply == NULL) { mpssas_log_command(tm, MPS_RECOVERY, "NULL reset reply for tm %p\n", tm); if ((sc->mps_flags & MPS_FLAGS_DIAGRESET) != 0) { /* this completion was due to a reset, just cleanup */ targ->tm = NULL; mpssas_free_tm(sc, tm); } else { /* we should have gotten a reply. */ mps_reinit(sc); } return; } mpssas_log_command(tm, MPS_RECOVERY, "target reset status 0x%x code 0x%x count %u\n", le16toh(reply->IOCStatus), le32toh(reply->ResponseCode), le32toh(reply->TerminationCount)); if (targ->outstanding == 0) { /* we've finished recovery for this target and all * of its logical units. */ mpssas_log_command(tm, MPS_RECOVERY|MPS_INFO, "recovery finished after target reset\n"); mpssas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid, CAM_LUN_WILDCARD); targ->tm = NULL; mpssas_free_tm(sc, tm); } else { /* after a target reset, if this target still has * outstanding commands, the reset effectively failed, * regardless of the status reported. escalate. */ mpssas_log_command(tm, MPS_RECOVERY, "target reset complete for tm %p, but still have %u command(s)\n", tm, targ->outstanding); mps_reinit(sc); } } #define MPS_RESET_TIMEOUT 30 int mpssas_send_reset(struct mps_softc *sc, struct mps_command *tm, uint8_t type) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mpssas_target *target; int err; target = tm->cm_targ; if (target->handle == 0) { mps_dprint(sc, MPS_ERROR,"%s null devhandle for target_id %d\n", __func__, target->tid); return -1; } req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; req->DevHandle = htole16(target->handle); req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = type; if (type == MPI2_SCSITASKMGMT_TASKTYPE_LOGICAL_UNIT_RESET) { /* XXX Need to handle invalid LUNs */ MPS_SET_LUN(req->LUN, tm->cm_lun); tm->cm_targ->logical_unit_resets++; mpssas_log_command(tm, MPS_RECOVERY|MPS_INFO, "sending logical unit reset\n"); tm->cm_complete = mpssas_logical_unit_reset_complete; mpssas_prepare_for_tm(sc, tm, target, tm->cm_lun); } else if (type == MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET) { /* * Target reset method = * SAS Hard Link Reset / SATA Link Reset */ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET; tm->cm_targ->target_resets++; mpssas_log_command(tm, MPS_RECOVERY|MPS_INFO, "sending target reset\n"); tm->cm_complete = mpssas_target_reset_complete; mpssas_prepare_for_tm(sc, tm, target, CAM_LUN_WILDCARD); } else { mps_dprint(sc, MPS_ERROR, "unexpected reset type 0x%x\n", type); return -1; } tm->cm_data = NULL; tm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; tm->cm_complete_data = (void *)tm; callout_reset(&tm->cm_callout, MPS_RESET_TIMEOUT * hz, mpssas_tm_timeout, tm); err = mps_map_command(sc, tm); if (err) mpssas_log_command(tm, MPS_RECOVERY, "error %d sending reset type %u\n", err, type); return err; } static void mpssas_abort_complete(struct mps_softc *sc, struct mps_command *tm) { struct mps_command *cm; MPI2_SCSI_TASK_MANAGE_REPLY *reply; MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mpssas_target *targ; callout_stop(&tm->cm_callout); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; targ = tm->cm_targ; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { mpssas_log_command(tm, MPS_RECOVERY, "cm_flags = %#x for abort %p TaskMID %u!\n", tm->cm_flags, tm, le16toh(req->TaskMID)); mpssas_free_tm(sc, tm); return; } if (reply == NULL) { mpssas_log_command(tm, MPS_RECOVERY, "NULL abort reply for tm %p TaskMID %u\n", tm, le16toh(req->TaskMID)); if ((sc->mps_flags & MPS_FLAGS_DIAGRESET) != 0) { /* this completion was due to a reset, just cleanup */ targ->tm = NULL; mpssas_free_tm(sc, tm); } else { /* we should have gotten a reply. */ mps_reinit(sc); } return; } mpssas_log_command(tm, MPS_RECOVERY, "abort TaskMID %u status 0x%x code 0x%x count %u\n", le16toh(req->TaskMID), le16toh(reply->IOCStatus), le32toh(reply->ResponseCode), le32toh(reply->TerminationCount)); cm = TAILQ_FIRST(&tm->cm_targ->timedout_commands); if (cm == NULL) { /* if there are no more timedout commands, we're done with * error recovery for this target. */ mpssas_log_command(tm, MPS_RECOVERY, "finished recovery after aborting TaskMID %u\n", le16toh(req->TaskMID)); targ->tm = NULL; mpssas_free_tm(sc, tm); } else if (le16toh(req->TaskMID) != cm->cm_desc.Default.SMID) { /* abort success, but we have more timedout commands to abort */ mpssas_log_command(tm, MPS_RECOVERY, "continuing recovery after aborting TaskMID %u\n", le16toh(req->TaskMID)); mpssas_send_abort(sc, tm, cm); } else { /* we didn't get a command completion, so the abort * failed as far as we're concerned. escalate. */ mpssas_log_command(tm, MPS_RECOVERY, "abort failed for TaskMID %u tm %p\n", le16toh(req->TaskMID), tm); mpssas_send_reset(sc, tm, MPI2_SCSITASKMGMT_TASKTYPE_LOGICAL_UNIT_RESET); } } #define MPS_ABORT_TIMEOUT 5 static int mpssas_send_abort(struct mps_softc *sc, struct mps_command *tm, struct mps_command *cm) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mpssas_target *targ; int err; targ = cm->cm_targ; if (targ->handle == 0) { mps_dprint(sc, MPS_ERROR,"%s null devhandle for target_id %d\n", __func__, cm->cm_ccb->ccb_h.target_id); return -1; } mpssas_log_command(tm, MPS_RECOVERY|MPS_INFO, "Aborting command %p\n", cm); req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; req->DevHandle = htole16(targ->handle); req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_ABORT_TASK; /* XXX Need to handle invalid LUNs */ MPS_SET_LUN(req->LUN, cm->cm_ccb->ccb_h.target_lun); req->TaskMID = htole16(cm->cm_desc.Default.SMID); tm->cm_data = NULL; tm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; tm->cm_complete = mpssas_abort_complete; tm->cm_complete_data = (void *)tm; tm->cm_targ = cm->cm_targ; tm->cm_lun = cm->cm_lun; callout_reset(&tm->cm_callout, MPS_ABORT_TIMEOUT * hz, mpssas_tm_timeout, tm); targ->aborts++; mps_dprint(sc, MPS_INFO, "Sending reset from %s for target ID %d\n", __func__, targ->tid); mpssas_prepare_for_tm(sc, tm, targ, tm->cm_lun); err = mps_map_command(sc, tm); if (err) mpssas_log_command(tm, MPS_RECOVERY, "error %d sending abort for cm %p SMID %u\n", err, cm, req->TaskMID); return err; } static void mpssas_scsiio_timeout(void *data) { struct mps_softc *sc; struct mps_command *cm; struct mpssas_target *targ; cm = (struct mps_command *)data; sc = cm->cm_sc; MPS_FUNCTRACE(sc); mtx_assert(&sc->mps_mtx, MA_OWNED); mps_dprint(sc, MPS_XINFO, "Timeout checking cm %p\n", sc); /* * Run the interrupt handler to make sure it's not pending. This * isn't perfect because the command could have already completed * and been re-used, though this is unlikely. */ mps_intr_locked(sc); if (cm->cm_state == MPS_CM_STATE_FREE) { mpssas_log_command(cm, MPS_XINFO, "SCSI command %p almost timed out\n", cm); return; } if (cm->cm_ccb == NULL) { mps_dprint(sc, MPS_ERROR, "command timeout with NULL ccb\n"); return; } mpssas_log_command(cm, MPS_INFO, "command timeout cm %p ccb %p\n", cm, cm->cm_ccb); targ = cm->cm_targ; targ->timeouts++; /* XXX first, check the firmware state, to see if it's still * operational. if not, do a diag reset. */ mpssas_set_ccbstatus(cm->cm_ccb, CAM_CMD_TIMEOUT); cm->cm_state = MPS_CM_STATE_TIMEDOUT; TAILQ_INSERT_TAIL(&targ->timedout_commands, cm, cm_recovery); if (targ->tm != NULL) { /* target already in recovery, just queue up another * timedout command to be processed later. */ mps_dprint(sc, MPS_RECOVERY, "queued timedout cm %p for processing by tm %p\n", cm, targ->tm); } else if ((targ->tm = mpssas_alloc_tm(sc)) != NULL) { mps_dprint(sc, MPS_RECOVERY, "timedout cm %p allocated tm %p\n", cm, targ->tm); /* start recovery by aborting the first timedout command */ mpssas_send_abort(sc, targ->tm, cm); } else { /* XXX queue this target up for recovery once a TM becomes * available. The firmware only has a limited number of * HighPriority credits for the high priority requests used * for task management, and we ran out. * * Isilon: don't worry about this for now, since we have * more credits than disks in an enclosure, and limit * ourselves to one TM per target for recovery. */ mps_dprint(sc, MPS_RECOVERY, "timedout cm %p failed to allocate a tm\n", cm); } } static void mpssas_action_scsiio(struct mpssas_softc *sassc, union ccb *ccb) { MPI2_SCSI_IO_REQUEST *req; struct ccb_scsiio *csio; struct mps_softc *sc; struct mpssas_target *targ; struct mpssas_lun *lun; struct mps_command *cm; uint8_t i, lba_byte, *ref_tag_addr; uint16_t eedp_flags; uint32_t mpi_control; sc = sassc->sc; MPS_FUNCTRACE(sc); mtx_assert(&sc->mps_mtx, MA_OWNED); csio = &ccb->csio; KASSERT(csio->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in XPT_SCSI_IO\n", csio->ccb_h.target_id)); targ = &sassc->targets[csio->ccb_h.target_id]; mps_dprint(sc, MPS_TRACE, "ccb %p target flag %x\n", ccb, targ->flags); if (targ->handle == 0x0) { mps_dprint(sc, MPS_ERROR, "%s NULL handle for target %u\n", __func__, csio->ccb_h.target_id); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); return; } if (targ->flags & MPS_TARGET_FLAGS_RAID_COMPONENT) { mps_dprint(sc, MPS_ERROR, "%s Raid component no SCSI IO " "supported %u\n", __func__, csio->ccb_h.target_id); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); return; } /* * Sometimes, it is possible to get a command that is not "In * Progress" and was actually aborted by the upper layer. Check for * this here and complete the command without error. */ if (mpssas_get_ccbstatus(ccb) != CAM_REQ_INPROG) { mps_dprint(sc, MPS_TRACE, "%s Command is not in progress for " "target %u\n", __func__, csio->ccb_h.target_id); xpt_done(ccb); return; } /* * If devinfo is 0 this will be a volume. In that case don't tell CAM * that the volume has timed out. We want volumes to be enumerated * until they are deleted/removed, not just failed. */ if (targ->flags & MPSSAS_TARGET_INREMOVAL) { if (targ->devinfo == 0) mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); else mpssas_set_ccbstatus(ccb, CAM_SEL_TIMEOUT); xpt_done(ccb); return; } if ((sc->mps_flags & MPS_FLAGS_SHUTDOWN) != 0) { mps_dprint(sc, MPS_INFO, "%s shutting down\n", __func__); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); return; } /* * If target has a reset in progress, freeze the devq and return. The * devq will be released when the TM reset is finished. */ if (targ->flags & MPSSAS_TARGET_INRESET) { ccb->ccb_h.status = CAM_BUSY | CAM_DEV_QFRZN; mps_dprint(sc, MPS_INFO, "%s: Freezing devq for target ID %d\n", __func__, targ->tid); xpt_freeze_devq(ccb->ccb_h.path, 1); xpt_done(ccb); return; } cm = mps_alloc_command(sc); if (cm == NULL || (sc->mps_flags & MPS_FLAGS_DIAGRESET)) { if (cm != NULL) { mps_free_command(sc, cm); } if ((sassc->flags & MPSSAS_QUEUE_FROZEN) == 0) { xpt_freeze_simq(sassc->sim, 1); sassc->flags |= MPSSAS_QUEUE_FROZEN; } ccb->ccb_h.status &= ~CAM_SIM_QUEUED; ccb->ccb_h.status |= CAM_REQUEUE_REQ; xpt_done(ccb); return; } req = (MPI2_SCSI_IO_REQUEST *)cm->cm_req; bzero(req, sizeof(*req)); req->DevHandle = htole16(targ->handle); req->Function = MPI2_FUNCTION_SCSI_IO_REQUEST; req->MsgFlags = 0; req->SenseBufferLowAddress = htole32(cm->cm_sense_busaddr); req->SenseBufferLength = MPS_SENSE_LEN; req->SGLFlags = 0; req->ChainOffset = 0; req->SGLOffset0 = 24; /* 32bit word offset to the SGL */ req->SGLOffset1= 0; req->SGLOffset2= 0; req->SGLOffset3= 0; req->SkipCount = 0; req->DataLength = htole32(csio->dxfer_len); req->BidirectionalDataLength = 0; req->IoFlags = htole16(csio->cdb_len); req->EEDPFlags = 0; /* Note: BiDirectional transfers are not supported */ switch (csio->ccb_h.flags & CAM_DIR_MASK) { case CAM_DIR_IN: mpi_control = MPI2_SCSIIO_CONTROL_READ; cm->cm_flags |= MPS_CM_FLAGS_DATAIN; break; case CAM_DIR_OUT: mpi_control = MPI2_SCSIIO_CONTROL_WRITE; cm->cm_flags |= MPS_CM_FLAGS_DATAOUT; break; case CAM_DIR_NONE: default: mpi_control = MPI2_SCSIIO_CONTROL_NODATATRANSFER; break; } if (csio->cdb_len == 32) mpi_control |= 4 << MPI2_SCSIIO_CONTROL_ADDCDBLEN_SHIFT; /* * It looks like the hardware doesn't require an explicit tag * number for each transaction. SAM Task Management not supported * at the moment. */ switch (csio->tag_action) { case MSG_HEAD_OF_Q_TAG: mpi_control |= MPI2_SCSIIO_CONTROL_HEADOFQ; break; case MSG_ORDERED_Q_TAG: mpi_control |= MPI2_SCSIIO_CONTROL_ORDEREDQ; break; case MSG_ACA_TASK: mpi_control |= MPI2_SCSIIO_CONTROL_ACAQ; break; case CAM_TAG_ACTION_NONE: case MSG_SIMPLE_Q_TAG: default: mpi_control |= MPI2_SCSIIO_CONTROL_SIMPLEQ; break; } mpi_control |= sc->mapping_table[csio->ccb_h.target_id].TLR_bits; req->Control = htole32(mpi_control); if (MPS_SET_LUN(req->LUN, csio->ccb_h.target_lun) != 0) { mps_free_command(sc, cm); mpssas_set_ccbstatus(ccb, CAM_LUN_INVALID); xpt_done(ccb); return; } if (csio->ccb_h.flags & CAM_CDB_POINTER) bcopy(csio->cdb_io.cdb_ptr, &req->CDB.CDB32[0], csio->cdb_len); else bcopy(csio->cdb_io.cdb_bytes, &req->CDB.CDB32[0],csio->cdb_len); req->IoFlags = htole16(csio->cdb_len); /* * Check if EEDP is supported and enabled. If it is then check if the * SCSI opcode could be using EEDP. If so, make sure the LUN exists and * is formatted for EEDP support. If all of this is true, set CDB up * for EEDP transfer. */ eedp_flags = op_code_prot[req->CDB.CDB32[0]]; if (sc->eedp_enabled && eedp_flags) { SLIST_FOREACH(lun, &targ->luns, lun_link) { if (lun->lun_id == csio->ccb_h.target_lun) { break; } } if ((lun != NULL) && (lun->eedp_formatted)) { req->EEDPBlockSize = htole16(lun->eedp_block_size); eedp_flags |= (MPI2_SCSIIO_EEDPFLAGS_INC_PRI_REFTAG | MPI2_SCSIIO_EEDPFLAGS_CHECK_REFTAG | MPI2_SCSIIO_EEDPFLAGS_CHECK_GUARD); req->EEDPFlags = htole16(eedp_flags); /* * If CDB less than 32, fill in Primary Ref Tag with * low 4 bytes of LBA. If CDB is 32, tag stuff is * already there. Also, set protection bit. FreeBSD * currently does not support CDBs bigger than 16, but * the code doesn't hurt, and will be here for the * future. */ if (csio->cdb_len != 32) { lba_byte = (csio->cdb_len == 16) ? 6 : 2; ref_tag_addr = (uint8_t *)&req->CDB.EEDP32. PrimaryReferenceTag; for (i = 0; i < 4; i++) { *ref_tag_addr = req->CDB.CDB32[lba_byte + i]; ref_tag_addr++; } req->CDB.EEDP32.PrimaryReferenceTag = htole32(req->CDB.EEDP32.PrimaryReferenceTag); req->CDB.EEDP32.PrimaryApplicationTagMask = 0xFFFF; req->CDB.CDB32[1] = (req->CDB.CDB32[1] & 0x1F) | 0x20; } else { eedp_flags |= MPI2_SCSIIO_EEDPFLAGS_INC_PRI_APPTAG; req->EEDPFlags = htole16(eedp_flags); req->CDB.CDB32[10] = (req->CDB.CDB32[10] & 0x1F) | 0x20; } } } cm->cm_length = csio->dxfer_len; if (cm->cm_length != 0) { cm->cm_data = ccb; cm->cm_flags |= MPS_CM_FLAGS_USE_CCB; } else { cm->cm_data = NULL; } cm->cm_sge = &req->SGL; cm->cm_sglsize = (32 - 24) * 4; cm->cm_desc.SCSIIO.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_SCSI_IO; cm->cm_desc.SCSIIO.DevHandle = htole16(targ->handle); cm->cm_complete = mpssas_scsiio_complete; cm->cm_complete_data = ccb; cm->cm_targ = targ; cm->cm_lun = csio->ccb_h.target_lun; cm->cm_ccb = ccb; /* * If HBA is a WD and the command is not for a retry, try to build a * direct I/O message. If failed, or the command is for a retry, send * the I/O to the IR volume itself. */ if (sc->WD_valid_config) { if (ccb->ccb_h.sim_priv.entries[0].field == MPS_WD_RETRY) { mpssas_direct_drive_io(sassc, cm, ccb); } else { mpssas_set_ccbstatus(ccb, CAM_REQ_INPROG); } } callout_reset_sbt(&cm->cm_callout, SBT_1MS * ccb->ccb_h.timeout, 0, mpssas_scsiio_timeout, cm, 0); targ->issued++; targ->outstanding++; TAILQ_INSERT_TAIL(&targ->commands, cm, cm_link); ccb->ccb_h.status |= CAM_SIM_QUEUED; mpssas_log_command(cm, MPS_XINFO, "%s cm %p ccb %p outstanding %u\n", __func__, cm, ccb, targ->outstanding); mps_map_command(sc, cm); return; } static void mps_response_code(struct mps_softc *sc, u8 response_code) { char *desc; switch (response_code) { case MPI2_SCSITASKMGMT_RSP_TM_COMPLETE: desc = "task management request completed"; break; case MPI2_SCSITASKMGMT_RSP_INVALID_FRAME: desc = "invalid frame"; break; case MPI2_SCSITASKMGMT_RSP_TM_NOT_SUPPORTED: desc = "task management request not supported"; break; case MPI2_SCSITASKMGMT_RSP_TM_FAILED: desc = "task management request failed"; break; case MPI2_SCSITASKMGMT_RSP_TM_SUCCEEDED: desc = "task management request succeeded"; break; case MPI2_SCSITASKMGMT_RSP_TM_INVALID_LUN: desc = "invalid lun"; break; case 0xA: desc = "overlapped tag attempted"; break; case MPI2_SCSITASKMGMT_RSP_IO_QUEUED_ON_IOC: desc = "task queued, however not sent to target"; break; default: desc = "unknown"; break; } mps_dprint(sc, MPS_XINFO, "response_code(0x%01x): %s\n", response_code, desc); } /** * mps_sc_failed_io_info - translated non-succesfull SCSI_IO request */ static void mps_sc_failed_io_info(struct mps_softc *sc, struct ccb_scsiio *csio, Mpi2SCSIIOReply_t *mpi_reply) { u32 response_info; u8 *response_bytes; u16 ioc_status = le16toh(mpi_reply->IOCStatus) & MPI2_IOCSTATUS_MASK; u8 scsi_state = mpi_reply->SCSIState; u8 scsi_status = mpi_reply->SCSIStatus; char *desc_ioc_state = NULL; char *desc_scsi_status = NULL; char *desc_scsi_state = sc->tmp_string; u32 log_info = le32toh(mpi_reply->IOCLogInfo); if (log_info == 0x31170000) return; switch (ioc_status) { case MPI2_IOCSTATUS_SUCCESS: desc_ioc_state = "success"; break; case MPI2_IOCSTATUS_INVALID_FUNCTION: desc_ioc_state = "invalid function"; break; case MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR: desc_ioc_state = "scsi recovered error"; break; case MPI2_IOCSTATUS_SCSI_INVALID_DEVHANDLE: desc_ioc_state = "scsi invalid dev handle"; break; case MPI2_IOCSTATUS_SCSI_DEVICE_NOT_THERE: desc_ioc_state = "scsi device not there"; break; case MPI2_IOCSTATUS_SCSI_DATA_OVERRUN: desc_ioc_state = "scsi data overrun"; break; case MPI2_IOCSTATUS_SCSI_DATA_UNDERRUN: desc_ioc_state = "scsi data underrun"; break; case MPI2_IOCSTATUS_SCSI_IO_DATA_ERROR: desc_ioc_state = "scsi io data error"; break; case MPI2_IOCSTATUS_SCSI_PROTOCOL_ERROR: desc_ioc_state = "scsi protocol error"; break; case MPI2_IOCSTATUS_SCSI_TASK_TERMINATED: desc_ioc_state = "scsi task terminated"; break; case MPI2_IOCSTATUS_SCSI_RESIDUAL_MISMATCH: desc_ioc_state = "scsi residual mismatch"; break; case MPI2_IOCSTATUS_SCSI_TASK_MGMT_FAILED: desc_ioc_state = "scsi task mgmt failed"; break; case MPI2_IOCSTATUS_SCSI_IOC_TERMINATED: desc_ioc_state = "scsi ioc terminated"; break; case MPI2_IOCSTATUS_SCSI_EXT_TERMINATED: desc_ioc_state = "scsi ext terminated"; break; case MPI2_IOCSTATUS_EEDP_GUARD_ERROR: desc_ioc_state = "eedp guard error"; break; case MPI2_IOCSTATUS_EEDP_REF_TAG_ERROR: desc_ioc_state = "eedp ref tag error"; break; case MPI2_IOCSTATUS_EEDP_APP_TAG_ERROR: desc_ioc_state = "eedp app tag error"; break; default: desc_ioc_state = "unknown"; break; } switch (scsi_status) { case MPI2_SCSI_STATUS_GOOD: desc_scsi_status = "good"; break; case MPI2_SCSI_STATUS_CHECK_CONDITION: desc_scsi_status = "check condition"; break; case MPI2_SCSI_STATUS_CONDITION_MET: desc_scsi_status = "condition met"; break; case MPI2_SCSI_STATUS_BUSY: desc_scsi_status = "busy"; break; case MPI2_SCSI_STATUS_INTERMEDIATE: desc_scsi_status = "intermediate"; break; case MPI2_SCSI_STATUS_INTERMEDIATE_CONDMET: desc_scsi_status = "intermediate condmet"; break; case MPI2_SCSI_STATUS_RESERVATION_CONFLICT: desc_scsi_status = "reservation conflict"; break; case MPI2_SCSI_STATUS_COMMAND_TERMINATED: desc_scsi_status = "command terminated"; break; case MPI2_SCSI_STATUS_TASK_SET_FULL: desc_scsi_status = "task set full"; break; case MPI2_SCSI_STATUS_ACA_ACTIVE: desc_scsi_status = "aca active"; break; case MPI2_SCSI_STATUS_TASK_ABORTED: desc_scsi_status = "task aborted"; break; default: desc_scsi_status = "unknown"; break; } desc_scsi_state[0] = '\0'; if (!scsi_state) desc_scsi_state = " "; if (scsi_state & MPI2_SCSI_STATE_RESPONSE_INFO_VALID) strcat(desc_scsi_state, "response info "); if (scsi_state & MPI2_SCSI_STATE_TERMINATED) strcat(desc_scsi_state, "state terminated "); if (scsi_state & MPI2_SCSI_STATE_NO_SCSI_STATUS) strcat(desc_scsi_state, "no status "); if (scsi_state & MPI2_SCSI_STATE_AUTOSENSE_FAILED) strcat(desc_scsi_state, "autosense failed "); if (scsi_state & MPI2_SCSI_STATE_AUTOSENSE_VALID) strcat(desc_scsi_state, "autosense valid "); mps_dprint(sc, MPS_XINFO, "\thandle(0x%04x), ioc_status(%s)(0x%04x)\n", le16toh(mpi_reply->DevHandle), desc_ioc_state, ioc_status); /* We can add more detail about underflow data here * TO-DO * */ mps_dprint(sc, MPS_XINFO, "\tscsi_status(%s)(0x%02x), " "scsi_state(%s)(0x%02x)\n", desc_scsi_status, scsi_status, desc_scsi_state, scsi_state); if (sc->mps_debug & MPS_XINFO && scsi_state & MPI2_SCSI_STATE_AUTOSENSE_VALID) { mps_dprint(sc, MPS_XINFO, "-> Sense Buffer Data : Start :\n"); scsi_sense_print(csio); mps_dprint(sc, MPS_XINFO, "-> Sense Buffer Data : End :\n"); } if (scsi_state & MPI2_SCSI_STATE_RESPONSE_INFO_VALID) { response_info = le32toh(mpi_reply->ResponseInfo); response_bytes = (u8 *)&response_info; mps_response_code(sc,response_bytes[0]); } } static void mpssas_scsiio_complete(struct mps_softc *sc, struct mps_command *cm) { MPI2_SCSI_IO_REPLY *rep; union ccb *ccb; struct ccb_scsiio *csio; struct mpssas_softc *sassc; struct scsi_vpd_supported_page_list *vpd_list = NULL; u8 *TLR_bits, TLR_on; int dir = 0, i; u16 alloc_len; struct mpssas_target *target; target_id_t target_id; MPS_FUNCTRACE(sc); mps_dprint(sc, MPS_TRACE, "cm %p SMID %u ccb %p reply %p outstanding %u\n", cm, cm->cm_desc.Default.SMID, cm->cm_ccb, cm->cm_reply, cm->cm_targ->outstanding); callout_stop(&cm->cm_callout); mtx_assert(&sc->mps_mtx, MA_OWNED); sassc = sc->sassc; ccb = cm->cm_complete_data; csio = &ccb->csio; target_id = csio->ccb_h.target_id; rep = (MPI2_SCSI_IO_REPLY *)cm->cm_reply; /* * XXX KDM if the chain allocation fails, does it matter if we do * the sync and unload here? It is simpler to do it in every case, * assuming it doesn't cause problems. */ if (cm->cm_data != NULL) { if (cm->cm_flags & MPS_CM_FLAGS_DATAIN) dir = BUS_DMASYNC_POSTREAD; else if (cm->cm_flags & MPS_CM_FLAGS_DATAOUT) dir = BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(sc->buffer_dmat, cm->cm_dmamap, dir); bus_dmamap_unload(sc->buffer_dmat, cm->cm_dmamap); } cm->cm_targ->completed++; cm->cm_targ->outstanding--; TAILQ_REMOVE(&cm->cm_targ->commands, cm, cm_link); ccb->ccb_h.status &= ~(CAM_STATUS_MASK | CAM_SIM_QUEUED); if (cm->cm_state == MPS_CM_STATE_TIMEDOUT) { TAILQ_REMOVE(&cm->cm_targ->timedout_commands, cm, cm_recovery); if (cm->cm_reply != NULL) mpssas_log_command(cm, MPS_RECOVERY, "completed timedout cm %p ccb %p during recovery " "ioc %x scsi %x state %x xfer %u\n", cm, cm->cm_ccb, le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); else mpssas_log_command(cm, MPS_RECOVERY, "completed timedout cm %p ccb %p during recovery\n", cm, cm->cm_ccb); } else if (cm->cm_targ->tm != NULL) { if (cm->cm_reply != NULL) mpssas_log_command(cm, MPS_RECOVERY, "completed cm %p ccb %p during recovery " "ioc %x scsi %x state %x xfer %u\n", cm, cm->cm_ccb, le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); else mpssas_log_command(cm, MPS_RECOVERY, "completed cm %p ccb %p during recovery\n", cm, cm->cm_ccb); } else if ((sc->mps_flags & MPS_FLAGS_DIAGRESET) != 0) { mpssas_log_command(cm, MPS_RECOVERY, "reset completed cm %p ccb %p\n", cm, cm->cm_ccb); } if ((cm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { /* * We ran into an error after we tried to map the command, * so we're getting a callback without queueing the command * to the hardware. So we set the status here, and it will * be retained below. We'll go through the "fast path", * because there can be no reply when we haven't actually * gone out to the hardware. */ mpssas_set_ccbstatus(ccb, CAM_REQUEUE_REQ); /* * Currently the only error included in the mask is * MPS_CM_FLAGS_CHAIN_FAILED, which means we're out of * chain frames. We need to freeze the queue until we get * a command that completed without this error, which will * hopefully have some chain frames attached that we can * use. If we wanted to get smarter about it, we would * only unfreeze the queue in this condition when we're * sure that we're getting some chain frames back. That's * probably unnecessary. */ if ((sassc->flags & MPSSAS_QUEUE_FROZEN) == 0) { xpt_freeze_simq(sassc->sim, 1); sassc->flags |= MPSSAS_QUEUE_FROZEN; mps_dprint(sc, MPS_XINFO, "Error sending command, " "freezing SIM queue\n"); } } /* * If this is a Start Stop Unit command and it was issued by the driver * during shutdown, decrement the refcount to account for all of the * commands that were sent. All SSU commands should be completed before * shutdown completes, meaning SSU_refcount will be 0 after SSU_started * is TRUE. */ if (sc->SSU_started && (csio->cdb_io.cdb_bytes[0] == START_STOP_UNIT)) { mps_dprint(sc, MPS_INFO, "Decrementing SSU count.\n"); sc->SSU_refcount--; } /* Take the fast path to completion */ if (cm->cm_reply == NULL) { if (mpssas_get_ccbstatus(ccb) == CAM_REQ_INPROG) { if ((sc->mps_flags & MPS_FLAGS_DIAGRESET) != 0) mpssas_set_ccbstatus(ccb, CAM_SCSI_BUS_RESET); else { mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); ccb->csio.scsi_status = SCSI_STATUS_OK; } if (sassc->flags & MPSSAS_QUEUE_FROZEN) { ccb->ccb_h.status |= CAM_RELEASE_SIMQ; sassc->flags &= ~MPSSAS_QUEUE_FROZEN; mps_dprint(sc, MPS_XINFO, "Unfreezing SIM queue\n"); } } /* * There are two scenarios where the status won't be * CAM_REQ_CMP. The first is if MPS_CM_FLAGS_ERROR_MASK is * set, the second is in the MPS_FLAGS_DIAGRESET above. */ if (mpssas_get_ccbstatus(ccb) != CAM_REQ_CMP) { /* * Freeze the dev queue so that commands are * executed in the correct order after error * recovery. */ ccb->ccb_h.status |= CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, /*count*/ 1); } mps_free_command(sc, cm); xpt_done(ccb); return; } mpssas_log_command(cm, MPS_XINFO, "ioc %x scsi %x state %x xfer %u\n", le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); /* * If this is a Direct Drive I/O, reissue the I/O to the original IR * Volume if an error occurred (normal I/O retry). Use the original * CCB, but set a flag that this will be a retry so that it's sent to * the original volume. Free the command but reuse the CCB. */ if (cm->cm_flags & MPS_CM_FLAGS_DD_IO) { mps_free_command(sc, cm); ccb->ccb_h.sim_priv.entries[0].field = MPS_WD_RETRY; mpssas_action_scsiio(sassc, ccb); return; } else ccb->ccb_h.sim_priv.entries[0].field = 0; switch (le16toh(rep->IOCStatus) & MPI2_IOCSTATUS_MASK) { case MPI2_IOCSTATUS_SCSI_DATA_UNDERRUN: csio->resid = cm->cm_length - le32toh(rep->TransferCount); /* FALLTHROUGH */ case MPI2_IOCSTATUS_SUCCESS: case MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR: if ((le16toh(rep->IOCStatus) & MPI2_IOCSTATUS_MASK) == MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR) mpssas_log_command(cm, MPS_XINFO, "recovered error\n"); /* Completion failed at the transport level. */ if (rep->SCSIState & (MPI2_SCSI_STATE_NO_SCSI_STATUS | MPI2_SCSI_STATE_TERMINATED)) { mpssas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); break; } /* In a modern packetized environment, an autosense failure * implies that there's not much else that can be done to * recover the command. */ if (rep->SCSIState & MPI2_SCSI_STATE_AUTOSENSE_FAILED) { mpssas_set_ccbstatus(ccb, CAM_AUTOSENSE_FAIL); break; } /* * CAM doesn't care about SAS Response Info data, but if this is * the state check if TLR should be done. If not, clear the * TLR_bits for the target. */ if ((rep->SCSIState & MPI2_SCSI_STATE_RESPONSE_INFO_VALID) && ((le32toh(rep->ResponseInfo) & MPI2_SCSI_RI_MASK_REASONCODE) == MPS_SCSI_RI_INVALID_FRAME)) { sc->mapping_table[target_id].TLR_bits = (u8)MPI2_SCSIIO_CONTROL_NO_TLR; } /* * Intentionally override the normal SCSI status reporting * for these two cases. These are likely to happen in a * multi-initiator environment, and we want to make sure that * CAM retries these commands rather than fail them. */ if ((rep->SCSIStatus == MPI2_SCSI_STATUS_COMMAND_TERMINATED) || (rep->SCSIStatus == MPI2_SCSI_STATUS_TASK_ABORTED)) { mpssas_set_ccbstatus(ccb, CAM_REQ_ABORTED); break; } /* Handle normal status and sense */ csio->scsi_status = rep->SCSIStatus; if (rep->SCSIStatus == MPI2_SCSI_STATUS_GOOD) mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); else mpssas_set_ccbstatus(ccb, CAM_SCSI_STATUS_ERROR); if (rep->SCSIState & MPI2_SCSI_STATE_AUTOSENSE_VALID) { int sense_len, returned_sense_len; returned_sense_len = min(le32toh(rep->SenseCount), sizeof(struct scsi_sense_data)); if (returned_sense_len < ccb->csio.sense_len) ccb->csio.sense_resid = ccb->csio.sense_len - returned_sense_len; else ccb->csio.sense_resid = 0; sense_len = min(returned_sense_len, ccb->csio.sense_len - ccb->csio.sense_resid); bzero(&ccb->csio.sense_data, sizeof(ccb->csio.sense_data)); bcopy(cm->cm_sense, &ccb->csio.sense_data, sense_len); ccb->ccb_h.status |= CAM_AUTOSNS_VALID; } /* * Check if this is an INQUIRY command. If it's a VPD inquiry, * and it's page code 0 (Supported Page List), and there is * inquiry data, and this is for a sequential access device, and * the device is an SSP target, and TLR is supported by the * controller, turn the TLR_bits value ON if page 0x90 is * supported. */ if ((csio->cdb_io.cdb_bytes[0] == INQUIRY) && (csio->cdb_io.cdb_bytes[1] & SI_EVPD) && (csio->cdb_io.cdb_bytes[2] == SVPD_SUPPORTED_PAGE_LIST) && ((csio->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR) && (csio->data_ptr != NULL) && ((csio->data_ptr[0] & 0x1f) == T_SEQUENTIAL) && (sc->control_TLR) && (sc->mapping_table[target_id].device_info & MPI2_SAS_DEVICE_INFO_SSP_TARGET)) { vpd_list = (struct scsi_vpd_supported_page_list *) csio->data_ptr; TLR_bits = &sc->mapping_table[target_id].TLR_bits; *TLR_bits = (u8)MPI2_SCSIIO_CONTROL_NO_TLR; TLR_on = (u8)MPI2_SCSIIO_CONTROL_TLR_ON; alloc_len = ((u16)csio->cdb_io.cdb_bytes[3] << 8) + csio->cdb_io.cdb_bytes[4]; alloc_len -= csio->resid; for (i = 0; i < MIN(vpd_list->length, alloc_len); i++) { if (vpd_list->list[i] == 0x90) { *TLR_bits = TLR_on; break; } } } /* * If this is a SATA direct-access end device, mark it so that * a SCSI StartStopUnit command will be sent to it when the * driver is being shutdown. */ if ((csio->cdb_io.cdb_bytes[0] == INQUIRY) && ((csio->data_ptr[0] & 0x1f) == T_DIRECT) && (sc->mapping_table[target_id].device_info & MPI2_SAS_DEVICE_INFO_SATA_DEVICE) && ((sc->mapping_table[target_id].device_info & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_END_DEVICE)) { target = &sassc->targets[target_id]; target->supports_SSU = TRUE; mps_dprint(sc, MPS_XINFO, "Target %d supports SSU\n", target_id); } break; case MPI2_IOCSTATUS_SCSI_INVALID_DEVHANDLE: case MPI2_IOCSTATUS_SCSI_DEVICE_NOT_THERE: /* * If devinfo is 0 this will be a volume. In that case don't * tell CAM that the volume is not there. We want volumes to * be enumerated until they are deleted/removed, not just * failed. */ if (cm->cm_targ->devinfo == 0) mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); else mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); break; case MPI2_IOCSTATUS_INVALID_SGL: mps_print_scsiio_cmd(sc, cm); mpssas_set_ccbstatus(ccb, CAM_UNREC_HBA_ERROR); break; case MPI2_IOCSTATUS_SCSI_TASK_TERMINATED: /* * This is one of the responses that comes back when an I/O * has been aborted. If it is because of a timeout that we * initiated, just set the status to CAM_CMD_TIMEOUT. * Otherwise set it to CAM_REQ_ABORTED. The effect on the * command is the same (it gets retried, subject to the * retry counter), the only difference is what gets printed * on the console. */ if (cm->cm_state == MPS_CM_STATE_TIMEDOUT) mpssas_set_ccbstatus(ccb, CAM_CMD_TIMEOUT); else mpssas_set_ccbstatus(ccb, CAM_REQ_ABORTED); break; case MPI2_IOCSTATUS_SCSI_DATA_OVERRUN: /* resid is ignored for this condition */ csio->resid = 0; mpssas_set_ccbstatus(ccb, CAM_DATA_RUN_ERR); break; case MPI2_IOCSTATUS_SCSI_IOC_TERMINATED: case MPI2_IOCSTATUS_SCSI_EXT_TERMINATED: /* - * Since these are generally external (i.e. hopefully - * transient transport-related) errors, retry these without - * decrementing the retry count. + * These can sometimes be transient transport-related + * errors, and sometimes persistent drive-related errors. + * We used to retry these without decrementing the retry + * count by returning CAM_REQUEUE_REQ. Unfortunately, if + * we hit a persistent drive problem that returns one of + * these error codes, we would retry indefinitely. So, + * return CAM_REQ_CMP_ERROR so that we decrement the retry + * count and avoid infinite retries. We're taking the + * potential risk of flagging false failures in the event + * of a topology-related error (e.g. a SAS expander problem + * causes a command addressed to a drive to fail), but + * avoiding getting into an infinite retry loop. */ - mpssas_set_ccbstatus(ccb, CAM_REQUEUE_REQ); + mpssas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); mpssas_log_command(cm, MPS_INFO, "terminated ioc %x scsi %x state %x xfer %u\n", le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); break; case MPI2_IOCSTATUS_INVALID_FUNCTION: case MPI2_IOCSTATUS_INTERNAL_ERROR: case MPI2_IOCSTATUS_INVALID_VPID: case MPI2_IOCSTATUS_INVALID_FIELD: case MPI2_IOCSTATUS_INVALID_STATE: case MPI2_IOCSTATUS_OP_STATE_NOT_SUPPORTED: case MPI2_IOCSTATUS_SCSI_IO_DATA_ERROR: case MPI2_IOCSTATUS_SCSI_PROTOCOL_ERROR: case MPI2_IOCSTATUS_SCSI_RESIDUAL_MISMATCH: case MPI2_IOCSTATUS_SCSI_TASK_MGMT_FAILED: default: mpssas_log_command(cm, MPS_XINFO, "completed ioc %x scsi %x state %x xfer %u\n", le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState, le32toh(rep->TransferCount)); csio->resid = cm->cm_length; mpssas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); break; } mps_sc_failed_io_info(sc,csio,rep); if (sassc->flags & MPSSAS_QUEUE_FROZEN) { ccb->ccb_h.status |= CAM_RELEASE_SIMQ; sassc->flags &= ~MPSSAS_QUEUE_FROZEN; mps_dprint(sc, MPS_XINFO, "Command completed, " "unfreezing SIM queue\n"); } if (mpssas_get_ccbstatus(ccb) != CAM_REQ_CMP) { ccb->ccb_h.status |= CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, /*count*/ 1); } mps_free_command(sc, cm); xpt_done(ccb); } /* All Request reached here are Endian safe */ static void mpssas_direct_drive_io(struct mpssas_softc *sassc, struct mps_command *cm, union ccb *ccb) { pMpi2SCSIIORequest_t pIO_req; struct mps_softc *sc = sassc->sc; uint64_t virtLBA; uint32_t physLBA, stripe_offset, stripe_unit; uint32_t io_size, column; uint8_t *ptrLBA, lba_idx, physLBA_byte, *CDB; /* * If this is a valid SCSI command (Read6, Read10, Read16, Write6, * Write10, or Write16), build a direct I/O message. Otherwise, the I/O * will be sent to the IR volume itself. Since Read6 and Write6 are a * bit different than the 10/16 CDBs, handle them separately. */ pIO_req = (pMpi2SCSIIORequest_t)cm->cm_req; CDB = pIO_req->CDB.CDB32; /* * Handle 6 byte CDBs. */ if ((pIO_req->DevHandle == sc->DD_dev_handle) && ((CDB[0] == READ_6) || (CDB[0] == WRITE_6))) { /* * Get the transfer size in blocks. */ io_size = (cm->cm_length >> sc->DD_block_exponent); /* * Get virtual LBA given in the CDB. */ virtLBA = ((uint64_t)(CDB[1] & 0x1F) << 16) | ((uint64_t)CDB[2] << 8) | (uint64_t)CDB[3]; /* * Check that LBA range for I/O does not exceed volume's * MaxLBA. */ if ((virtLBA + (uint64_t)io_size - 1) <= sc->DD_max_lba) { /* * Check if the I/O crosses a stripe boundary. If not, * translate the virtual LBA to a physical LBA and set * the DevHandle for the PhysDisk to be used. If it * does cross a boundary, do normal I/O. To get the * right DevHandle to use, get the map number for the * column, then use that map number to look up the * DevHandle of the PhysDisk. */ stripe_offset = (uint32_t)virtLBA & (sc->DD_stripe_size - 1); if ((stripe_offset + io_size) <= sc->DD_stripe_size) { physLBA = (uint32_t)virtLBA >> sc->DD_stripe_exponent; stripe_unit = physLBA / sc->DD_num_phys_disks; column = physLBA % sc->DD_num_phys_disks; pIO_req->DevHandle = htole16(sc->DD_column_map[column].dev_handle); /* ???? Is this endian safe*/ cm->cm_desc.SCSIIO.DevHandle = pIO_req->DevHandle; physLBA = (stripe_unit << sc->DD_stripe_exponent) + stripe_offset; ptrLBA = &pIO_req->CDB.CDB32[1]; physLBA_byte = (uint8_t)(physLBA >> 16); *ptrLBA = physLBA_byte; ptrLBA = &pIO_req->CDB.CDB32[2]; physLBA_byte = (uint8_t)(physLBA >> 8); *ptrLBA = physLBA_byte; ptrLBA = &pIO_req->CDB.CDB32[3]; physLBA_byte = (uint8_t)physLBA; *ptrLBA = physLBA_byte; /* * Set flag that Direct Drive I/O is * being done. */ cm->cm_flags |= MPS_CM_FLAGS_DD_IO; } } return; } /* * Handle 10, 12 or 16 byte CDBs. */ if ((pIO_req->DevHandle == sc->DD_dev_handle) && ((CDB[0] == READ_10) || (CDB[0] == WRITE_10) || (CDB[0] == READ_16) || (CDB[0] == WRITE_16) || (CDB[0] == READ_12) || (CDB[0] == WRITE_12))) { /* * For 16-byte CDB's, verify that the upper 4 bytes of the CDB * are 0. If not, this is accessing beyond 2TB so handle it in * the else section. 10-byte and 12-byte CDB's are OK. * FreeBSD sends very rare 12 byte READ/WRITE, but driver is * ready to accept 12byte CDB for Direct IOs. */ if ((CDB[0] == READ_10 || CDB[0] == WRITE_10) || (CDB[0] == READ_12 || CDB[0] == WRITE_12) || !(CDB[2] | CDB[3] | CDB[4] | CDB[5])) { /* * Get the transfer size in blocks. */ io_size = (cm->cm_length >> sc->DD_block_exponent); /* * Get virtual LBA. Point to correct lower 4 bytes of * LBA in the CDB depending on command. */ lba_idx = ((CDB[0] == READ_12) || (CDB[0] == WRITE_12) || (CDB[0] == READ_10) || (CDB[0] == WRITE_10))? 2 : 6; virtLBA = ((uint64_t)CDB[lba_idx] << 24) | ((uint64_t)CDB[lba_idx + 1] << 16) | ((uint64_t)CDB[lba_idx + 2] << 8) | (uint64_t)CDB[lba_idx + 3]; /* * Check that LBA range for I/O does not exceed volume's * MaxLBA. */ if ((virtLBA + (uint64_t)io_size - 1) <= sc->DD_max_lba) { /* * Check if the I/O crosses a stripe boundary. * If not, translate the virtual LBA to a * physical LBA and set the DevHandle for the * PhysDisk to be used. If it does cross a * boundary, do normal I/O. To get the right * DevHandle to use, get the map number for the * column, then use that map number to look up * the DevHandle of the PhysDisk. */ stripe_offset = (uint32_t)virtLBA & (sc->DD_stripe_size - 1); if ((stripe_offset + io_size) <= sc->DD_stripe_size) { physLBA = (uint32_t)virtLBA >> sc->DD_stripe_exponent; stripe_unit = physLBA / sc->DD_num_phys_disks; column = physLBA % sc->DD_num_phys_disks; pIO_req->DevHandle = htole16(sc->DD_column_map[column]. dev_handle); cm->cm_desc.SCSIIO.DevHandle = pIO_req->DevHandle; physLBA = (stripe_unit << sc->DD_stripe_exponent) + stripe_offset; ptrLBA = &pIO_req->CDB.CDB32[lba_idx]; physLBA_byte = (uint8_t)(physLBA >> 24); *ptrLBA = physLBA_byte; ptrLBA = &pIO_req->CDB.CDB32[lba_idx + 1]; physLBA_byte = (uint8_t)(physLBA >> 16); *ptrLBA = physLBA_byte; ptrLBA = &pIO_req->CDB.CDB32[lba_idx + 2]; physLBA_byte = (uint8_t)(physLBA >> 8); *ptrLBA = physLBA_byte; ptrLBA = &pIO_req->CDB.CDB32[lba_idx + 3]; physLBA_byte = (uint8_t)physLBA; *ptrLBA = physLBA_byte; /* * Set flag that Direct Drive I/O is * being done. */ cm->cm_flags |= MPS_CM_FLAGS_DD_IO; } } } else { /* * 16-byte CDB and the upper 4 bytes of the CDB are not * 0. Get the transfer size in blocks. */ io_size = (cm->cm_length >> sc->DD_block_exponent); /* * Get virtual LBA. */ virtLBA = ((uint64_t)CDB[2] << 54) | ((uint64_t)CDB[3] << 48) | ((uint64_t)CDB[4] << 40) | ((uint64_t)CDB[5] << 32) | ((uint64_t)CDB[6] << 24) | ((uint64_t)CDB[7] << 16) | ((uint64_t)CDB[8] << 8) | (uint64_t)CDB[9]; /* * Check that LBA range for I/O does not exceed volume's * MaxLBA. */ if ((virtLBA + (uint64_t)io_size - 1) <= sc->DD_max_lba) { /* * Check if the I/O crosses a stripe boundary. * If not, translate the virtual LBA to a * physical LBA and set the DevHandle for the * PhysDisk to be used. If it does cross a * boundary, do normal I/O. To get the right * DevHandle to use, get the map number for the * column, then use that map number to look up * the DevHandle of the PhysDisk. */ stripe_offset = (uint32_t)virtLBA & (sc->DD_stripe_size - 1); if ((stripe_offset + io_size) <= sc->DD_stripe_size) { physLBA = (uint32_t)(virtLBA >> sc->DD_stripe_exponent); stripe_unit = physLBA / sc->DD_num_phys_disks; column = physLBA % sc->DD_num_phys_disks; pIO_req->DevHandle = htole16(sc->DD_column_map[column]. dev_handle); cm->cm_desc.SCSIIO.DevHandle = pIO_req->DevHandle; physLBA = (stripe_unit << sc->DD_stripe_exponent) + stripe_offset; /* * Set upper 4 bytes of LBA to 0. We * assume that the phys disks are less * than 2 TB's in size. Then, set the * lower 4 bytes. */ pIO_req->CDB.CDB32[2] = 0; pIO_req->CDB.CDB32[3] = 0; pIO_req->CDB.CDB32[4] = 0; pIO_req->CDB.CDB32[5] = 0; ptrLBA = &pIO_req->CDB.CDB32[6]; physLBA_byte = (uint8_t)(physLBA >> 24); *ptrLBA = physLBA_byte; ptrLBA = &pIO_req->CDB.CDB32[7]; physLBA_byte = (uint8_t)(physLBA >> 16); *ptrLBA = physLBA_byte; ptrLBA = &pIO_req->CDB.CDB32[8]; physLBA_byte = (uint8_t)(physLBA >> 8); *ptrLBA = physLBA_byte; ptrLBA = &pIO_req->CDB.CDB32[9]; physLBA_byte = (uint8_t)physLBA; *ptrLBA = physLBA_byte; /* * Set flag that Direct Drive I/O is * being done. */ cm->cm_flags |= MPS_CM_FLAGS_DD_IO; } } } } } #if __FreeBSD_version >= 900026 static void mpssas_smpio_complete(struct mps_softc *sc, struct mps_command *cm) { MPI2_SMP_PASSTHROUGH_REPLY *rpl; MPI2_SMP_PASSTHROUGH_REQUEST *req; uint64_t sasaddr; union ccb *ccb; ccb = cm->cm_complete_data; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and SMP * commands require two S/G elements only. That should be handled * in the standard request size. */ if ((cm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { mps_dprint(sc, MPS_ERROR,"%s: cm_flags = %#x on SMP request!\n", __func__, cm->cm_flags); mpssas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); goto bailout; } rpl = (MPI2_SMP_PASSTHROUGH_REPLY *)cm->cm_reply; if (rpl == NULL) { mps_dprint(sc, MPS_ERROR, "%s: NULL cm_reply!\n", __func__); mpssas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); goto bailout; } req = (MPI2_SMP_PASSTHROUGH_REQUEST *)cm->cm_req; sasaddr = le32toh(req->SASAddress.Low); sasaddr |= ((uint64_t)(le32toh(req->SASAddress.High))) << 32; if ((le16toh(rpl->IOCStatus) & MPI2_IOCSTATUS_MASK) != MPI2_IOCSTATUS_SUCCESS || rpl->SASStatus != MPI2_SASSTATUS_SUCCESS) { mps_dprint(sc, MPS_XINFO, "%s: IOCStatus %04x SASStatus %02x\n", __func__, le16toh(rpl->IOCStatus), rpl->SASStatus); mpssas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); goto bailout; } mps_dprint(sc, MPS_XINFO, "%s: SMP request to SAS address " "%#jx completed successfully\n", __func__, (uintmax_t)sasaddr); if (ccb->smpio.smp_response[2] == SMP_FR_ACCEPTED) mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); else mpssas_set_ccbstatus(ccb, CAM_SMP_STATUS_ERROR); bailout: /* * We sync in both directions because we had DMAs in the S/G list * in both directions. */ bus_dmamap_sync(sc->buffer_dmat, cm->cm_dmamap, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->buffer_dmat, cm->cm_dmamap); mps_free_command(sc, cm); xpt_done(ccb); } static void mpssas_send_smpcmd(struct mpssas_softc *sassc, union ccb *ccb, uint64_t sasaddr) { struct mps_command *cm; uint8_t *request, *response; MPI2_SMP_PASSTHROUGH_REQUEST *req; struct mps_softc *sc; int error; sc = sassc->sc; error = 0; /* * XXX We don't yet support physical addresses here. */ switch ((ccb->ccb_h.flags & CAM_DATA_MASK)) { case CAM_DATA_PADDR: case CAM_DATA_SG_PADDR: mps_dprint(sc, MPS_ERROR, "%s: physical addresses not supported\n", __func__); mpssas_set_ccbstatus(ccb, CAM_REQ_INVALID); xpt_done(ccb); return; case CAM_DATA_SG: /* * The chip does not support more than one buffer for the * request or response. */ if ((ccb->smpio.smp_request_sglist_cnt > 1) || (ccb->smpio.smp_response_sglist_cnt > 1)) { mps_dprint(sc, MPS_ERROR, "%s: multiple request or response " "buffer segments not supported for SMP\n", __func__); mpssas_set_ccbstatus(ccb, CAM_REQ_INVALID); xpt_done(ccb); return; } /* * The CAM_SCATTER_VALID flag was originally implemented * for the XPT_SCSI_IO CCB, which only has one data pointer. * We have two. So, just take that flag to mean that we * might have S/G lists, and look at the S/G segment count * to figure out whether that is the case for each individual * buffer. */ if (ccb->smpio.smp_request_sglist_cnt != 0) { bus_dma_segment_t *req_sg; req_sg = (bus_dma_segment_t *)ccb->smpio.smp_request; request = (uint8_t *)(uintptr_t)req_sg[0].ds_addr; } else request = ccb->smpio.smp_request; if (ccb->smpio.smp_response_sglist_cnt != 0) { bus_dma_segment_t *rsp_sg; rsp_sg = (bus_dma_segment_t *)ccb->smpio.smp_response; response = (uint8_t *)(uintptr_t)rsp_sg[0].ds_addr; } else response = ccb->smpio.smp_response; break; case CAM_DATA_VADDR: request = ccb->smpio.smp_request; response = ccb->smpio.smp_response; break; default: mpssas_set_ccbstatus(ccb, CAM_REQ_INVALID); xpt_done(ccb); return; } cm = mps_alloc_command(sc); if (cm == NULL) { mps_dprint(sc, MPS_ERROR, "%s: cannot allocate command\n", __func__); mpssas_set_ccbstatus(ccb, CAM_RESRC_UNAVAIL); xpt_done(ccb); return; } req = (MPI2_SMP_PASSTHROUGH_REQUEST *)cm->cm_req; bzero(req, sizeof(*req)); req->Function = MPI2_FUNCTION_SMP_PASSTHROUGH; /* Allow the chip to use any route to this SAS address. */ req->PhysicalPort = 0xff; req->RequestDataLength = htole16(ccb->smpio.smp_request_len); req->SGLFlags = MPI2_SGLFLAGS_SYSTEM_ADDRESS_SPACE | MPI2_SGLFLAGS_SGL_TYPE_MPI; mps_dprint(sc, MPS_XINFO, "%s: sending SMP request to SAS " "address %#jx\n", __func__, (uintmax_t)sasaddr); mpi_init_sge(cm, req, &req->SGL); /* * Set up a uio to pass into mps_map_command(). This allows us to * do one map command, and one busdma call in there. */ cm->cm_uio.uio_iov = cm->cm_iovec; cm->cm_uio.uio_iovcnt = 2; cm->cm_uio.uio_segflg = UIO_SYSSPACE; /* * The read/write flag isn't used by busdma, but set it just in * case. This isn't exactly accurate, either, since we're going in * both directions. */ cm->cm_uio.uio_rw = UIO_WRITE; cm->cm_iovec[0].iov_base = request; cm->cm_iovec[0].iov_len = le16toh(req->RequestDataLength); cm->cm_iovec[1].iov_base = response; cm->cm_iovec[1].iov_len = ccb->smpio.smp_response_len; cm->cm_uio.uio_resid = cm->cm_iovec[0].iov_len + cm->cm_iovec[1].iov_len; /* * Trigger a warning message in mps_data_cb() for the user if we * wind up exceeding two S/G segments. The chip expects one * segment for the request and another for the response. */ cm->cm_max_segs = 2; cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE; cm->cm_complete = mpssas_smpio_complete; cm->cm_complete_data = ccb; /* * Tell the mapping code that we're using a uio, and that this is * an SMP passthrough request. There is a little special-case * logic there (in mps_data_cb()) to handle the bidirectional * transfer. */ cm->cm_flags |= MPS_CM_FLAGS_USE_UIO | MPS_CM_FLAGS_SMP_PASS | MPS_CM_FLAGS_DATAIN | MPS_CM_FLAGS_DATAOUT; /* The chip data format is little endian. */ req->SASAddress.High = htole32(sasaddr >> 32); req->SASAddress.Low = htole32(sasaddr); /* * XXX Note that we don't have a timeout/abort mechanism here. * From the manual, it looks like task management requests only * work for SCSI IO and SATA passthrough requests. We may need to * have a mechanism to retry requests in the event of a chip reset * at least. Hopefully the chip will insure that any errors short * of that are relayed back to the driver. */ error = mps_map_command(sc, cm); if ((error != 0) && (error != EINPROGRESS)) { mps_dprint(sc, MPS_ERROR, "%s: error %d returned from mps_map_command()\n", __func__, error); goto bailout_error; } return; bailout_error: mps_free_command(sc, cm); mpssas_set_ccbstatus(ccb, CAM_RESRC_UNAVAIL); xpt_done(ccb); return; } static void mpssas_action_smpio(struct mpssas_softc *sassc, union ccb *ccb) { struct mps_softc *sc; struct mpssas_target *targ; uint64_t sasaddr = 0; sc = sassc->sc; /* * Make sure the target exists. */ KASSERT(ccb->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in XPT_SMP_IO\n", ccb->ccb_h.target_id)); targ = &sassc->targets[ccb->ccb_h.target_id]; if (targ->handle == 0x0) { mps_dprint(sc, MPS_ERROR, "%s: target %d does not exist!\n", __func__, ccb->ccb_h.target_id); mpssas_set_ccbstatus(ccb, CAM_SEL_TIMEOUT); xpt_done(ccb); return; } /* * If this device has an embedded SMP target, we'll talk to it * directly. * figure out what the expander's address is. */ if ((targ->devinfo & MPI2_SAS_DEVICE_INFO_SMP_TARGET) != 0) sasaddr = targ->sasaddr; /* * If we don't have a SAS address for the expander yet, try * grabbing it from the page 0x83 information cached in the * transport layer for this target. LSI expanders report the * expander SAS address as the port-associated SAS address in * Inquiry VPD page 0x83. Maxim expanders don't report it in page * 0x83. * * XXX KDM disable this for now, but leave it commented out so that * it is obvious that this is another possible way to get the SAS * address. * * The parent handle method below is a little more reliable, and * the other benefit is that it works for devices other than SES * devices. So you can send a SMP request to a da(4) device and it * will get routed to the expander that device is attached to. * (Assuming the da(4) device doesn't contain an SMP target...) */ #if 0 if (sasaddr == 0) sasaddr = xpt_path_sas_addr(ccb->ccb_h.path); #endif /* * If we still don't have a SAS address for the expander, look for * the parent device of this device, which is probably the expander. */ if (sasaddr == 0) { #ifdef OLD_MPS_PROBE struct mpssas_target *parent_target; #endif if (targ->parent_handle == 0x0) { mps_dprint(sc, MPS_ERROR, "%s: handle %d does not have a valid " "parent handle!\n", __func__, targ->handle); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } #ifdef OLD_MPS_PROBE parent_target = mpssas_find_target_by_handle(sassc, 0, targ->parent_handle); if (parent_target == NULL) { mps_dprint(sc, MPS_ERROR, "%s: handle %d does not have a valid " "parent target!\n", __func__, targ->handle); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } if ((parent_target->devinfo & MPI2_SAS_DEVICE_INFO_SMP_TARGET) == 0) { mps_dprint(sc, MPS_ERROR, "%s: handle %d parent %d does not " "have an SMP target!\n", __func__, targ->handle, parent_target->handle); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } sasaddr = parent_target->sasaddr; #else /* OLD_MPS_PROBE */ if ((targ->parent_devinfo & MPI2_SAS_DEVICE_INFO_SMP_TARGET) == 0) { mps_dprint(sc, MPS_ERROR, "%s: handle %d parent %d does not " "have an SMP target!\n", __func__, targ->handle, targ->parent_handle); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } if (targ->parent_sasaddr == 0x0) { mps_dprint(sc, MPS_ERROR, "%s: handle %d parent handle %d does " "not have a valid SAS address!\n", __func__, targ->handle, targ->parent_handle); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } sasaddr = targ->parent_sasaddr; #endif /* OLD_MPS_PROBE */ } if (sasaddr == 0) { mps_dprint(sc, MPS_INFO, "%s: unable to find SAS address for handle %d\n", __func__, targ->handle); mpssas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE); goto bailout; } mpssas_send_smpcmd(sassc, ccb, sasaddr); return; bailout: xpt_done(ccb); } #endif //__FreeBSD_version >= 900026 static void mpssas_action_resetdev(struct mpssas_softc *sassc, union ccb *ccb) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; struct mps_softc *sc; struct mps_command *tm; struct mpssas_target *targ; MPS_FUNCTRACE(sassc->sc); mtx_assert(&sassc->sc->mps_mtx, MA_OWNED); KASSERT(ccb->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in XPT_RESET_DEV\n", ccb->ccb_h.target_id)); sc = sassc->sc; tm = mps_alloc_command(sc); if (tm == NULL) { mps_dprint(sc, MPS_ERROR, "command alloc failure in mpssas_action_resetdev\n"); mpssas_set_ccbstatus(ccb, CAM_RESRC_UNAVAIL); xpt_done(ccb); return; } targ = &sassc->targets[ccb->ccb_h.target_id]; req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; req->DevHandle = htole16(targ->handle); req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT; req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET; /* SAS Hard Link Reset / SATA Link Reset */ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET; tm->cm_data = NULL; tm->cm_desc.HighPriority.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY; tm->cm_complete = mpssas_resetdev_complete; tm->cm_complete_data = ccb; tm->cm_targ = targ; targ->flags |= MPSSAS_TARGET_INRESET; mps_map_command(sc, tm); } static void mpssas_resetdev_complete(struct mps_softc *sc, struct mps_command *tm) { MPI2_SCSI_TASK_MANAGE_REPLY *resp; union ccb *ccb; MPS_FUNCTRACE(sc); mtx_assert(&sc->mps_mtx, MA_OWNED); resp = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply; ccb = tm->cm_complete_data; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * task management commands don't have S/G lists. */ if ((tm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { MPI2_SCSI_TASK_MANAGE_REQUEST *req; req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req; mps_dprint(sc, MPS_ERROR, "%s: cm_flags = %#x for reset of handle %#04x! " "This should not happen!\n", __func__, tm->cm_flags, req->DevHandle); mpssas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); goto bailout; } mps_dprint(sc, MPS_XINFO, "%s: IOCStatus = 0x%x ResponseCode = 0x%x\n", __func__, le16toh(resp->IOCStatus), le32toh(resp->ResponseCode)); if (le32toh(resp->ResponseCode) == MPI2_SCSITASKMGMT_RSP_TM_COMPLETE) { mpssas_set_ccbstatus(ccb, CAM_REQ_CMP); mpssas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid, CAM_LUN_WILDCARD); } else mpssas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR); bailout: mpssas_free_tm(sc, tm); xpt_done(ccb); } static void mpssas_poll(struct cam_sim *sim) { struct mpssas_softc *sassc; sassc = cam_sim_softc(sim); if (sassc->sc->mps_debug & MPS_TRACE) { /* frequent debug messages during a panic just slow * everything down too much. */ mps_printf(sassc->sc, "%s clearing MPS_TRACE\n", __func__); sassc->sc->mps_debug &= ~MPS_TRACE; } mps_intr_locked(sassc->sc); } static void mpssas_async(void *callback_arg, uint32_t code, struct cam_path *path, void *arg) { struct mps_softc *sc; sc = (struct mps_softc *)callback_arg; switch (code) { #if (__FreeBSD_version >= 1000006) || \ ((__FreeBSD_version >= 901503) && (__FreeBSD_version < 1000000)) case AC_ADVINFO_CHANGED: { struct mpssas_target *target; struct mpssas_softc *sassc; struct scsi_read_capacity_data_long rcap_buf; struct ccb_dev_advinfo cdai; struct mpssas_lun *lun; lun_id_t lunid; int found_lun; uintptr_t buftype; buftype = (uintptr_t)arg; found_lun = 0; sassc = sc->sassc; /* * We're only interested in read capacity data changes. */ if (buftype != CDAI_TYPE_RCAPLONG) break; /* * We should have a handle for this, but check to make sure. */ KASSERT(xpt_path_target_id(path) < sassc->maxtargets, ("Target %d out of bounds in mpssas_async\n", xpt_path_target_id(path))); target = &sassc->targets[xpt_path_target_id(path)]; if (target->handle == 0) break; lunid = xpt_path_lun_id(path); SLIST_FOREACH(lun, &target->luns, lun_link) { if (lun->lun_id == lunid) { found_lun = 1; break; } } if (found_lun == 0) { lun = malloc(sizeof(struct mpssas_lun), M_MPT2, M_NOWAIT | M_ZERO); if (lun == NULL) { mps_dprint(sc, MPS_ERROR, "Unable to alloc " "LUN for EEDP support.\n"); break; } lun->lun_id = lunid; SLIST_INSERT_HEAD(&target->luns, lun, lun_link); } bzero(&rcap_buf, sizeof(rcap_buf)); xpt_setup_ccb(&cdai.ccb_h, path, CAM_PRIORITY_NORMAL); cdai.ccb_h.func_code = XPT_DEV_ADVINFO; cdai.ccb_h.flags = CAM_DIR_IN; cdai.buftype = CDAI_TYPE_RCAPLONG; #if (__FreeBSD_version >= 1100061) || \ ((__FreeBSD_version >= 1001510) && (__FreeBSD_version < 1100000)) cdai.flags = CDAI_FLAG_NONE; #else cdai.flags = 0; #endif cdai.bufsiz = sizeof(rcap_buf); cdai.buf = (uint8_t *)&rcap_buf; xpt_action((union ccb *)&cdai); if ((cdai.ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(cdai.ccb_h.path, 0, 0, 0, FALSE); if ((mpssas_get_ccbstatus((union ccb *)&cdai) == CAM_REQ_CMP) && (rcap_buf.prot & SRC16_PROT_EN)) { lun->eedp_formatted = TRUE; lun->eedp_block_size = scsi_4btoul(rcap_buf.length); } else { lun->eedp_formatted = FALSE; lun->eedp_block_size = 0; } break; } #else case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; cgd = arg; mpssas_check_eedp(sc, path, cgd); break; } #endif default: break; } } #if (__FreeBSD_version < 901503) || \ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006)) static void mpssas_check_eedp(struct mps_softc *sc, struct cam_path *path, struct ccb_getdev *cgd) { struct mpssas_softc *sassc = sc->sassc; struct ccb_scsiio *csio; struct scsi_read_capacity_16 *scsi_cmd; struct scsi_read_capacity_eedp *rcap_buf; path_id_t pathid; target_id_t targetid; lun_id_t lunid; union ccb *ccb; struct cam_path *local_path; struct mpssas_target *target; struct mpssas_lun *lun; uint8_t found_lun; char path_str[64]; sassc = sc->sassc; pathid = cam_sim_path(sassc->sim); targetid = xpt_path_target_id(path); lunid = xpt_path_lun_id(path); KASSERT(targetid < sassc->maxtargets, ("Target %d out of bounds in mpssas_check_eedp\n", targetid)); target = &sassc->targets[targetid]; if (target->handle == 0x0) return; /* * Determine if the device is EEDP capable. * * If this flag is set in the inquiry data, * the device supports protection information, * and must support the 16 byte read * capacity command, otherwise continue without * sending read cap 16 */ if ((cgd->inq_data.spc3_flags & SPC3_SID_PROTECT) == 0) return; /* * Issue a READ CAPACITY 16 command. This info * is used to determine if the LUN is formatted * for EEDP support. */ ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { mps_dprint(sc, MPS_ERROR, "Unable to alloc CCB " "for EEDP support.\n"); return; } if (xpt_create_path(&local_path, xpt_periph, pathid, targetid, lunid) != CAM_REQ_CMP) { mps_dprint(sc, MPS_ERROR, "Unable to create " "path for EEDP support\n"); xpt_free_ccb(ccb); return; } /* * If LUN is already in list, don't create a new * one. */ found_lun = FALSE; SLIST_FOREACH(lun, &target->luns, lun_link) { if (lun->lun_id == lunid) { found_lun = TRUE; break; } } if (!found_lun) { lun = malloc(sizeof(struct mpssas_lun), M_MPT2, M_NOWAIT | M_ZERO); if (lun == NULL) { mps_dprint(sc, MPS_ERROR, "Unable to alloc LUN for EEDP support.\n"); xpt_free_path(local_path); xpt_free_ccb(ccb); return; } lun->lun_id = lunid; SLIST_INSERT_HEAD(&target->luns, lun, lun_link); } xpt_path_string(local_path, path_str, sizeof(path_str)); mps_dprint(sc, MPS_INFO, "Sending read cap: path %s handle %d\n", path_str, target->handle); /* * Issue a READ CAPACITY 16 command for the LUN. * The mpssas_read_cap_done function will load * the read cap info into the LUN struct. */ rcap_buf = malloc(sizeof(struct scsi_read_capacity_eedp), M_MPT2, M_NOWAIT | M_ZERO); if (rcap_buf == NULL) { mps_dprint(sc, MPS_FAULT, "Unable to alloc read capacity buffer for EEDP support.\n"); xpt_free_path(ccb->ccb_h.path); xpt_free_ccb(ccb); return; } xpt_setup_ccb(&ccb->ccb_h, local_path, CAM_PRIORITY_XPT); csio = &ccb->csio; csio->ccb_h.func_code = XPT_SCSI_IO; csio->ccb_h.flags = CAM_DIR_IN; csio->ccb_h.retry_count = 4; csio->ccb_h.cbfcnp = mpssas_read_cap_done; csio->ccb_h.timeout = 60000; csio->data_ptr = (uint8_t *)rcap_buf; csio->dxfer_len = sizeof(struct scsi_read_capacity_eedp); csio->sense_len = MPS_SENSE_LEN; csio->cdb_len = sizeof(*scsi_cmd); csio->tag_action = MSG_SIMPLE_Q_TAG; scsi_cmd = (struct scsi_read_capacity_16 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = 0x9E; scsi_cmd->service_action = SRC16_SERVICE_ACTION; ((uint8_t *)scsi_cmd)[13] = sizeof(struct scsi_read_capacity_eedp); ccb->ccb_h.ppriv_ptr1 = sassc; xpt_action(ccb); } static void mpssas_read_cap_done(struct cam_periph *periph, union ccb *done_ccb) { struct mpssas_softc *sassc; struct mpssas_target *target; struct mpssas_lun *lun; struct scsi_read_capacity_eedp *rcap_buf; if (done_ccb == NULL) return; /* Driver need to release devq, it Scsi command is * generated by driver internally. * Currently there is a single place where driver * calls scsi command internally. In future if driver * calls more scsi command internally, it needs to release * devq internally, since those command will not go back to * cam_periph. */ if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) ) { done_ccb->ccb_h.status &= ~CAM_DEV_QFRZN; xpt_release_devq(done_ccb->ccb_h.path, /*count*/ 1, /*run_queue*/TRUE); } rcap_buf = (struct scsi_read_capacity_eedp *)done_ccb->csio.data_ptr; /* * Get the LUN ID for the path and look it up in the LUN list for the * target. */ sassc = (struct mpssas_softc *)done_ccb->ccb_h.ppriv_ptr1; KASSERT(done_ccb->ccb_h.target_id < sassc->maxtargets, ("Target %d out of bounds in mpssas_read_cap_done\n", done_ccb->ccb_h.target_id)); target = &sassc->targets[done_ccb->ccb_h.target_id]; SLIST_FOREACH(lun, &target->luns, lun_link) { if (lun->lun_id != done_ccb->ccb_h.target_lun) continue; /* * Got the LUN in the target's LUN list. Fill it in * with EEDP info. If the READ CAP 16 command had some * SCSI error (common if command is not supported), mark * the lun as not supporting EEDP and set the block size * to 0. */ if ((mpssas_get_ccbstatus(done_ccb) != CAM_REQ_CMP) || (done_ccb->csio.scsi_status != SCSI_STATUS_OK)) { lun->eedp_formatted = FALSE; lun->eedp_block_size = 0; break; } if (rcap_buf->protect & 0x01) { mps_dprint(sassc->sc, MPS_INFO, "LUN %d for " "target ID %d is formatted for EEDP " "support.\n", done_ccb->ccb_h.target_lun, done_ccb->ccb_h.target_id); lun->eedp_formatted = TRUE; lun->eedp_block_size = scsi_4btoul(rcap_buf->length); } break; } // Finished with this CCB and path. free(rcap_buf, M_MPT2); xpt_free_path(done_ccb->ccb_h.path); xpt_free_ccb(done_ccb); } #endif /* (__FreeBSD_version < 901503) || \ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006)) */ void mpssas_prepare_for_tm(struct mps_softc *sc, struct mps_command *tm, struct mpssas_target *target, lun_id_t lun_id) { union ccb *ccb; path_id_t path_id; /* * Set the INRESET flag for this target so that no I/O will be sent to * the target until the reset has completed. If an I/O request does * happen, the devq will be frozen. The CCB holds the path which is * used to release the devq. The devq is released and the CCB is freed * when the TM completes. */ ccb = xpt_alloc_ccb_nowait(); if (ccb) { path_id = cam_sim_path(sc->sassc->sim); if (xpt_create_path(&ccb->ccb_h.path, xpt_periph, path_id, target->tid, lun_id) != CAM_REQ_CMP) { xpt_free_ccb(ccb); } else { tm->cm_ccb = ccb; tm->cm_targ = target; target->flags |= MPSSAS_TARGET_INRESET; } } } int mpssas_startup(struct mps_softc *sc) { /* * Send the port enable message and set the wait_for_port_enable flag. * This flag helps to keep the simq frozen until all discovery events * are processed. */ sc->wait_for_port_enable = 1; mpssas_send_portenable(sc); return (0); } static int mpssas_send_portenable(struct mps_softc *sc) { MPI2_PORT_ENABLE_REQUEST *request; struct mps_command *cm; MPS_FUNCTRACE(sc); if ((cm = mps_alloc_command(sc)) == NULL) return (EBUSY); request = (MPI2_PORT_ENABLE_REQUEST *)cm->cm_req; request->Function = MPI2_FUNCTION_PORT_ENABLE; request->MsgFlags = 0; request->VP_ID = 0; cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE; cm->cm_complete = mpssas_portenable_complete; cm->cm_data = NULL; cm->cm_sge = NULL; mps_map_command(sc, cm); mps_dprint(sc, MPS_XINFO, "mps_send_portenable finished cm %p req %p complete %p\n", cm, cm->cm_req, cm->cm_complete); return (0); } static void mpssas_portenable_complete(struct mps_softc *sc, struct mps_command *cm) { MPI2_PORT_ENABLE_REPLY *reply; struct mpssas_softc *sassc; MPS_FUNCTRACE(sc); sassc = sc->sassc; /* * Currently there should be no way we can hit this case. It only * happens when we have a failure to allocate chain frames, and * port enable commands don't have S/G lists. */ if ((cm->cm_flags & MPS_CM_FLAGS_ERROR_MASK) != 0) { mps_dprint(sc, MPS_ERROR, "%s: cm_flags = %#x for port enable! " "This should not happen!\n", __func__, cm->cm_flags); } reply = (MPI2_PORT_ENABLE_REPLY *)cm->cm_reply; if (reply == NULL) mps_dprint(sc, MPS_FAULT, "Portenable NULL reply\n"); else if (le16toh(reply->IOCStatus & MPI2_IOCSTATUS_MASK) != MPI2_IOCSTATUS_SUCCESS) mps_dprint(sc, MPS_FAULT, "Portenable failed\n"); mps_free_command(sc, cm); if (sc->mps_ich.ich_arg != NULL) { mps_dprint(sc, MPS_XINFO, "disestablish config intrhook\n"); config_intrhook_disestablish(&sc->mps_ich); sc->mps_ich.ich_arg = NULL; } /* * Get WarpDrive info after discovery is complete but before the scan * starts. At this point, all devices are ready to be exposed to the * OS. If devices should be hidden instead, take them out of the * 'targets' array before the scan. The devinfo for a disk will have * some info and a volume's will be 0. Use that to remove disks. */ mps_wd_config_pages(sc); /* * Done waiting for port enable to complete. Decrement the refcount. * If refcount is 0, discovery is complete and a rescan of the bus can * take place. Since the simq was explicitly frozen before port * enable, it must be explicitly released here to keep the * freeze/release count in sync. */ sc->wait_for_port_enable = 0; sc->port_enable_complete = 1; wakeup(&sc->port_enable_complete); mpssas_startup_decrement(sassc); } int mpssas_check_id(struct mpssas_softc *sassc, int id) { struct mps_softc *sc = sassc->sc; char *ids; char *name; ids = &sc->exclude_ids[0]; while((name = strsep(&ids, ",")) != NULL) { if (name[0] == '\0') continue; if (strtol(name, NULL, 0) == (long)id) return (1); } return (0); } void mpssas_realloc_targets(struct mps_softc *sc, int maxtargets) { struct mpssas_softc *sassc; struct mpssas_lun *lun, *lun_tmp; struct mpssas_target *targ; int i; sassc = sc->sassc; /* * The number of targets is based on IOC Facts, so free all of * the allocated LUNs for each target and then the target buffer * itself. */ for (i=0; i< maxtargets; i++) { targ = &sassc->targets[i]; SLIST_FOREACH_SAFE(lun, &targ->luns, lun_link, lun_tmp) { free(lun, M_MPT2); } } free(sassc->targets, M_MPT2); sassc->targets = malloc(sizeof(struct mpssas_target) * maxtargets, M_MPT2, M_WAITOK|M_ZERO); if (!sassc->targets) { panic("%s failed to alloc targets with error %d\n", __func__, ENOMEM); } } Index: user/ngie/detangle-rc/sys/kern/subr_intr.c =================================================================== --- user/ngie/detangle-rc/sys/kern/subr_intr.c (revision 299126) +++ user/ngie/detangle-rc/sys/kern/subr_intr.c (revision 299127) @@ -1,1289 +1,1303 @@ /*- * Copyright (c) 2015-2016 Svatopluk Kraus * Copyright (c) 2015-2016 Michal Meloun * 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. */ #include __FBSDID("$FreeBSD$"); /* * New-style Interrupt Framework * * TODO: - to support IPI (PPI) enabling on other CPUs if already started * - to complete things for removable PICs */ #include "opt_acpi.h" #include "opt_ddb.h" #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif #ifdef DDB #include #endif #include "pic_if.h" #define INTRNAME_LEN (2*MAXCOMLEN + 1) #ifdef DEBUG #define debugf(fmt, args...) do { printf("%s(): ", __func__); \ printf(fmt,##args); } while (0) #else #define debugf(fmt, args...) #endif MALLOC_DECLARE(M_INTRNG); MALLOC_DEFINE(M_INTRNG, "intr", "intr interrupt handling"); /* Main interrupt handler called from assembler -> 'hidden' for C code. */ void intr_irq_handler(struct trapframe *tf); /* Root interrupt controller stuff. */ device_t intr_irq_root_dev; static intr_irq_filter_t *irq_root_filter; static void *irq_root_arg; static u_int irq_root_ipicount; /* Interrupt controller definition. */ struct intr_pic { SLIST_ENTRY(intr_pic) pic_next; intptr_t pic_xref; /* hardware identification */ device_t pic_dev; }; static struct mtx pic_list_lock; static SLIST_HEAD(, intr_pic) pic_list; static struct intr_pic *pic_lookup(device_t dev, intptr_t xref); /* Interrupt source definition. */ static struct mtx isrc_table_lock; static struct intr_irqsrc *irq_sources[NIRQ]; u_int irq_next_free; /* * XXX - All stuff around struct intr_dev_data is considered as temporary * until better place for storing struct intr_map_data will be find. * * For now, there are two global interrupt numbers spaces: * <0, NIRQ) ... interrupts without config data * managed in irq_sources[] * IRQ_DDATA_BASE + <0, 2 * NIRQ) ... interrupts with config data * managed in intr_ddata_tab[] * * Read intr_ddata_lookup() to see how these spaces are worked with. * Note that each interrupt number from second space duplicates some number * from first space at this moment. An interrupt number from first space can * be duplicated even multiple times in second space. */ struct intr_dev_data { device_t idd_dev; intptr_t idd_xref; u_int idd_irq; - struct intr_map_data idd_data; + struct intr_map_data * idd_data; struct intr_irqsrc * idd_isrc; }; static struct intr_dev_data *intr_ddata_tab[2 * NIRQ]; static u_int intr_ddata_first_unused; #define IRQ_DDATA_BASE 10000 CTASSERT(IRQ_DDATA_BASE > nitems(irq_sources)); #ifdef SMP static boolean_t irq_assign_cpu = FALSE; #endif /* * - 2 counters for each I/O interrupt. * - MAXCPU counters for each IPI counters for SMP. */ #ifdef SMP #define INTRCNT_COUNT (NIRQ * 2 + INTR_IPI_COUNT * MAXCPU) #else #define INTRCNT_COUNT (NIRQ * 2) #endif /* Data for MI statistics reporting. */ u_long intrcnt[INTRCNT_COUNT]; char intrnames[INTRCNT_COUNT * INTRNAME_LEN]; size_t sintrcnt = sizeof(intrcnt); size_t sintrnames = sizeof(intrnames); static u_int intrcnt_index; /* * Interrupt framework initialization routine. */ static void intr_irq_init(void *dummy __unused) { SLIST_INIT(&pic_list); mtx_init(&pic_list_lock, "intr pic list", NULL, MTX_DEF); mtx_init(&isrc_table_lock, "intr isrc table", NULL, MTX_DEF); } SYSINIT(intr_irq_init, SI_SUB_INTR, SI_ORDER_FIRST, intr_irq_init, NULL); static void intrcnt_setname(const char *name, int index) { snprintf(intrnames + INTRNAME_LEN * index, INTRNAME_LEN, "%-*s", INTRNAME_LEN - 1, name); } /* * Update name for interrupt source with interrupt event. */ static void intrcnt_updatename(struct intr_irqsrc *isrc) { /* QQQ: What about stray counter name? */ mtx_assert(&isrc_table_lock, MA_OWNED); intrcnt_setname(isrc->isrc_event->ie_fullname, isrc->isrc_index); } /* * Virtualization for interrupt source interrupt counter increment. */ static inline void isrc_increment_count(struct intr_irqsrc *isrc) { if (isrc->isrc_flags & INTR_ISRCF_PPI) atomic_add_long(&isrc->isrc_count[0], 1); else isrc->isrc_count[0]++; } /* * Virtualization for interrupt source interrupt stray counter increment. */ static inline void isrc_increment_straycount(struct intr_irqsrc *isrc) { isrc->isrc_count[1]++; } /* * Virtualization for interrupt source interrupt name update. */ static void isrc_update_name(struct intr_irqsrc *isrc, const char *name) { char str[INTRNAME_LEN]; mtx_assert(&isrc_table_lock, MA_OWNED); if (name != NULL) { snprintf(str, INTRNAME_LEN, "%s: %s", isrc->isrc_name, name); intrcnt_setname(str, isrc->isrc_index); snprintf(str, INTRNAME_LEN, "stray %s: %s", isrc->isrc_name, name); intrcnt_setname(str, isrc->isrc_index + 1); } else { snprintf(str, INTRNAME_LEN, "%s:", isrc->isrc_name); intrcnt_setname(str, isrc->isrc_index); snprintf(str, INTRNAME_LEN, "stray %s:", isrc->isrc_name); intrcnt_setname(str, isrc->isrc_index + 1); } } /* * Virtualization for interrupt source interrupt counters setup. */ static void isrc_setup_counters(struct intr_irqsrc *isrc) { u_int index; /* * XXX - it does not work well with removable controllers and * interrupt sources !!! */ index = atomic_fetchadd_int(&intrcnt_index, 2); isrc->isrc_index = index; isrc->isrc_count = &intrcnt[index]; isrc_update_name(isrc, NULL); } /* * Virtualization for interrupt source interrupt counters release. */ static void isrc_release_counters(struct intr_irqsrc *isrc) { panic("%s: not implemented", __func__); } #ifdef SMP /* * Virtualization for interrupt source IPI counters setup. */ u_long * intr_ipi_setup_counters(const char *name) { u_int index, i; char str[INTRNAME_LEN]; index = atomic_fetchadd_int(&intrcnt_index, MAXCPU); for (i = 0; i < MAXCPU; i++) { snprintf(str, INTRNAME_LEN, "cpu%d:%s", i, name); intrcnt_setname(str, index + i); } return (&intrcnt[index]); } #endif /* * Main interrupt dispatch handler. It's called straight * from the assembler, where CPU interrupt is served. */ void intr_irq_handler(struct trapframe *tf) { struct trapframe * oldframe; struct thread * td; KASSERT(irq_root_filter != NULL, ("%s: no filter", __func__)); PCPU_INC(cnt.v_intr); critical_enter(); td = curthread; oldframe = td->td_intr_frame; td->td_intr_frame = tf; irq_root_filter(irq_root_arg); td->td_intr_frame = oldframe; critical_exit(); } /* * interrupt controller dispatch function for interrupts. It should * be called straight from the interrupt controller, when associated interrupt * source is learned. */ int intr_isrc_dispatch(struct intr_irqsrc *isrc, struct trapframe *tf) { KASSERT(isrc != NULL, ("%s: no source", __func__)); isrc_increment_count(isrc); #ifdef INTR_SOLO if (isrc->isrc_filter != NULL) { int error; error = isrc->isrc_filter(isrc->isrc_arg, tf); PIC_POST_FILTER(isrc->isrc_dev, isrc); if (error == FILTER_HANDLED) return (0); } else #endif if (isrc->isrc_event != NULL) { if (intr_event_handle(isrc->isrc_event, tf) == 0) return (0); } isrc_increment_straycount(isrc); return (EINVAL); } /* * Alloc unique interrupt number (resource handle) for interrupt source. * * There could be various strategies how to allocate free interrupt number * (resource handle) for new interrupt source. * * 1. Handles are always allocated forward, so handles are not recycled * immediately. However, if only one free handle left which is reused * constantly... */ static inline int isrc_alloc_irq(struct intr_irqsrc *isrc) { u_int maxirqs, irq; mtx_assert(&isrc_table_lock, MA_OWNED); maxirqs = nitems(irq_sources); if (irq_next_free >= maxirqs) return (ENOSPC); for (irq = irq_next_free; irq < maxirqs; irq++) { if (irq_sources[irq] == NULL) goto found; } for (irq = 0; irq < irq_next_free; irq++) { if (irq_sources[irq] == NULL) goto found; } irq_next_free = maxirqs; return (ENOSPC); found: isrc->isrc_irq = irq; irq_sources[irq] = isrc; irq_next_free = irq + 1; if (irq_next_free >= maxirqs) irq_next_free = 0; return (0); } /* * Free unique interrupt number (resource handle) from interrupt source. */ static inline int isrc_free_irq(struct intr_irqsrc *isrc) { mtx_assert(&isrc_table_lock, MA_OWNED); if (isrc->isrc_irq >= nitems(irq_sources)) return (EINVAL); if (irq_sources[isrc->isrc_irq] != isrc) return (EINVAL); irq_sources[isrc->isrc_irq] = NULL; isrc->isrc_irq = INTR_IRQ_INVALID; /* just to be safe */ return (0); } /* * Lookup interrupt source by interrupt number (resource handle). */ static inline struct intr_irqsrc * isrc_lookup(u_int irq) { if (irq < nitems(irq_sources)) return (irq_sources[irq]); return (NULL); } /* * Initialize interrupt source and register it into global interrupt table. */ int intr_isrc_register(struct intr_irqsrc *isrc, device_t dev, u_int flags, const char *fmt, ...) { int error; va_list ap; bzero(isrc, sizeof(struct intr_irqsrc)); isrc->isrc_dev = dev; isrc->isrc_irq = INTR_IRQ_INVALID; /* just to be safe */ isrc->isrc_flags = flags; va_start(ap, fmt); vsnprintf(isrc->isrc_name, INTR_ISRC_NAMELEN, fmt, ap); va_end(ap); mtx_lock(&isrc_table_lock); error = isrc_alloc_irq(isrc); if (error != 0) { mtx_unlock(&isrc_table_lock); return (error); } /* * Setup interrupt counters, but not for IPI sources. Those are setup * later and only for used ones (up to INTR_IPI_COUNT) to not exhaust * our counter pool. */ if ((isrc->isrc_flags & INTR_ISRCF_IPI) == 0) isrc_setup_counters(isrc); mtx_unlock(&isrc_table_lock); return (0); } /* * Deregister interrupt source from global interrupt table. */ int intr_isrc_deregister(struct intr_irqsrc *isrc) { int error; mtx_lock(&isrc_table_lock); if ((isrc->isrc_flags & INTR_ISRCF_IPI) == 0) isrc_release_counters(isrc); error = isrc_free_irq(isrc); mtx_unlock(&isrc_table_lock); return (error); } #ifdef SMP /* * A support function for a PIC to decide if provided ISRC should be inited * on given cpu. The logic of INTR_ISRCF_BOUND flag and isrc_cpu member of * struct intr_irqsrc is the following: * * If INTR_ISRCF_BOUND is set, the ISRC should be inited only on cpus * set in isrc_cpu. If not, the ISRC should be inited on every cpu and * isrc_cpu is kept consistent with it. Thus isrc_cpu is always correct. */ bool intr_isrc_init_on_cpu(struct intr_irqsrc *isrc, u_int cpu) { if (isrc->isrc_handlers == 0) return (false); if ((isrc->isrc_flags & (INTR_ISRCF_PPI | INTR_ISRCF_IPI)) == 0) return (false); if (isrc->isrc_flags & INTR_ISRCF_BOUND) return (CPU_ISSET(cpu, &isrc->isrc_cpu)); CPU_SET(cpu, &isrc->isrc_cpu); return (true); } #endif static struct intr_dev_data * intr_ddata_alloc(u_int extsize) { struct intr_dev_data *ddata; + size_t size; - ddata = malloc(sizeof(*ddata) + extsize, M_INTRNG, M_WAITOK | M_ZERO); + size = sizeof(*ddata); + ddata = malloc(size + extsize, M_INTRNG, M_WAITOK | M_ZERO); mtx_lock(&isrc_table_lock); if (intr_ddata_first_unused >= nitems(intr_ddata_tab)) { mtx_unlock(&isrc_table_lock); free(ddata, M_INTRNG); return (NULL); } intr_ddata_tab[intr_ddata_first_unused] = ddata; ddata->idd_irq = IRQ_DDATA_BASE + intr_ddata_first_unused++; mtx_unlock(&isrc_table_lock); + + ddata->idd_data = (struct intr_map_data *)((uintptr_t)ddata + size); + ddata->idd_data->size = size; return (ddata); } static struct intr_irqsrc * intr_ddata_lookup(u_int irq, struct intr_map_data **datap) { int error; struct intr_irqsrc *isrc; struct intr_dev_data *ddata; isrc = isrc_lookup(irq); if (isrc != NULL) { if (datap != NULL) *datap = NULL; return (isrc); } if (irq < IRQ_DDATA_BASE) return (NULL); irq -= IRQ_DDATA_BASE; if (irq >= nitems(intr_ddata_tab)) return (NULL); ddata = intr_ddata_tab[irq]; if (ddata->idd_isrc == NULL) { error = intr_map_irq(ddata->idd_dev, ddata->idd_xref, - &ddata->idd_data, &irq); + ddata->idd_data, &irq); if (error != 0) return (NULL); ddata->idd_isrc = isrc_lookup(irq); } if (datap != NULL) - *datap = &ddata->idd_data; + *datap = ddata->idd_data; return (ddata->idd_isrc); } #ifdef DEV_ACPI /* * Map interrupt source according to ACPI info into framework. If such mapping * does not exist, create it. Return unique interrupt number (resource handle) * associated with mapped interrupt source. */ u_int intr_acpi_map_irq(device_t dev, u_int irq, enum intr_polarity pol, enum intr_trigger trig) { + struct intr_map_data_acpi *daa; struct intr_dev_data *ddata; - ddata = intr_ddata_alloc(0); + ddata = intr_ddata_alloc(sizeof(struct intr_map_data_acpi)); if (ddata == NULL) return (INTR_IRQ_INVALID); /* no space left */ ddata->idd_dev = dev; - ddata->idd_data.type = INTR_MAP_DATA_ACPI; - ddata->idd_data.acpi.irq = irq; - ddata->idd_data.acpi.pol = pol; - ddata->idd_data.acpi.trig = trig; + ddata->idd_data->type = INTR_MAP_DATA_ACPI; + + daa = (struct intr_map_data_acpi *)ddata->idd_data; + daa->irq = irq; + daa->pol = pol; + daa->trig = trig; + return (ddata->idd_irq); } #endif #ifdef FDT /* * Map interrupt source according to FDT data into framework. If such mapping * does not exist, create it. Return unique interrupt number (resource handle) * associated with mapped interrupt source. */ u_int intr_fdt_map_irq(phandle_t node, pcell_t *cells, u_int ncells) { + size_t cellsize; struct intr_dev_data *ddata; - u_int cellsize; + struct intr_map_data_fdt *daf; cellsize = ncells * sizeof(*cells); - ddata = intr_ddata_alloc(cellsize); + ddata = intr_ddata_alloc(sizeof(struct intr_map_data_fdt) + cellsize); if (ddata == NULL) return (INTR_IRQ_INVALID); /* no space left */ ddata->idd_xref = (intptr_t)node; - ddata->idd_data.type = INTR_MAP_DATA_FDT; - ddata->idd_data.fdt.ncells = ncells; - ddata->idd_data.fdt.cells = (pcell_t *)(ddata + 1); - memcpy(ddata->idd_data.fdt.cells, cells, cellsize); + ddata->idd_data->type = INTR_MAP_DATA_FDT; + + daf = (struct intr_map_data_fdt *)ddata->idd_data; + daf->ncells = ncells; + memcpy(daf->cells, cells, cellsize); return (ddata->idd_irq); } #endif /* * Store GPIO interrupt decription in framework and return unique interrupt * number (resource handle) associated with it. */ u_int intr_gpio_map_irq(device_t dev, u_int pin_num, u_int pin_flags, u_int intr_mode) { struct intr_dev_data *ddata; + struct intr_map_data_gpio *dag; - ddata = intr_ddata_alloc(0); + ddata = intr_ddata_alloc(sizeof(struct intr_map_data_gpio)); if (ddata == NULL) return (INTR_IRQ_INVALID); /* no space left */ ddata->idd_dev = dev; - ddata->idd_data.type = INTR_MAP_DATA_GPIO; - ddata->idd_data.gpio.gpio_pin_num = pin_num; - ddata->idd_data.gpio.gpio_pin_flags = pin_flags; - ddata->idd_data.gpio.gpio_intr_mode = intr_mode; + ddata->idd_data->type = INTR_MAP_DATA_GPIO; + + dag = (struct intr_map_data_gpio *)ddata->idd_data; + dag->gpio_pin_num = pin_num; + dag->gpio_pin_flags = pin_flags; + dag->gpio_intr_mode = intr_mode; return (ddata->idd_irq); } #ifdef INTR_SOLO /* * Setup filter into interrupt source. */ static int iscr_setup_filter(struct intr_irqsrc *isrc, const char *name, intr_irq_filter_t *filter, void *arg, void **cookiep) { if (filter == NULL) return (EINVAL); mtx_lock(&isrc_table_lock); /* * Make sure that we do not mix the two ways * how we handle interrupt sources. */ if (isrc->isrc_filter != NULL || isrc->isrc_event != NULL) { mtx_unlock(&isrc_table_lock); return (EBUSY); } isrc->isrc_filter = filter; isrc->isrc_arg = arg; isrc_update_name(isrc, name); mtx_unlock(&isrc_table_lock); *cookiep = isrc; return (0); } #endif /* * Interrupt source pre_ithread method for MI interrupt framework. */ static void intr_isrc_pre_ithread(void *arg) { struct intr_irqsrc *isrc = arg; PIC_PRE_ITHREAD(isrc->isrc_dev, isrc); } /* * Interrupt source post_ithread method for MI interrupt framework. */ static void intr_isrc_post_ithread(void *arg) { struct intr_irqsrc *isrc = arg; PIC_POST_ITHREAD(isrc->isrc_dev, isrc); } /* * Interrupt source post_filter method for MI interrupt framework. */ static void intr_isrc_post_filter(void *arg) { struct intr_irqsrc *isrc = arg; PIC_POST_FILTER(isrc->isrc_dev, isrc); } /* * Interrupt source assign_cpu method for MI interrupt framework. */ static int intr_isrc_assign_cpu(void *arg, int cpu) { #ifdef SMP struct intr_irqsrc *isrc = arg; int error; if (isrc->isrc_dev != intr_irq_root_dev) return (EINVAL); mtx_lock(&isrc_table_lock); if (cpu == NOCPU) { CPU_ZERO(&isrc->isrc_cpu); isrc->isrc_flags &= ~INTR_ISRCF_BOUND; } else { CPU_SETOF(cpu, &isrc->isrc_cpu); isrc->isrc_flags |= INTR_ISRCF_BOUND; } /* * In NOCPU case, it's up to PIC to either leave ISRC on same CPU or * re-balance it to another CPU or enable it on more CPUs. However, * PIC is expected to change isrc_cpu appropriately to keep us well * informed if the call is successful. */ if (irq_assign_cpu) { error = PIC_BIND_INTR(isrc->isrc_dev, isrc); if (error) { CPU_ZERO(&isrc->isrc_cpu); mtx_unlock(&isrc_table_lock); return (error); } } mtx_unlock(&isrc_table_lock); return (0); #else return (EOPNOTSUPP); #endif } /* * Create interrupt event for interrupt source. */ static int isrc_event_create(struct intr_irqsrc *isrc) { struct intr_event *ie; int error; error = intr_event_create(&ie, isrc, 0, isrc->isrc_irq, intr_isrc_pre_ithread, intr_isrc_post_ithread, intr_isrc_post_filter, intr_isrc_assign_cpu, "%s:", isrc->isrc_name); if (error) return (error); mtx_lock(&isrc_table_lock); /* * Make sure that we do not mix the two ways * how we handle interrupt sources. Let contested event wins. */ #ifdef INTR_SOLO if (isrc->isrc_filter != NULL || isrc->isrc_event != NULL) { #else if (isrc->isrc_event != NULL) { #endif mtx_unlock(&isrc_table_lock); intr_event_destroy(ie); return (isrc->isrc_event != NULL ? EBUSY : 0); } isrc->isrc_event = ie; mtx_unlock(&isrc_table_lock); return (0); } #ifdef notyet /* * Destroy interrupt event for interrupt source. */ static void isrc_event_destroy(struct intr_irqsrc *isrc) { struct intr_event *ie; mtx_lock(&isrc_table_lock); ie = isrc->isrc_event; isrc->isrc_event = NULL; mtx_unlock(&isrc_table_lock); if (ie != NULL) intr_event_destroy(ie); } #endif /* * Add handler to interrupt source. */ static int isrc_add_handler(struct intr_irqsrc *isrc, const char *name, driver_filter_t filter, driver_intr_t handler, void *arg, enum intr_type flags, void **cookiep) { int error; if (isrc->isrc_event == NULL) { error = isrc_event_create(isrc); if (error) return (error); } error = intr_event_add_handler(isrc->isrc_event, name, filter, handler, arg, intr_priority(flags), flags, cookiep); if (error == 0) { mtx_lock(&isrc_table_lock); intrcnt_updatename(isrc); mtx_unlock(&isrc_table_lock); } return (error); } /* * Lookup interrupt controller locked. */ static inline struct intr_pic * pic_lookup_locked(device_t dev, intptr_t xref) { struct intr_pic *pic; mtx_assert(&pic_list_lock, MA_OWNED); if (dev == NULL && xref == 0) return (NULL); /* Note that pic->pic_dev is never NULL on registered PIC. */ SLIST_FOREACH(pic, &pic_list, pic_next) { if (dev == NULL) { if (xref == pic->pic_xref) return (pic); } else if (xref == 0 || pic->pic_xref == 0) { if (dev == pic->pic_dev) return (pic); } else if (xref == pic->pic_xref && dev == pic->pic_dev) return (pic); } return (NULL); } /* * Lookup interrupt controller. */ static struct intr_pic * pic_lookup(device_t dev, intptr_t xref) { struct intr_pic *pic; mtx_lock(&pic_list_lock); pic = pic_lookup_locked(dev, xref); mtx_unlock(&pic_list_lock); return (pic); } /* * Create interrupt controller. */ static struct intr_pic * pic_create(device_t dev, intptr_t xref) { struct intr_pic *pic; mtx_lock(&pic_list_lock); pic = pic_lookup_locked(dev, xref); if (pic != NULL) { mtx_unlock(&pic_list_lock); return (pic); } pic = malloc(sizeof(*pic), M_INTRNG, M_NOWAIT | M_ZERO); pic->pic_xref = xref; pic->pic_dev = dev; SLIST_INSERT_HEAD(&pic_list, pic, pic_next); mtx_unlock(&pic_list_lock); return (pic); } #ifdef notyet /* * Destroy interrupt controller. */ static void pic_destroy(device_t dev, intptr_t xref) { struct intr_pic *pic; mtx_lock(&pic_list_lock); pic = pic_lookup_locked(dev, xref); if (pic == NULL) { mtx_unlock(&pic_list_lock); return; } SLIST_REMOVE(&pic_list, pic, intr_pic, pic_next); mtx_unlock(&pic_list_lock); free(pic, M_INTRNG); } #endif /* * Register interrupt controller. */ int intr_pic_register(device_t dev, intptr_t xref) { struct intr_pic *pic; if (dev == NULL) return (EINVAL); pic = pic_create(dev, xref); if (pic == NULL) return (ENOMEM); debugf("PIC %p registered for %s \n", pic, device_get_nameunit(dev), dev, xref); return (0); } /* * Unregister interrupt controller. */ int intr_pic_deregister(device_t dev, intptr_t xref) { panic("%s: not implemented", __func__); } /* * Mark interrupt controller (itself) as a root one. * * Note that only an interrupt controller can really know its position * in interrupt controller's tree. So root PIC must claim itself as a root. * * In FDT case, according to ePAPR approved version 1.1 from 08 April 2011, * page 30: * "The root of the interrupt tree is determined when traversal * of the interrupt tree reaches an interrupt controller node without * an interrupts property and thus no explicit interrupt parent." */ int intr_pic_claim_root(device_t dev, intptr_t xref, intr_irq_filter_t *filter, void *arg, u_int ipicount) { if (pic_lookup(dev, xref) == NULL) { device_printf(dev, "not registered\n"); return (EINVAL); } if (filter == NULL) { device_printf(dev, "filter missing\n"); return (EINVAL); } /* * Only one interrupt controllers could be on the root for now. * Note that we further suppose that there is not threaded interrupt * routine (handler) on the root. See intr_irq_handler(). */ if (intr_irq_root_dev != NULL) { device_printf(dev, "another root already set\n"); return (EBUSY); } intr_irq_root_dev = dev; irq_root_filter = filter; irq_root_arg = arg; irq_root_ipicount = ipicount; debugf("irq root set to %s\n", device_get_nameunit(dev)); return (0); } int intr_map_irq(device_t dev, intptr_t xref, struct intr_map_data *data, u_int *irqp) { int error; struct intr_irqsrc *isrc; struct intr_pic *pic; if (data == NULL) return (EINVAL); pic = pic_lookup(dev, xref); - if (pic == NULL || pic->pic_dev == NULL) + if (pic == NULL) return (ESRCH); error = PIC_MAP_INTR(pic->pic_dev, data, &isrc); if (error == 0) *irqp = isrc->isrc_irq; return (error); } int intr_alloc_irq(device_t dev, struct resource *res) { struct intr_map_data *data; struct intr_irqsrc *isrc; KASSERT(rman_get_start(res) == rman_get_end(res), ("%s: more interrupts in resource", __func__)); isrc = intr_ddata_lookup(rman_get_start(res), &data); if (isrc == NULL) return (EINVAL); return (PIC_ALLOC_INTR(isrc->isrc_dev, isrc, res, data)); } int intr_release_irq(device_t dev, struct resource *res) { struct intr_map_data *data; struct intr_irqsrc *isrc; KASSERT(rman_get_start(res) == rman_get_end(res), ("%s: more interrupts in resource", __func__)); isrc = intr_ddata_lookup(rman_get_start(res), &data); if (isrc == NULL) return (EINVAL); return (PIC_RELEASE_INTR(isrc->isrc_dev, isrc, res, data)); } int intr_setup_irq(device_t dev, struct resource *res, driver_filter_t filt, driver_intr_t hand, void *arg, int flags, void **cookiep) { int error; struct intr_map_data *data; struct intr_irqsrc *isrc; const char *name; KASSERT(rman_get_start(res) == rman_get_end(res), ("%s: more interrupts in resource", __func__)); isrc = intr_ddata_lookup(rman_get_start(res), &data); if (isrc == NULL) return (EINVAL); name = device_get_nameunit(dev); #ifdef INTR_SOLO /* * Standard handling is done through MI interrupt framework. However, * some interrupts could request solely own special handling. This * non standard handling can be used for interrupt controllers without * handler (filter only), so in case that interrupt controllers are * chained, MI interrupt framework is called only in leaf controller. * * Note that root interrupt controller routine is served as well, * however in intr_irq_handler(), i.e. main system dispatch routine. */ if (flags & INTR_SOLO && hand != NULL) { debugf("irq %u cannot solo on %s\n", irq, name); return (EINVAL); } if (flags & INTR_SOLO) { error = iscr_setup_filter(isrc, name, (intr_irq_filter_t *)filt, arg, cookiep); debugf("irq %u setup filter error %d on %s\n", irq, error, name); } else #endif { error = isrc_add_handler(isrc, name, filt, hand, arg, flags, cookiep); debugf("irq %u add handler error %d on %s\n", irq, error, name); } if (error != 0) return (error); mtx_lock(&isrc_table_lock); error = PIC_SETUP_INTR(isrc->isrc_dev, isrc, res, data); if (error == 0) { isrc->isrc_handlers++; if (isrc->isrc_handlers == 1) PIC_ENABLE_INTR(isrc->isrc_dev, isrc); } mtx_unlock(&isrc_table_lock); if (error != 0) intr_event_remove_handler(*cookiep); return (error); } int intr_teardown_irq(device_t dev, struct resource *res, void *cookie) { int error; struct intr_map_data *data; struct intr_irqsrc *isrc; KASSERT(rman_get_start(res) == rman_get_end(res), ("%s: more interrupts in resource", __func__)); isrc = intr_ddata_lookup(rman_get_start(res), &data); if (isrc == NULL || isrc->isrc_handlers == 0) return (EINVAL); #ifdef INTR_SOLO if (isrc->isrc_filter != NULL) { if (isrc != cookie) return (EINVAL); mtx_lock(&isrc_table_lock); isrc->isrc_filter = NULL; isrc->isrc_arg = NULL; isrc->isrc_handlers = 0; PIC_DISABLE_INTR(isrc->isrc_dev, isrc); PIC_TEARDOWN_INTR(isrc->isrc_dev, isrc, res, data); isrc_update_name(isrc, NULL); mtx_unlock(&isrc_table_lock); return (0); } #endif if (isrc != intr_handler_source(cookie)) return (EINVAL); error = intr_event_remove_handler(cookie); if (error == 0) { mtx_lock(&isrc_table_lock); isrc->isrc_handlers--; if (isrc->isrc_handlers == 0) PIC_DISABLE_INTR(isrc->isrc_dev, isrc); PIC_TEARDOWN_INTR(isrc->isrc_dev, isrc, res, data); intrcnt_updatename(isrc); mtx_unlock(&isrc_table_lock); } return (error); } int intr_describe_irq(device_t dev, struct resource *res, void *cookie, const char *descr) { int error; struct intr_irqsrc *isrc; KASSERT(rman_get_start(res) == rman_get_end(res), ("%s: more interrupts in resource", __func__)); isrc = intr_ddata_lookup(rman_get_start(res), NULL); if (isrc == NULL || isrc->isrc_handlers == 0) return (EINVAL); #ifdef INTR_SOLO if (isrc->isrc_filter != NULL) { if (isrc != cookie) return (EINVAL); mtx_lock(&isrc_table_lock); isrc_update_name(isrc, descr); mtx_unlock(&isrc_table_lock); return (0); } #endif error = intr_event_describe_handler(isrc->isrc_event, cookie, descr); if (error == 0) { mtx_lock(&isrc_table_lock); intrcnt_updatename(isrc); mtx_unlock(&isrc_table_lock); } return (error); } #ifdef SMP int intr_bind_irq(device_t dev, struct resource *res, int cpu) { struct intr_irqsrc *isrc; KASSERT(rman_get_start(res) == rman_get_end(res), ("%s: more interrupts in resource", __func__)); isrc = intr_ddata_lookup(rman_get_start(res), NULL); if (isrc == NULL || isrc->isrc_handlers == 0) return (EINVAL); #ifdef INTR_SOLO if (isrc->isrc_filter != NULL) return (intr_isrc_assign_cpu(isrc, cpu)); #endif return (intr_event_bind(isrc->isrc_event, cpu)); } /* * Return the CPU that the next interrupt source should use. * For now just returns the next CPU according to round-robin. */ u_int intr_irq_next_cpu(u_int last_cpu, cpuset_t *cpumask) { if (!irq_assign_cpu || mp_ncpus == 1) return (PCPU_GET(cpuid)); do { last_cpu++; if (last_cpu > mp_maxid) last_cpu = 0; } while (!CPU_ISSET(last_cpu, cpumask)); return (last_cpu); } /* * Distribute all the interrupt sources among the available * CPUs once the AP's have been launched. */ static void intr_irq_shuffle(void *arg __unused) { struct intr_irqsrc *isrc; u_int i; if (mp_ncpus == 1) return; mtx_lock(&isrc_table_lock); irq_assign_cpu = TRUE; for (i = 0; i < NIRQ; i++) { isrc = irq_sources[i]; if (isrc == NULL || isrc->isrc_handlers == 0 || isrc->isrc_flags & (INTR_ISRCF_PPI | INTR_ISRCF_IPI)) continue; if (isrc->isrc_event != NULL && isrc->isrc_flags & INTR_ISRCF_BOUND && isrc->isrc_event->ie_cpu != CPU_FFS(&isrc->isrc_cpu) - 1) panic("%s: CPU inconsistency", __func__); if ((isrc->isrc_flags & INTR_ISRCF_BOUND) == 0) CPU_ZERO(&isrc->isrc_cpu); /* start again */ /* * We are in wicked position here if the following call fails * for bound ISRC. The best thing we can do is to clear * isrc_cpu so inconsistency with ie_cpu will be detectable. */ if (PIC_BIND_INTR(isrc->isrc_dev, isrc) != 0) CPU_ZERO(&isrc->isrc_cpu); } mtx_unlock(&isrc_table_lock); } SYSINIT(intr_irq_shuffle, SI_SUB_SMP, SI_ORDER_SECOND, intr_irq_shuffle, NULL); #else u_int intr_irq_next_cpu(u_int current_cpu, cpuset_t *cpumask) { return (PCPU_GET(cpuid)); } #endif void dosoftints(void); void dosoftints(void) { } #ifdef SMP /* * Init interrupt controller on another CPU. */ void intr_pic_init_secondary(void) { /* * QQQ: Only root PIC is aware of other CPUs ??? */ KASSERT(intr_irq_root_dev != NULL, ("%s: no root attached", __func__)); //mtx_lock(&isrc_table_lock); PIC_INIT_SECONDARY(intr_irq_root_dev); //mtx_unlock(&isrc_table_lock); } #endif #ifdef DDB DB_SHOW_COMMAND(irqs, db_show_irqs) { u_int i, irqsum; u_long num; struct intr_irqsrc *isrc; for (irqsum = 0, i = 0; i < NIRQ; i++) { isrc = irq_sources[i]; if (isrc == NULL) continue; num = isrc->isrc_count != NULL ? isrc->isrc_count[0] : 0; db_printf("irq%-3u <%s>: cpu %02lx%s cnt %lu\n", i, isrc->isrc_name, isrc->isrc_cpu.__bits[0], isrc->isrc_flags & INTR_ISRCF_BOUND ? " (bound)" : "", num); irqsum += num; } db_printf("irq total %u\n", irqsum); } #endif Index: user/ngie/detangle-rc/sys/kern/subr_unit.c =================================================================== --- user/ngie/detangle-rc/sys/kern/subr_unit.c (revision 299126) +++ user/ngie/detangle-rc/sys/kern/subr_unit.c (revision 299127) @@ -1,1061 +1,1061 @@ /*- * Copyright (c) 2004 Poul-Henning Kamp * 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. * * $FreeBSD$ * * * Unit number allocation functions. * * These functions implement a mixed run-length/bitmap management of unit * number spaces in a very memory efficient manner. * * Allocation policy is always lowest free number first. * * A return value of -1 signals that no more unit numbers are available. * * There is no cost associated with the range of unitnumbers, so unless * the resource really is finite, specify INT_MAX to new_unrhdr() and * forget about checking the return value. * * If a mutex is not provided when the unit number space is created, a * default global mutex is used. The advantage to passing a mutex in, is * that the alloc_unrl() function can be called with the mutex already * held (it will not be released by alloc_unrl()). * * The allocation function alloc_unr{l}() never sleeps (but it may block on * the mutex of course). * * Freeing a unit number may require allocating memory, and can therefore * sleep so the free_unr() function does not come in a pre-locked variant. * * A userland test program is included. * * Memory usage is a very complex function of the exact allocation * pattern, but always very compact: * * For the very typical case where a single unbroken run of unit * numbers are allocated 44 bytes are used on i386. * * For a unit number space of 1000 units and the random pattern * in the usermode test program included, the worst case usage * was 252 bytes on i386 for 500 allocated and 500 free units. * * For a unit number space of 10000 units and the random pattern * in the usermode test program included, the worst case usage * was 798 bytes on i386 for 5000 allocated and 5000 free units. * * The worst case is where every other unit number is allocated and * the rest are free. In that case 44 + N/4 bytes are used where * N is the number of the highest unit allocated. */ #include #include #include #ifdef _KERNEL #include #include #include #include #include #include #include /* * In theory it would be smarter to allocate the individual blocks * with the zone allocator, but at this time the expectation is that * there will typically not even be enough allocations to fill a single * page, so we stick with malloc for now. */ static MALLOC_DEFINE(M_UNIT, "Unitno", "Unit number allocation"); #define Malloc(foo) malloc(foo, M_UNIT, M_WAITOK | M_ZERO) #define Free(foo) free(foo, M_UNIT) static struct mtx unitmtx; MTX_SYSINIT(unit, &unitmtx, "unit# allocation", MTX_DEF); #else /* ...USERLAND */ #include #include #include #include #include #include #include #include #define KASSERT(cond, arg) \ do { \ if (!(cond)) { \ printf arg; \ abort(); \ } \ } while (0) static int no_alloc; #define Malloc(foo) _Malloc(foo, __LINE__) static void * _Malloc(size_t foo, int line) { KASSERT(no_alloc == 0, ("malloc in wrong place() line %d", line)); return (calloc(foo, 1)); } #define Free(foo) free(foo) struct unrhdr; struct mtx { int state; } unitmtx; static void mtx_lock(struct mtx *mp) { KASSERT(mp->state == 0, ("mutex already locked")); mp->state = 1; } static void mtx_unlock(struct mtx *mp) { KASSERT(mp->state == 1, ("mutex not locked")); mp->state = 0; } #define MA_OWNED 9 static void mtx_assert(struct mtx *mp, int flag) { if (flag == MA_OWNED) { KASSERT(mp->state == 1, ("mtx_assert(MA_OWNED) not true")); } } #define CTASSERT(foo) #define WITNESS_WARN(flags, lock, fmt, ...) (void)0 #endif /* USERLAND */ /* * This is our basic building block. * * It can be used in three different ways depending on the value of the ptr * element: * If ptr is NULL, it represents a run of free items. * If ptr points to the unrhdr it represents a run of allocated items. * Otherwise it points to a bitstring of allocated items. * * For runs the len field is the length of the run. * For bitmaps the len field represents the number of allocated items. * * The bitmap is the same size as struct unr to optimize memory management. */ struct unr { TAILQ_ENTRY(unr) list; u_int len; void *ptr; }; struct unrb { bitstr_t map[sizeof(struct unr) / sizeof(bitstr_t)]; }; CTASSERT((sizeof(struct unr) % sizeof(bitstr_t)) == 0); /* Number of bits we can store in the bitmap */ #define NBITS (8 * sizeof(((struct unrb*)NULL)->map)) /* Is the unrb empty in at least the first len bits? */ static inline bool ub_empty(struct unrb *ub, int len) { int first_set; bit_ffs(ub->map, len, &first_set); return (first_set == -1); } /* Is the unrb full? That is, is the number of set elements equal to len? */ static inline bool ub_full(struct unrb *ub, int len) { int first_clear; bit_ffc(ub->map, len, &first_clear); return (first_clear == -1); } #if defined(DIAGNOSTIC) || !defined(_KERNEL) /* * Consistency check function. * * Checks the internal consistency as well as we can. * * Called at all boundaries of this API. */ static void check_unrhdr(struct unrhdr *uh, int line) { struct unr *up; struct unrb *ub; u_int x, y, z, w; y = uh->first; z = 0; TAILQ_FOREACH(up, &uh->head, list) { z++; if (up->ptr != uh && up->ptr != NULL) { ub = up->ptr; KASSERT (up->len <= NBITS, ("UNR inconsistency: len %u max %zd (line %d)\n", up->len, NBITS, line)); z++; w = 0; for (x = 0; x < up->len; x++) if (bit_test(ub->map, x)) w++; y += w; } else if (up->ptr != NULL) y += up->len; } KASSERT (y == uh->busy, ("UNR inconsistency: items %u found %u (line %d)\n", uh->busy, y, line)); KASSERT (z == uh->alloc, ("UNR inconsistency: chunks %u found %u (line %d)\n", uh->alloc, z, line)); } #else static __inline void check_unrhdr(struct unrhdr *uh __unused, int line __unused) { } #endif /* * Userland memory management. Just use calloc and keep track of how * many elements we have allocated for check_unrhdr(). */ static __inline void * new_unr(struct unrhdr *uh, void **p1, void **p2) { void *p; uh->alloc++; KASSERT(*p1 != NULL || *p2 != NULL, ("Out of cached memory")); if (*p1 != NULL) { p = *p1; *p1 = NULL; return (p); } else { p = *p2; *p2 = NULL; return (p); } } static __inline void delete_unr(struct unrhdr *uh, void *ptr) { struct unr *up; uh->alloc--; up = ptr; TAILQ_INSERT_TAIL(&uh->ppfree, up, list); } void clean_unrhdrl(struct unrhdr *uh) { struct unr *up; mtx_assert(uh->mtx, MA_OWNED); while ((up = TAILQ_FIRST(&uh->ppfree)) != NULL) { TAILQ_REMOVE(&uh->ppfree, up, list); mtx_unlock(uh->mtx); Free(up); mtx_lock(uh->mtx); } } void clean_unrhdr(struct unrhdr *uh) { mtx_lock(uh->mtx); clean_unrhdrl(uh); mtx_unlock(uh->mtx); } void init_unrhdr(struct unrhdr *uh, int low, int high, struct mtx *mutex) { KASSERT(low >= 0 && low <= high, ("UNR: use error: new_unrhdr(%d, %d)", low, high)); if (mutex != NULL) uh->mtx = mutex; else uh->mtx = &unitmtx; TAILQ_INIT(&uh->head); TAILQ_INIT(&uh->ppfree); uh->low = low; uh->high = high; uh->first = 0; uh->last = 1 + (high - low); check_unrhdr(uh, __LINE__); } /* * Allocate a new unrheader set. * * Highest and lowest valid values given as parameters. */ struct unrhdr * new_unrhdr(int low, int high, struct mtx *mutex) { struct unrhdr *uh; uh = Malloc(sizeof *uh); init_unrhdr(uh, low, high, mutex); return (uh); } void delete_unrhdr(struct unrhdr *uh) { check_unrhdr(uh, __LINE__); KASSERT(uh->busy == 0, ("unrhdr has %u allocations", uh->busy)); KASSERT(uh->alloc == 0, ("UNR memory leak in delete_unrhdr")); KASSERT(TAILQ_FIRST(&uh->ppfree) == NULL, ("unrhdr has postponed item for free")); Free(uh); } static __inline int is_bitmap(struct unrhdr *uh, struct unr *up) { return (up->ptr != uh && up->ptr != NULL); } /* * Look for sequence of items which can be combined into a bitmap, if * multiple are present, take the one which saves most memory. * * Return (1) if a sequence was found to indicate that another call * might be able to do more. Return (0) if we found no suitable sequence. * * NB: called from alloc_unr(), no new memory allocation allowed. */ static int optimize_unr(struct unrhdr *uh) { struct unr *up, *uf, *us; struct unrb *ub, *ubf; u_int a, l, ba; /* * Look for the run of items (if any) which when collapsed into * a bitmap would save most memory. */ us = NULL; ba = 0; TAILQ_FOREACH(uf, &uh->head, list) { if (uf->len >= NBITS) continue; a = 1; if (is_bitmap(uh, uf)) a++; l = uf->len; up = uf; while (1) { up = TAILQ_NEXT(up, list); if (up == NULL) break; if ((up->len + l) > NBITS) break; a++; if (is_bitmap(uh, up)) a++; l += up->len; } if (a > ba) { ba = a; us = uf; } } if (ba < 3) return (0); /* * If the first element is not a bitmap, make it one. * Trying to do so without allocating more memory complicates things * a bit */ if (!is_bitmap(uh, us)) { uf = TAILQ_NEXT(us, list); TAILQ_REMOVE(&uh->head, us, list); a = us->len; l = us->ptr == uh ? 1 : 0; ub = (void *)us; bit_nclear(ub->map, 0, NBITS - 1); if (l) bit_nset(ub->map, 0, a); if (!is_bitmap(uh, uf)) { if (uf->ptr == NULL) bit_nclear(ub->map, a, a + uf->len - 1); else bit_nset(ub->map, a, a + uf->len - 1); uf->ptr = ub; uf->len += a; us = uf; } else { ubf = uf->ptr; for (l = 0; l < uf->len; l++, a++) { if (bit_test(ubf->map, l)) bit_set(ub->map, a); else bit_clear(ub->map, a); } uf->len = a; delete_unr(uh, uf->ptr); uf->ptr = ub; us = uf; } } ub = us->ptr; while (1) { uf = TAILQ_NEXT(us, list); if (uf == NULL) return (1); if (uf->len + us->len > NBITS) return (1); if (uf->ptr == NULL) { bit_nclear(ub->map, us->len, us->len + uf->len - 1); us->len += uf->len; TAILQ_REMOVE(&uh->head, uf, list); delete_unr(uh, uf); } else if (uf->ptr == uh) { bit_nset(ub->map, us->len, us->len + uf->len - 1); us->len += uf->len; TAILQ_REMOVE(&uh->head, uf, list); delete_unr(uh, uf); } else { ubf = uf->ptr; for (l = 0; l < uf->len; l++, us->len++) { if (bit_test(ubf->map, l)) bit_set(ub->map, us->len); else bit_clear(ub->map, us->len); } TAILQ_REMOVE(&uh->head, uf, list); delete_unr(uh, ubf); delete_unr(uh, uf); } } } /* * See if a given unr should be collapsed with a neighbor. * * NB: called from alloc_unr(), no new memory allocation allowed. */ static void collapse_unr(struct unrhdr *uh, struct unr *up) { struct unr *upp; struct unrb *ub; /* If bitmap is all set or clear, change it to runlength */ if (is_bitmap(uh, up)) { ub = up->ptr; if (ub_full(ub, up->len)) { delete_unr(uh, up->ptr); up->ptr = uh; } else if (ub_empty(ub, up->len)) { delete_unr(uh, up->ptr); up->ptr = NULL; } } /* If nothing left in runlength, delete it */ if (up->len == 0) { upp = TAILQ_PREV(up, unrhd, list); if (upp == NULL) upp = TAILQ_NEXT(up, list); TAILQ_REMOVE(&uh->head, up, list); delete_unr(uh, up); up = upp; } /* If we have "hot-spot" still, merge with neighbor if possible */ if (up != NULL) { upp = TAILQ_PREV(up, unrhd, list); if (upp != NULL && up->ptr == upp->ptr) { up->len += upp->len; TAILQ_REMOVE(&uh->head, upp, list); delete_unr(uh, upp); } upp = TAILQ_NEXT(up, list); if (upp != NULL && up->ptr == upp->ptr) { up->len += upp->len; TAILQ_REMOVE(&uh->head, upp, list); delete_unr(uh, upp); } } /* Merge into ->first if possible */ upp = TAILQ_FIRST(&uh->head); if (upp != NULL && upp->ptr == uh) { uh->first += upp->len; TAILQ_REMOVE(&uh->head, upp, list); delete_unr(uh, upp); if (up == upp) up = NULL; } /* Merge into ->last if possible */ upp = TAILQ_LAST(&uh->head, unrhd); if (upp != NULL && upp->ptr == NULL) { uh->last += upp->len; TAILQ_REMOVE(&uh->head, upp, list); delete_unr(uh, upp); if (up == upp) up = NULL; } /* Try to make bitmaps */ while (optimize_unr(uh)) continue; } /* * Allocate a free unr. */ int alloc_unrl(struct unrhdr *uh) { struct unr *up; struct unrb *ub; u_int x; int y; mtx_assert(uh->mtx, MA_OWNED); check_unrhdr(uh, __LINE__); x = uh->low + uh->first; up = TAILQ_FIRST(&uh->head); /* * If we have an ideal split, just adjust the first+last */ if (up == NULL && uh->last > 0) { uh->first++; uh->last--; uh->busy++; return (x); } /* * We can always allocate from the first list element, so if we have * nothing on the list, we must have run out of unit numbers. */ if (up == NULL) return (-1); KASSERT(up->ptr != uh, ("UNR first element is allocated")); if (up->ptr == NULL) { /* free run */ uh->first++; up->len--; } else { /* bitmap */ ub = up->ptr; bit_ffc(ub->map, up->len, &y); KASSERT(y != -1, ("UNR corruption: No clear bit in bitmap.")); bit_set(ub->map, y); x += y; } uh->busy++; collapse_unr(uh, up); return (x); } int alloc_unr(struct unrhdr *uh) { int i; mtx_lock(uh->mtx); i = alloc_unrl(uh); clean_unrhdrl(uh); mtx_unlock(uh->mtx); return (i); } static int alloc_unr_specificl(struct unrhdr *uh, u_int item, void **p1, void **p2) { struct unr *up, *upn; struct unrb *ub; u_int i, last, tl; mtx_assert(uh->mtx, MA_OWNED); if (item < uh->low + uh->first || item > uh->high) return (-1); up = TAILQ_FIRST(&uh->head); /* Ideal split. */ if (up == NULL && item - uh->low == uh->first) { uh->first++; uh->last--; uh->busy++; check_unrhdr(uh, __LINE__); return (item); } i = item - uh->low - uh->first; if (up == NULL) { up = new_unr(uh, p1, p2); up->ptr = NULL; up->len = i; TAILQ_INSERT_TAIL(&uh->head, up, list); up = new_unr(uh, p1, p2); up->ptr = uh; up->len = 1; TAILQ_INSERT_TAIL(&uh->head, up, list); uh->last = uh->high - uh->low - i; uh->busy++; check_unrhdr(uh, __LINE__); return (item); } else { /* Find the item which contains the unit we want to allocate. */ TAILQ_FOREACH(up, &uh->head, list) { if (up->len > i) break; i -= up->len; } } if (up == NULL) { if (i > 0) { up = new_unr(uh, p1, p2); up->ptr = NULL; up->len = i; TAILQ_INSERT_TAIL(&uh->head, up, list); } up = new_unr(uh, p1, p2); up->ptr = uh; up->len = 1; TAILQ_INSERT_TAIL(&uh->head, up, list); goto done; } if (is_bitmap(uh, up)) { ub = up->ptr; if (bit_test(ub->map, i) == 0) { bit_set(ub->map, i); goto done; } else return (-1); } else if (up->ptr == uh) return (-1); KASSERT(up->ptr == NULL, ("alloc_unr_specificl: up->ptr != NULL (up=%p)", up)); /* Split off the tail end, if any. */ tl = up->len - (1 + i); if (tl > 0) { upn = new_unr(uh, p1, p2); upn->ptr = NULL; upn->len = tl; TAILQ_INSERT_AFTER(&uh->head, up, upn, list); } /* Split off head end, if any */ if (i > 0) { upn = new_unr(uh, p1, p2); upn->len = i; upn->ptr = NULL; TAILQ_INSERT_BEFORE(up, upn, list); } up->len = 1; up->ptr = uh; done: last = uh->high - uh->low - (item - uh->low); if (uh->last > last) uh->last = last; uh->busy++; collapse_unr(uh, up); check_unrhdr(uh, __LINE__); return (item); } int alloc_unr_specific(struct unrhdr *uh, u_int item) { void *p1, *p2; int i; WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, "alloc_unr_specific"); p1 = Malloc(sizeof(struct unr)); p2 = Malloc(sizeof(struct unr)); mtx_lock(uh->mtx); i = alloc_unr_specificl(uh, item, &p1, &p2); mtx_unlock(uh->mtx); if (p1 != NULL) Free(p1); if (p2 != NULL) Free(p2); return (i); } /* * Free a unr. * * If we can save unrs by using a bitmap, do so. */ static void free_unrl(struct unrhdr *uh, u_int item, void **p1, void **p2) { struct unr *up, *upp, *upn; struct unrb *ub; u_int pl; KASSERT(item >= uh->low && item <= uh->high, ("UNR: free_unr(%u) out of range [%u...%u]", item, uh->low, uh->high)); check_unrhdr(uh, __LINE__); item -= uh->low; upp = TAILQ_FIRST(&uh->head); /* * Freeing in the ideal split case */ if (item + 1 == uh->first && upp == NULL) { uh->last++; uh->first--; uh->busy--; check_unrhdr(uh, __LINE__); return; } /* * Freeing in the ->first section. Create a run starting at the * freed item. The code below will subdivide it. */ if (item < uh->first) { up = new_unr(uh, p1, p2); up->ptr = uh; up->len = uh->first - item; TAILQ_INSERT_HEAD(&uh->head, up, list); uh->first -= up->len; } item -= uh->first; /* Find the item which contains the unit we want to free */ TAILQ_FOREACH(up, &uh->head, list) { if (up->len > item) break; item -= up->len; } /* Handle bitmap items */ if (is_bitmap(uh, up)) { ub = up->ptr; KASSERT(bit_test(ub->map, item) != 0, ("UNR: Freeing free item %d (bitmap)\n", item)); bit_clear(ub->map, item); uh->busy--; collapse_unr(uh, up); return; } KASSERT(up->ptr == uh, ("UNR Freeing free item %d (run))\n", item)); /* Just this one left, reap it */ if (up->len == 1) { up->ptr = NULL; uh->busy--; collapse_unr(uh, up); return; } /* Check if we can shift the item into the previous 'free' run */ upp = TAILQ_PREV(up, unrhd, list); if (item == 0 && upp != NULL && upp->ptr == NULL) { upp->len++; up->len--; uh->busy--; collapse_unr(uh, up); return; } /* Check if we can shift the item to the next 'free' run */ upn = TAILQ_NEXT(up, list); if (item == up->len - 1 && upn != NULL && upn->ptr == NULL) { upn->len++; up->len--; uh->busy--; collapse_unr(uh, up); return; } /* Split off the tail end, if any. */ pl = up->len - (1 + item); if (pl > 0) { upp = new_unr(uh, p1, p2); upp->ptr = uh; upp->len = pl; TAILQ_INSERT_AFTER(&uh->head, up, upp, list); } /* Split off head end, if any */ if (item > 0) { upp = new_unr(uh, p1, p2); upp->len = item; upp->ptr = uh; TAILQ_INSERT_BEFORE(up, upp, list); } up->len = 1; up->ptr = NULL; uh->busy--; collapse_unr(uh, up); } void free_unr(struct unrhdr *uh, u_int item) { void *p1, *p2; WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, "free_unr"); p1 = Malloc(sizeof(struct unr)); p2 = Malloc(sizeof(struct unr)); mtx_lock(uh->mtx); free_unrl(uh, item, &p1, &p2); clean_unrhdrl(uh); mtx_unlock(uh->mtx); if (p1 != NULL) Free(p1); if (p2 != NULL) Free(p2); } #ifndef _KERNEL /* USERLAND test driver */ /* * Simple stochastic test driver for the above functions. The code resides * here so that it can access static functions and structures. */ static bool verbose; #define VPRINTF(...) {if (verbose) printf(__VA_ARGS__);} static void print_unr(struct unrhdr *uh, struct unr *up) { u_int x; struct unrb *ub; printf(" %p len = %5u ", up, up->len); if (up->ptr == NULL) printf("free\n"); else if (up->ptr == uh) printf("alloc\n"); else { ub = up->ptr; printf("bitmap ["); for (x = 0; x < up->len; x++) { if (bit_test(ub->map, x)) printf("#"); else printf(" "); } printf("]\n"); } } static void print_unrhdr(struct unrhdr *uh) { struct unr *up; u_int x; printf( "%p low = %u high = %u first = %u last = %u busy %u chunks = %u\n", uh, uh->low, uh->high, uh->first, uh->last, uh->busy, uh->alloc); x = uh->low + uh->first; TAILQ_FOREACH(up, &uh->head, list) { printf(" from = %5u", x); print_unr(uh, up); if (up->ptr == NULL || up->ptr == uh) x += up->len; else x += NBITS; } } static void test_alloc_unr(struct unrhdr *uh, u_int i, char a[]) { int j; if (a[i]) { VPRINTF("F %u\n", i); free_unr(uh, i); a[i] = 0; } else { no_alloc = 1; j = alloc_unr(uh); if (j != -1) { a[j] = 1; VPRINTF("A %d\n", j); } no_alloc = 0; } } static void test_alloc_unr_specific(struct unrhdr *uh, u_int i, char a[]) { int j; j = alloc_unr_specific(uh, i); if (j == -1) { VPRINTF("F %u\n", i); a[i] = 0; free_unr(uh, i); } else { a[i] = 1; VPRINTF("A %d\n", j); } } static void usage(char** argv) { printf("%s [-h] [-r REPETITIONS] [-v]\n", argv[0]); } int main(int argc, char **argv) { struct unrhdr *uh; char *a; long count = 10000; /* Number of unrs to test */ long reps = 1; int ch; u_int i, x, m, j; verbose = false; while ((ch = getopt(argc, argv, "hr:v")) != -1) { switch (ch) { case 'r': errno = 0; reps = strtol(optarg, NULL, 0); if (errno == ERANGE || errno == EINVAL) { usage(argv); exit(2); } break; case 'v': verbose = true; break; case 'h': default: usage(argv); exit(2); } } setbuf(stdout, NULL); uh = new_unrhdr(0, count - 1, NULL); print_unrhdr(uh); a = calloc(count, sizeof(char)); if (a == NULL) err(1, "calloc failed"); srandomdev(); printf("sizeof(struct unr) %zu\n", sizeof(struct unr)); printf("sizeof(struct unrb) %zu\n", sizeof(struct unrb)); printf("sizeof(struct unrhdr) %zu\n", sizeof(struct unrhdr)); - printf("NBITS %lu\n", NBITS); + printf("NBITS %lu\n", (unsigned long)NBITS); x = 1; for (m = 0; m < count * reps; m++) { j = random(); i = (j >> 1) % count; #if 0 if (a[i] && (j & 1)) continue; #endif if ((random() & 1) != 0) test_alloc_unr(uh, i, a); else test_alloc_unr_specific(uh, i, a); if (verbose) print_unrhdr(uh); check_unrhdr(uh, __LINE__); } for (i = 0; i < count; i++) { if (a[i]) { if (verbose) { printf("C %u\n", i); print_unrhdr(uh); } free_unr(uh, i); } } print_unrhdr(uh); delete_unrhdr(uh); free(a); return (0); } #endif Index: user/ngie/detangle-rc/sys/mips/mediatek/mtk_gpio_v1.c =================================================================== --- user/ngie/detangle-rc/sys/mips/mediatek/mtk_gpio_v1.c (revision 299126) +++ user/ngie/detangle-rc/sys/mips/mediatek/mtk_gpio_v1.c (revision 299127) @@ -1,678 +1,682 @@ /*- * Copyright 2016 Stanislav Galabov * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "pic_if.h" #define MTK_GPIO_PINS 32 enum mtk_gpio_regs { GPIO_PIOINT = 0, GPIO_PIOEDGE, GPIO_PIORENA, GPIO_PIOFENA, GPIO_PIODATA, GPIO_PIODIR, GPIO_PIOPOL, GPIO_PIOSET, GPIO_PIORESET, GPIO_PIOTOG, GPIO_PIOMAX }; struct mtk_gpio_pin_irqsrc { struct intr_irqsrc isrc; u_int irq; }; struct mtk_gpio_pin { uint32_t pin_caps; uint32_t pin_flags; enum intr_trigger intr_trigger; enum intr_polarity intr_polarity; char pin_name[GPIOMAXNAME]; struct mtk_gpio_pin_irqsrc pin_irqsrc; }; struct mtk_gpio_softc { device_t dev; device_t busdev; struct resource *res[2]; struct mtx mtx; struct mtk_gpio_pin pins[MTK_GPIO_PINS]; void *intrhand; uint8_t regs[GPIO_PIOMAX]; uint32_t num_pins; uint8_t do_remap; }; #define PIC_INTR_ISRC(sc, irq) (&(sc)->pins[(irq)].pin_irqsrc.isrc) static struct resource_spec mtk_gpio_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; static int mtk_gpio_probe(device_t dev); static int mtk_gpio_attach(device_t dev); static int mtk_gpio_detach(device_t dev); static int mtk_gpio_intr(void *arg); #define MTK_GPIO_LOCK(sc) mtx_lock_spin(&(sc)->mtx) #define MTK_GPIO_UNLOCK(sc) mtx_unlock_spin(&(sc)->mtx) #define MTK_GPIO_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "mtk_gpio", MTX_SPIN) #define MTK_GPIO_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx) #define MTK_WRITE_4(sc, reg, val) \ bus_write_4((sc)->res[0], (sc)->regs[(reg)], (val)) #define MTK_READ_4(sc, reg) \ bus_read_4((sc)->res[0], (sc)->regs[(reg)]) static struct ofw_compat_data compat_data[] = { { "ralink,rt2880-gpio", 1 }, { "ralink,rt3050-gpio", 1 }, { "ralink,rt3352-gpio", 1 }, { "ralink,rt3883-gpio", 1 }, { "ralink,rt5350-gpio", 1 }, { "ralink,mt7620a-gpio", 1 }, { NULL, 0 } }; static int mtk_gpio_probe(device_t dev) { phandle_t node; if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); node = ofw_bus_get_node(dev); if (!OF_hasprop(node, "gpio-controller")) return (ENXIO); device_set_desc(dev, "MTK GPIO Controller (v1)"); return (BUS_PROBE_DEFAULT); } static int mtk_pic_register_isrcs(struct mtk_gpio_softc *sc) { int error; uint32_t irq; struct intr_irqsrc *isrc; const char *name; name = device_get_nameunit(sc->dev); for (irq = 0; irq < sc->num_pins; irq++) { sc->pins[irq].pin_irqsrc.irq = irq; isrc = PIC_INTR_ISRC(sc, irq); error = intr_isrc_register(isrc, sc->dev, 0, "%s", name); if (error != 0) { /* XXX call intr_isrc_deregister */ device_printf(sc->dev, "%s failed", __func__); return (error); } } return (0); } static int mtk_gpio_pin_set_direction(struct mtk_gpio_softc *sc, uint32_t pin, uint32_t dir) { uint32_t regval, mask = (1u << pin); if (!(sc->pins[pin].pin_caps & dir)) return (EINVAL); regval = MTK_READ_4(sc, GPIO_PIODIR); if (dir == GPIO_PIN_INPUT) regval &= ~mask; else regval |= mask; MTK_WRITE_4(sc, GPIO_PIODIR, regval); sc->pins[pin].pin_flags &= ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); sc->pins[pin].pin_flags |= dir; return (0); } static int mtk_gpio_pin_set_invert(struct mtk_gpio_softc *sc, uint32_t pin, uint32_t val) { uint32_t regval, mask = (1u << pin); regval = MTK_READ_4(sc, GPIO_PIOPOL); if (val) regval |= mask; else regval &= ~mask; MTK_WRITE_4(sc, GPIO_PIOPOL, regval); sc->pins[pin].pin_flags &= ~(GPIO_PIN_INVIN | GPIO_PIN_INVOUT); sc->pins[pin].pin_flags |= val; return (0); } static void mtk_gpio_pin_probe(struct mtk_gpio_softc *sc, uint32_t pin) { uint32_t mask = (1u << pin); uint32_t val; /* Clear cached gpio config */ sc->pins[pin].pin_flags = 0; val = MTK_READ_4(sc, GPIO_PIORENA) | MTK_READ_4(sc, GPIO_PIOFENA); if (val & mask) { /* Pin is in interrupt mode */ sc->pins[pin].intr_trigger = INTR_TRIGGER_EDGE; val = MTK_READ_4(sc, GPIO_PIORENA); if (val & mask) sc->pins[pin].intr_polarity = INTR_POLARITY_HIGH; else sc->pins[pin].intr_polarity = INTR_POLARITY_LOW; } val = MTK_READ_4(sc, GPIO_PIODIR); if (val & mask) sc->pins[pin].pin_flags |= GPIO_PIN_OUTPUT; else sc->pins[pin].pin_flags |= GPIO_PIN_INPUT; val = MTK_READ_4(sc, GPIO_PIOPOL); if (val & mask) { if (sc->pins[pin].pin_flags & GPIO_PIN_INPUT) { sc->pins[pin].pin_flags |= GPIO_PIN_INVIN; } else { sc->pins[pin].pin_flags |= GPIO_PIN_INVOUT; } } } static int mtk_gpio_attach(device_t dev) { struct mtk_gpio_softc *sc; phandle_t node; uint32_t i, num_pins; sc = device_get_softc(dev); sc->dev = dev; if (bus_alloc_resources(dev, mtk_gpio_spec, sc->res)) { device_printf(dev, "could not allocate resources for device\n"); return (ENXIO); } MTK_GPIO_LOCK_INIT(sc); node = ofw_bus_get_node(dev); if (OF_hasprop(node, "clocks")) mtk_soc_start_clock(dev); if (OF_hasprop(node, "resets")) mtk_soc_reset_device(dev); if (OF_getprop(node, "ralink,register-map", sc->regs, GPIO_PIOMAX) <= 0) { device_printf(dev, "Failed to read register map\n"); return (ENXIO); } if (OF_hasprop(node, "ralink,num-gpios") && (OF_getencprop(node, "ralink,num-gpios", &num_pins, sizeof(num_pins)) >= 0)) sc->num_pins = num_pins; else sc->num_pins = MTK_GPIO_PINS; for (i = 0; i < num_pins; i++) { sc->pins[i].pin_caps |= GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_INVIN | GPIO_PIN_INVOUT; sc->pins[i].intr_polarity = INTR_POLARITY_HIGH; sc->pins[i].intr_trigger = INTR_TRIGGER_EDGE; snprintf(sc->pins[i].pin_name, GPIOMAXNAME - 1, "gpio%c%d", device_get_unit(dev) + 'a', i); sc->pins[i].pin_name[GPIOMAXNAME - 1] = '\0'; mtk_gpio_pin_probe(sc, i); } if (mtk_pic_register_isrcs(sc) != 0) { device_printf(dev, "could not register PIC ISRCs\n"); goto fail; } if (intr_pic_register(dev, OF_xref_from_node(node)) != 0) { device_printf(dev, "could not register PIC\n"); goto fail; } if (bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, mtk_gpio_intr, NULL, sc, &sc->intrhand) != 0) goto fail_pic; sc->busdev = gpiobus_attach_bus(dev); if (sc->busdev == NULL) goto fail_pic; return (0); fail_pic: intr_pic_deregister(dev, OF_xref_from_node(node)); fail: if(sc->intrhand != NULL) bus_teardown_intr(dev, sc->res[1], sc->intrhand); bus_release_resources(dev, mtk_gpio_spec, sc->res); MTK_GPIO_LOCK_DESTROY(sc); return (ENXIO); } static int mtk_gpio_detach(device_t dev) { struct mtk_gpio_softc *sc = device_get_softc(dev); phandle_t node; node = ofw_bus_get_node(dev); intr_pic_deregister(dev, OF_xref_from_node(node)); if (sc->intrhand != NULL) bus_teardown_intr(dev, sc->res[1], sc->intrhand); bus_release_resources(dev, mtk_gpio_spec, sc->res); MTK_GPIO_LOCK_DESTROY(sc); return (0); } static device_t mtk_gpio_get_bus(device_t dev) { struct mtk_gpio_softc *sc = device_get_softc(dev); return (sc->busdev); } static int mtk_gpio_pin_max(device_t dev, int *maxpin) { struct mtk_gpio_softc *sc = device_get_softc(dev); *maxpin = sc->num_pins - 1; return (0); } static int mtk_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct mtk_gpio_softc *sc = device_get_softc(dev); if (pin >= sc->num_pins) return (EINVAL); MTK_GPIO_LOCK(sc); *caps = sc->pins[pin].pin_caps; MTK_GPIO_UNLOCK(sc); return (0); } static int mtk_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct mtk_gpio_softc *sc = device_get_softc(dev); if (pin >= sc->num_pins) return (EINVAL); MTK_GPIO_LOCK(sc); *flags = sc->pins[pin].pin_flags; MTK_GPIO_UNLOCK(sc); return (0); } static int mtk_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct mtk_gpio_softc *sc = device_get_softc(dev); if (pin >= sc->num_pins) return (EINVAL); strncpy(name, sc->pins[pin].pin_name, GPIOMAXNAME - 1); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int mtk_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct mtk_gpio_softc *sc; int retval; sc = device_get_softc(dev); if (pin >= sc->num_pins) return (EINVAL); MTK_GPIO_LOCK(sc); retval = mtk_gpio_pin_set_direction(sc, pin, flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)); if (retval == 0) retval = mtk_gpio_pin_set_invert(sc, pin, flags & (GPIO_PIN_INVIN | GPIO_PIN_INVOUT)); MTK_GPIO_UNLOCK(sc); return (retval); } static int mtk_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct mtk_gpio_softc *sc; int ret; sc = device_get_softc(dev); ret = 0; if (pin >= sc->num_pins) return (EINVAL); MTK_GPIO_LOCK(sc); if(!(sc->pins[pin].pin_flags & GPIO_PIN_OUTPUT)) { ret = EINVAL; goto out; } if (value) MTK_WRITE_4(sc, GPIO_PIOSET, (1u << pin)); else MTK_WRITE_4(sc, GPIO_PIORESET, (1u << pin)); out: MTK_GPIO_UNLOCK(sc); return (ret); } static int mtk_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct mtk_gpio_softc *sc; uint32_t data; int ret; sc = device_get_softc(dev); ret = 0; if (pin >= sc->num_pins) return (EINVAL); MTK_GPIO_LOCK(sc); if(!(sc->pins[pin].pin_flags & GPIO_PIN_INPUT)) { ret = EINVAL; goto out; } data = MTK_READ_4(sc, GPIO_PIODATA); *val = (data & (1u << pin)) ? 1 : 0; out: MTK_GPIO_UNLOCK(sc); return (ret); } static int mtk_gpio_pin_toggle(device_t dev, uint32_t pin) { struct mtk_gpio_softc *sc; int ret; if (pin >= sc->num_pins) return (EINVAL); sc = device_get_softc(dev); ret = 0; MTK_GPIO_LOCK(sc); if (!(sc->pins[pin].pin_flags & GPIO_PIN_OUTPUT)) { ret = EINVAL; goto out; } MTK_WRITE_4(sc, GPIO_PIOTOG, (1u << pin)); out: MTK_GPIO_UNLOCK(sc); return (ret); } static int mtk_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { + struct intr_map_data_fdt *daf; struct mtk_gpio_softc *sc; + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; - if (data == NULL || data->type != INTR_MAP_DATA_FDT || - data->fdt.ncells != 1 || data->fdt.cells[0] >= sc->num_pins) + if (daf->ncells != 1 || daf->cells[0] >= sc->num_pins) return (EINVAL); - *isrcp = PIC_INTR_ISRC(sc, data->fdt.cells[0]); + *isrcp = PIC_INTR_ISRC(sc, daf->cells[0]); return (0); } static void mtk_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct mtk_gpio_softc *sc; struct mtk_gpio_pin_irqsrc *pisrc; uint32_t pin, mask, val; sc = device_get_softc(dev); pisrc = (struct mtk_gpio_pin_irqsrc *)isrc; pin = pisrc->irq; mask = 1u << pin; MTK_GPIO_LOCK(sc); if (sc->pins[pin].intr_polarity == INTR_POLARITY_LOW) { val = MTK_READ_4(sc, GPIO_PIORENA) & ~mask; MTK_WRITE_4(sc, GPIO_PIORENA, val); val = MTK_READ_4(sc, GPIO_PIOFENA) | mask; MTK_WRITE_4(sc, GPIO_PIOFENA, val); } else { val = MTK_READ_4(sc, GPIO_PIOFENA) & ~mask; MTK_WRITE_4(sc, GPIO_PIOFENA, val); val = MTK_READ_4(sc, GPIO_PIORENA) | mask; MTK_WRITE_4(sc, GPIO_PIORENA, val); } MTK_GPIO_UNLOCK(sc); } static void mtk_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct mtk_gpio_softc *sc; struct mtk_gpio_pin_irqsrc *pisrc; uint32_t pin, mask, val; sc = device_get_softc(dev); pisrc = (struct mtk_gpio_pin_irqsrc *)isrc; pin = pisrc->irq; mask = 1u << pin; MTK_GPIO_LOCK(sc); val = MTK_READ_4(sc, GPIO_PIORENA) & ~mask; MTK_WRITE_4(sc, GPIO_PIORENA, val); val = MTK_READ_4(sc, GPIO_PIOFENA) & ~mask; MTK_WRITE_4(sc, GPIO_PIOFENA, val); MTK_GPIO_UNLOCK(sc); } static void mtk_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_gpio_pic_disable_intr(dev, isrc); } static void mtk_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_gpio_pic_enable_intr(dev, isrc); } static void mtk_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct mtk_gpio_softc *sc; struct mtk_gpio_pin_irqsrc *pisrc; pisrc = (struct mtk_gpio_pin_irqsrc *)isrc; sc = device_get_softc(dev); MTK_GPIO_LOCK(sc); MTK_WRITE_4(sc, GPIO_PIOINT, 1u << pisrc->irq); MTK_GPIO_UNLOCK(sc); } static int mtk_gpio_intr(void *arg) { struct mtk_gpio_softc *sc; uint32_t i, interrupts; sc = arg; interrupts = MTK_READ_4(sc, GPIO_PIOINT); for (i = 0; interrupts != 0; i++, interrupts >>= 1) { if ((interrupts & 0x1) == 0) continue; if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->dev, "spurious interrupt %d\n", i); } } return (FILTER_HANDLED); } static phandle_t mtk_gpio_get_node(device_t bus, device_t dev) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } static device_method_t mtk_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mtk_gpio_probe), DEVMETHOD(device_attach, mtk_gpio_attach), DEVMETHOD(device_detach, mtk_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, mtk_gpio_get_bus), DEVMETHOD(gpio_pin_max, mtk_gpio_pin_max), DEVMETHOD(gpio_pin_getname, mtk_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, mtk_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, mtk_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, mtk_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, mtk_gpio_pin_get), DEVMETHOD(gpio_pin_set, mtk_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, mtk_gpio_pin_toggle), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, mtk_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, mtk_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, mtk_gpio_pic_map_intr), DEVMETHOD(pic_post_filter, mtk_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, mtk_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, mtk_gpio_pic_pre_ithread), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, mtk_gpio_get_node), DEVMETHOD_END }; static driver_t mtk_gpio_driver = { "gpio", mtk_gpio_methods, sizeof(struct mtk_gpio_softc), }; static devclass_t mtk_gpio_devclass; EARLY_DRIVER_MODULE(mtk_gpio_v1, simplebus, mtk_gpio_driver, mtk_gpio_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); Index: user/ngie/detangle-rc/sys/mips/mediatek/mtk_gpio_v2.c =================================================================== --- user/ngie/detangle-rc/sys/mips/mediatek/mtk_gpio_v2.c (revision 299126) +++ user/ngie/detangle-rc/sys/mips/mediatek/mtk_gpio_v2.c (revision 299127) @@ -1,675 +1,679 @@ /*- * Copyright 2016 Stanislav Galabov * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "pic_if.h" #define MTK_GPIO_PINS 32 struct mtk_gpio_pin_irqsrc { struct intr_irqsrc isrc; u_int irq; }; struct mtk_gpio_pin { uint32_t pin_caps; uint32_t pin_flags; enum intr_trigger intr_trigger; enum intr_polarity intr_polarity; char pin_name[GPIOMAXNAME]; struct mtk_gpio_pin_irqsrc pin_irqsrc; }; struct mtk_gpio_softc { device_t dev; device_t busdev; struct resource *res[2]; struct mtx mtx; struct mtk_gpio_pin pins[MTK_GPIO_PINS]; void *intrhand; uint32_t num_pins; uint32_t bank_id; }; #define PIC_INTR_ISRC(sc, irq) (&(sc)->pins[(irq)].pin_irqsrc.isrc) static struct resource_spec mtk_gpio_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; static int mtk_gpio_probe(device_t dev); static int mtk_gpio_attach(device_t dev); static int mtk_gpio_detach(device_t dev); static int mtk_gpio_intr(void *arg); #define MTK_GPIO_LOCK(sc) mtx_lock_spin(&(sc)->mtx) #define MTK_GPIO_UNLOCK(sc) mtx_unlock_spin(&(sc)->mtx) #define MTK_GPIO_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "mtk_gpio", MTX_SPIN) #define MTK_GPIO_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx) #define MTK_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) #define MTK_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) /* Register definitions */ #define GPIO_REG(_sc, _reg) ((_reg) + (_sc)->bank_id * 0x4) #define GPIO_PIOINT(_sc) GPIO_REG((_sc), 0x0090) #define GPIO_PIOEDGE(_sc) GPIO_REG((_sc), 0x00A0) #define GPIO_PIORENA(_sc) GPIO_REG((_sc), 0x0050) #define GPIO_PIOFENA(_sc) GPIO_REG((_sc), 0x0060) #define GPIO_PIODATA(_sc) GPIO_REG((_sc), 0x0020) #define GPIO_PIODIR(_sc) GPIO_REG((_sc), 0x0000) #define GPIO_PIOPOL(_sc) GPIO_REG((_sc), 0x0010) #define GPIO_PIOSET(_sc) GPIO_REG((_sc), 0x0030) #define GPIO_PIORESET(_sc) GPIO_REG((_sc), 0x0040) static struct ofw_compat_data compat_data[] = { { "mtk,mt7621-gpio-bank", 1 }, { "mtk,mt7628-gpio-bank", 1 }, { NULL, 0 } }; static int mtk_gpio_probe(device_t dev) { phandle_t node; if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); node = ofw_bus_get_node(dev); if (!OF_hasprop(node, "gpio-controller")) return (ENXIO); device_set_desc(dev, "MTK GPIO Controller (v2)"); return (BUS_PROBE_DEFAULT); } static int mtk_pic_register_isrcs(struct mtk_gpio_softc *sc) { int error; uint32_t irq; struct intr_irqsrc *isrc; const char *name; name = device_get_nameunit(sc->dev); for (irq = 0; irq < sc->num_pins; irq++) { sc->pins[irq].pin_irqsrc.irq = irq; isrc = PIC_INTR_ISRC(sc, irq); error = intr_isrc_register(isrc, sc->dev, 0, "%s", name); if (error != 0) { /* XXX call intr_isrc_deregister */ device_printf(sc->dev, "%s failed", __func__); return (error); } } return (0); } static int mtk_gpio_pin_set_direction(struct mtk_gpio_softc *sc, uint32_t pin, uint32_t dir) { uint32_t regval, mask = (1u << pin); if (!(sc->pins[pin].pin_caps & dir)) return (EINVAL); regval = MTK_READ_4(sc, GPIO_PIODIR(sc)); if (dir == GPIO_PIN_INPUT) regval &= ~mask; else regval |= mask; MTK_WRITE_4(sc, GPIO_PIODIR(sc), regval); sc->pins[pin].pin_flags &= ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); sc->pins[pin].pin_flags |= dir; return (0); } static int mtk_gpio_pin_set_invert(struct mtk_gpio_softc *sc, uint32_t pin, uint32_t val) { uint32_t regval, mask = (1u << pin); regval = MTK_READ_4(sc, GPIO_PIOPOL(sc)); if (val) regval |= mask; else regval &= ~mask; MTK_WRITE_4(sc, GPIO_PIOPOL(sc), regval); sc->pins[pin].pin_flags &= ~(GPIO_PIN_INVIN | GPIO_PIN_INVOUT); sc->pins[pin].pin_flags |= val; return (0); } static void mtk_gpio_pin_probe(struct mtk_gpio_softc *sc, uint32_t pin) { uint32_t mask = (1u << pin); uint32_t val; /* Clear cached gpio config */ sc->pins[pin].pin_flags = 0; val = MTK_READ_4(sc, GPIO_PIORENA(sc)) | MTK_READ_4(sc, GPIO_PIOFENA(sc)); if (val & mask) { /* Pin is in interrupt mode */ sc->pins[pin].intr_trigger = INTR_TRIGGER_EDGE; val = MTK_READ_4(sc, GPIO_PIORENA(sc)); if (val & mask) sc->pins[pin].intr_polarity = INTR_POLARITY_HIGH; else sc->pins[pin].intr_polarity = INTR_POLARITY_LOW; } val = MTK_READ_4(sc, GPIO_PIODIR(sc)); if (val & mask) sc->pins[pin].pin_flags |= GPIO_PIN_OUTPUT; else sc->pins[pin].pin_flags |= GPIO_PIN_INPUT; val = MTK_READ_4(sc, GPIO_PIOPOL(sc)); if (val & mask) { if (sc->pins[pin].pin_flags & GPIO_PIN_INPUT) { sc->pins[pin].pin_flags |= GPIO_PIN_INVIN; } else { sc->pins[pin].pin_flags |= GPIO_PIN_INVOUT; } } } static int mtk_gpio_attach(device_t dev) { struct mtk_gpio_softc *sc; phandle_t node; uint32_t i, num_pins, bank_id; sc = device_get_softc(dev); sc->dev = dev; if (bus_alloc_resources(dev, mtk_gpio_spec, sc->res)) { device_printf(dev, "could not allocate resources for device\n"); return (ENXIO); } MTK_GPIO_LOCK_INIT(sc); node = ofw_bus_get_node(dev); if (OF_hasprop(node, "clocks")) mtk_soc_start_clock(dev); if (OF_hasprop(node, "resets")) mtk_soc_reset_device(dev); if (OF_hasprop(node, "mtk,bank-id") && (OF_getencprop(node, "mtk,bank-id", &bank_id, sizeof(bank_id)) >= 0)) sc->bank_id = bank_id; else sc->bank_id = device_get_unit(dev); if (OF_hasprop(node, "mtk,num-pins") && (OF_getencprop(node, "mtk,num-pins", &num_pins, sizeof(num_pins)) >= 0)) sc->num_pins = num_pins; else sc->num_pins = MTK_GPIO_PINS; for (i = 0; i < sc->num_pins; i++) { sc->pins[i].pin_caps |= GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_INVIN | GPIO_PIN_INVOUT; sc->pins[i].intr_polarity = INTR_POLARITY_HIGH; sc->pins[i].intr_trigger = INTR_TRIGGER_EDGE; snprintf(sc->pins[i].pin_name, GPIOMAXNAME - 1, "gpio%c%d", device_get_unit(dev) + 'a', i); sc->pins[i].pin_name[GPIOMAXNAME - 1] = '\0'; mtk_gpio_pin_probe(sc, i); } if (mtk_pic_register_isrcs(sc) != 0) { device_printf(dev, "could not register PIC ISRCs\n"); goto fail; } if (intr_pic_register(dev, OF_xref_from_node(node)) != 0) { device_printf(dev, "could not register PIC\n"); goto fail; } if (bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, mtk_gpio_intr, NULL, sc, &sc->intrhand) != 0) goto fail_pic; sc->busdev = gpiobus_attach_bus(dev); if (sc->busdev == NULL) goto fail_pic; return (0); fail_pic: intr_pic_deregister(dev, OF_xref_from_node(node)); fail: if(sc->intrhand != NULL) bus_teardown_intr(dev, sc->res[1], sc->intrhand); bus_release_resources(dev, mtk_gpio_spec, sc->res); MTK_GPIO_LOCK_DESTROY(sc); return (ENXIO); } static int mtk_gpio_detach(device_t dev) { struct mtk_gpio_softc *sc = device_get_softc(dev); phandle_t node; node = ofw_bus_get_node(dev); intr_pic_deregister(dev, OF_xref_from_node(node)); if (sc->intrhand != NULL) bus_teardown_intr(dev, sc->res[1], sc->intrhand); bus_release_resources(dev, mtk_gpio_spec, sc->res); MTK_GPIO_LOCK_DESTROY(sc); return (0); } static device_t mtk_gpio_get_bus(device_t dev) { struct mtk_gpio_softc *sc = device_get_softc(dev); return (sc->busdev); } static int mtk_gpio_pin_max(device_t dev, int *maxpin) { struct mtk_gpio_softc *sc = device_get_softc(dev); *maxpin = sc->num_pins - 1; return (0); } static int mtk_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct mtk_gpio_softc *sc = device_get_softc(dev); if (pin >= sc->num_pins) return (EINVAL); MTK_GPIO_LOCK(sc); *caps = sc->pins[pin].pin_caps; MTK_GPIO_UNLOCK(sc); return (0); } static int mtk_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct mtk_gpio_softc *sc = device_get_softc(dev); if (pin >= sc->num_pins) return (EINVAL); MTK_GPIO_LOCK(sc); *flags = sc->pins[pin].pin_flags; MTK_GPIO_UNLOCK(sc); return (0); } static int mtk_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct mtk_gpio_softc *sc = device_get_softc(dev); if (pin >= sc->num_pins) return (EINVAL); strncpy(name, sc->pins[pin].pin_name, GPIOMAXNAME - 1); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int mtk_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct mtk_gpio_softc *sc; int retval; sc = device_get_softc(dev); if (pin >= sc->num_pins) return (EINVAL); MTK_GPIO_LOCK(sc); retval = mtk_gpio_pin_set_direction(sc, pin, flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)); if (retval == 0) retval = mtk_gpio_pin_set_invert(sc, pin, flags & (GPIO_PIN_INVIN | GPIO_PIN_INVOUT)); MTK_GPIO_UNLOCK(sc); return (retval); } static int mtk_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct mtk_gpio_softc *sc; int ret; if (pin >= sc->num_pins) return (EINVAL); sc = device_get_softc(dev); ret = 0; MTK_GPIO_LOCK(sc); if (!(sc->pins[pin].pin_flags & GPIO_PIN_OUTPUT)) { ret = EINVAL; goto out; } if (value) MTK_WRITE_4(sc, GPIO_PIOSET(sc), (1u << pin)); else MTK_WRITE_4(sc, GPIO_PIORESET(sc), (1u << pin)); out: MTK_GPIO_UNLOCK(sc); return (ret); } static int mtk_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct mtk_gpio_softc *sc; uint32_t data; int ret; if (pin >= sc->num_pins) return (EINVAL); sc = device_get_softc(dev); ret = 0; MTK_GPIO_LOCK(sc); if (!(sc->pins[pin].pin_flags & GPIO_PIN_INPUT)) { ret = EINVAL; goto out; } data = MTK_READ_4(sc, GPIO_PIODATA(sc)); *val = (data & (1u << pin)) ? 1 : 0; out: MTK_GPIO_UNLOCK(sc); return (ret); } static int mtk_gpio_pin_toggle(device_t dev, uint32_t pin) { struct mtk_gpio_softc *sc; uint32_t val; int ret; if (pin >= sc->num_pins) return (EINVAL); sc = device_get_softc(dev); ret = 0; MTK_GPIO_LOCK(sc); if(!(sc->pins[pin].pin_flags & GPIO_PIN_OUTPUT)) { ret = EINVAL; goto out; } val = MTK_READ_4(sc, GPIO_PIODATA(sc)); val &= (1u << pin); if (val) MTK_WRITE_4(sc, GPIO_PIORESET(sc), (1u << pin)); else MTK_WRITE_4(sc, GPIO_PIOSET(sc), (1u << pin)); out: MTK_GPIO_UNLOCK(sc); return (ret); } static int mtk_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { + struct intr_map_data_fdt *daf; struct mtk_gpio_softc *sc; + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; - if (data == NULL || data->type != INTR_MAP_DATA_FDT || - data->fdt.ncells != 1 || data->fdt.cells[0] >= sc->num_pins) + if (daf->ncells != 1 || daf->cells[0] >= sc->num_pins) return (EINVAL); - *isrcp = PIC_INTR_ISRC(sc, data->fdt.cells[0]); + *isrcp = PIC_INTR_ISRC(sc, daf->cells[0]); return (0); } static void mtk_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct mtk_gpio_softc *sc; struct mtk_gpio_pin_irqsrc *pisrc; uint32_t pin, mask, val; sc = device_get_softc(dev); pisrc = (struct mtk_gpio_pin_irqsrc *)isrc; pin = pisrc->irq; mask = 1u << pin; MTK_GPIO_LOCK(sc); if (sc->pins[pin].intr_polarity == INTR_POLARITY_LOW) { val = MTK_READ_4(sc, GPIO_PIORENA(sc)) & ~mask; MTK_WRITE_4(sc, GPIO_PIORENA(sc), val); val = MTK_READ_4(sc, GPIO_PIOFENA(sc)) | mask; MTK_WRITE_4(sc, GPIO_PIOFENA(sc), val); } else { val = MTK_READ_4(sc, GPIO_PIOFENA(sc)) & ~mask; MTK_WRITE_4(sc, GPIO_PIOFENA(sc), val); val = MTK_READ_4(sc, GPIO_PIORENA(sc)) | mask; MTK_WRITE_4(sc, GPIO_PIORENA(sc), val); } MTK_GPIO_UNLOCK(sc); } static void mtk_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct mtk_gpio_softc *sc; struct mtk_gpio_pin_irqsrc *pisrc; uint32_t pin, mask, val; sc = device_get_softc(dev); pisrc = (struct mtk_gpio_pin_irqsrc *)isrc; pin = pisrc->irq; mask = 1u << pin; MTK_GPIO_LOCK(sc); val = MTK_READ_4(sc, GPIO_PIORENA(sc)) & ~mask; MTK_WRITE_4(sc, GPIO_PIORENA(sc), val); val = MTK_READ_4(sc, GPIO_PIOFENA(sc)) & ~mask; MTK_WRITE_4(sc, GPIO_PIOFENA(sc), val); MTK_GPIO_UNLOCK(sc); } static void mtk_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_gpio_pic_disable_intr(dev, isrc); } static void mtk_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_gpio_pic_enable_intr(dev, isrc); } static void mtk_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct mtk_gpio_softc *sc; struct mtk_gpio_pin_irqsrc *pisrc; pisrc = (struct mtk_gpio_pin_irqsrc *)isrc; sc = device_get_softc(dev); MTK_GPIO_LOCK(sc); MTK_WRITE_4(sc, GPIO_PIOINT(sc), 1u << pisrc->irq); MTK_GPIO_UNLOCK(sc); } static int mtk_gpio_intr(void *arg) { struct mtk_gpio_softc *sc; uint32_t i, interrupts; sc = arg; interrupts = MTK_READ_4(sc, GPIO_PIOINT(sc)); for (i = 0; interrupts != 0; i++, interrupts >>= 1) { if ((interrupts & 0x1) == 0) continue; if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->dev, "spurious interrupt %d\n", i); } } return (FILTER_HANDLED); } static phandle_t mtk_gpio_get_node(device_t bus, device_t dev) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } static device_method_t mtk_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mtk_gpio_probe), DEVMETHOD(device_attach, mtk_gpio_attach), DEVMETHOD(device_detach, mtk_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, mtk_gpio_get_bus), DEVMETHOD(gpio_pin_max, mtk_gpio_pin_max), DEVMETHOD(gpio_pin_getname, mtk_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, mtk_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, mtk_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, mtk_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, mtk_gpio_pin_get), DEVMETHOD(gpio_pin_set, mtk_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, mtk_gpio_pin_toggle), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, mtk_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, mtk_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, mtk_gpio_pic_map_intr), DEVMETHOD(pic_post_filter, mtk_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, mtk_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, mtk_gpio_pic_pre_ithread), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, mtk_gpio_get_node), DEVMETHOD_END }; static driver_t mtk_gpio_driver = { "gpio", mtk_gpio_methods, sizeof(struct mtk_gpio_softc), }; static devclass_t mtk_gpio_devclass; EARLY_DRIVER_MODULE(mtk_gpio_v2, simplebus, mtk_gpio_driver, mtk_gpio_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); Index: user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_gic.c =================================================================== --- user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_gic.c (revision 299126) +++ user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_gic.c (revision 299127) @@ -1,366 +1,370 @@ /*- * Copyright (c) 2016 Stanislav Galabov * Copyright (c) 2015 Alexander Kabaev * 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, * without modification, immediately at the beginning of the file. * 2. 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 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. * */ #include "opt_platform.h" #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pic_if.h" #define MTK_NIRQS 64 /* We'll only use 64 for now */ #define MTK_INTPOL 0x0100 #define MTK_INTTRIG 0x0180 #define MTK_INTDIS 0x0300 #define MTK_INTENA 0x0380 #define MTK_INTMASK 0x0400 #define MTK_INTSTAT 0x0480 #define MTK_MAPPIN(_i) (0x0500 + (4 * (_i))) #define MTK_MAPVPE(_i, _v) (0x2000 + (32 * (_i)) + (((_v) / 32) * 4)) #define MTK_INTPOL_POS 1 #define MTK_INTPOL_NEG 0 #define MTK_INTTRIG_EDGE 1 #define MTK_INTTRIG_LEVEL 0 #define MTK_PIN_BITS(_i) ((1 << 31) | (_i)) #define MTK_VPE_BITS(_v) (1 << ((_v) % 32)) static int mtk_gic_intr(void *); struct mtk_gic_irqsrc { struct intr_irqsrc isrc; u_int irq; }; struct mtk_gic_softc { device_t gic_dev; void * gic_intrhand; struct resource * gic_res[2]; struct mtk_gic_irqsrc gic_irqs[MTK_NIRQS]; struct mtx mutex; uint32_t nirqs; }; #define GIC_INTR_ISRC(sc, irq) (&(sc)->gic_irqs[(irq)].isrc) static struct resource_spec mtk_gic_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, /* Registers */ { -1, 0 } }; static struct ofw_compat_data compat_data[] = { { "mti,gic", 1 }, { NULL, 0 } }; #define READ4(_sc, _reg) bus_read_4((_sc)->gic_res[0], (_reg)) #define WRITE4(_sc, _reg, _val) bus_write_4((_sc)->gic_res[0], (_reg), (_val)) static int mtk_gic_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "MTK Interrupt Controller (GIC)"); return (BUS_PROBE_DEFAULT); } static inline void gic_irq_unmask(struct mtk_gic_softc *sc, u_int irq) { WRITE4(sc, MTK_INTENA, (1u << (irq))); } static inline void gic_irq_mask(struct mtk_gic_softc *sc, u_int irq) { WRITE4(sc, MTK_INTDIS, (1u << (irq))); } static inline intptr_t gic_xref(device_t dev) { return (OF_xref_from_node(ofw_bus_get_node(dev))); } static int mtk_gic_register_isrcs(struct mtk_gic_softc *sc) { int error; uint32_t irq; struct intr_irqsrc *isrc; const char *name; name = device_get_nameunit(sc->gic_dev); for (irq = 0; irq < sc->nirqs; irq++) { sc->gic_irqs[irq].irq = irq; isrc = GIC_INTR_ISRC(sc, irq); error = intr_isrc_register(isrc, sc->gic_dev, 0, "%s", name); if (error != 0) { /* XXX call intr_isrc_deregister */ device_printf(sc->gic_dev, "%s failed", __func__); return (error); } } return (0); } static int mtk_gic_attach(device_t dev) { struct mtk_gic_softc *sc; intptr_t xref = gic_xref(dev); int i; sc = device_get_softc(dev); if (bus_alloc_resources(dev, mtk_gic_spec, sc->gic_res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } sc->gic_dev = dev; /* Initialize mutex */ mtx_init(&sc->mutex, "PIC lock", "", MTX_SPIN); /* Set the number of interrupts */ sc->nirqs = nitems(sc->gic_irqs); /* Mask all interrupts */ WRITE4(sc, MTK_INTDIS, 0xFFFFFFFF); /* All interrupts are of type level */ WRITE4(sc, MTK_INTTRIG, 0x00000000); /* All interrupts are of positive polarity */ WRITE4(sc, MTK_INTPOL, 0xFFFFFFFF); /* * Route all interrupts to pin 0 on VPE 0; */ for (i = 0; i < 32; i++) { WRITE4(sc, MTK_MAPPIN(i), MTK_PIN_BITS(0)); WRITE4(sc, MTK_MAPVPE(i, 0), MTK_VPE_BITS(0)); } /* Register the interrupts */ if (mtk_gic_register_isrcs(sc) != 0) { device_printf(dev, "could not register GIC ISRCs\n"); goto cleanup; } /* * Now, when everything is initialized, it's right time to * register interrupt controller to interrupt framefork. */ if (intr_pic_register(dev, xref) != 0) { device_printf(dev, "could not register PIC\n"); goto cleanup; } cpu_establish_hardintr("gic", mtk_gic_intr, NULL, sc, 0, INTR_TYPE_CLK, NULL); return (0); cleanup: bus_release_resources(dev, mtk_gic_spec, sc->gic_res); return(ENXIO); } static int mtk_gic_intr(void *arg) { struct mtk_gic_softc *sc = arg; struct thread *td; uint32_t i, intr; td = curthread; /* Workaround: do not inflate intr nesting level */ td->td_intr_nesting_level--; intr = READ4(sc, MTK_INTSTAT) & READ4(sc, MTK_INTMASK); while ((i = fls(intr)) != 0) { i--; intr &= ~(1u << i); if (intr_isrc_dispatch(GIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->gic_dev, "Stray interrupt %u detected\n", i); gic_irq_mask(sc, i); continue; } } KASSERT(i == 0, ("all interrupts handled")); td->td_intr_nesting_level++; return (FILTER_HANDLED); } static int mtk_gic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { #ifdef FDT + struct intr_map_data_fdt *daf; struct mtk_gic_softc *sc; + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; - if (data == NULL || data->type != INTR_MAP_DATA_FDT || - data->fdt.ncells != 3 || data->fdt.cells[1] >= sc->nirqs) + if (daf->ncells != 3 || daf->cells[1] >= sc->nirqs) return (EINVAL); - *isrcp = GIC_INTR_ISRC(sc, data->fdt.cells[1]); + *isrcp = GIC_INTR_ISRC(sc, daf->cells[1]); return (0); #else - return (EINVAL); + return (ENOTSUP); #endif } static void mtk_gic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mtk_gic_irqsrc *)isrc)->irq; gic_irq_unmask(device_get_softc(dev), irq); } static void mtk_gic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mtk_gic_irqsrc *)isrc)->irq; gic_irq_mask(device_get_softc(dev), irq); } static void mtk_gic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_gic_disable_intr(dev, isrc); } static void mtk_gic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_gic_enable_intr(dev, isrc); } static void mtk_gic_post_filter(device_t dev, struct intr_irqsrc *isrc) { } #ifdef SMP static int mtk_gic_bind(device_t dev, struct intr_irqsrc *isrc) { return (EOPNOTSUPP); } static void mtk_gic_init_secondary(device_t dev) { } static void mtk_gic_ipi_send(device_t dev, struct intr_irqsrc *isrc, cpuset_t cpus) { } #endif static device_method_t mtk_gic_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mtk_gic_probe), DEVMETHOD(device_attach, mtk_gic_attach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, mtk_gic_disable_intr), DEVMETHOD(pic_enable_intr, mtk_gic_enable_intr), DEVMETHOD(pic_map_intr, mtk_gic_map_intr), DEVMETHOD(pic_post_filter, mtk_gic_post_filter), DEVMETHOD(pic_post_ithread, mtk_gic_post_ithread), DEVMETHOD(pic_pre_ithread, mtk_gic_pre_ithread), #ifdef SMP DEVMETHOD(pic_bind, mtk_gic_bind), DEVMETHOD(pic_init_secondary, mtk_gic_init_secondary), DEVMETHOD(pic_ipi_send, mtk_gic_ipi_send), #endif { 0, 0 } }; static driver_t mtk_gic_driver = { "intc", mtk_gic_methods, sizeof(struct mtk_gic_softc), }; static devclass_t mtk_gic_devclass; EARLY_DRIVER_MODULE(intc_gic, simplebus, mtk_gic_driver, mtk_gic_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_v1.c =================================================================== --- user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_v1.c (revision 299126) +++ user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_v1.c (revision 299127) @@ -1,353 +1,357 @@ /*- * Copyright (c) 2015 Stanislav Galabov * Copyright (c) 2015 Alexander Kabaev * 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, * without modification, immediately at the beginning of the file. * 2. 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 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. * */ #include "opt_platform.h" #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pic_if.h" #define MTK_NIRQS 32 #define MTK_IRQ0STAT 0x0000 #define MTK_IRQ1STAT 0x0004 #define MTK_INTTYPE 0x0020 #define MTK_INTRAW 0x0030 #define MTK_INTENA 0x0034 #define MTK_INTDIS 0x0038 static int mtk_pic_intr(void *); struct mtk_pic_irqsrc { struct intr_irqsrc isrc; u_int irq; }; struct mtk_pic_softc { device_t pic_dev; void * pic_intrhand; struct resource * pic_res[2]; struct mtk_pic_irqsrc pic_irqs[MTK_NIRQS]; struct mtx mutex; uint32_t nirqs; }; #define PIC_INTR_ISRC(sc, irq) (&(sc)->pic_irqs[(irq)].isrc) static struct resource_spec mtk_pic_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, /* Registers */ { SYS_RES_IRQ, 0, RF_ACTIVE }, /* Parent interrupt 1 */ // { SYS_RES_IRQ, 1, RF_ACTIVE }, /* Parent interrupt 2 */ { -1, 0 } }; static struct ofw_compat_data compat_data[] = { { "ralink,rt2880-intc", 1 }, { "ralink,rt3050-intc", 1 }, { "ralink,rt3352-intc", 1 }, { "ralink,rt3883-intc", 1 }, { "ralink,rt5350-intc", 1 }, { "ralink,mt7620a-intc", 1 }, { NULL, 0 } }; #define READ4(_sc, _reg) bus_read_4((_sc)->pic_res[0], _reg) #define WRITE4(_sc, _reg, _val) bus_write_4((_sc)->pic_res[0], _reg, _val) static int mtk_pic_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "MTK Interrupt Controller (v2)"); return (BUS_PROBE_DEFAULT); } static inline void pic_irq_unmask(struct mtk_pic_softc *sc, u_int irq) { WRITE4(sc, MTK_INTENA, (1u << (irq))); } static inline void pic_irq_mask(struct mtk_pic_softc *sc, u_int irq) { WRITE4(sc, MTK_INTDIS, (1u << (irq))); } static inline intptr_t pic_xref(device_t dev) { return (OF_xref_from_node(ofw_bus_get_node(dev))); } static int mtk_pic_register_isrcs(struct mtk_pic_softc *sc) { int error; uint32_t irq; struct intr_irqsrc *isrc; const char *name; name = device_get_nameunit(sc->pic_dev); for (irq = 0; irq < sc->nirqs; irq++) { sc->pic_irqs[irq].irq = irq; isrc = PIC_INTR_ISRC(sc, irq); error = intr_isrc_register(isrc, sc->pic_dev, 0, "%s", name); if (error != 0) { /* XXX call intr_isrc_deregister */ device_printf(sc->pic_dev, "%s failed", __func__); return (error); } } return (0); } static int mtk_pic_attach(device_t dev) { struct mtk_pic_softc *sc; intptr_t xref = pic_xref(dev); sc = device_get_softc(dev); if (bus_alloc_resources(dev, mtk_pic_spec, sc->pic_res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } sc->pic_dev = dev; /* Initialize mutex */ mtx_init(&sc->mutex, "PIC lock", "", MTX_SPIN); /* Set the number of interrupts */ sc->nirqs = nitems(sc->pic_irqs); /* Mask all interrupts */ WRITE4(sc, MTK_INTDIS, 0x7FFFFFFF); /* But enable interrupt generation/masking */ WRITE4(sc, MTK_INTENA, 0x80000000); /* Set all interrupts to type 0 */ WRITE4(sc, MTK_INTTYPE, 0x00000000); /* Register the interrupts */ if (mtk_pic_register_isrcs(sc) != 0) { device_printf(dev, "could not register PIC ISRCs\n"); goto cleanup; } /* * Now, when everything is initialized, it's right time to * register interrupt controller to interrupt framefork. */ if (intr_pic_register(dev, xref) != 0) { device_printf(dev, "could not register PIC\n"); goto cleanup; } if (bus_setup_intr(dev, sc->pic_res[1], INTR_TYPE_CLK, mtk_pic_intr, NULL, sc, &sc->pic_intrhand)) { device_printf(dev, "could not setup irq handler\n"); intr_pic_deregister(dev, xref); goto cleanup; } return (0); cleanup: bus_release_resources(dev, mtk_pic_spec, sc->pic_res); return(ENXIO); } static int mtk_pic_intr(void *arg) { struct mtk_pic_softc *sc = arg; struct thread *td; uint32_t i, intr; td = curthread; /* Workaround: do not inflate intr nesting level */ td->td_intr_nesting_level--; #ifdef _notyet_ intr = READ4(sc, MTK_IRQ1STAT); while ((i = fls(intr)) != 0) { i--; intr &= ~(1u << i); if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->pic_dev, "Stray interrupt %u detected\n", i); pic_irq_mask(sc, i); continue; } } KASSERT(i == 0, ("all interrupts handled")); #endif intr = READ4(sc, MTK_IRQ0STAT); while ((i = fls(intr)) != 0) { i--; intr &= ~(1u << i); if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->pic_dev, "Stray interrupt %u detected\n", i); pic_irq_mask(sc, i); continue; } } KASSERT(i == 0, ("all interrupts handled")); td->td_intr_nesting_level++; return (FILTER_HANDLED); } static int mtk_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { #ifdef FDT + struct intr_map_data_fdt *daf; struct mtk_pic_softc *sc; + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; - if (data == NULL || data->type != INTR_MAP_DATA_FDT || - data->fdt.ncells != 1 || data->fdt.cells[0] >= sc->nirqs) + if (daf->ncells != 1 || daf->cells[0] >= sc->nirqs) return (EINVAL); - *isrcp = PIC_INTR_ISRC(sc, data->fdt.cells[0]); + *isrcp = PIC_INTR_ISRC(sc, daf->cells[0]); return (0); #else - return (EINVAL); + return (ENOTSUP); #endif } static void mtk_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mtk_pic_irqsrc *)isrc)->irq; pic_irq_unmask(device_get_softc(dev), irq); } static void mtk_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mtk_pic_irqsrc *)isrc)->irq; pic_irq_mask(device_get_softc(dev), irq); } static void mtk_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_pic_disable_intr(dev, isrc); } static void mtk_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_pic_enable_intr(dev, isrc); } static void mtk_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { } static device_method_t mtk_pic_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mtk_pic_probe), DEVMETHOD(device_attach, mtk_pic_attach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, mtk_pic_disable_intr), DEVMETHOD(pic_enable_intr, mtk_pic_enable_intr), DEVMETHOD(pic_map_intr, mtk_pic_map_intr), DEVMETHOD(pic_post_filter, mtk_pic_post_filter), DEVMETHOD(pic_post_ithread, mtk_pic_post_ithread), DEVMETHOD(pic_pre_ithread, mtk_pic_pre_ithread), { 0, 0 } }; static driver_t mtk_pic_driver = { "intc", mtk_pic_methods, sizeof(struct mtk_pic_softc), }; static devclass_t mtk_pic_devclass; EARLY_DRIVER_MODULE(intc_v1, simplebus, mtk_pic_driver, mtk_pic_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_v2.c =================================================================== --- user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_v2.c (revision 299126) +++ user/ngie/detangle-rc/sys/mips/mediatek/mtk_intr_v2.c (revision 299127) @@ -1,348 +1,352 @@ /*- * Copyright (c) 2015 Stanislav Galabov * Copyright (c) 2015 Alexander Kabaev * 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, * without modification, immediately at the beginning of the file. * 2. 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 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. * */ #include "opt_platform.h" #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pic_if.h" #define MTK_NIRQS 32 #define MTK_IRQ0STAT 0x009c #define MTK_IRQ1STAT 0x00a0 #define MTK_INTTYPE 0x0000 #define MTK_INTRAW 0x00a4 #define MTK_INTENA 0x0080 #define MTK_INTDIS 0x0078 static int mtk_pic_intr(void *); struct mtk_pic_irqsrc { struct intr_irqsrc isrc; u_int irq; }; struct mtk_pic_softc { device_t pic_dev; void * pic_intrhand; struct resource * pic_res[2]; struct mtk_pic_irqsrc pic_irqs[MTK_NIRQS]; struct mtx mutex; uint32_t nirqs; }; #define PIC_INTR_ISRC(sc, irq) (&(sc)->pic_irqs[(irq)].isrc) static struct resource_spec mtk_pic_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, /* Registers */ { SYS_RES_IRQ, 0, RF_ACTIVE }, /* Parent interrupt 1 */ // { SYS_RES_IRQ, 1, RF_ACTIVE }, /* Parent interrupt 2 */ { -1, 0 } }; static struct ofw_compat_data compat_data[] = { { "ralink,mt7628an-intc", 1 }, { NULL, 0 } }; #define READ4(_sc, _reg) bus_read_4((_sc)->pic_res[0], _reg) #define WRITE4(_sc, _reg, _val) bus_write_4((_sc)->pic_res[0], _reg, _val) static int mtk_pic_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "MTK Interrupt Controller (v2)"); return (BUS_PROBE_DEFAULT); } static inline void pic_irq_unmask(struct mtk_pic_softc *sc, u_int irq) { WRITE4(sc, MTK_INTENA, (1u << (irq))); } static inline void pic_irq_mask(struct mtk_pic_softc *sc, u_int irq) { WRITE4(sc, MTK_INTDIS, (1u << (irq))); } static inline intptr_t pic_xref(device_t dev) { return (OF_xref_from_node(ofw_bus_get_node(dev))); } static int mtk_pic_register_isrcs(struct mtk_pic_softc *sc) { int error; uint32_t irq; struct intr_irqsrc *isrc; const char *name; name = device_get_nameunit(sc->pic_dev); for (irq = 0; irq < sc->nirqs; irq++) { sc->pic_irqs[irq].irq = irq; isrc = PIC_INTR_ISRC(sc, irq); error = intr_isrc_register(isrc, sc->pic_dev, 0, "%s", name); if (error != 0) { /* XXX call intr_isrc_deregister */ device_printf(sc->pic_dev, "%s failed", __func__); return (error); } } return (0); } static int mtk_pic_attach(device_t dev) { struct mtk_pic_softc *sc; intptr_t xref = pic_xref(dev); sc = device_get_softc(dev); if (bus_alloc_resources(dev, mtk_pic_spec, sc->pic_res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } sc->pic_dev = dev; /* Initialize mutex */ mtx_init(&sc->mutex, "PIC lock", "", MTX_SPIN); /* Set the number of interrupts */ sc->nirqs = nitems(sc->pic_irqs); /* Mask all interrupts */ WRITE4(sc, MTK_INTDIS, 0xFFFFFFFF); /* But enable interrupt generation/masking */ WRITE4(sc, MTK_INTENA, 0x00000000); /* Set all interrupts to type 0 */ WRITE4(sc, MTK_INTTYPE, 0xFFFFFFFF); /* Register the interrupts */ if (mtk_pic_register_isrcs(sc) != 0) { device_printf(dev, "could not register PIC ISRCs\n"); goto cleanup; } /* * Now, when everything is initialized, it's right time to * register interrupt controller to interrupt framefork. */ if (intr_pic_register(dev, xref) != 0) { device_printf(dev, "could not register PIC\n"); goto cleanup; } if (bus_setup_intr(dev, sc->pic_res[1], INTR_TYPE_CLK, mtk_pic_intr, NULL, sc, &sc->pic_intrhand)) { device_printf(dev, "could not setup irq handler\n"); intr_pic_deregister(dev, xref); goto cleanup; } return (0); cleanup: bus_release_resources(dev, mtk_pic_spec, sc->pic_res); return(ENXIO); } static int mtk_pic_intr(void *arg) { struct mtk_pic_softc *sc = arg; struct thread *td; uint32_t i, intr; td = curthread; /* Workaround: do not inflate intr nesting level */ td->td_intr_nesting_level--; #ifdef _notyet_ intr = READ4(sc, MTK_IRQ1STAT); while ((i = fls(intr)) != 0) { i--; intr &= ~(1u << i); if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->pic_dev, "Stray interrupt %u detected\n", i); pic_irq_mask(sc, i); continue; } } KASSERT(i == 0, ("all interrupts handled")); #endif intr = READ4(sc, MTK_IRQ0STAT); while ((i = fls(intr)) != 0) { i--; intr &= ~(1u << i); if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->pic_dev, "Stray interrupt %u detected\n", i); pic_irq_mask(sc, i); continue; } } KASSERT(i == 0, ("all interrupts handled")); td->td_intr_nesting_level++; return (FILTER_HANDLED); } static int mtk_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { #ifdef FDT + struct intr_map_data_fdt *daf; struct mtk_pic_softc *sc; + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; - if (data == NULL || data->type != INTR_MAP_DATA_FDT || - data->fdt.ncells != 1 || data->fdt.cells[0] >= sc->nirqs) + if (daf->ncells != 1 || daf->cells[0] >= sc->nirqs) return (EINVAL); - *isrcp = PIC_INTR_ISRC(sc, data->fdt.cells[0]); + *isrcp = PIC_INTR_ISRC(sc, daf->cells[0]); return (0); #else - return (EINVAL); + return (ENOTSUP); #endif } static void mtk_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mtk_pic_irqsrc *)isrc)->irq; pic_irq_unmask(device_get_softc(dev), irq); } static void mtk_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mtk_pic_irqsrc *)isrc)->irq; pic_irq_mask(device_get_softc(dev), irq); } static void mtk_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_pic_disable_intr(dev, isrc); } static void mtk_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { mtk_pic_enable_intr(dev, isrc); } static void mtk_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { } static device_method_t mtk_pic_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mtk_pic_probe), DEVMETHOD(device_attach, mtk_pic_attach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, mtk_pic_disable_intr), DEVMETHOD(pic_enable_intr, mtk_pic_enable_intr), DEVMETHOD(pic_map_intr, mtk_pic_map_intr), DEVMETHOD(pic_post_filter, mtk_pic_post_filter), DEVMETHOD(pic_post_ithread, mtk_pic_post_ithread), DEVMETHOD(pic_pre_ithread, mtk_pic_pre_ithread), { 0, 0 } }; static driver_t mtk_pic_driver = { "intc", mtk_pic_methods, sizeof(struct mtk_pic_softc), }; static devclass_t mtk_pic_devclass; EARLY_DRIVER_MODULE(intc_v2, simplebus, mtk_pic_driver, mtk_pic_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); Index: user/ngie/detangle-rc/sys/mips/mips/exception.S =================================================================== --- user/ngie/detangle-rc/sys/mips/mips/exception.S (revision 299126) +++ user/ngie/detangle-rc/sys/mips/mips/exception.S (revision 299127) @@ -1,1272 +1,1265 @@ /* $OpenBSD: locore.S,v 1.18 1998/09/15 10:58:53 pefo Exp $ */ /*- * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Digital Equipment Corporation and Ralph Campbell. * * 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * Copyright (C) 1989 Digital Equipment Corporation. * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby granted, * provided that the above copyright notice appears in all copies. * Digital Equipment Corporation makes no representations about the * suitability of this software for any purpose. It is provided "as is" * without express or implied warranty. * * from: Header: /sprite/src/kernel/mach/ds3100.md/RCS/loMem.s, * v 1.1 89/07/11 17:55:04 nelson Exp SPRITE (DECWRL) * from: Header: /sprite/src/kernel/mach/ds3100.md/RCS/machAsm.s, * v 9.2 90/01/29 18:00:39 shirriff Exp SPRITE (DECWRL) * from: Header: /sprite/src/kernel/vm/ds3100.md/vmPmaxAsm.s, * v 1.1 89/07/10 14:27:41 nelson Exp SPRITE (DECWRL) * from: @(#)locore.s 8.5 (Berkeley) 1/4/94 * JNPR: exception.S,v 1.5 2007/01/08 04:58:37 katta * $FreeBSD$ */ /* * Contains code that is the first executed at boot time plus * assembly language support routines. */ #include "opt_ddb.h" #include #include #include #include #include #include #include "assym.s" .set noreorder # Noreorder is default style! #ifdef KDTRACE_HOOKS .data - .globl dtrace_invop_jump_addr - .align 4 - .type dtrace_invop_jump_addr, @object - .size dtrace_invop_jump_addr, 8 -dtrace_invop_jump_addr: - .word 0 - .word 0 .globl dtrace_invop_calltrap_addr .align 4 .type dtrace_invop_calltrap_addr, @object .size dtrace_invop_calltrap_addr, 8 dtrace_invop_calltrap_addr: .word 0 .word 0 .text #endif /* * Reasonable limit */ #define INTRCNT_COUNT 256 /* *---------------------------------------------------------------------------- * * MipsTLBMiss -- * * Vector code for the TLB-miss exception vector 0x80000000. * * This code is copied to the TLB exception vector address to * which the CPU jumps in response to an exception or a TLB miss. * NOTE: This code must be position independent!!! * * */ VECTOR(MipsTLBMiss, unknown) .set push .set noat j MipsDoTLBMiss MFC0 k0, MIPS_COP_0_BAD_VADDR # get the fault address .set pop VECTOR_END(MipsTLBMiss) /* *---------------------------------------------------------------------------- * * MipsDoTLBMiss -- * * This is the real TLB Miss Handler code. * 'segbase' points to the base of the segment table for user processes. * * Don't check for invalid pte's here. We load them as well and * let the processor trap to load the correct value after service. *---------------------------------------------------------------------------- */ .set push .set noat MipsDoTLBMiss: bltz k0, 1f #02: k0<0 -> 1f (kernel fault) PTR_SRL k0, k0, SEGSHIFT - PTRSHIFT #03: k0=seg offset (almost) GET_CPU_PCPU(k1) PTR_L k1, PC_SEGBASE(k1) beqz k1, 2f #05: make sure segbase is not null andi k0, k0, PDEPTRMASK #06: k0=seg offset PTR_ADDU k1, k0, k1 #07: k1=seg entry address PTR_L k1, 0(k1) #08: k1=seg entry MFC0 k0, MIPS_COP_0_BAD_VADDR #09: k0=bad address (again) beq k1, zero, 2f #0a: ==0 -- no page table #ifdef __mips_n64 PTR_SRL k0, PDRSHIFT - PTRSHIFT # k0=VPN andi k0, k0, PDEPTRMASK # k0=pde offset PTR_ADDU k1, k0, k1 # k1=pde entry address PTR_L k1, 0(k1) # k1=pde entry MFC0 k0, MIPS_COP_0_BAD_VADDR # k0=bad address (again) beq k1, zero, 2f # ==0 -- no page table #endif PTR_SRL k0, PAGE_SHIFT - PTESHIFT #0b: k0=VPN (aka va>>10) andi k0, k0, PTE2MASK #0c: k0=page tab offset PTR_ADDU k1, k1, k0 #0d: k1=pte address PTE_L k0, 0(k1) #0e: k0=lo0 pte PTE_L k1, PTESIZE(k1) #0f: k1=lo0 pte CLEAR_PTE_SWBITS(k0) PTE_MTC0 k0, MIPS_COP_0_TLB_LO0 #12: lo0 is loaded COP0_SYNC CLEAR_PTE_SWBITS(k1) PTE_MTC0 k1, MIPS_COP_0_TLB_LO1 #15: lo1 is loaded COP0_SYNC tlbwr #1a: write to tlb HAZARD_DELAY eret #1f: retUrn from exception 1: j MipsTLBMissException #20: kernel exception nop #21: branch delay slot 2: j SlowFault #22: no page table present nop #23: branch delay slot .set pop /* * This code is copied to the general exception vector address to * handle all execptions except RESET and TLBMiss. * NOTE: This code must be position independent!!! */ VECTOR(MipsException, unknown) /* * Find out what mode we came from and jump to the proper handler. * * Note: at turned off here because we cannot trash the at register * in this exception code. Only k0 and k1 may be modified before * we save registers. This is true of all functions called through * the pointer magic: Mips{User,Kern}Intr, Mips{User,Kern}GenException * and MipsTLBInvalidException */ .set noat mfc0 k0, MIPS_COP_0_STATUS # Get the status register mfc0 k1, MIPS_COP_0_CAUSE # Get the cause register value. and k0, k0, MIPS_SR_KSU_USER # test for user mode # sneaky but the bits are # with us........ sll k0, k0, 3 # shift user bit for cause index and k1, k1, MIPS_CR_EXC_CODE # Mask out the cause bits. or k1, k1, k0 # change index to user table #if defined(__mips_n64) PTR_SLL k1, k1, 1 # shift to get 8-byte offset #endif 1: PTR_LA k0, _C_LABEL(machExceptionTable) # get base of the jump table PTR_ADDU k0, k0, k1 # Get the address of the # function entry. Note that # the cause is already # shifted left by 2 bits so # we dont have to shift. PTR_L k0, 0(k0) # Get the function address nop j k0 # Jump to the function. nop .set at VECTOR_END(MipsException) /* * We couldn't find a TLB entry. * Find out what mode we came from and call the appropriate handler. */ SlowFault: .set noat mfc0 k0, MIPS_COP_0_STATUS nop and k0, k0, MIPS_SR_KSU_USER bne k0, zero, _C_LABEL(MipsUserGenException) nop .set at /* * Fall though ... */ /*---------------------------------------------------------------------------- * * MipsKernGenException -- * * Handle an exception from kernel mode. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------------- */ #define SAVE_REG(reg, offs, base) \ REG_S reg, CALLFRAME_SIZ + (SZREG * offs) (base) #if defined(CPU_CNMIPS) #define CLEAR_STATUS \ mfc0 a0, MIPS_COP_0_STATUS ;\ li a2, (MIPS_SR_KX | MIPS_SR_SX | MIPS_SR_UX) ; \ or a0, a0, a2 ; \ li a2, ~(MIPS_SR_INT_IE | MIPS_SR_EXL | MIPS_SR_KSU_USER) ; \ and a0, a0, a2 ; \ mtc0 a0, MIPS_COP_0_STATUS ; \ ITLBNOPFIX #elif defined(CPU_RMI) || defined(CPU_NLM) #define CLEAR_STATUS \ mfc0 a0, MIPS_COP_0_STATUS ;\ li a2, (MIPS_SR_KX | MIPS_SR_UX | MIPS_SR_COP_2_BIT) ; \ or a0, a0, a2 ; \ li a2, ~(MIPS_SR_INT_IE | MIPS_SR_EXL | MIPS_SR_KSU_USER) ; \ and a0, a0, a2 ; \ mtc0 a0, MIPS_COP_0_STATUS ; \ ITLBNOPFIX #else #define CLEAR_STATUS \ mfc0 a0, MIPS_COP_0_STATUS ;\ li a2, ~(MIPS_SR_INT_IE | MIPS_SR_EXL | MIPS_SR_KSU_USER) ; \ and a0, a0, a2 ; \ mtc0 a0, MIPS_COP_0_STATUS ; \ ITLBNOPFIX #endif /* * Save CPU and CP0 register state. * * This is straightforward except for saving the exception program * counter. The ddb backtrace code looks for the first instruction * matching the form "sw ra, (off)sp" to figure out the address of the * calling function. So we must make sure that we save the exception * PC by staging it through 'ra' as opposed to any other register. */ #define SAVE_CPU \ SAVE_REG(AT, AST, sp) ;\ .set at ; \ SAVE_REG(v0, V0, sp) ;\ SAVE_REG(v1, V1, sp) ;\ SAVE_REG(a0, A0, sp) ;\ SAVE_REG(a1, A1, sp) ;\ SAVE_REG(a2, A2, sp) ;\ SAVE_REG(a3, A3, sp) ;\ SAVE_REG(t0, T0, sp) ;\ SAVE_REG(t1, T1, sp) ;\ SAVE_REG(t2, T2, sp) ;\ SAVE_REG(t3, T3, sp) ;\ SAVE_REG(ta0, TA0, sp) ;\ SAVE_REG(ta1, TA1, sp) ;\ SAVE_REG(ta2, TA2, sp) ;\ SAVE_REG(ta3, TA3, sp) ;\ SAVE_REG(t8, T8, sp) ;\ SAVE_REG(t9, T9, sp) ;\ SAVE_REG(gp, GP, sp) ;\ SAVE_REG(s0, S0, sp) ;\ SAVE_REG(s1, S1, sp) ;\ SAVE_REG(s2, S2, sp) ;\ SAVE_REG(s3, S3, sp) ;\ SAVE_REG(s4, S4, sp) ;\ SAVE_REG(s5, S5, sp) ;\ SAVE_REG(s6, S6, sp) ;\ SAVE_REG(s7, S7, sp) ;\ SAVE_REG(s8, S8, sp) ;\ mflo v0 ;\ mfhi v1 ;\ mfc0 a0, MIPS_COP_0_STATUS ;\ mfc0 a1, MIPS_COP_0_CAUSE ;\ MFC0 a2, MIPS_COP_0_BAD_VADDR;\ MFC0 a3, MIPS_COP_0_EXC_PC ;\ SAVE_REG(v0, MULLO, sp) ;\ SAVE_REG(v1, MULHI, sp) ;\ SAVE_REG(a0, SR, sp) ;\ SAVE_REG(a1, CAUSE, sp) ;\ SAVE_REG(a2, BADVADDR, sp) ;\ move t0, ra ;\ move ra, a3 ;\ SAVE_REG(ra, PC, sp) ;\ move ra, t0 ;\ SAVE_REG(ra, RA, sp) ;\ PTR_ADDU v0, sp, KERN_EXC_FRAME_SIZE ;\ SAVE_REG(v0, SP, sp) ;\ CLEAR_STATUS ;\ PTR_ADDU a0, sp, CALLFRAME_SIZ ;\ ITLBNOPFIX #define RESTORE_REG(reg, offs, base) \ REG_L reg, CALLFRAME_SIZ + (SZREG * offs) (base) #define RESTORE_CPU \ CLEAR_STATUS ;\ RESTORE_REG(k0, SR, sp) ;\ RESTORE_REG(t0, MULLO, sp) ;\ RESTORE_REG(t1, MULHI, sp) ;\ mtlo t0 ;\ mthi t1 ;\ MTC0 v0, MIPS_COP_0_EXC_PC ;\ .set noat ;\ RESTORE_REG(AT, AST, sp) ;\ RESTORE_REG(v0, V0, sp) ;\ RESTORE_REG(v1, V1, sp) ;\ RESTORE_REG(a0, A0, sp) ;\ RESTORE_REG(a1, A1, sp) ;\ RESTORE_REG(a2, A2, sp) ;\ RESTORE_REG(a3, A3, sp) ;\ RESTORE_REG(t0, T0, sp) ;\ RESTORE_REG(t1, T1, sp) ;\ RESTORE_REG(t2, T2, sp) ;\ RESTORE_REG(t3, T3, sp) ;\ RESTORE_REG(ta0, TA0, sp) ;\ RESTORE_REG(ta1, TA1, sp) ;\ RESTORE_REG(ta2, TA2, sp) ;\ RESTORE_REG(ta3, TA3, sp) ;\ RESTORE_REG(t8, T8, sp) ;\ RESTORE_REG(t9, T9, sp) ;\ RESTORE_REG(s0, S0, sp) ;\ RESTORE_REG(s1, S1, sp) ;\ RESTORE_REG(s2, S2, sp) ;\ RESTORE_REG(s3, S3, sp) ;\ RESTORE_REG(s4, S4, sp) ;\ RESTORE_REG(s5, S5, sp) ;\ RESTORE_REG(s6, S6, sp) ;\ RESTORE_REG(s7, S7, sp) ;\ RESTORE_REG(s8, S8, sp) ;\ RESTORE_REG(gp, GP, sp) ;\ RESTORE_REG(ra, RA, sp) ;\ PTR_ADDU sp, sp, KERN_EXC_FRAME_SIZE;\ mtc0 k0, MIPS_COP_0_STATUS /* * The kernel exception stack contains 18 saved general registers, * the status register and the multiply lo and high registers. * In addition, we set this up for linkage conventions. */ #define KERN_REG_SIZE (NUMSAVEREGS * SZREG) #define KERN_EXC_FRAME_SIZE (CALLFRAME_SIZ + KERN_REG_SIZE + 16) NESTED_NOPROFILE(MipsKernGenException, KERN_EXC_FRAME_SIZE, ra) .set noat PTR_SUBU sp, sp, KERN_EXC_FRAME_SIZE .mask 0x80000000, (CALLFRAME_RA - KERN_EXC_FRAME_SIZE) /* * Save CPU state, building 'frame'. */ SAVE_CPU /* * Call the exception handler. a0 points at the saved frame. */ PTR_LA gp, _C_LABEL(_gp) PTR_LA k0, _C_LABEL(trap) jalr k0 REG_S a3, CALLFRAME_RA + KERN_REG_SIZE(sp) # for debugging /* * Update interrupt and CPU mask in saved status register * Some of interrupts could be disabled by * intr filters if interrupts are enabled later * in trap handler */ mfc0 a0, MIPS_COP_0_STATUS and a0, a0, (MIPS_SR_INT_MASK|MIPS_SR_COP_USABILITY) RESTORE_REG(a1, SR, sp) and a1, a1, ~(MIPS_SR_INT_MASK|MIPS_SR_COP_USABILITY) or a1, a1, a0 SAVE_REG(a1, SR, sp) RESTORE_CPU # v0 contains the return address. sync eret .set at END(MipsKernGenException) /*---------------------------------------------------------------------------- * * MipsUserGenException -- * * Handle an exception from user mode. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------------- */ NESTED_NOPROFILE(MipsUserGenException, CALLFRAME_SIZ, ra) .set noat .mask 0x80000000, (CALLFRAME_RA - CALLFRAME_SIZ) /* * Save all of the registers except for the kernel temporaries in u.u_pcb. */ GET_CPU_PCPU(k1) PTR_L k1, PC_CURPCB(k1) SAVE_U_PCB_REG(AT, AST, k1) .set at SAVE_U_PCB_REG(v0, V0, k1) SAVE_U_PCB_REG(v1, V1, k1) SAVE_U_PCB_REG(a0, A0, k1) mflo v0 SAVE_U_PCB_REG(a1, A1, k1) SAVE_U_PCB_REG(a2, A2, k1) SAVE_U_PCB_REG(a3, A3, k1) SAVE_U_PCB_REG(t0, T0, k1) mfhi v1 SAVE_U_PCB_REG(t1, T1, k1) SAVE_U_PCB_REG(t2, T2, k1) SAVE_U_PCB_REG(t3, T3, k1) SAVE_U_PCB_REG(ta0, TA0, k1) mfc0 a0, MIPS_COP_0_STATUS # First arg is the status reg. SAVE_U_PCB_REG(ta1, TA1, k1) SAVE_U_PCB_REG(ta2, TA2, k1) SAVE_U_PCB_REG(ta3, TA3, k1) SAVE_U_PCB_REG(s0, S0, k1) mfc0 a1, MIPS_COP_0_CAUSE # Second arg is the cause reg. SAVE_U_PCB_REG(s1, S1, k1) SAVE_U_PCB_REG(s2, S2, k1) SAVE_U_PCB_REG(s3, S3, k1) SAVE_U_PCB_REG(s4, S4, k1) MFC0 a2, MIPS_COP_0_BAD_VADDR # Third arg is the fault addr SAVE_U_PCB_REG(s5, S5, k1) SAVE_U_PCB_REG(s6, S6, k1) SAVE_U_PCB_REG(s7, S7, k1) SAVE_U_PCB_REG(t8, T8, k1) MFC0 a3, MIPS_COP_0_EXC_PC # Fourth arg is the pc. SAVE_U_PCB_REG(t9, T9, k1) SAVE_U_PCB_REG(gp, GP, k1) SAVE_U_PCB_REG(sp, SP, k1) SAVE_U_PCB_REG(s8, S8, k1) PTR_SUBU sp, k1, CALLFRAME_SIZ # switch to kernel SP SAVE_U_PCB_REG(ra, RA, k1) SAVE_U_PCB_REG(v0, MULLO, k1) SAVE_U_PCB_REG(v1, MULHI, k1) SAVE_U_PCB_REG(a0, SR, k1) SAVE_U_PCB_REG(a1, CAUSE, k1) SAVE_U_PCB_REG(a2, BADVADDR, k1) SAVE_U_PCB_REG(a3, PC, k1) REG_S a3, CALLFRAME_RA(sp) # for debugging PTR_LA gp, _C_LABEL(_gp) # switch to kernel GP # Turn off fpu and enter kernel mode and t0, a0, ~(MIPS_SR_COP_1_BIT | MIPS_SR_EXL | MIPS_SR_KSU_MASK | MIPS_SR_INT_IE) #if defined(CPU_CNMIPS) and t0, t0, ~(MIPS_SR_COP_2_BIT) or t0, t0, (MIPS_SR_KX | MIPS_SR_SX | MIPS_SR_UX | MIPS_SR_PX) #elif defined(CPU_RMI) || defined(CPU_NLM) or t0, t0, (MIPS_SR_KX | MIPS_SR_UX | MIPS_SR_COP_2_BIT) #endif mtc0 t0, MIPS_COP_0_STATUS PTR_ADDU a0, k1, U_PCB_REGS ITLBNOPFIX /* * Call the exception handler. */ PTR_LA k0, _C_LABEL(trap) jalr k0 nop /* * Restore user registers and return. * First disable interrupts and set exeption level. */ DO_AST CLEAR_STATUS /* * The use of k1 for storing the PCB pointer must be done only * after interrupts are disabled. Otherwise it will get overwritten * by the interrupt code. */ GET_CPU_PCPU(k1) PTR_L k1, PC_CURPCB(k1) /* * Update interrupt mask in saved status register * Some of interrupts could be enabled by ithread * scheduled by ast() */ mfc0 a0, MIPS_COP_0_STATUS and a0, a0, MIPS_SR_INT_MASK RESTORE_U_PCB_REG(a1, SR, k1) and a1, a1, ~MIPS_SR_INT_MASK or a1, a1, a0 SAVE_U_PCB_REG(a1, SR, k1) RESTORE_U_PCB_REG(t0, MULLO, k1) RESTORE_U_PCB_REG(t1, MULHI, k1) mtlo t0 mthi t1 RESTORE_U_PCB_REG(a0, PC, k1) RESTORE_U_PCB_REG(v0, V0, k1) MTC0 a0, MIPS_COP_0_EXC_PC # set return address RESTORE_U_PCB_REG(v1, V1, k1) RESTORE_U_PCB_REG(a0, A0, k1) RESTORE_U_PCB_REG(a1, A1, k1) RESTORE_U_PCB_REG(a2, A2, k1) RESTORE_U_PCB_REG(a3, A3, k1) RESTORE_U_PCB_REG(t0, T0, k1) RESTORE_U_PCB_REG(t1, T1, k1) RESTORE_U_PCB_REG(t2, T2, k1) RESTORE_U_PCB_REG(t3, T3, k1) RESTORE_U_PCB_REG(ta0, TA0, k1) RESTORE_U_PCB_REG(ta1, TA1, k1) RESTORE_U_PCB_REG(ta2, TA2, k1) RESTORE_U_PCB_REG(ta3, TA3, k1) RESTORE_U_PCB_REG(s0, S0, k1) RESTORE_U_PCB_REG(s1, S1, k1) RESTORE_U_PCB_REG(s2, S2, k1) RESTORE_U_PCB_REG(s3, S3, k1) RESTORE_U_PCB_REG(s4, S4, k1) RESTORE_U_PCB_REG(s5, S5, k1) RESTORE_U_PCB_REG(s6, S6, k1) RESTORE_U_PCB_REG(s7, S7, k1) RESTORE_U_PCB_REG(t8, T8, k1) RESTORE_U_PCB_REG(t9, T9, k1) RESTORE_U_PCB_REG(gp, GP, k1) RESTORE_U_PCB_REG(sp, SP, k1) RESTORE_U_PCB_REG(k0, SR, k1) RESTORE_U_PCB_REG(s8, S8, k1) RESTORE_U_PCB_REG(ra, RA, k1) .set noat RESTORE_U_PCB_REG(AT, AST, k1) mtc0 k0, MIPS_COP_0_STATUS # still exception level ITLBNOPFIX sync eret .set at END(MipsUserGenException) .set push .set noat NESTED(mips_wait, CALLFRAME_SIZ, ra) PTR_SUBU sp, sp, CALLFRAME_SIZ .mask 0x80000000, (CALLFRAME_RA - CALLFRAME_SIZ) REG_S ra, CALLFRAME_RA(sp) # save RA mfc0 t0, MIPS_COP_0_STATUS xori t1, t0, MIPS_SR_INT_IE mtc0 t1, MIPS_COP_0_STATUS COP0_SYNC jal sched_runnable nop REG_L ra, CALLFRAME_RA(sp) mfc0 t0, MIPS_COP_0_STATUS ori t1, t0, MIPS_SR_INT_IE .align 4 GLOBAL(MipsWaitStart) # this is 16 byte aligned mtc0 t1, MIPS_COP_0_STATUS bnez v0, MipsWaitEnd nop wait GLOBAL(MipsWaitEnd) # MipsWaitStart + 16 jr ra PTR_ADDU sp, sp, CALLFRAME_SIZ END(mips_wait) .set pop /*---------------------------------------------------------------------------- * * MipsKernIntr -- * * Handle an interrupt from kernel mode. * Interrupts use the standard kernel stack. * switch_exit sets up a kernel stack after exit so interrupts won't fail. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------------- */ NESTED_NOPROFILE(MipsKernIntr, KERN_EXC_FRAME_SIZE, ra) .set noat PTR_SUBU sp, sp, KERN_EXC_FRAME_SIZE .mask 0x80000000, (CALLFRAME_RA - KERN_EXC_FRAME_SIZE) /* * Check for getting interrupts just before wait */ MFC0 k0, MIPS_COP_0_EXC_PC ori k0, 0xf xori k0, 0xf # 16 byte align PTR_LA k1, MipsWaitStart bne k0, k1, 1f nop PTR_ADDU k1, 16 # skip over wait MTC0 k1, MIPS_COP_0_EXC_PC 1: /* * Save CPU state, building 'frame'. */ SAVE_CPU /* * Call the interrupt handler. a0 points at the saved frame. */ PTR_LA gp, _C_LABEL(_gp) #ifdef INTRNG PTR_LA k0, _C_LABEL(intr_irq_handler) #else PTR_LA k0, _C_LABEL(cpu_intr) #endif jalr k0 REG_S a3, CALLFRAME_RA + KERN_REG_SIZE(sp) # for debugging /* * Update interrupt and CPU mask in saved status register * Some of interrupts could be disabled by * intr filters if interrupts are enabled later * in trap handler */ mfc0 a0, MIPS_COP_0_STATUS and a0, a0, (MIPS_SR_INT_MASK|MIPS_SR_COP_USABILITY) RESTORE_REG(a1, SR, sp) and a1, a1, ~(MIPS_SR_INT_MASK|MIPS_SR_COP_USABILITY) or a1, a1, a0 SAVE_REG(a1, SR, sp) REG_L v0, CALLFRAME_RA + KERN_REG_SIZE(sp) RESTORE_CPU # v0 contains the return address. sync eret .set at END(MipsKernIntr) /*---------------------------------------------------------------------------- * * MipsUserIntr -- * * Handle an interrupt from user mode. * Note: we save minimal state in the u.u_pcb struct and use the standard * kernel stack since there has to be a u page if we came from user mode. * If there is a pending software interrupt, then save the remaining state * and call softintr(). This is all because if we call switch() inside * interrupt(), not all the user registers have been saved in u.u_pcb. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------------- */ NESTED_NOPROFILE(MipsUserIntr, CALLFRAME_SIZ, ra) .set noat .mask 0x80000000, (CALLFRAME_RA - CALLFRAME_SIZ) /* * Save the relevant user registers into the u.u_pcb struct. * We don't need to save s0 - s8 because the compiler does it for us. */ GET_CPU_PCPU(k1) PTR_L k1, PC_CURPCB(k1) SAVE_U_PCB_REG(AT, AST, k1) .set at SAVE_U_PCB_REG(v0, V0, k1) SAVE_U_PCB_REG(v1, V1, k1) SAVE_U_PCB_REG(a0, A0, k1) SAVE_U_PCB_REG(a1, A1, k1) SAVE_U_PCB_REG(a2, A2, k1) SAVE_U_PCB_REG(a3, A3, k1) SAVE_U_PCB_REG(t0, T0, k1) SAVE_U_PCB_REG(t1, T1, k1) SAVE_U_PCB_REG(t2, T2, k1) SAVE_U_PCB_REG(t3, T3, k1) SAVE_U_PCB_REG(ta0, TA0, k1) SAVE_U_PCB_REG(ta1, TA1, k1) SAVE_U_PCB_REG(ta2, TA2, k1) SAVE_U_PCB_REG(ta3, TA3, k1) SAVE_U_PCB_REG(t8, T8, k1) SAVE_U_PCB_REG(t9, T9, k1) SAVE_U_PCB_REG(gp, GP, k1) SAVE_U_PCB_REG(sp, SP, k1) SAVE_U_PCB_REG(ra, RA, k1) /* * save remaining user state in u.u_pcb. */ SAVE_U_PCB_REG(s0, S0, k1) SAVE_U_PCB_REG(s1, S1, k1) SAVE_U_PCB_REG(s2, S2, k1) SAVE_U_PCB_REG(s3, S3, k1) SAVE_U_PCB_REG(s4, S4, k1) SAVE_U_PCB_REG(s5, S5, k1) SAVE_U_PCB_REG(s6, S6, k1) SAVE_U_PCB_REG(s7, S7, k1) SAVE_U_PCB_REG(s8, S8, k1) mflo v0 # get lo/hi late to avoid stall mfhi v1 mfc0 a0, MIPS_COP_0_STATUS mfc0 a1, MIPS_COP_0_CAUSE MFC0 a3, MIPS_COP_0_EXC_PC SAVE_U_PCB_REG(v0, MULLO, k1) SAVE_U_PCB_REG(v1, MULHI, k1) SAVE_U_PCB_REG(a0, SR, k1) SAVE_U_PCB_REG(a1, CAUSE, k1) SAVE_U_PCB_REG(a3, PC, k1) # PC in a3, note used later! PTR_SUBU sp, k1, CALLFRAME_SIZ # switch to kernel SP PTR_LA gp, _C_LABEL(_gp) # switch to kernel GP # Turn off fpu, disable interrupts, set kernel mode kernel mode, clear exception level. and t0, a0, ~(MIPS_SR_COP_1_BIT | MIPS_SR_EXL | MIPS_SR_INT_IE | MIPS_SR_KSU_MASK) #ifdef CPU_CNMIPS and t0, t0, ~(MIPS_SR_COP_2_BIT) or t0, t0, (MIPS_SR_KX | MIPS_SR_SX | MIPS_SR_UX | MIPS_SR_PX) #elif defined(CPU_RMI) || defined(CPU_NLM) or t0, t0, (MIPS_SR_KX | MIPS_SR_UX | MIPS_SR_COP_2_BIT) #endif mtc0 t0, MIPS_COP_0_STATUS ITLBNOPFIX PTR_ADDU a0, k1, U_PCB_REGS /* * Call the interrupt handler. */ #ifdef INTRNG PTR_LA k0, _C_LABEL(intr_irq_handler) #else PTR_LA k0, _C_LABEL(cpu_intr) #endif jalr k0 REG_S a3, CALLFRAME_RA(sp) # for debugging /* * Enable interrupts before doing ast(). * * On SMP kernels the AST processing might trigger IPI to other processors. * If that processor is also doing AST processing with interrupts disabled * then we may deadlock. */ mfc0 a0, MIPS_COP_0_STATUS or a0, a0, MIPS_SR_INT_IE mtc0 a0, MIPS_COP_0_STATUS ITLBNOPFIX /* * DO_AST enabled interrupts */ DO_AST /* * Restore user registers and return. */ CLEAR_STATUS GET_CPU_PCPU(k1) PTR_L k1, PC_CURPCB(k1) /* * Update interrupt mask in saved status register * Some of interrupts could be disabled by * intr filters */ mfc0 a0, MIPS_COP_0_STATUS and a0, a0, MIPS_SR_INT_MASK RESTORE_U_PCB_REG(a1, SR, k1) and a1, a1, ~MIPS_SR_INT_MASK or a1, a1, a0 SAVE_U_PCB_REG(a1, SR, k1) RESTORE_U_PCB_REG(s0, S0, k1) RESTORE_U_PCB_REG(s1, S1, k1) RESTORE_U_PCB_REG(s2, S2, k1) RESTORE_U_PCB_REG(s3, S3, k1) RESTORE_U_PCB_REG(s4, S4, k1) RESTORE_U_PCB_REG(s5, S5, k1) RESTORE_U_PCB_REG(s6, S6, k1) RESTORE_U_PCB_REG(s7, S7, k1) RESTORE_U_PCB_REG(s8, S8, k1) RESTORE_U_PCB_REG(t0, MULLO, k1) RESTORE_U_PCB_REG(t1, MULHI, k1) RESTORE_U_PCB_REG(t2, PC, k1) mtlo t0 mthi t1 MTC0 t2, MIPS_COP_0_EXC_PC # set return address RESTORE_U_PCB_REG(v0, V0, k1) RESTORE_U_PCB_REG(v1, V1, k1) RESTORE_U_PCB_REG(a0, A0, k1) RESTORE_U_PCB_REG(a1, A1, k1) RESTORE_U_PCB_REG(a2, A2, k1) RESTORE_U_PCB_REG(a3, A3, k1) RESTORE_U_PCB_REG(t0, T0, k1) RESTORE_U_PCB_REG(t1, T1, k1) RESTORE_U_PCB_REG(t2, T2, k1) RESTORE_U_PCB_REG(t3, T3, k1) RESTORE_U_PCB_REG(ta0, TA0, k1) RESTORE_U_PCB_REG(ta1, TA1, k1) RESTORE_U_PCB_REG(ta2, TA2, k1) RESTORE_U_PCB_REG(ta3, TA3, k1) RESTORE_U_PCB_REG(t8, T8, k1) RESTORE_U_PCB_REG(t9, T9, k1) RESTORE_U_PCB_REG(gp, GP, k1) RESTORE_U_PCB_REG(k0, SR, k1) RESTORE_U_PCB_REG(sp, SP, k1) RESTORE_U_PCB_REG(ra, RA, k1) .set noat RESTORE_U_PCB_REG(AT, AST, k1) mtc0 k0, MIPS_COP_0_STATUS # SR with EXL set. ITLBNOPFIX sync eret .set at END(MipsUserIntr) LEAF_NOPROFILE(MipsTLBInvalidException) .set push .set noat .set noreorder MFC0 k0, MIPS_COP_0_BAD_VADDR PTR_LI k1, VM_MAXUSER_ADDRESS sltu k1, k0, k1 bnez k1, 1f nop /* Kernel address. */ lui k1, %hi(kernel_segmap) # k1=hi of segbase b 2f PTR_L k1, %lo(kernel_segmap)(k1) # k1=segment tab base 1: /* User address. */ GET_CPU_PCPU(k1) PTR_L k1, PC_SEGBASE(k1) 2: /* Validate page directory pointer. */ beqz k1, 3f nop PTR_SRL k0, SEGSHIFT - PTRSHIFT # k0=seg offset (almost) beq k1, zero, MipsKernGenException # ==0 -- no seg tab andi k0, k0, PDEPTRMASK #06: k0=seg offset PTR_ADDU k1, k0, k1 # k1=seg entry address PTR_L k1, 0(k1) # k1=seg entry /* Validate page table pointer. */ beqz k1, 3f nop #ifdef __mips_n64 MFC0 k0, MIPS_COP_0_BAD_VADDR PTR_SRL k0, PDRSHIFT - PTRSHIFT # k0=pde offset (almost) beq k1, zero, MipsKernGenException # ==0 -- no pde tab andi k0, k0, PDEPTRMASK # k0=pde offset PTR_ADDU k1, k0, k1 # k1=pde entry address PTR_L k1, 0(k1) # k1=pde entry /* Validate pde table pointer. */ beqz k1, 3f nop #endif MFC0 k0, MIPS_COP_0_BAD_VADDR # k0=bad address (again) PTR_SRL k0, PAGE_SHIFT - PTESHIFT # k0=VPN andi k0, k0, PTEMASK # k0=page tab offset PTR_ADDU k1, k1, k0 # k1=pte address PTE_L k0, 0(k1) # k0=this PTE /* Validate page table entry. */ andi k0, PTE_V beqz k0, 3f nop /* Check whether this is an even or odd entry. */ andi k0, k1, PTESIZE bnez k0, odd_page nop PTE_L k0, 0(k1) PTE_L k1, PTESIZE(k1) CLEAR_PTE_SWBITS(k0) PTE_MTC0 k0, MIPS_COP_0_TLB_LO0 COP0_SYNC CLEAR_PTE_SWBITS(k1) PTE_MTC0 k1, MIPS_COP_0_TLB_LO1 COP0_SYNC b tlb_insert_entry nop odd_page: PTE_L k0, -PTESIZE(k1) PTE_L k1, 0(k1) CLEAR_PTE_SWBITS(k0) PTE_MTC0 k0, MIPS_COP_0_TLB_LO0 COP0_SYNC CLEAR_PTE_SWBITS(k1) PTE_MTC0 k1, MIPS_COP_0_TLB_LO1 COP0_SYNC tlb_insert_entry: tlbp HAZARD_DELAY mfc0 k0, MIPS_COP_0_TLB_INDEX bltz k0, tlb_insert_random nop tlbwi eret ssnop tlb_insert_random: tlbwr eret ssnop 3: /* * Branch to the comprehensive exception processing. */ mfc0 k1, MIPS_COP_0_STATUS andi k1, k1, MIPS_SR_KSU_USER bnez k1, _C_LABEL(MipsUserGenException) nop /* * Check for kernel stack overflow. */ GET_CPU_PCPU(k1) PTR_L k0, PC_CURTHREAD(k1) PTR_L k0, TD_KSTACK(k0) sltu k0, k0, sp bnez k0, _C_LABEL(MipsKernGenException) nop /* * Kernel stack overflow. * * Move to a valid stack before we call panic. We use the boot stack * for this purpose. */ GET_CPU_PCPU(k1) lw k1, PC_CPUID(k1) sll k1, k1, PAGE_SHIFT + 1 PTR_LA k0, _C_LABEL(pcpu_space) PTR_ADDU k0, PAGE_SIZE * 2 PTR_ADDU k0, k0, k1 /* * Stash the original value of 'sp' so we can update trapframe later. * We assume that SAVE_CPU does not trash 'k1'. */ move k1, sp move sp, k0 PTR_SUBU sp, sp, KERN_EXC_FRAME_SIZE move k0, ra move ra, zero REG_S ra, CALLFRAME_RA(sp) /* stop the ddb backtrace right here */ REG_S zero, CALLFRAME_SP(sp) move ra, k0 SAVE_CPU /* * Now restore the value of 'sp' at the time of the tlb exception in * the trapframe. */ SAVE_REG(k1, SP, sp) /* * Squelch any more overflow checks by setting the stack base to 0. */ GET_CPU_PCPU(k1) PTR_L k0, PC_CURTHREAD(k1) PTR_S zero, TD_KSTACK(k0) move a1, a0 PANIC("kernel stack overflow - trapframe at %p") /* * This nop is necessary so that the 'ra' remains within the bounds * of this handler. Otherwise the ddb backtrace code will think that * the panic() was called from MipsTLBMissException. */ nop .set pop END(MipsTLBInvalidException) /*---------------------------------------------------------------------------- * * MipsTLBMissException -- * * Handle a TLB miss exception from kernel mode in kernel space. * The BaddVAddr, Context, and EntryHi registers contain the failed * virtual address. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------------- */ LEAF_NOPROFILE(MipsTLBMissException) .set noat MFC0 k0, MIPS_COP_0_BAD_VADDR # k0=bad address PTR_LI k1, VM_MAX_KERNEL_ADDRESS # check fault address against sltu k1, k1, k0 # upper bound of kernel_segmap bnez k1, MipsKernGenException # out of bound lui k1, %hi(kernel_segmap) # k1=hi of segbase PTR_SRL k0, SEGSHIFT - PTRSHIFT # k0=seg offset (almost) PTR_L k1, %lo(kernel_segmap)(k1) # k1=segment tab base beq k1, zero, MipsKernGenException # ==0 -- no seg tab andi k0, k0, PDEPTRMASK #06: k0=seg offset PTR_ADDU k1, k0, k1 # k1=seg entry address PTR_L k1, 0(k1) # k1=seg entry MFC0 k0, MIPS_COP_0_BAD_VADDR # k0=bad address (again) beq k1, zero, MipsKernGenException # ==0 -- no page table #ifdef __mips_n64 PTR_SRL k0, PDRSHIFT - PTRSHIFT # k0=VPN andi k0, k0, PDEPTRMASK # k0=pde offset PTR_ADDU k1, k0, k1 # k1=pde entry address PTR_L k1, 0(k1) # k1=pde entry MFC0 k0, MIPS_COP_0_BAD_VADDR # k0=bad address (again) beq k1, zero, MipsKernGenException # ==0 -- no page table #endif PTR_SRL k0, PAGE_SHIFT - PTESHIFT # k0=VPN andi k0, k0, PTE2MASK # k0=page tab offset PTR_ADDU k1, k1, k0 # k1=pte address PTE_L k0, 0(k1) # k0=lo0 pte PTE_L k1, PTESIZE(k1) # k1=lo1 pte CLEAR_PTE_SWBITS(k0) PTE_MTC0 k0, MIPS_COP_0_TLB_LO0 # lo0 is loaded COP0_SYNC CLEAR_PTE_SWBITS(k1) PTE_MTC0 k1, MIPS_COP_0_TLB_LO1 # lo1 is loaded COP0_SYNC tlbwr # write to tlb HAZARD_DELAY eret # return from exception .set at END(MipsTLBMissException) /*---------------------------------------------------------------------------- * * MipsFPTrap -- * * Handle a floating point Trap. * * MipsFPTrap(statusReg, causeReg, pc) * unsigned statusReg; * unsigned causeReg; * unsigned pc; * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------------- */ NESTED(MipsFPTrap, CALLFRAME_SIZ, ra) PTR_SUBU sp, sp, CALLFRAME_SIZ mfc0 t0, MIPS_COP_0_STATUS REG_S ra, CALLFRAME_RA(sp) .mask 0x80000000, (CALLFRAME_RA - CALLFRAME_SIZ) or t1, t0, MIPS_SR_COP_1_BIT mtc0 t1, MIPS_COP_0_STATUS ITLBNOPFIX cfc1 t1, MIPS_FPU_CSR # stall til FP done cfc1 t1, MIPS_FPU_CSR # now get status nop sll t2, t1, (31 - 17) # unimplemented operation? bgez t2, 3f # no, normal trap nop /* * We got an unimplemented operation trap so * fetch the instruction, compute the next PC and emulate the instruction. */ bgez a1, 1f # Check the branch delay bit. nop /* * The instruction is in the branch delay slot so the branch will have to * be emulated to get the resulting PC. */ PTR_S a2, CALLFRAME_SIZ + 8(sp) GET_CPU_PCPU(a0) #mips64 unsafe? PTR_L a0, PC_CURPCB(a0) PTR_ADDU a0, a0, U_PCB_REGS # first arg is ptr to CPU registers move a1, a2 # second arg is instruction PC move a2, t1 # third arg is floating point CSR PTR_LA t3, _C_LABEL(MipsEmulateBranch) # compute PC after branch jalr t3 # compute PC after branch move a3, zero # fourth arg is FALSE /* * Now load the floating-point instruction in the branch delay slot * to be emulated. */ PTR_L a2, CALLFRAME_SIZ + 8(sp) # restore EXC pc b 2f lw a0, 4(a2) # a0 = coproc instruction /* * This is not in the branch delay slot so calculate the resulting * PC (epc + 4) into v0 and continue to MipsEmulateFP(). */ 1: lw a0, 0(a2) # a0 = coproc instruction #xxx mips64 unsafe? PTR_ADDU v0, a2, 4 # v0 = next pc 2: GET_CPU_PCPU(t2) PTR_L t2, PC_CURPCB(t2) SAVE_U_PCB_REG(v0, PC, t2) # save new pc /* * Check to see if the instruction to be emulated is a floating-point * instruction. */ srl a3, a0, MIPS_OPCODE_SHIFT beq a3, MIPS_OPCODE_C1, 4f # this should never fail nop /* * Send a floating point exception signal to the current process. */ 3: GET_CPU_PCPU(a0) PTR_L a0, PC_CURTHREAD(a0) # get current thread cfc1 a2, MIPS_FPU_CSR # code = FP execptions ctc1 zero, MIPS_FPU_CSR # Clear exceptions PTR_LA t3, _C_LABEL(trapsignal) jalr t3 li a1, SIGFPE b FPReturn nop /* * Finally, we can call MipsEmulateFP() where a0 is the instruction to emulate. */ 4: PTR_LA t3, _C_LABEL(MipsEmulateFP) jalr t3 nop /* * Turn off the floating point coprocessor and return. */ FPReturn: mfc0 t0, MIPS_COP_0_STATUS PTR_L ra, CALLFRAME_RA(sp) and t0, t0, ~MIPS_SR_COP_1_BIT mtc0 t0, MIPS_COP_0_STATUS ITLBNOPFIX j ra PTR_ADDU sp, sp, CALLFRAME_SIZ END(MipsFPTrap) #ifndef INTRNG /* * Interrupt counters for vmstat. */ .data .globl intrcnt .globl sintrcnt .globl intrnames .globl sintrnames intrnames: .space INTRCNT_COUNT * (MAXCOMLEN + 1) * 2 sintrnames: #ifdef __mips_n64 .quad INTRCNT_COUNT * (MAXCOMLEN + 1) * 2 #else .int INTRCNT_COUNT * (MAXCOMLEN + 1) * 2 #endif .align (_MIPS_SZLONG / 8) intrcnt: .space INTRCNT_COUNT * (_MIPS_SZLONG / 8) * 2 sintrcnt: #ifdef __mips_n64 .quad INTRCNT_COUNT * (_MIPS_SZLONG / 8) * 2 #else .int INTRCNT_COUNT * (_MIPS_SZLONG / 8) * 2 #endif #endif /* INTRNG */ /* * Vector to real handler in KSEG1. */ .text VECTOR(MipsCache, unknown) PTR_LA k0, _C_LABEL(MipsCacheException) li k1, MIPS_KSEG0_PHYS_MASK and k0, k1 PTR_LI k1, MIPS_KSEG1_START or k0, k1 j k0 nop VECTOR_END(MipsCache) .set at /* * Panic on cache errors. A lot more could be done to recover * from some types of errors but it is tricky. */ NESTED_NOPROFILE(MipsCacheException, KERN_EXC_FRAME_SIZE, ra) .set noat .mask 0x80000000, -4 PTR_LA k0, _C_LABEL(panic) # return to panic PTR_LA a0, 9f # panicstr MFC0 a1, MIPS_COP_0_ERROR_PC mfc0 a2, MIPS_COP_0_CACHE_ERR # 3rd arg cache error MTC0 k0, MIPS_COP_0_ERROR_PC # set return address mfc0 k0, MIPS_COP_0_STATUS # restore status li k1, MIPS_SR_DIAG_PE # ignore further errors or k0, k1 mtc0 k0, MIPS_COP_0_STATUS # restore status COP0_SYNC eret MSG("cache error @ EPC 0x%x CachErr 0x%x"); .set at END(MipsCacheException) Index: user/ngie/detangle-rc/sys/mips/mips/mips_pic.c =================================================================== --- user/ngie/detangle-rc/sys/mips/mips/mips_pic.c (revision 299126) +++ user/ngie/detangle-rc/sys/mips/mips/mips_pic.c (revision 299127) @@ -1,418 +1,422 @@ /*- * Copyright (c) 2015 Alexander Kabaev * Copyright (c) 2006 Oleksandr Tymoshenko * Copyright (c) 2002-2004 Juli Mallett * 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, * without modification, immediately at the beginning of the file. * 2. 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 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. * */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include "opt_hwpmc_hooks.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #include #endif #include "pic_if.h" #define NHARD_IRQS 6 #define NSOFT_IRQS 2 #define NREAL_IRQS (NHARD_IRQS + NSOFT_IRQS) static int mips_pic_intr(void *); struct mips_pic_irqsrc { struct intr_irqsrc isrc; struct resource *res; u_int irq; }; struct mips_pic_softc { device_t pic_dev; struct mips_pic_irqsrc pic_irqs[NREAL_IRQS]; struct rman pic_irq_rman; struct mtx mutex; uint32_t nirqs; }; static struct mips_pic_softc *pic_sc; #define PIC_INTR_ISRC(sc, irq) (&(sc)->pic_irqs[(irq)].isrc) #ifdef FDT static struct ofw_compat_data compat_data[] = { {"mti,cpu-interrupt-controller", true}, {NULL, false} }; #endif #ifndef FDT static void mips_pic_identify(driver_t *drv, device_t parent) { BUS_ADD_CHILD(parent, 0, "cpupic", 0); } #endif static int mips_pic_probe(device_t dev) { #ifdef FDT if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); #endif device_set_desc(dev, "MIPS32 Interrupt Controller"); return (BUS_PROBE_DEFAULT); } static inline void pic_irq_unmask(struct mips_pic_softc *sc, u_int irq) { mips_wr_status(mips_rd_status() | ((1 << irq) << 8)); } static inline void pic_irq_mask(struct mips_pic_softc *sc, u_int irq) { mips_wr_status(mips_rd_status() & ~((1 << irq) << 8)); } static inline intptr_t pic_xref(device_t dev) { #ifdef FDT return (OF_xref_from_node(ofw_bus_get_node(dev))); #else return (0); #endif } static int mips_pic_register_isrcs(struct mips_pic_softc *sc) { int error; uint32_t irq, i, tmpirq; struct intr_irqsrc *isrc; char *name; for (irq = 0; irq < sc->nirqs; irq++) { sc->pic_irqs[irq].irq = irq; sc->pic_irqs[irq].res = rman_reserve_resource(&sc->pic_irq_rman, irq, irq, 1, RF_ACTIVE, sc->pic_dev); if (sc->pic_irqs[irq].res == NULL) { device_printf(sc->pic_dev, "%s failed to alloc resource for irq %u", __func__, irq); return (ENOMEM); } isrc = PIC_INTR_ISRC(sc, irq); if (irq < NSOFT_IRQS) { name = "sint"; tmpirq = irq; } else { name = "int"; tmpirq = irq - NSOFT_IRQS; } error = intr_isrc_register(isrc, sc->pic_dev, 0, "%s%u", name, tmpirq); if (error != 0) { for (i = 0; i < irq; i++) { intr_isrc_deregister(PIC_INTR_ISRC(sc, i)); } device_printf(sc->pic_dev, "%s failed", __func__); return (error); } } return (0); } static int mips_pic_attach(device_t dev) { struct mips_pic_softc *sc; intptr_t xref = pic_xref(dev); if (pic_sc) return (ENXIO); sc = device_get_softc(dev); sc->pic_dev = dev; pic_sc = sc; /* Initialize mutex */ mtx_init(&sc->mutex, "PIC lock", "", MTX_SPIN); /* Set the number of interrupts */ sc->nirqs = nitems(sc->pic_irqs); /* Init the IRQ rman */ sc->pic_irq_rman.rm_type = RMAN_ARRAY; sc->pic_irq_rman.rm_descr = "MIPS PIC IRQs"; if (rman_init(&sc->pic_irq_rman) != 0 || rman_manage_region(&sc->pic_irq_rman, 0, sc->nirqs - 1) != 0) { device_printf(dev, "failed to setup IRQ rman\n"); goto cleanup; } /* Register the interrupts */ if (mips_pic_register_isrcs(sc) != 0) { device_printf(dev, "could not register PIC ISRCs\n"); goto cleanup; } /* * Now, when everything is initialized, it's right time to * register interrupt controller to interrupt framefork. */ if (intr_pic_register(dev, xref) != 0) { device_printf(dev, "could not register PIC\n"); goto cleanup; } /* Claim our root controller role */ if (intr_pic_claim_root(dev, xref, mips_pic_intr, sc, 0) != 0) { device_printf(dev, "could not set PIC as a root\n"); intr_pic_deregister(dev, xref); goto cleanup; } return (0); cleanup: return(ENXIO); } int mips_pic_intr(void *arg) { struct mips_pic_softc *sc = arg; register_t cause, status; int i, intr; cause = mips_rd_cause(); status = mips_rd_status(); intr = (cause & MIPS_INT_MASK) >> 8; /* * Do not handle masked interrupts. They were masked by * pre_ithread function (mips_mask_XXX_intr) and will be * unmasked once ithread is through with handler */ intr &= (status & MIPS_INT_MASK) >> 8; while ((i = fls(intr)) != 0) { i--; /* Get a 0-offset interrupt. */ intr &= ~(1 << i); if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->pic_dev, "Stray interrupt %u detected\n", i); pic_irq_mask(sc, i); continue; } } KASSERT(i == 0, ("all interrupts handled")); #ifdef HWPMC_HOOKS if (pmc_hook && (PCPU_GET(curthread)->td_pflags & TDP_CALLCHAIN)) { struct trapframe *tf = PCPU_GET(curthread)->td_intr_frame; pmc_hook(PCPU_GET(curthread), PMC_FN_USER_CALLCHAIN, tf); } #endif return (FILTER_HANDLED); } static void mips_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mips_pic_irqsrc *)isrc)->irq; pic_irq_mask(device_get_softc(dev), irq); } static void mips_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mips_pic_irqsrc *)isrc)->irq; pic_irq_unmask(device_get_softc(dev), irq); } static int mips_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { #ifdef FDT + struct intr_map_data_fdt *daf; struct mips_pic_softc *sc; + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; - if (data == NULL || data->type != INTR_MAP_DATA_FDT || - data->fdt.ncells != 1 || data->fdt.cells[0] >= sc->nirqs) + if (daf->ncells != 1 || daf->cells[0] >= sc->nirqs) return (EINVAL); - *isrcp = PIC_INTR_ISRC(sc, data->fdt.cells[0]); + *isrcp = PIC_INTR_ISRC(sc, daf->cells[0]); return (0); #else - return (EINVAL); + return (ENOTSUP); #endif } static void mips_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { mips_pic_disable_intr(dev, isrc); } static void mips_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { mips_pic_enable_intr(dev, isrc); } static void mips_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { } static device_method_t mips_pic_methods[] = { /* Device interface */ #ifndef FDT DEVMETHOD(device_identify, mips_pic_identify), #endif DEVMETHOD(device_probe, mips_pic_probe), DEVMETHOD(device_attach, mips_pic_attach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, mips_pic_disable_intr), DEVMETHOD(pic_enable_intr, mips_pic_enable_intr), DEVMETHOD(pic_map_intr, mips_pic_map_intr), DEVMETHOD(pic_pre_ithread, mips_pic_pre_ithread), DEVMETHOD(pic_post_ithread, mips_pic_post_ithread), DEVMETHOD(pic_post_filter, mips_pic_post_filter), { 0, 0 } }; static driver_t mips_pic_driver = { "cpupic", mips_pic_methods, sizeof(struct mips_pic_softc), }; static devclass_t mips_pic_devclass; #ifdef FDT EARLY_DRIVER_MODULE(cpupic, ofwbus, mips_pic_driver, mips_pic_devclass, 0, 0, BUS_PASS_INTERRUPT); #else EARLY_DRIVER_MODULE(cpupic, nexus, mips_pic_driver, mips_pic_devclass, 0, 0, BUS_PASS_INTERRUPT); #endif void cpu_init_interrupts(void) { } void cpu_establish_hardintr(const char *name, driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, int flags, void **cookiep) { int res; /* * We have 6 levels, but thats 0 - 5 (not including 6) */ if (irq < 0 || irq >= NHARD_IRQS) panic("%s called for unknown hard intr %d", __func__, irq); KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); irq += NSOFT_IRQS; res = intr_setup_irq(pic_sc->pic_dev, pic_sc->pic_irqs[irq].res, filt, handler, arg, flags, cookiep); if (res != 0) panic("Unable to add hard IRQ %d handler", irq); } void cpu_establish_softintr(const char *name, driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, int flags, void **cookiep) { int res; if (irq < 0 || irq > NSOFT_IRQS) panic("%s called for unknown soft intr %d", __func__, irq); KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); res = intr_setup_irq(pic_sc->pic_dev, pic_sc->pic_irqs[irq].res, filt, handler, arg, flags, cookiep); if (res != 0) panic("Unable to add soft IRQ %d handler", irq); } Index: user/ngie/detangle-rc/sys/mips/mips/trap.c =================================================================== --- user/ngie/detangle-rc/sys/mips/mips/trap.c (revision 299126) +++ user/ngie/detangle-rc/sys/mips/mips/trap.c (revision 299127) @@ -1,1724 +1,1734 @@ /* $OpenBSD: trap.c,v 1.19 1998/09/30 12:40:41 pefo Exp $ */ /* tracked to 1.23 */ /*- * Copyright (c) 1988 University of Utah. * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * the Systems Programming Group of the University of Utah Computer * Science Department and Ralph Campbell. * * 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * from: Utah Hdr: trap.c 1.32 91/04/06 * * from: @(#)trap.c 8.5 (Berkeley) 1/11/94 * JNPR: trap.c,v 1.13.2.2 2007/08/29 10:03:49 girish */ #include __FBSDID("$FreeBSD$"); #include "opt_compat.h" #include "opt_ddb.h" #include "opt_ktrace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KTRACE #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef DDB #include #include #include #include #endif #ifdef KDTRACE_HOOKS #include #endif #ifdef TRAP_DEBUG int trap_debug = 0; SYSCTL_INT(_machdep, OID_AUTO, trap_debug, CTLFLAG_RW, &trap_debug, 0, "Debug information on all traps"); #endif #define lbu_macro(data, addr) \ __asm __volatile ("lbu %0, 0x0(%1)" \ : "=r" (data) /* outputs */ \ : "r" (addr)); /* inputs */ #define lb_macro(data, addr) \ __asm __volatile ("lb %0, 0x0(%1)" \ : "=r" (data) /* outputs */ \ : "r" (addr)); /* inputs */ #define lwl_macro(data, addr) \ __asm __volatile ("lwl %0, 0x0(%1)" \ : "=r" (data) /* outputs */ \ : "r" (addr)); /* inputs */ #define lwr_macro(data, addr) \ __asm __volatile ("lwr %0, 0x0(%1)" \ : "=r" (data) /* outputs */ \ : "r" (addr)); /* inputs */ #define ldl_macro(data, addr) \ __asm __volatile ("ldl %0, 0x0(%1)" \ : "=r" (data) /* outputs */ \ : "r" (addr)); /* inputs */ #define ldr_macro(data, addr) \ __asm __volatile ("ldr %0, 0x0(%1)" \ : "=r" (data) /* outputs */ \ : "r" (addr)); /* inputs */ #define sb_macro(data, addr) \ __asm __volatile ("sb %0, 0x0(%1)" \ : /* outputs */ \ : "r" (data), "r" (addr)); /* inputs */ #define swl_macro(data, addr) \ __asm __volatile ("swl %0, 0x0(%1)" \ : /* outputs */ \ : "r" (data), "r" (addr)); /* inputs */ #define swr_macro(data, addr) \ __asm __volatile ("swr %0, 0x0(%1)" \ : /* outputs */ \ : "r" (data), "r" (addr)); /* inputs */ #define sdl_macro(data, addr) \ __asm __volatile ("sdl %0, 0x0(%1)" \ : /* outputs */ \ : "r" (data), "r" (addr)); /* inputs */ #define sdr_macro(data, addr) \ __asm __volatile ("sdr %0, 0x0(%1)" \ : /* outputs */ \ : "r" (data), "r" (addr)); /* inputs */ static void log_illegal_instruction(const char *, struct trapframe *); static void log_bad_page_fault(char *, struct trapframe *, int); static void log_frame_dump(struct trapframe *frame); static void get_mapping_info(vm_offset_t, pd_entry_t **, pt_entry_t **); +int (*dtrace_invop_jump_addr)(struct trapframe *); + #ifdef TRAP_DEBUG static void trap_frame_dump(struct trapframe *frame); #endif void (*machExceptionTable[]) (void)= { /* * The kernel exception handlers. */ MipsKernIntr, /* external interrupt */ MipsKernGenException, /* TLB modification */ MipsTLBInvalidException,/* TLB miss (load or instr. fetch) */ MipsTLBInvalidException,/* TLB miss (store) */ MipsKernGenException, /* address error (load or I-fetch) */ MipsKernGenException, /* address error (store) */ MipsKernGenException, /* bus error (I-fetch) */ MipsKernGenException, /* bus error (load or store) */ MipsKernGenException, /* system call */ MipsKernGenException, /* breakpoint */ MipsKernGenException, /* reserved instruction */ MipsKernGenException, /* coprocessor unusable */ MipsKernGenException, /* arithmetic overflow */ MipsKernGenException, /* trap exception */ MipsKernGenException, /* virtual coherence exception inst */ MipsKernGenException, /* floating point exception */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* watch exception */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* reserved */ MipsKernGenException, /* virtual coherence exception data */ /* * The user exception handlers. */ MipsUserIntr, /* 0 */ MipsUserGenException, /* 1 */ MipsTLBInvalidException,/* 2 */ MipsTLBInvalidException,/* 3 */ MipsUserGenException, /* 4 */ MipsUserGenException, /* 5 */ MipsUserGenException, /* 6 */ MipsUserGenException, /* 7 */ MipsUserGenException, /* 8 */ MipsUserGenException, /* 9 */ MipsUserGenException, /* 10 */ MipsUserGenException, /* 11 */ MipsUserGenException, /* 12 */ MipsUserGenException, /* 13 */ MipsUserGenException, /* 14 */ MipsUserGenException, /* 15 */ MipsUserGenException, /* 16 */ MipsUserGenException, /* 17 */ MipsUserGenException, /* 18 */ MipsUserGenException, /* 19 */ MipsUserGenException, /* 20 */ MipsUserGenException, /* 21 */ MipsUserGenException, /* 22 */ MipsUserGenException, /* 23 */ MipsUserGenException, /* 24 */ MipsUserGenException, /* 25 */ MipsUserGenException, /* 26 */ MipsUserGenException, /* 27 */ MipsUserGenException, /* 28 */ MipsUserGenException, /* 29 */ MipsUserGenException, /* 20 */ MipsUserGenException, /* 31 */ }; char *trap_type[] = { "external interrupt", "TLB modification", "TLB miss (load or instr. fetch)", "TLB miss (store)", "address error (load or I-fetch)", "address error (store)", "bus error (I-fetch)", "bus error (load or store)", "system call", "breakpoint", "reserved instruction", "coprocessor unusable", "arithmetic overflow", "trap", "virtual coherency instruction", "floating point", "reserved 16", "reserved 17", "reserved 18", "reserved 19", "reserved 20", "reserved 21", "reserved 22", "watch", "reserved 24", "reserved 25", "reserved 26", "reserved 27", "reserved 28", "reserved 29", "reserved 30", "virtual coherency data", }; #if !defined(SMP) && (defined(DDB) || defined(DEBUG)) struct trapdebug trapdebug[TRAPSIZE], *trp = trapdebug; #endif #if defined(DDB) || defined(DEBUG) void stacktrace(struct trapframe *); void logstacktrace(struct trapframe *); #endif #define KERNLAND(x) ((vm_offset_t)(x) >= VM_MIN_KERNEL_ADDRESS && (vm_offset_t)(x) < VM_MAX_KERNEL_ADDRESS) #define DELAYBRANCH(x) ((int)(x) < 0) /* * MIPS load/store access type */ enum { MIPS_LHU_ACCESS = 1, MIPS_LH_ACCESS, MIPS_LWU_ACCESS, MIPS_LW_ACCESS, MIPS_LD_ACCESS, MIPS_SH_ACCESS, MIPS_SW_ACCESS, MIPS_SD_ACCESS }; char *access_name[] = { "Load Halfword Unsigned", "Load Halfword", "Load Word Unsigned", "Load Word", "Load Doubleword", "Store Halfword", "Store Word", "Store Doubleword" }; #ifdef CPU_CNMIPS #include #endif static int allow_unaligned_acc = 1; SYSCTL_INT(_vm, OID_AUTO, allow_unaligned_acc, CTLFLAG_RW, &allow_unaligned_acc, 0, "Allow unaligned accesses"); /* * FP emulation is assumed to work on O32, but the code is outdated and crufty * enough that it's a more sensible default to have it disabled when using * other ABIs. At the very least, it needs a lot of help in using * type-semantic ABI-oblivious macros for everything it does. */ #if defined(__mips_o32) static int emulate_fp = 1; #else static int emulate_fp = 0; #endif SYSCTL_INT(_machdep, OID_AUTO, emulate_fp, CTLFLAG_RW, &emulate_fp, 0, "Emulate unimplemented FPU instructions"); static int emulate_unaligned_access(struct trapframe *frame, int mode); extern void fswintrberr(void); /* XXX */ int cpu_fetch_syscall_args(struct thread *td, struct syscall_args *sa) { struct trapframe *locr0 = td->td_frame; struct sysentvec *se; int error, nsaved; bzero(sa->args, sizeof(sa->args)); /* compute next PC after syscall instruction */ td->td_pcb->pcb_tpc = sa->trapframe->pc; /* Remember if restart */ if (DELAYBRANCH(sa->trapframe->cause)) /* Check BD bit */ locr0->pc = MipsEmulateBranch(locr0, sa->trapframe->pc, 0, 0); else locr0->pc += sizeof(int); sa->code = locr0->v0; switch (sa->code) { case SYS___syscall: case SYS_syscall: /* * This is an indirect syscall, in which the code is the first argument. */ #if (!defined(__mips_n32) && !defined(__mips_n64)) || defined(COMPAT_FREEBSD32) if (sa->code == SYS___syscall && SV_PROC_FLAG(td->td_proc, SV_ILP32)) { /* * Like syscall, but code is a quad, so as to maintain alignment * for the rest of the arguments. */ if (_QUAD_LOWWORD == 0) sa->code = locr0->a0; else sa->code = locr0->a1; sa->args[0] = locr0->a2; sa->args[1] = locr0->a3; nsaved = 2; break; } #endif /* * This is either not a quad syscall, or is a quad syscall with a * new ABI in which quads fit in a single register. */ sa->code = locr0->a0; sa->args[0] = locr0->a1; sa->args[1] = locr0->a2; sa->args[2] = locr0->a3; nsaved = 3; #if defined(__mips_n32) || defined(__mips_n64) #ifdef COMPAT_FREEBSD32 if (!SV_PROC_FLAG(td->td_proc, SV_ILP32)) { #endif /* * Non-o32 ABIs support more arguments in registers. */ sa->args[3] = locr0->a4; sa->args[4] = locr0->a5; sa->args[5] = locr0->a6; sa->args[6] = locr0->a7; nsaved += 4; #ifdef COMPAT_FREEBSD32 } #endif #endif break; default: /* * A direct syscall, arguments are just parameters to the syscall. */ sa->args[0] = locr0->a0; sa->args[1] = locr0->a1; sa->args[2] = locr0->a2; sa->args[3] = locr0->a3; nsaved = 4; #if defined (__mips_n32) || defined(__mips_n64) #ifdef COMPAT_FREEBSD32 if (!SV_PROC_FLAG(td->td_proc, SV_ILP32)) { #endif /* * Non-o32 ABIs support more arguments in registers. */ sa->args[4] = locr0->a4; sa->args[5] = locr0->a5; sa->args[6] = locr0->a6; sa->args[7] = locr0->a7; nsaved += 4; #ifdef COMPAT_FREEBSD32 } #endif #endif break; } #ifdef TRAP_DEBUG if (trap_debug) printf("SYSCALL #%d pid:%u\n", sa->code, td->td_proc->p_pid); #endif se = td->td_proc->p_sysent; /* * XXX * Shouldn't this go before switching on the code? */ if (se->sv_mask) sa->code &= se->sv_mask; if (sa->code >= se->sv_size) sa->callp = &se->sv_table[0]; else sa->callp = &se->sv_table[sa->code]; sa->narg = sa->callp->sy_narg; if (sa->narg > nsaved) { #if defined(__mips_n32) || defined(__mips_n64) /* * XXX * Is this right for new ABIs? I think the 4 there * should be 8, size there are 8 registers to skip, * not 4, but I'm not certain. */ #ifdef COMPAT_FREEBSD32 if (!SV_PROC_FLAG(td->td_proc, SV_ILP32)) #endif printf("SYSCALL #%u pid:%u, narg (%u) > nsaved (%u).\n", sa->code, td->td_proc->p_pid, sa->narg, nsaved); #endif #if (defined(__mips_n32) || defined(__mips_n64)) && defined(COMPAT_FREEBSD32) if (SV_PROC_FLAG(td->td_proc, SV_ILP32)) { unsigned i; int32_t arg; error = 0; /* XXX GCC is awful. */ for (i = nsaved; i < sa->narg; i++) { error = copyin((caddr_t)(intptr_t)(locr0->sp + (4 + (i - nsaved)) * sizeof(int32_t)), (caddr_t)&arg, sizeof arg); if (error != 0) break; sa->args[i] = arg; } } else #endif error = copyin((caddr_t)(intptr_t)(locr0->sp + 4 * sizeof(register_t)), (caddr_t)&sa->args[nsaved], (u_int)(sa->narg - nsaved) * sizeof(register_t)); if (error != 0) { locr0->v0 = error; locr0->a3 = 1; } } else error = 0; if (error == 0) { td->td_retval[0] = 0; td->td_retval[1] = locr0->v1; } return (error); } #undef __FBSDID #define __FBSDID(x) #include "../../kern/subr_syscall.c" /* * Handle an exception. * Called from MipsKernGenException() or MipsUserGenException() * when a processor trap occurs. * In the case of a kernel trap, we return the pc where to resume if * p->p_addr->u_pcb.pcb_onfault is set, otherwise, return old pc. */ register_t trap(struct trapframe *trapframe) { int type, usermode; int i = 0; unsigned ucode = 0; struct thread *td = curthread; struct proc *p = curproc; vm_prot_t ftype; pmap_t pmap; int access_type; ksiginfo_t ksi; char *msg = NULL; intptr_t addr = 0; register_t pc; int cop; register_t *frame_regs; trapdebug_enter(trapframe, 0); type = (trapframe->cause & MIPS_CR_EXC_CODE) >> MIPS_CR_EXC_CODE_SHIFT; if (TRAPF_USERMODE(trapframe)) { type |= T_USER; usermode = 1; } else { usermode = 0; } /* * Enable hardware interrupts if they were on before the trap. If it * was off disable all so we don't accidently enable it when doing a * return to userland. */ if (trapframe->sr & MIPS_SR_INT_IE) { set_intr_mask(trapframe->sr & MIPS_SR_INT_MASK); intr_enable(); } else { intr_disable(); } #ifdef TRAP_DEBUG if (trap_debug) { static vm_offset_t last_badvaddr = 0; static vm_offset_t this_badvaddr = 0; static int count = 0; u_int32_t pid; printf("trap type %x (%s - ", type, trap_type[type & (~T_USER)]); if (type & T_USER) printf("user mode)\n"); else printf("kernel mode)\n"); #ifdef SMP printf("cpuid = %d\n", PCPU_GET(cpuid)); #endif pid = mips_rd_entryhi() & TLBHI_ASID_MASK; printf("badaddr = %#jx, pc = %#jx, ra = %#jx, sp = %#jx, sr = %jx, pid = %d, ASID = %u\n", (intmax_t)trapframe->badvaddr, (intmax_t)trapframe->pc, (intmax_t)trapframe->ra, (intmax_t)trapframe->sp, (intmax_t)trapframe->sr, (curproc ? curproc->p_pid : -1), pid); switch (type & ~T_USER) { case T_TLB_MOD: case T_TLB_LD_MISS: case T_TLB_ST_MISS: case T_ADDR_ERR_LD: case T_ADDR_ERR_ST: this_badvaddr = trapframe->badvaddr; break; case T_SYSCALL: this_badvaddr = trapframe->ra; break; default: this_badvaddr = trapframe->pc; break; } if ((last_badvaddr == this_badvaddr) && ((type & ~T_USER) != T_SYSCALL)) { if (++count == 3) { trap_frame_dump(trapframe); panic("too many faults at %p\n", (void *)last_badvaddr); } } else { last_badvaddr = this_badvaddr; count = 0; } } #endif #ifdef KDTRACE_HOOKS /* * A trap can occur while DTrace executes a probe. Before * executing the probe, DTrace blocks re-scheduling and sets * a flag in its per-cpu flags to indicate that it doesn't * want to fault. On returning from the probe, the no-fault * flag is cleared and finally re-scheduling is enabled. * * If the DTrace kernel module has registered a trap handler, * call it and if it returns non-zero, assume that it has * handled the trap and modified the trap frame so that this * function can return normally. */ /* * XXXDTRACE: add pid probe handler here (if ever) */ if (!usermode) { if (dtrace_trap_func != NULL && (*dtrace_trap_func)(trapframe, type) != 0) return (trapframe->pc); } #endif switch (type) { case T_MCHECK: #ifdef DDB kdb_trap(type, 0, trapframe); #endif panic("MCHECK\n"); break; case T_TLB_MOD: /* check for kernel address */ if (KERNLAND(trapframe->badvaddr)) { if (pmap_emulate_modified(kernel_pmap, trapframe->badvaddr) != 0) { ftype = VM_PROT_WRITE; goto kernel_fault; } return (trapframe->pc); } /* FALLTHROUGH */ case T_TLB_MOD + T_USER: pmap = &p->p_vmspace->vm_pmap; if (pmap_emulate_modified(pmap, trapframe->badvaddr) != 0) { ftype = VM_PROT_WRITE; goto dofault; } if (!usermode) return (trapframe->pc); goto out; case T_TLB_LD_MISS: case T_TLB_ST_MISS: ftype = (type == T_TLB_ST_MISS) ? VM_PROT_WRITE : VM_PROT_READ; /* check for kernel address */ if (KERNLAND(trapframe->badvaddr)) { vm_offset_t va; int rv; kernel_fault: va = trunc_page((vm_offset_t)trapframe->badvaddr); rv = vm_fault(kernel_map, va, ftype, VM_FAULT_NORMAL); if (rv == KERN_SUCCESS) return (trapframe->pc); if (td->td_pcb->pcb_onfault != NULL) { pc = (register_t)(intptr_t)td->td_pcb->pcb_onfault; td->td_pcb->pcb_onfault = NULL; return (pc); } goto err; } /* * It is an error for the kernel to access user space except * through the copyin/copyout routines. */ if (td->td_pcb->pcb_onfault == NULL) goto err; /* check for fuswintr() or suswintr() getting a page fault */ /* XXX There must be a nicer way to do this. */ if (td->td_pcb->pcb_onfault == fswintrberr) { pc = (register_t)(intptr_t)td->td_pcb->pcb_onfault; td->td_pcb->pcb_onfault = NULL; return (pc); } goto dofault; case T_TLB_LD_MISS + T_USER: ftype = VM_PROT_READ; goto dofault; case T_TLB_ST_MISS + T_USER: ftype = VM_PROT_WRITE; dofault: { vm_offset_t va; struct vmspace *vm; vm_map_t map; int rv = 0; vm = p->p_vmspace; map = &vm->vm_map; va = trunc_page((vm_offset_t)trapframe->badvaddr); if (KERNLAND(trapframe->badvaddr)) { /* * Don't allow user-mode faults in kernel * address space. */ goto nogo; } rv = vm_fault(map, va, ftype, VM_FAULT_NORMAL); /* * XXXDTRACE: add dtrace_doubletrap_func here? */ #ifdef VMFAULT_TRACE printf("vm_fault(%p (pmap %p), %p (%p), %x, %d) -> %x at pc %p\n", map, &vm->vm_pmap, (void *)va, (void *)(intptr_t)trapframe->badvaddr, ftype, VM_FAULT_NORMAL, rv, (void *)(intptr_t)trapframe->pc); #endif if (rv == KERN_SUCCESS) { if (!usermode) { return (trapframe->pc); } goto out; } nogo: if (!usermode) { if (td->td_pcb->pcb_onfault != NULL) { pc = (register_t)(intptr_t)td->td_pcb->pcb_onfault; td->td_pcb->pcb_onfault = NULL; return (pc); } goto err; } ucode = ftype; i = ((rv == KERN_PROTECTION_FAILURE) ? SIGBUS : SIGSEGV); addr = trapframe->pc; msg = "BAD_PAGE_FAULT"; log_bad_page_fault(msg, trapframe, type); break; } case T_ADDR_ERR_LD + T_USER: /* misaligned or kseg access */ case T_ADDR_ERR_ST + T_USER: /* misaligned or kseg access */ if (trapframe->badvaddr < 0 || trapframe->badvaddr >= VM_MAXUSER_ADDRESS) { msg = "ADDRESS_SPACE_ERR"; } else if (allow_unaligned_acc) { int mode; if (type == (T_ADDR_ERR_LD + T_USER)) mode = VM_PROT_READ; else mode = VM_PROT_WRITE; access_type = emulate_unaligned_access(trapframe, mode); if (access_type != 0) goto out; msg = "ALIGNMENT_FIX_ERR"; } else { msg = "ADDRESS_ERR"; } /* FALL THROUGH */ case T_BUS_ERR_IFETCH + T_USER: /* BERR asserted to cpu */ case T_BUS_ERR_LD_ST + T_USER: /* BERR asserted to cpu */ ucode = 0; /* XXX should be VM_PROT_something */ i = SIGBUS; addr = trapframe->pc; if (!msg) msg = "BUS_ERR"; log_bad_page_fault(msg, trapframe, type); break; case T_SYSCALL + T_USER: { struct syscall_args sa; int error; sa.trapframe = trapframe; error = syscallenter(td, &sa); #if !defined(SMP) && (defined(DDB) || defined(DEBUG)) if (trp == trapdebug) trapdebug[TRAPSIZE - 1].code = sa.code; else trp[-1].code = sa.code; #endif trapdebug_enter(td->td_frame, -sa.code); /* * The sync'ing of I & D caches for SYS_ptrace() is * done by procfs_domem() through procfs_rwmem() * instead of being done here under a special check * for SYS_ptrace(). */ syscallret(td, error, &sa); return (trapframe->pc); } -#ifdef DDB +#if defined(KDTRACE_HOOKS) || defined(DDB) case T_BREAK: +#ifdef KDTRACE_HOOKS + if (!usermode && dtrace_invop_jump_addr != 0) { + dtrace_invop_jump_addr(trapframe); + return (trapframe->pc); + } +#endif +#ifdef DDB kdb_trap(type, 0, trapframe); return (trapframe->pc); +#endif #endif case T_BREAK + T_USER: { intptr_t va; uint32_t instr; /* compute address of break instruction */ va = trapframe->pc; if (DELAYBRANCH(trapframe->cause)) va += sizeof(int); /* read break instruction */ instr = fuword32((caddr_t)va); #if 0 printf("trap: %s (%d) breakpoint %x at %x: (adr %x ins %x)\n", p->p_comm, p->p_pid, instr, trapframe->pc, p->p_md.md_ss_addr, p->p_md.md_ss_instr); /* XXX */ #endif if (td->td_md.md_ss_addr != va || instr != MIPS_BREAK_SSTEP) { i = SIGTRAP; addr = trapframe->pc; break; } /* * The restoration of the original instruction and * the clearing of the berakpoint will be done later * by the call to ptrace_clear_single_step() in * issignal() when SIGTRAP is processed. */ addr = trapframe->pc; i = SIGTRAP; break; } case T_IWATCH + T_USER: case T_DWATCH + T_USER: { intptr_t va; /* compute address of trapped instruction */ va = trapframe->pc; if (DELAYBRANCH(trapframe->cause)) va += sizeof(int); printf("watch exception @ %p\n", (void *)va); i = SIGTRAP; addr = va; break; } case T_TRAP + T_USER: { intptr_t va; uint32_t instr; struct trapframe *locr0 = td->td_frame; /* compute address of trap instruction */ va = trapframe->pc; if (DELAYBRANCH(trapframe->cause)) va += sizeof(int); /* read break instruction */ instr = fuword32((caddr_t)va); if (DELAYBRANCH(trapframe->cause)) { /* Check BD bit */ locr0->pc = MipsEmulateBranch(locr0, trapframe->pc, 0, 0); } else { locr0->pc += sizeof(int); } addr = va; i = SIGEMT; /* Stuff it with something for now */ break; } case T_RES_INST + T_USER: { InstFmt inst; inst = *(InstFmt *)(intptr_t)trapframe->pc; switch (inst.RType.op) { case OP_SPECIAL3: switch (inst.RType.func) { case OP_RDHWR: /* Register 29 used for TLS */ if (inst.RType.rd == 29) { frame_regs = &(trapframe->zero); frame_regs[inst.RType.rt] = (register_t)(intptr_t)td->td_md.md_tls; #if defined(__mips_n64) && defined(COMPAT_FREEBSD32) if (SV_PROC_FLAG(td->td_proc, SV_ILP32)) frame_regs[inst.RType.rt] += TLS_TP_OFFSET + TLS_TCB_SIZE32; else #endif frame_regs[inst.RType.rt] += TLS_TP_OFFSET + TLS_TCB_SIZE; trapframe->pc += sizeof(int); goto out; } break; } break; } log_illegal_instruction("RES_INST", trapframe); i = SIGILL; addr = trapframe->pc; } break; case T_C2E: case T_C2E + T_USER: goto err; break; case T_COP_UNUSABLE: #ifdef CPU_CNMIPS cop = (trapframe->cause & MIPS_CR_COP_ERR) >> MIPS_CR_COP_ERR_SHIFT; /* Handle only COP2 exception */ if (cop != 2) goto err; addr = trapframe->pc; /* save userland cop2 context if it has been touched */ if ((td->td_md.md_flags & MDTD_COP2USED) && (td->td_md.md_cop2owner == COP2_OWNER_USERLAND)) { if (td->td_md.md_ucop2) octeon_cop2_save(td->td_md.md_ucop2); else panic("COP2 was used in user mode but md_ucop2 is NULL"); } if (td->td_md.md_cop2 == NULL) { td->td_md.md_cop2 = octeon_cop2_alloc_ctx(); if (td->td_md.md_cop2 == NULL) panic("Failed to allocate COP2 context"); memset(td->td_md.md_cop2, 0, sizeof(*td->td_md.md_cop2)); } octeon_cop2_restore(td->td_md.md_cop2); /* Make userland re-request its context */ td->td_frame->sr &= ~MIPS_SR_COP_2_BIT; td->td_md.md_flags |= MDTD_COP2USED; td->td_md.md_cop2owner = COP2_OWNER_KERNEL; /* Enable COP2, it will be disabled in cpu_switch */ mips_wr_status(mips_rd_status() | MIPS_SR_COP_2_BIT); return (trapframe->pc); #else goto err; break; #endif case T_COP_UNUSABLE + T_USER: cop = (trapframe->cause & MIPS_CR_COP_ERR) >> MIPS_CR_COP_ERR_SHIFT; if (cop == 1) { #if !defined(CPU_HAVEFPU) /* FP (COP1) instruction */ log_illegal_instruction("COP1_UNUSABLE", trapframe); i = SIGILL; break; #else addr = trapframe->pc; MipsSwitchFPState(PCPU_GET(fpcurthread), td->td_frame); PCPU_SET(fpcurthread, td); td->td_frame->sr |= MIPS_SR_COP_1_BIT; td->td_md.md_flags |= MDTD_FPUSED; goto out; #endif } #ifdef CPU_CNMIPS else if (cop == 2) { addr = trapframe->pc; if ((td->td_md.md_flags & MDTD_COP2USED) && (td->td_md.md_cop2owner == COP2_OWNER_KERNEL)) { if (td->td_md.md_cop2) octeon_cop2_save(td->td_md.md_cop2); else panic("COP2 was used in kernel mode but md_cop2 is NULL"); } if (td->td_md.md_ucop2 == NULL) { td->td_md.md_ucop2 = octeon_cop2_alloc_ctx(); if (td->td_md.md_ucop2 == NULL) panic("Failed to allocate userland COP2 context"); memset(td->td_md.md_ucop2, 0, sizeof(*td->td_md.md_ucop2)); } octeon_cop2_restore(td->td_md.md_ucop2); td->td_frame->sr |= MIPS_SR_COP_2_BIT; td->td_md.md_flags |= MDTD_COP2USED; td->td_md.md_cop2owner = COP2_OWNER_USERLAND; goto out; } #endif else { log_illegal_instruction("COPn_UNUSABLE", trapframe); i = SIGILL; /* only FPU instructions allowed */ break; } case T_FPE: #if !defined(SMP) && (defined(DDB) || defined(DEBUG)) trapDump("fpintr"); #else printf("FPU Trap: PC %#jx CR %x SR %x\n", (intmax_t)trapframe->pc, (unsigned)trapframe->cause, (unsigned)trapframe->sr); goto err; #endif case T_FPE + T_USER: if (!emulate_fp) { i = SIGILL; addr = trapframe->pc; break; } MipsFPTrap(trapframe->sr, trapframe->cause, trapframe->pc); goto out; case T_OVFLOW + T_USER: i = SIGFPE; addr = trapframe->pc; break; case T_ADDR_ERR_LD: /* misaligned access */ case T_ADDR_ERR_ST: /* misaligned access */ #ifdef TRAP_DEBUG if (trap_debug) { printf("+++ ADDR_ERR: type = %d, badvaddr = %#jx\n", type, (intmax_t)trapframe->badvaddr); } #endif /* Only allow emulation on a user address */ if (allow_unaligned_acc && ((vm_offset_t)trapframe->badvaddr < VM_MAXUSER_ADDRESS)) { int mode; if (type == T_ADDR_ERR_LD) mode = VM_PROT_READ; else mode = VM_PROT_WRITE; access_type = emulate_unaligned_access(trapframe, mode); if (access_type != 0) return (trapframe->pc); } /* FALLTHROUGH */ case T_BUS_ERR_LD_ST: /* BERR asserted to cpu */ if (td->td_pcb->pcb_onfault != NULL) { pc = (register_t)(intptr_t)td->td_pcb->pcb_onfault; td->td_pcb->pcb_onfault = NULL; return (pc); } /* FALLTHROUGH */ default: err: #if !defined(SMP) && defined(DEBUG) stacktrace(!usermode ? trapframe : td->td_frame); trapDump("trap"); #endif #ifdef SMP printf("cpu:%d-", PCPU_GET(cpuid)); #endif printf("Trap cause = %d (%s - ", type, trap_type[type & (~T_USER)]); if (type & T_USER) printf("user mode)\n"); else printf("kernel mode)\n"); #ifdef TRAP_DEBUG if (trap_debug) printf("badvaddr = %#jx, pc = %#jx, ra = %#jx, sr = %#jxx\n", (intmax_t)trapframe->badvaddr, (intmax_t)trapframe->pc, (intmax_t)trapframe->ra, (intmax_t)trapframe->sr); #endif #ifdef KDB if (debugger_on_panic || kdb_active) { kdb_trap(type, 0, trapframe); } #endif panic("trap"); } td->td_frame->pc = trapframe->pc; td->td_frame->cause = trapframe->cause; td->td_frame->badvaddr = trapframe->badvaddr; ksiginfo_init_trap(&ksi); ksi.ksi_signo = i; ksi.ksi_code = ucode; ksi.ksi_addr = (void *)addr; ksi.ksi_trapno = type; trapsignal(td, &ksi); out: /* * Note: we should only get here if returning to user mode. */ userret(td, trapframe); return (trapframe->pc); } #if !defined(SMP) && (defined(DDB) || defined(DEBUG)) void trapDump(char *msg) { register_t s; int i; s = intr_disable(); printf("trapDump(%s)\n", msg); for (i = 0; i < TRAPSIZE; i++) { if (trp == trapdebug) { trp = &trapdebug[TRAPSIZE - 1]; } else { trp--; } if (trp->cause == 0) break; printf("%s: ADR %jx PC %jx CR %jx SR %jx\n", trap_type[(trp->cause & MIPS_CR_EXC_CODE) >> MIPS_CR_EXC_CODE_SHIFT], (intmax_t)trp->vadr, (intmax_t)trp->pc, (intmax_t)trp->cause, (intmax_t)trp->status); printf(" RA %jx SP %jx code %d\n", (intmax_t)trp->ra, (intmax_t)trp->sp, (int)trp->code); } intr_restore(s); } #endif /* * Return the resulting PC as if the branch was executed. */ uintptr_t MipsEmulateBranch(struct trapframe *framePtr, uintptr_t instPC, int fpcCSR, uintptr_t instptr) { InstFmt inst; register_t *regsPtr = (register_t *) framePtr; uintptr_t retAddr = 0; int condition; #define GetBranchDest(InstPtr, inst) \ (InstPtr + 4 + ((short)inst.IType.imm << 2)) if (instptr) { if (instptr < MIPS_KSEG0_START) inst.word = fuword32((void *)instptr); else inst = *(InstFmt *) instptr; } else { if ((vm_offset_t)instPC < MIPS_KSEG0_START) inst.word = fuword32((void *)instPC); else inst = *(InstFmt *) instPC; } switch ((int)inst.JType.op) { case OP_SPECIAL: switch ((int)inst.RType.func) { case OP_JR: case OP_JALR: retAddr = regsPtr[inst.RType.rs]; break; default: retAddr = instPC + 4; break; } break; case OP_BCOND: switch ((int)inst.IType.rt) { case OP_BLTZ: case OP_BLTZL: case OP_BLTZAL: case OP_BLTZALL: if ((int)(regsPtr[inst.RType.rs]) < 0) retAddr = GetBranchDest(instPC, inst); else retAddr = instPC + 8; break; case OP_BGEZ: case OP_BGEZL: case OP_BGEZAL: case OP_BGEZALL: if ((int)(regsPtr[inst.RType.rs]) >= 0) retAddr = GetBranchDest(instPC, inst); else retAddr = instPC + 8; break; case OP_TGEI: case OP_TGEIU: case OP_TLTI: case OP_TLTIU: case OP_TEQI: case OP_TNEI: retAddr = instPC + 4; /* Like syscall... */ break; default: panic("MipsEmulateBranch: Bad branch cond"); } break; case OP_J: case OP_JAL: retAddr = (inst.JType.target << 2) | ((unsigned)(instPC + 4) & 0xF0000000); break; case OP_BEQ: case OP_BEQL: if (regsPtr[inst.RType.rs] == regsPtr[inst.RType.rt]) retAddr = GetBranchDest(instPC, inst); else retAddr = instPC + 8; break; case OP_BNE: case OP_BNEL: if (regsPtr[inst.RType.rs] != regsPtr[inst.RType.rt]) retAddr = GetBranchDest(instPC, inst); else retAddr = instPC + 8; break; case OP_BLEZ: case OP_BLEZL: if ((int)(regsPtr[inst.RType.rs]) <= 0) retAddr = GetBranchDest(instPC, inst); else retAddr = instPC + 8; break; case OP_BGTZ: case OP_BGTZL: if ((int)(regsPtr[inst.RType.rs]) > 0) retAddr = GetBranchDest(instPC, inst); else retAddr = instPC + 8; break; case OP_COP1: switch (inst.RType.rs) { case OP_BCx: case OP_BCy: if ((inst.RType.rt & COPz_BC_TF_MASK) == COPz_BC_TRUE) condition = fpcCSR & MIPS_FPU_COND_BIT; else condition = !(fpcCSR & MIPS_FPU_COND_BIT); if (condition) retAddr = GetBranchDest(instPC, inst); else retAddr = instPC + 8; break; default: retAddr = instPC + 4; } break; default: retAddr = instPC + 4; } return (retAddr); } #if defined(DDB) || defined(DEBUG) /* * Print a stack backtrace. */ void stacktrace(struct trapframe *regs) { stacktrace_subr(regs->pc, regs->sp, regs->ra, printf); } #endif static void log_frame_dump(struct trapframe *frame) { log(LOG_ERR, "Trapframe Register Dump:\n"); log(LOG_ERR, "\tzero: %#jx\tat: %#jx\tv0: %#jx\tv1: %#jx\n", (intmax_t)0, (intmax_t)frame->ast, (intmax_t)frame->v0, (intmax_t)frame->v1); log(LOG_ERR, "\ta0: %#jx\ta1: %#jx\ta2: %#jx\ta3: %#jx\n", (intmax_t)frame->a0, (intmax_t)frame->a1, (intmax_t)frame->a2, (intmax_t)frame->a3); #if defined(__mips_n32) || defined(__mips_n64) log(LOG_ERR, "\ta4: %#jx\ta5: %#jx\ta6: %#jx\ta6: %#jx\n", (intmax_t)frame->a4, (intmax_t)frame->a5, (intmax_t)frame->a6, (intmax_t)frame->a7); log(LOG_ERR, "\tt0: %#jx\tt1: %#jx\tt2: %#jx\tt3: %#jx\n", (intmax_t)frame->t0, (intmax_t)frame->t1, (intmax_t)frame->t2, (intmax_t)frame->t3); #else log(LOG_ERR, "\tt0: %#jx\tt1: %#jx\tt2: %#jx\tt3: %#jx\n", (intmax_t)frame->t0, (intmax_t)frame->t1, (intmax_t)frame->t2, (intmax_t)frame->t3); log(LOG_ERR, "\tt4: %#jx\tt5: %#jx\tt6: %#jx\tt7: %#jx\n", (intmax_t)frame->t4, (intmax_t)frame->t5, (intmax_t)frame->t6, (intmax_t)frame->t7); #endif log(LOG_ERR, "\tt8: %#jx\tt9: %#jx\ts0: %#jx\ts1: %#jx\n", (intmax_t)frame->t8, (intmax_t)frame->t9, (intmax_t)frame->s0, (intmax_t)frame->s1); log(LOG_ERR, "\ts2: %#jx\ts3: %#jx\ts4: %#jx\ts5: %#jx\n", (intmax_t)frame->s2, (intmax_t)frame->s3, (intmax_t)frame->s4, (intmax_t)frame->s5); log(LOG_ERR, "\ts6: %#jx\ts7: %#jx\tk0: %#jx\tk1: %#jx\n", (intmax_t)frame->s6, (intmax_t)frame->s7, (intmax_t)frame->k0, (intmax_t)frame->k1); log(LOG_ERR, "\tgp: %#jx\tsp: %#jx\ts8: %#jx\tra: %#jx\n", (intmax_t)frame->gp, (intmax_t)frame->sp, (intmax_t)frame->s8, (intmax_t)frame->ra); log(LOG_ERR, "\tsr: %#jx\tmullo: %#jx\tmulhi: %#jx\tbadvaddr: %#jx\n", (intmax_t)frame->sr, (intmax_t)frame->mullo, (intmax_t)frame->mulhi, (intmax_t)frame->badvaddr); #ifdef IC_REG log(LOG_ERR, "\tcause: %#jx\tpc: %#jx\tic: %#jx\n", (intmax_t)frame->cause, (intmax_t)frame->pc, (intmax_t)frame->ic); #else log(LOG_ERR, "\tcause: %#jx\tpc: %#jx\n", (intmax_t)frame->cause, (intmax_t)frame->pc); #endif } #ifdef TRAP_DEBUG static void trap_frame_dump(struct trapframe *frame) { printf("Trapframe Register Dump:\n"); printf("\tzero: %#jx\tat: %#jx\tv0: %#jx\tv1: %#jx\n", (intmax_t)0, (intmax_t)frame->ast, (intmax_t)frame->v0, (intmax_t)frame->v1); printf("\ta0: %#jx\ta1: %#jx\ta2: %#jx\ta3: %#jx\n", (intmax_t)frame->a0, (intmax_t)frame->a1, (intmax_t)frame->a2, (intmax_t)frame->a3); #if defined(__mips_n32) || defined(__mips_n64) printf("\ta4: %#jx\ta5: %#jx\ta6: %#jx\ta7: %#jx\n", (intmax_t)frame->a4, (intmax_t)frame->a5, (intmax_t)frame->a6, (intmax_t)frame->a7); printf("\tt0: %#jx\tt1: %#jx\tt2: %#jx\tt3: %#jx\n", (intmax_t)frame->t0, (intmax_t)frame->t1, (intmax_t)frame->t2, (intmax_t)frame->t3); #else printf("\tt0: %#jx\tt1: %#jx\tt2: %#jx\tt3: %#jx\n", (intmax_t)frame->t0, (intmax_t)frame->t1, (intmax_t)frame->t2, (intmax_t)frame->t3); printf("\tt4: %#jx\tt5: %#jx\tt6: %#jx\tt7: %#jx\n", (intmax_t)frame->t4, (intmax_t)frame->t5, (intmax_t)frame->t6, (intmax_t)frame->t7); #endif printf("\tt8: %#jx\tt9: %#jx\ts0: %#jx\ts1: %#jx\n", (intmax_t)frame->t8, (intmax_t)frame->t9, (intmax_t)frame->s0, (intmax_t)frame->s1); printf("\ts2: %#jx\ts3: %#jx\ts4: %#jx\ts5: %#jx\n", (intmax_t)frame->s2, (intmax_t)frame->s3, (intmax_t)frame->s4, (intmax_t)frame->s5); printf("\ts6: %#jx\ts7: %#jx\tk0: %#jx\tk1: %#jx\n", (intmax_t)frame->s6, (intmax_t)frame->s7, (intmax_t)frame->k0, (intmax_t)frame->k1); printf("\tgp: %#jx\tsp: %#jx\ts8: %#jx\tra: %#jx\n", (intmax_t)frame->gp, (intmax_t)frame->sp, (intmax_t)frame->s8, (intmax_t)frame->ra); printf("\tsr: %#jx\tmullo: %#jx\tmulhi: %#jx\tbadvaddr: %#jx\n", (intmax_t)frame->sr, (intmax_t)frame->mullo, (intmax_t)frame->mulhi, (intmax_t)frame->badvaddr); #ifdef IC_REG printf("\tcause: %#jx\tpc: %#jx\tic: %#jx\n", (intmax_t)frame->cause, (intmax_t)frame->pc, (intmax_t)frame->ic); #else printf("\tcause: %#jx\tpc: %#jx\n", (intmax_t)frame->cause, (intmax_t)frame->pc); #endif } #endif static void get_mapping_info(vm_offset_t va, pd_entry_t **pdepp, pt_entry_t **ptepp) { pt_entry_t *ptep; pd_entry_t *pdep; struct proc *p = curproc; pdep = (&(p->p_vmspace->vm_pmap.pm_segtab[(va >> SEGSHIFT) & (NPDEPG - 1)])); if (*pdep) ptep = pmap_pte(&p->p_vmspace->vm_pmap, va); else ptep = (pt_entry_t *)0; *pdepp = pdep; *ptepp = ptep; } static void log_illegal_instruction(const char *msg, struct trapframe *frame) { pt_entry_t *ptep; pd_entry_t *pdep; unsigned int *addr; struct thread *td; struct proc *p; register_t pc; td = curthread; p = td->td_proc; #ifdef SMP printf("cpuid = %d\n", PCPU_GET(cpuid)); #endif pc = frame->pc + (DELAYBRANCH(frame->cause) ? 4 : 0); log(LOG_ERR, "%s: pid %d tid %ld (%s), uid %d: pc %#jx ra %#jx\n", msg, p->p_pid, (long)td->td_tid, p->p_comm, p->p_ucred ? p->p_ucred->cr_uid : -1, (intmax_t)pc, (intmax_t)frame->ra); /* log registers in trap frame */ log_frame_dump(frame); get_mapping_info((vm_offset_t)pc, &pdep, &ptep); /* * Dump a few words around faulting instruction, if the addres is * valid. */ if (!(pc & 3) && useracc((caddr_t)(intptr_t)pc, sizeof(int) * 4, VM_PROT_READ)) { /* dump page table entry for faulting instruction */ log(LOG_ERR, "Page table info for pc address %#jx: pde = %p, pte = %#jx\n", (intmax_t)pc, (void *)(intptr_t)*pdep, (uintmax_t)(ptep ? *ptep : 0)); addr = (unsigned int *)(intptr_t)pc; log(LOG_ERR, "Dumping 4 words starting at pc address %p: \n", addr); log(LOG_ERR, "%08x %08x %08x %08x\n", addr[0], addr[1], addr[2], addr[3]); } else { log(LOG_ERR, "pc address %#jx is inaccessible, pde = %p, pte = %#jx\n", (intmax_t)pc, (void *)(intptr_t)*pdep, (uintmax_t)(ptep ? *ptep : 0)); } } static void log_bad_page_fault(char *msg, struct trapframe *frame, int trap_type) { pt_entry_t *ptep; pd_entry_t *pdep; unsigned int *addr; struct thread *td; struct proc *p; char *read_or_write; register_t pc; trap_type &= ~T_USER; td = curthread; p = td->td_proc; #ifdef SMP printf("cpuid = %d\n", PCPU_GET(cpuid)); #endif switch (trap_type) { case T_TLB_MOD: case T_TLB_ST_MISS: case T_ADDR_ERR_ST: read_or_write = "write"; break; case T_TLB_LD_MISS: case T_ADDR_ERR_LD: case T_BUS_ERR_IFETCH: read_or_write = "read"; break; default: read_or_write = "unknown"; } pc = frame->pc + (DELAYBRANCH(frame->cause) ? 4 : 0); log(LOG_ERR, "%s: pid %d tid %ld (%s), uid %d: pc %#jx got a %s fault " "(type %#x) at %#jx\n", msg, p->p_pid, (long)td->td_tid, p->p_comm, p->p_ucred ? p->p_ucred->cr_uid : -1, (intmax_t)pc, read_or_write, trap_type, (intmax_t)frame->badvaddr); /* log registers in trap frame */ log_frame_dump(frame); get_mapping_info((vm_offset_t)pc, &pdep, &ptep); /* * Dump a few words around faulting instruction, if the addres is * valid. */ if (!(pc & 3) && (pc != frame->badvaddr) && (trap_type != T_BUS_ERR_IFETCH) && useracc((caddr_t)(intptr_t)pc, sizeof(int) * 4, VM_PROT_READ)) { /* dump page table entry for faulting instruction */ log(LOG_ERR, "Page table info for pc address %#jx: pde = %p, pte = %#jx\n", (intmax_t)pc, (void *)(intptr_t)*pdep, (uintmax_t)(ptep ? *ptep : 0)); addr = (unsigned int *)(intptr_t)pc; log(LOG_ERR, "Dumping 4 words starting at pc address %p: \n", addr); log(LOG_ERR, "%08x %08x %08x %08x\n", addr[0], addr[1], addr[2], addr[3]); } else { log(LOG_ERR, "pc address %#jx is inaccessible, pde = %p, pte = %#jx\n", (intmax_t)pc, (void *)(intptr_t)*pdep, (uintmax_t)(ptep ? *ptep : 0)); } get_mapping_info((vm_offset_t)frame->badvaddr, &pdep, &ptep); log(LOG_ERR, "Page table info for bad address %#jx: pde = %p, pte = %#jx\n", (intmax_t)frame->badvaddr, (void *)(intptr_t)*pdep, (uintmax_t)(ptep ? *ptep : 0)); } /* * Unaligned load/store emulation */ static int mips_unaligned_load_store(struct trapframe *frame, int mode, register_t addr, register_t pc) { register_t *reg = (register_t *) frame; u_int32_t inst = *((u_int32_t *)(intptr_t)pc); register_t value_msb, value; unsigned size; /* * ADDR_ERR faults have higher priority than TLB * Miss faults. Therefore, it is necessary to * verify that the faulting address is a valid * virtual address within the process' address space * before trying to emulate the unaligned access. */ switch (MIPS_INST_OPCODE(inst)) { case OP_LHU: case OP_LH: case OP_SH: size = 2; break; case OP_LWU: case OP_LW: case OP_SW: size = 4; break; case OP_LD: case OP_SD: size = 8; break; default: printf("%s: unhandled opcode in address error: %#x\n", __func__, MIPS_INST_OPCODE(inst)); return (0); } if (!useracc((void *)rounddown2((vm_offset_t)addr, size), size * 2, mode)) return (0); /* * XXX * Handle LL/SC LLD/SCD. */ switch (MIPS_INST_OPCODE(inst)) { case OP_LHU: KASSERT(mode == VM_PROT_READ, ("access mode must be read for load instruction.")); lbu_macro(value_msb, addr); addr += 1; lbu_macro(value, addr); value |= value_msb << 8; reg[MIPS_INST_RT(inst)] = value; return (MIPS_LHU_ACCESS); case OP_LH: KASSERT(mode == VM_PROT_READ, ("access mode must be read for load instruction.")); lb_macro(value_msb, addr); addr += 1; lbu_macro(value, addr); value |= value_msb << 8; reg[MIPS_INST_RT(inst)] = value; return (MIPS_LH_ACCESS); case OP_LWU: KASSERT(mode == VM_PROT_READ, ("access mode must be read for load instruction.")); lwl_macro(value, addr); addr += 3; lwr_macro(value, addr); value &= 0xffffffff; reg[MIPS_INST_RT(inst)] = value; return (MIPS_LWU_ACCESS); case OP_LW: KASSERT(mode == VM_PROT_READ, ("access mode must be read for load instruction.")); lwl_macro(value, addr); addr += 3; lwr_macro(value, addr); reg[MIPS_INST_RT(inst)] = value; return (MIPS_LW_ACCESS); #if defined(__mips_n32) || defined(__mips_n64) case OP_LD: KASSERT(mode == VM_PROT_READ, ("access mode must be read for load instruction.")); ldl_macro(value, addr); addr += 7; ldr_macro(value, addr); reg[MIPS_INST_RT(inst)] = value; return (MIPS_LD_ACCESS); #endif case OP_SH: KASSERT(mode == VM_PROT_WRITE, ("access mode must be write for store instruction.")); value = reg[MIPS_INST_RT(inst)]; value_msb = value >> 8; sb_macro(value_msb, addr); addr += 1; sb_macro(value, addr); return (MIPS_SH_ACCESS); case OP_SW: KASSERT(mode == VM_PROT_WRITE, ("access mode must be write for store instruction.")); value = reg[MIPS_INST_RT(inst)]; swl_macro(value, addr); addr += 3; swr_macro(value, addr); return (MIPS_SW_ACCESS); #if defined(__mips_n32) || defined(__mips_n64) case OP_SD: KASSERT(mode == VM_PROT_WRITE, ("access mode must be write for store instruction.")); value = reg[MIPS_INST_RT(inst)]; sdl_macro(value, addr); addr += 7; sdr_macro(value, addr); return (MIPS_SD_ACCESS); #endif } panic("%s: should not be reached.", __func__); } /* * XXX TODO: SMP? */ static struct timeval unaligned_lasterr; static int unaligned_curerr; static int unaligned_pps_log_limit = 4; SYSCTL_INT(_machdep, OID_AUTO, unaligned_log_pps_limit, CTLFLAG_RWTUN, &unaligned_pps_log_limit, 0, "limit number of userland unaligned log messages per second"); static int emulate_unaligned_access(struct trapframe *frame, int mode) { register_t pc; int access_type = 0; struct thread *td = curthread; struct proc *p = curproc; pc = frame->pc + (DELAYBRANCH(frame->cause) ? 4 : 0); /* * Fall through if it's instruction fetch exception */ if (!((pc & 3) || (pc == frame->badvaddr))) { /* * Handle unaligned load and store */ /* * Return access type if the instruction was emulated. * Otherwise restore pc and fall through. */ access_type = mips_unaligned_load_store(frame, mode, frame->badvaddr, pc); if (access_type) { if (DELAYBRANCH(frame->cause)) frame->pc = MipsEmulateBranch(frame, frame->pc, 0, 0); else frame->pc += 4; if (ppsratecheck(&unaligned_lasterr, &unaligned_curerr, unaligned_pps_log_limit)) { /* XXX TODO: keep global/tid/pid counters? */ log(LOG_INFO, "Unaligned %s: pid=%ld (%s), tid=%ld, " "pc=%#jx, badvaddr=%#jx\n", access_name[access_type - 1], (long) p->p_pid, p->p_comm, (long) td->td_tid, (intmax_t)pc, (intmax_t)frame->badvaddr); } } } return access_type; } Index: user/ngie/detangle-rc/sys/modules/bhnd/bhndb/Makefile =================================================================== --- user/ngie/detangle-rc/sys/modules/bhnd/bhndb/Makefile (revision 299126) +++ user/ngie/detangle-rc/sys/modules/bhnd/bhndb/Makefile (revision 299127) @@ -1,13 +1,13 @@ # $FreeBSD$ .PATH: ${.CURDIR}/../../../dev/bhnd/bhndb KMOD= bhndb SRCS= bhndb.c bhndb_subr.c bhndb_hwdata.c \ bhndb_bus_if.c bhndb_bus_if.h \ bhndb_if.c bhndb_if.h \ bhnd_bus_if.h -SRCS+= device_if.h bus_if.h +SRCS+= device_if.h bus_if.h pci_if.h .include Index: user/ngie/detangle-rc/sys/modules/bhnd/bhndb_pci/Makefile =================================================================== --- user/ngie/detangle-rc/sys/modules/bhnd/bhndb_pci/Makefile (revision 299126) +++ user/ngie/detangle-rc/sys/modules/bhnd/bhndb_pci/Makefile (revision 299127) @@ -1,11 +1,11 @@ # $FreeBSD$ .PATH: ${.CURDIR}/../../../dev/bhnd/bhndb KMOD= bhndb_pci SRCS= bhndb_pci.c bhndb_pci_hwdata.c SRCS+= bhnd_bus_if.h bhndb_bus_if.h bhndb_if.h -SRCS+= device_if.h bus_if.h +SRCS+= device_if.h bus_if.h pci_if.h .include Index: user/ngie/detangle-rc/sys/sys/_bitset.h =================================================================== --- user/ngie/detangle-rc/sys/sys/_bitset.h (revision 299126) +++ user/ngie/detangle-rc/sys/sys/_bitset.h (revision 299127) @@ -1,61 +1,50 @@ /*- * Copyright (c) 2008, Jeffrey Roberson * All rights reserved. * * Copyright (c) 2008 Nokia Corporation * 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 _SYS__BITSET_H_ #define _SYS__BITSET_H_ /* * Macros addressing word and bit within it, tuned to make compiler * optimize cases when SETSIZE fits into single machine word. */ -#define _BITSET_BITS (sizeof(long) * NBBY) +#define _BITSET_BITS (sizeof(long) * 8) -#define __bitset_words(_s) (howmany(_s, _BITSET_BITS)) +#define __howmany(x, y) (((x) + ((y) - 1)) / (y)) -#define __bitset_mask(_s, n) \ - (1L << ((__bitset_words((_s)) == 1) ? \ - (__size_t)(n) : ((n) % _BITSET_BITS))) +#define __bitset_words(_s) (__howmany(_s, _BITSET_BITS)) -#define __bitset_word(_s, n) \ - ((__bitset_words((_s)) == 1) ? 0 : ((n) / _BITSET_BITS)) - #define BITSET_DEFINE(t, _s) \ struct t { \ long __bits[__bitset_words((_s))]; \ } - -#define BITSET_T_INITIALIZER(x) \ - { .__bits = { x } } - -#define BITSET_FSET(n) \ - [ 0 ... ((n) - 1) ] = (-1L) #endif /* !_SYS__BITSET_H_ */ Index: user/ngie/detangle-rc/sys/sys/_cpuset.h =================================================================== --- user/ngie/detangle-rc/sys/sys/_cpuset.h (revision 299126) +++ user/ngie/detangle-rc/sys/sys/_cpuset.h (revision 299127) @@ -1,56 +1,50 @@ /*- * Copyright (c) 2008, Jeffrey Roberson * All rights reserved. * * Copyright (c) 2008 Nokia Corporation * 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 _SYS__CPUSET_H_ #define _SYS__CPUSET_H_ #include #ifdef _KERNEL #define CPU_SETSIZE MAXCPU #endif #define CPU_MAXSIZE 256 #ifndef CPU_SETSIZE #define CPU_SETSIZE CPU_MAXSIZE #endif -#define _NCPUBITS _BITSET_BITS -#define _NCPUWORDS __bitset_words(CPU_SETSIZE) - BITSET_DEFINE(_cpuset, CPU_SETSIZE); typedef struct _cpuset cpuset_t; - -#define CPUSET_FSET BITSET_FSET(_NCPUWORDS) -#define CPUSET_T_INITIALIZER BITSET_T_INITIALIZER #endif /* !_SYS__CPUSET_H_ */ Index: user/ngie/detangle-rc/sys/sys/bitset.h =================================================================== --- user/ngie/detangle-rc/sys/sys/bitset.h (revision 299126) +++ user/ngie/detangle-rc/sys/sys/bitset.h (revision 299127) @@ -1,189 +1,202 @@ /*- * Copyright (c) 2008, Jeffrey Roberson * All rights reserved. * * Copyright (c) 2008 Nokia Corporation * 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 _SYS_BITSET_H_ #define _SYS_BITSET_H_ +#define __bitset_mask(_s, n) \ + (1L << ((__bitset_words((_s)) == 1) ? \ + (__size_t)(n) : ((n) % _BITSET_BITS))) + +#define __bitset_word(_s, n) \ + ((__bitset_words((_s)) == 1) ? 0 : ((n) / _BITSET_BITS)) + #define BIT_CLR(_s, n, p) \ ((p)->__bits[__bitset_word(_s, n)] &= ~__bitset_mask((_s), (n))) #define BIT_COPY(_s, f, t) (void)(*(t) = *(f)) #define BIT_ISSET(_s, n, p) \ ((((p)->__bits[__bitset_word(_s, n)] & __bitset_mask((_s), (n))) != 0)) #define BIT_SET(_s, n, p) \ ((p)->__bits[__bitset_word(_s, n)] |= __bitset_mask((_s), (n))) #define BIT_ZERO(_s, p) do { \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ (p)->__bits[__i] = 0L; \ } while (0) #define BIT_FILL(_s, p) do { \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ (p)->__bits[__i] = -1L; \ } while (0) #define BIT_SETOF(_s, n, p) do { \ BIT_ZERO(_s, p); \ (p)->__bits[__bitset_word(_s, n)] = __bitset_mask((_s), (n)); \ } while (0) /* Is p empty. */ #define BIT_EMPTY(_s, p) __extension__ ({ \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ if ((p)->__bits[__i]) \ break; \ __i == __bitset_words((_s)); \ }) /* Is p full set. */ #define BIT_ISFULLSET(_s, p) __extension__ ({ \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ if ((p)->__bits[__i] != (long)-1) \ break; \ __i == __bitset_words((_s)); \ }) /* Is c a subset of p. */ #define BIT_SUBSET(_s, p, c) __extension__ ({ \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ if (((c)->__bits[__i] & \ (p)->__bits[__i]) != \ (c)->__bits[__i]) \ break; \ __i == __bitset_words((_s)); \ }) /* Are there any common bits between b & c? */ #define BIT_OVERLAP(_s, p, c) __extension__ ({ \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ if (((c)->__bits[__i] & \ (p)->__bits[__i]) != 0) \ break; \ __i != __bitset_words((_s)); \ }) /* Compare two sets, returns 0 if equal 1 otherwise. */ #define BIT_CMP(_s, p, c) __extension__ ({ \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ if (((c)->__bits[__i] != \ (p)->__bits[__i])) \ break; \ __i != __bitset_words((_s)); \ }) #define BIT_OR(_s, d, s) do { \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ (d)->__bits[__i] |= (s)->__bits[__i]; \ } while (0) #define BIT_AND(_s, d, s) do { \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ (d)->__bits[__i] &= (s)->__bits[__i]; \ } while (0) #define BIT_NAND(_s, d, s) do { \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ (d)->__bits[__i] &= ~(s)->__bits[__i]; \ } while (0) #define BIT_CLR_ATOMIC(_s, n, p) \ atomic_clear_long(&(p)->__bits[__bitset_word(_s, n)], \ __bitset_mask((_s), n)) #define BIT_SET_ATOMIC(_s, n, p) \ atomic_set_long(&(p)->__bits[__bitset_word(_s, n)], \ __bitset_mask((_s), n)) #define BIT_SET_ATOMIC_ACQ(_s, n, p) \ atomic_set_acq_long(&(p)->__bits[__bitset_word(_s, n)], \ __bitset_mask((_s), n)) /* Convenience functions catering special cases. */ #define BIT_AND_ATOMIC(_s, d, s) do { \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ atomic_clear_long(&(d)->__bits[__i], \ ~(s)->__bits[__i]); \ } while (0) #define BIT_OR_ATOMIC(_s, d, s) do { \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ atomic_set_long(&(d)->__bits[__i], \ (s)->__bits[__i]); \ } while (0) #define BIT_COPY_STORE_REL(_s, f, t) do { \ __size_t __i; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ atomic_store_rel_long(&(t)->__bits[__i], \ (f)->__bits[__i]); \ } while (0) #define BIT_FFS(_s, p) __extension__ ({ \ __size_t __i; \ int __bit; \ \ __bit = 0; \ for (__i = 0; __i < __bitset_words((_s)); __i++) { \ if ((p)->__bits[__i] != 0) { \ __bit = ffsl((p)->__bits[__i]); \ __bit += __i * _BITSET_BITS; \ break; \ } \ } \ __bit; \ }) #define BIT_COUNT(_s, p) __extension__ ({ \ __size_t __i; \ int __count; \ \ __count = 0; \ for (__i = 0; __i < __bitset_words((_s)); __i++) \ __count += __bitcountl((p)->__bits[__i]); \ __count; \ }) - + +#define BITSET_T_INITIALIZER(x) \ + { .__bits = { x } } + +#define BITSET_FSET(n) \ + [ 0 ... ((n) - 1) ] = (-1L) + #endif /* !_SYS_BITSET_H_ */ Index: user/ngie/detangle-rc/sys/sys/cpuset.h =================================================================== --- user/ngie/detangle-rc/sys/sys/cpuset.h (revision 299126) +++ user/ngie/detangle-rc/sys/sys/cpuset.h (revision 299127) @@ -1,146 +1,152 @@ /*- * Copyright (c) 2008, Jeffrey Roberson * All rights reserved. * * Copyright (c) 2008 Nokia Corporation * 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 _SYS_CPUSET_H_ #define _SYS_CPUSET_H_ #include #include +#include +#define _NCPUBITS _BITSET_BITS +#define _NCPUWORDS __bitset_words(CPU_SETSIZE) + #define CPUSETBUFSIZ ((2 + sizeof(long) * 2) * _NCPUWORDS) #define CPU_CLR(n, p) BIT_CLR(CPU_SETSIZE, n, p) #define CPU_COPY(f, t) BIT_COPY(CPU_SETSIZE, f, t) #define CPU_ISSET(n, p) BIT_ISSET(CPU_SETSIZE, n, p) #define CPU_SET(n, p) BIT_SET(CPU_SETSIZE, n, p) #define CPU_ZERO(p) BIT_ZERO(CPU_SETSIZE, p) #define CPU_FILL(p) BIT_FILL(CPU_SETSIZE, p) #define CPU_SETOF(n, p) BIT_SETOF(CPU_SETSIZE, n, p) #define CPU_EMPTY(p) BIT_EMPTY(CPU_SETSIZE, p) #define CPU_ISFULLSET(p) BIT_ISFULLSET(CPU_SETSIZE, p) #define CPU_SUBSET(p, c) BIT_SUBSET(CPU_SETSIZE, p, c) #define CPU_OVERLAP(p, c) BIT_OVERLAP(CPU_SETSIZE, p, c) #define CPU_CMP(p, c) BIT_CMP(CPU_SETSIZE, p, c) #define CPU_OR(d, s) BIT_OR(CPU_SETSIZE, d, s) #define CPU_AND(d, s) BIT_AND(CPU_SETSIZE, d, s) #define CPU_NAND(d, s) BIT_NAND(CPU_SETSIZE, d, s) #define CPU_CLR_ATOMIC(n, p) BIT_CLR_ATOMIC(CPU_SETSIZE, n, p) #define CPU_SET_ATOMIC(n, p) BIT_SET_ATOMIC(CPU_SETSIZE, n, p) #define CPU_SET_ATOMIC_ACQ(n, p) BIT_SET_ATOMIC_ACQ(CPU_SETSIZE, n, p) #define CPU_AND_ATOMIC(n, p) BIT_AND_ATOMIC(CPU_SETSIZE, n, p) #define CPU_OR_ATOMIC(d, s) BIT_OR_ATOMIC(CPU_SETSIZE, d, s) #define CPU_COPY_STORE_REL(f, t) BIT_COPY_STORE_REL(CPU_SETSIZE, f, t) #define CPU_FFS(p) BIT_FFS(CPU_SETSIZE, p) #define CPU_COUNT(p) BIT_COUNT(CPU_SETSIZE, p) +#define CPUSET_FSET BITSET_FSET(_NCPUWORDS) +#define CPUSET_T_INITIALIZER BITSET_T_INITIALIZER /* * Valid cpulevel_t values. */ #define CPU_LEVEL_ROOT 1 /* All system cpus. */ #define CPU_LEVEL_CPUSET 2 /* Available cpus for which. */ #define CPU_LEVEL_WHICH 3 /* Actual mask/id for which. */ /* * Valid cpuwhich_t values. */ #define CPU_WHICH_TID 1 /* Specifies a thread id. */ #define CPU_WHICH_PID 2 /* Specifies a process id. */ #define CPU_WHICH_CPUSET 3 /* Specifies a set id. */ #define CPU_WHICH_IRQ 4 /* Specifies an irq #. */ #define CPU_WHICH_JAIL 5 /* Specifies a jail id. */ #define CPU_WHICH_DOMAIN 6 /* Specifies a NUMA domain id. */ /* * Reserved cpuset identifiers. */ #define CPUSET_INVALID -1 #define CPUSET_DEFAULT 0 #ifdef _KERNEL LIST_HEAD(setlist, cpuset); /* * cpusets encapsulate cpu binding information for one or more threads. * * a - Accessed with atomics. * s - Set at creation, never modified. Only a ref required to read. * c - Locked internally by a cpuset lock. * * The bitmask is only modified while holding the cpuset lock. It may be * read while only a reference is held but the consumer must be prepared * to deal with inconsistent results. */ struct cpuset { cpuset_t cs_mask; /* bitmask of valid cpus. */ volatile u_int cs_ref; /* (a) Reference count. */ int cs_flags; /* (s) Flags from below. */ cpusetid_t cs_id; /* (s) Id or INVALID. */ struct cpuset *cs_parent; /* (s) Pointer to our parent. */ LIST_ENTRY(cpuset) cs_link; /* (c) All identified sets. */ LIST_ENTRY(cpuset) cs_siblings; /* (c) Sibling set link. */ struct setlist cs_children; /* (c) List of children. */ }; #define CPU_SET_ROOT 0x0001 /* Set is a root set. */ #define CPU_SET_RDONLY 0x0002 /* No modification allowed. */ extern cpuset_t *cpuset_root; struct prison; struct proc; struct thread; struct cpuset *cpuset_thread0(void); struct cpuset *cpuset_ref(struct cpuset *); void cpuset_rel(struct cpuset *); int cpuset_setthread(lwpid_t id, cpuset_t *); int cpuset_setithread(lwpid_t id, int cpu); int cpuset_create_root(struct prison *, struct cpuset **); int cpuset_setproc_update_set(struct proc *, struct cpuset *); int cpuset_which(cpuwhich_t, id_t, struct proc **, struct thread **, struct cpuset **); char *cpusetobj_strprint(char *, const cpuset_t *); int cpusetobj_strscan(cpuset_t *, const char *); #ifdef DDB void ddb_display_cpuset(const cpuset_t *); #endif #else __BEGIN_DECLS int cpuset(cpusetid_t *); int cpuset_setid(cpuwhich_t, id_t, cpusetid_t); int cpuset_getid(cpulevel_t, cpuwhich_t, id_t, cpusetid_t *); int cpuset_getaffinity(cpulevel_t, cpuwhich_t, id_t, size_t, cpuset_t *); int cpuset_setaffinity(cpulevel_t, cpuwhich_t, id_t, size_t, const cpuset_t *); __END_DECLS #endif #endif /* !_SYS_CPUSET_H_ */ Index: user/ngie/detangle-rc/sys/sys/intr.h =================================================================== --- user/ngie/detangle-rc/sys/sys/intr.h (revision 299126) +++ user/ngie/detangle-rc/sys/sys/intr.h (revision 299127) @@ -1,164 +1,159 @@ /*- * Copyright (c) 2015-2016 Svatopluk Kraus * Copyright (c) 2015-2016 Michal Meloun * 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. * * $FreeBSD$ */ #ifndef _SYS_INTR_H_ #define _SYS_INTR_H_ #include #define INTR_IRQ_INVALID 0xFFFFFFFF enum intr_map_data_type { INTR_MAP_DATA_ACPI, INTR_MAP_DATA_FDT, INTR_MAP_DATA_GPIO, }; +struct intr_map_data { + enum intr_map_data_type type; + size_t size; +}; + #ifdef DEV_ACPI struct intr_map_data_acpi { + struct intr_map_data hdr; u_int irq; enum intr_polarity pol; enum intr_trigger trig; }; #endif #ifdef FDT struct intr_map_data_fdt { - u_int ncells; - pcell_t *cells; + struct intr_map_data hdr; + u_int ncells; + pcell_t cells[0]; }; #endif struct intr_map_data_gpio { + struct intr_map_data hdr; u_int gpio_pin_num; u_int gpio_pin_flags; u_int gpio_intr_mode; -}; - -struct intr_map_data { - enum intr_map_data_type type; - union { -#ifdef DEV_ACPI - struct intr_map_data_acpi acpi; -#endif -#ifdef FDT - struct intr_map_data_fdt fdt; -#endif - struct intr_map_data_gpio gpio; - }; }; #ifdef notyet #define INTR_SOLO INTR_MD1 typedef int intr_irq_filter_t(void *arg, struct trapframe *tf); #else typedef int intr_irq_filter_t(void *arg); #endif #define INTR_ISRC_NAMELEN (MAXCOMLEN + 1) #define INTR_ISRCF_IPI 0x01 /* IPI interrupt */ #define INTR_ISRCF_PPI 0x02 /* PPI interrupt */ #define INTR_ISRCF_BOUND 0x04 /* bound to a CPU */ /* Interrupt source definition. */ struct intr_irqsrc { device_t isrc_dev; /* where isrc is mapped */ u_int isrc_irq; /* unique identificator */ u_int isrc_flags; char isrc_name[INTR_ISRC_NAMELEN]; cpuset_t isrc_cpu; /* on which CPUs is enabled */ u_int isrc_index; u_long * isrc_count; u_int isrc_handlers; struct intr_event * isrc_event; #ifdef INTR_SOLO intr_irq_filter_t * isrc_filter; void * isrc_arg; #endif }; /* Intr interface for PIC. */ int intr_isrc_deregister(struct intr_irqsrc *); int intr_isrc_register(struct intr_irqsrc *, device_t, u_int, const char *, ...) __printflike(4, 5); #ifdef SMP bool intr_isrc_init_on_cpu(struct intr_irqsrc *isrc, u_int cpu); #endif int intr_isrc_dispatch(struct intr_irqsrc *, struct trapframe *); u_int intr_irq_next_cpu(u_int current_cpu, cpuset_t *cpumask); int intr_pic_register(device_t, intptr_t); int intr_pic_deregister(device_t, intptr_t); int intr_pic_claim_root(device_t, intptr_t, intr_irq_filter_t *, void *, u_int); extern device_t intr_irq_root_dev; /* Intr interface for BUS. */ int intr_map_irq(device_t, intptr_t, struct intr_map_data *, u_int *); int intr_alloc_irq(device_t, struct resource *); int intr_release_irq(device_t, struct resource *); int intr_setup_irq(device_t, struct resource *, driver_filter_t, driver_intr_t, void *, int, void **); int intr_teardown_irq(device_t, struct resource *, void *); int intr_describe_irq(device_t, struct resource *, void *, const char *); #ifdef DEV_ACPI u_int intr_acpi_map_irq(device_t, u_int, enum intr_polarity, enum intr_trigger); #endif #ifdef FDT u_int intr_fdt_map_irq(phandle_t, pcell_t *, u_int); #endif u_int intr_gpio_map_irq(device_t dev, u_int pin_num, u_int pin_flags, u_int intr_mode); #ifdef SMP int intr_bind_irq(device_t, struct resource *, int); void intr_pic_init_secondary(void); /* Virtualization for interrupt source IPI counter increment. */ static inline void intr_ipi_increment_count(u_long *counter, u_int cpu) { KASSERT(cpu < MAXCPU, ("%s: too big cpu %u", __func__, cpu)); counter[cpu]++; } /* Virtualization for interrupt source IPI counters setup. */ u_long * intr_ipi_setup_counters(const char *name); #endif #endif /* _SYS_INTR_H */ Index: user/ngie/detangle-rc/sys/sys/pcpu.h =================================================================== --- user/ngie/detangle-rc/sys/sys/pcpu.h (revision 299126) +++ user/ngie/detangle-rc/sys/sys/pcpu.h (revision 299127) @@ -1,239 +1,239 @@ /*- * Copyright (c) 2001 Wind River Systems, Inc. * All rights reserved. * Written by: John Baldwin * * 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. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * 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. * * $FreeBSD$ */ #ifndef _SYS_PCPU_H_ #define _SYS_PCPU_H_ #ifdef LOCORE #error "no assembler-serviceable parts inside" #endif #include #include #include #include #include #include #include #include #include #define DPCPU_SETNAME "set_pcpu" #define DPCPU_SYMPREFIX "pcpu_entry_" #ifdef _KERNEL /* * Define a set for pcpu data. */ extern uintptr_t *__start_set_pcpu; __GLOBL(__start_set_pcpu); extern uintptr_t *__stop_set_pcpu; __GLOBL(__stop_set_pcpu); /* * Array of dynamic pcpu base offsets. Indexed by id. */ extern uintptr_t dpcpu_off[]; /* * Convenience defines. */ #define DPCPU_START ((uintptr_t)&__start_set_pcpu) #define DPCPU_STOP ((uintptr_t)&__stop_set_pcpu) #define DPCPU_BYTES (DPCPU_STOP - DPCPU_START) #define DPCPU_MODMIN 2048 #define DPCPU_SIZE roundup2(DPCPU_BYTES, PAGE_SIZE) #define DPCPU_MODSIZE (DPCPU_SIZE - (DPCPU_BYTES - DPCPU_MODMIN)) /* * Declaration and definition. */ #define DPCPU_NAME(n) pcpu_entry_##n #define DPCPU_DECLARE(t, n) extern t DPCPU_NAME(n) #define DPCPU_DEFINE(t, n) t DPCPU_NAME(n) __section(DPCPU_SETNAME) __used /* * Accessors with a given base. */ #define _DPCPU_PTR(b, n) \ (__typeof(DPCPU_NAME(n))*)((b) + (uintptr_t)&DPCPU_NAME(n)) #define _DPCPU_GET(b, n) (*_DPCPU_PTR(b, n)) #define _DPCPU_SET(b, n, v) (*_DPCPU_PTR(b, n) = v) /* * Accessors for the current cpu. */ #define DPCPU_PTR(n) _DPCPU_PTR(PCPU_GET(dynamic), n) #define DPCPU_GET(n) (*DPCPU_PTR(n)) #define DPCPU_SET(n, v) (*DPCPU_PTR(n) = v) /* * Accessors for remote cpus. */ #define DPCPU_ID_PTR(i, n) _DPCPU_PTR(dpcpu_off[(i)], n) #define DPCPU_ID_GET(i, n) (*DPCPU_ID_PTR(i, n)) #define DPCPU_ID_SET(i, n, v) (*DPCPU_ID_PTR(i, n) = v) /* * Utility macros. */ #define DPCPU_SUM(n) __extension__ \ ({ \ u_int _i; \ __typeof(*DPCPU_PTR(n)) sum; \ \ sum = 0; \ CPU_FOREACH(_i) { \ sum += *DPCPU_ID_PTR(_i, n); \ } \ sum; \ }) #define DPCPU_VARSUM(n, var) __extension__ \ ({ \ u_int _i; \ __typeof((DPCPU_PTR(n))->var) sum; \ \ sum = 0; \ CPU_FOREACH(_i) { \ sum += (DPCPU_ID_PTR(_i, n))->var; \ } \ sum; \ }) #define DPCPU_ZERO(n) do { \ u_int _i; \ \ CPU_FOREACH(_i) { \ bzero(DPCPU_ID_PTR(_i, n), sizeof(*DPCPU_PTR(n))); \ } \ } while(0) #endif /* _KERNEL */ /* * This structure maps out the global data that needs to be kept on a * per-cpu basis. The members are accessed via the PCPU_GET/SET/PTR * macros defined in . Machine dependent fields are * defined in the PCPU_MD_FIELDS macro defined in . */ struct pcpu { struct thread *pc_curthread; /* Current thread */ struct thread *pc_idlethread; /* Idle thread */ struct thread *pc_fpcurthread; /* Fp state owner */ struct thread *pc_deadthread; /* Zombie thread or NULL */ struct pcb *pc_curpcb; /* Current pcb */ uint64_t pc_switchtime; /* cpu_ticks() at last csw */ int pc_switchticks; /* `ticks' at last csw */ u_int pc_cpuid; /* This cpu number */ STAILQ_ENTRY(pcpu) pc_allcpu; struct lock_list_entry *pc_spinlocks; struct vmmeter pc_cnt; /* VM stats counters */ long pc_cp_time[CPUSTATES]; /* statclock ticks */ - device_t pc_device; + struct device *pc_device; void *pc_netisr; /* netisr SWI cookie */ int pc_unused1; /* unused field */ int pc_domain; /* Memory domain. */ struct rm_queue pc_rm_queue; /* rmlock list of trackers */ uintptr_t pc_dynamic; /* Dynamic per-cpu data area */ /* * Keep MD fields last, so that CPU-specific variations on a * single architecture don't result in offset variations of * the machine-independent fields of the pcpu. Even though * the pcpu structure is private to the kernel, some ports * (e.g., lsof, part of gtop) define _KERNEL and include this * header. While strictly speaking this is wrong, there's no * reason not to keep the offsets of the MI fields constant * if only to make kernel debugging easier. */ PCPU_MD_FIELDS; } __aligned(CACHE_LINE_SIZE); #ifdef CTASSERT /* * To minimize memory waste in per-cpu UMA zones, size of struct pcpu * should be denominator of PAGE_SIZE. */ CTASSERT((PAGE_SIZE / sizeof(struct pcpu)) * sizeof(struct pcpu) == PAGE_SIZE); #endif #ifdef _KERNEL STAILQ_HEAD(cpuhead, pcpu); extern struct cpuhead cpuhead; extern struct pcpu *cpuid_to_pcpu[]; #define curcpu PCPU_GET(cpuid) #define curproc (curthread->td_proc) #ifndef curthread #define curthread PCPU_GET(curthread) #endif #define curvidata PCPU_GET(vidata) /* Accessor to elements allocated via UMA_ZONE_PCPU zone. */ static inline void * zpcpu_get(void *base) { return ((char *)(base) + sizeof(struct pcpu) * curcpu); } static inline void * zpcpu_get_cpu(void *base, int cpu) { return ((char *)(base) + sizeof(struct pcpu) * cpu); } /* * Machine dependent callouts. cpu_pcpu_init() is responsible for * initializing machine dependent fields of struct pcpu, and * db_show_mdpcpu() is responsible for handling machine dependent * fields for the DDB 'show pcpu' command. */ void cpu_pcpu_init(struct pcpu *pcpu, int cpuid, size_t size); void db_show_mdpcpu(struct pcpu *pcpu); void *dpcpu_alloc(int size); void dpcpu_copy(void *s, int size); void dpcpu_free(void *s, int size); void dpcpu_init(void *dpcpu, int cpuid); void pcpu_destroy(struct pcpu *pcpu); struct pcpu *pcpu_find(u_int cpuid); void pcpu_init(struct pcpu *pcpu, int cpuid, size_t size); #endif /* _KERNEL */ #endif /* !_SYS_PCPU_H_ */ Index: user/ngie/detangle-rc/sys/sys/rman.h =================================================================== --- user/ngie/detangle-rc/sys/sys/rman.h (revision 299126) +++ user/ngie/detangle-rc/sys/sys/rman.h (revision 299127) @@ -1,162 +1,162 @@ /*- * Copyright 1998 Massachusetts Institute of Technology * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that both the above copyright notice and this * permission notice appear in all copies, that both the above * copyright notice and this permission notice appear in all * supporting documentation, and that the name of M.I.T. not be used * in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. M.I.T. makes * no representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. * * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT * SHALL M.I.T. 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 _SYS_RMAN_H_ #define _SYS_RMAN_H_ 1 #ifndef _KERNEL #include #else #include #include #endif #define RF_ALLOCATED 0x0001 /* resource has been reserved */ #define RF_ACTIVE 0x0002 /* resource allocation has been activated */ #define RF_SHAREABLE 0x0004 /* resource permits contemporaneous sharing */ #define RF_SPARE1 0x0008 #define RF_SPARE2 0x0010 #define RF_FIRSTSHARE 0x0020 /* first in sharing list */ #define RF_PREFETCHABLE 0x0040 /* resource is prefetchable */ #define RF_OPTIONAL 0x0080 /* for bus_alloc_resources() */ #define RF_ALIGNMENT_SHIFT 10 /* alignment size bit starts bit 10 */ #define RF_ALIGNMENT_MASK (0x003F << RF_ALIGNMENT_SHIFT) /* resource address alignment size bit mask */ #define RF_ALIGNMENT_LOG2(x) ((x) << RF_ALIGNMENT_SHIFT) #define RF_ALIGNMENT(x) (((x) & RF_ALIGNMENT_MASK) >> RF_ALIGNMENT_SHIFT) enum rman_type { RMAN_UNINIT = 0, RMAN_GAUGE, RMAN_ARRAY }; /* * String length exported to userspace for resource names, etc. */ #define RM_TEXTLEN 32 #define RM_MAX_END (~(rman_res_t)0) #define RMAN_IS_DEFAULT_RANGE(s,e) ((s) == 0 && (e) == RM_MAX_END) /* * Userspace-exported structures. */ struct u_resource { uintptr_t r_handle; /* resource uniquifier */ uintptr_t r_parent; /* parent rman */ uintptr_t r_device; /* device owning this resource */ char r_devname[RM_TEXTLEN]; /* device name XXX obsolete */ rman_res_t r_start; /* offset in resource space */ rman_res_t r_size; /* size in resource space */ u_int r_flags; /* RF_* flags */ }; struct u_rman { uintptr_t rm_handle; /* rman uniquifier */ char rm_descr[RM_TEXTLEN]; /* rman description */ rman_res_t rm_start; /* base of managed region */ rman_res_t rm_size; /* size of managed region */ enum rman_type rm_type; /* region type */ }; #ifdef _KERNEL /* * The public (kernel) view of struct resource * * NB: Changing the offset/size/type of existing fields in struct resource * NB: breaks the device driver ABI and is strongly FORBIDDEN. * NB: Appending new fields is probably just misguided. */ struct resource { struct resource_i *__r_i; bus_space_tag_t r_bustag; /* bus_space tag */ bus_space_handle_t r_bushandle; /* bus_space handle */ }; struct resource_i; TAILQ_HEAD(resource_head, resource_i); struct rman { struct resource_head rm_list; struct mtx *rm_mtx; /* mutex used to protect rm_list */ TAILQ_ENTRY(rman) rm_link; /* link in list of all rmans */ rman_res_t rm_start; /* index of globally first entry */ rman_res_t rm_end; /* index of globally last entry */ enum rman_type rm_type; /* what type of resource this is */ const char *rm_descr; /* text descripion of this resource */ }; TAILQ_HEAD(rman_head, rman); int rman_activate_resource(struct resource *r); int rman_adjust_resource(struct resource *r, rman_res_t start, rman_res_t end); int rman_await_resource(struct resource *r, int pri, int timo); int rman_first_free_region(struct rman *rm, rman_res_t *start, rman_res_t *end); bus_space_handle_t rman_get_bushandle(struct resource *); bus_space_tag_t rman_get_bustag(struct resource *); rman_res_t rman_get_end(struct resource *); -device_t rman_get_device(struct resource *); +struct device *rman_get_device(struct resource *); u_int rman_get_flags(struct resource *); int rman_get_rid(struct resource *); rman_res_t rman_get_size(struct resource *); rman_res_t rman_get_start(struct resource *); void *rman_get_virtual(struct resource *); int rman_deactivate_resource(struct resource *r); int rman_fini(struct rman *rm); int rman_init(struct rman *rm); int rman_init_from_resource(struct rman *rm, struct resource *r); int rman_last_free_region(struct rman *rm, rman_res_t *start, rman_res_t *end); uint32_t rman_make_alignment_flags(uint32_t size); int rman_manage_region(struct rman *rm, rman_res_t start, rman_res_t end); int rman_is_region_manager(struct resource *r, struct rman *rm); int rman_release_resource(struct resource *r); struct resource *rman_reserve_resource(struct rman *rm, rman_res_t start, rman_res_t end, rman_res_t count, - u_int flags, device_t dev); + u_int flags, struct device *dev); struct resource *rman_reserve_resource_bound(struct rman *rm, rman_res_t start, rman_res_t end, rman_res_t count, rman_res_t bound, - u_int flags, device_t dev); + u_int flags, struct device *dev); void rman_set_bushandle(struct resource *_r, bus_space_handle_t _h); void rman_set_bustag(struct resource *_r, bus_space_tag_t _t); -void rman_set_device(struct resource *_r, device_t _dev); +void rman_set_device(struct resource *_r, struct device *_dev); void rman_set_end(struct resource *_r, rman_res_t _end); void rman_set_rid(struct resource *_r, int _rid); void rman_set_start(struct resource *_r, rman_res_t _start); void rman_set_virtual(struct resource *_r, void *_v); extern struct rman_head rman_head; #endif /* _KERNEL */ #endif /* !_SYS_RMAN_H_ */ Index: user/ngie/detangle-rc =================================================================== --- user/ngie/detangle-rc (revision 299126) +++ user/ngie/detangle-rc (revision 299127) Property changes on: user/ngie/detangle-rc ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r299100-299126